mirror of
https://github.com/flarum/core.git
synced 2025-08-13 11:54:32 +02:00
Compare commits
20 Commits
dk/harden-
...
as/fronten
Author | SHA1 | Date | |
---|---|---|---|
|
72f7c3cae9 | ||
|
d7a5bf7546 | ||
|
2c8cc0c92b | ||
|
7cf0df88f3 | ||
|
43d00fcb79 | ||
|
897dd578b8 | ||
|
235dfebfd4 | ||
|
c8424689ef | ||
|
6b799028b5 | ||
|
058f0c50d2 | ||
|
b5d2cbda9c | ||
|
827366fa9c | ||
|
f5fbaf46a9 | ||
|
5eff7b010d | ||
|
7035625899 | ||
|
bf20ac663d | ||
|
122f566e10 | ||
|
194cf555db | ||
|
f879182ef7 | ||
|
053b3fd96b |
4
js/dist/admin.js
vendored
4
js/dist/admin.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/admin.js.map
vendored
2
js/dist/admin.js.map
vendored
File diff suppressed because one or more lines are too long
6
js/dist/forum.js
vendored
6
js/dist/forum.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js.map
vendored
2
js/dist/forum.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -24,6 +24,7 @@ import { flattenDeep } from 'lodash-es';
|
||||
import PageState from './states/PageState';
|
||||
import ModalManagerState from './states/ModalManagerState';
|
||||
import AlertManagerState from './states/AlertManagerState';
|
||||
import ErrorPage from './components/ErrorPage';
|
||||
|
||||
/**
|
||||
* The `App` class provides a container for an application, as well as various
|
||||
@@ -199,6 +200,9 @@ export default class Application {
|
||||
|
||||
this.drawer = new Drawer();
|
||||
|
||||
// Needed so 404s don't get their URLs changed to the homepage.
|
||||
// Won't affect extension routes since its added last.
|
||||
this.routes.error = { path: '/:4xx...', component: ErrorPage };
|
||||
m.route(document.getElementById('content'), basePath + '/', mapRoutes(this.routes, basePath));
|
||||
|
||||
// Add a class to the body which indicates that the page has been scrolled
|
||||
|
@@ -69,7 +69,7 @@ export default class Button extends Component {
|
||||
return [
|
||||
iconName && iconName !== true ? icon(iconName, { className: 'Button-icon' }) : '',
|
||||
children ? <span className="Button-label">{children}</span> : '',
|
||||
this.attrs.loading ? <LoadingIndicator size="small" display="inline" /> : '',
|
||||
this.attrs.loading ? <LoadingIndicator size="tiny" className="LoadingIndicator--inline" /> : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -46,7 +46,7 @@ export default class Checkbox extends Component {
|
||||
* @protected
|
||||
*/
|
||||
getDisplay() {
|
||||
return this.attrs.loading ? <LoadingIndicator display="unset" size="small" /> : icon(this.attrs.state ? 'fas fa-check' : 'fas fa-times');
|
||||
return this.attrs.loading ? <LoadingIndicator size="tiny" /> : icon(this.attrs.state ? 'fas fa-check' : 'fas fa-times');
|
||||
}
|
||||
|
||||
/**
|
||||
|
40
js/src/common/components/ErrorPage.js
Normal file
40
js/src/common/components/ErrorPage.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import Link from './Link';
|
||||
import Page from './Page';
|
||||
|
||||
export default class ErrorPage extends Page {
|
||||
oninit(vnode) {
|
||||
super.oninit(vnode);
|
||||
|
||||
this.title = app.translator.trans(`core.forum.error.${this.attrs.errorCode}_title`);
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
super.oncreate(vnode);
|
||||
|
||||
app.setTitle(this.title);
|
||||
}
|
||||
|
||||
view() {
|
||||
const links = {
|
||||
404: '/',
|
||||
};
|
||||
|
||||
const link = links[this.attrs.errorCode];
|
||||
|
||||
return (
|
||||
<div className="ErrorPage">
|
||||
<div className="container">
|
||||
<h2>{this.title}</h2>
|
||||
<p>{app.translator.trans(`core.forum.error.${this.attrs.errorCode}_text`)}</p>
|
||||
<p>
|
||||
{link && (
|
||||
<Link href={link}>
|
||||
{app.translator.trans(`core.forum.error.${this.attrs.errorCode}_link_text`, { forum: app.forum.attribute('title') })}
|
||||
</Link>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@@ -18,12 +18,6 @@ export interface LoadingIndicatorAttrs extends ComponentAttrs {
|
||||
* Optional attributes to apply to the loading indicator's container.
|
||||
*/
|
||||
containerAttrs?: Partial<ComponentAttrs>;
|
||||
/**
|
||||
* Display type of the spinner.
|
||||
*
|
||||
* @default 'block'
|
||||
*/
|
||||
display?: 'block' | 'inline' | 'unset';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,13 +26,12 @@ export interface LoadingIndicatorAttrs extends ComponentAttrs {
|
||||
* To set a custom color, use the CSS `color` property.
|
||||
*
|
||||
* To increase spacing around the spinner, use the CSS `height` property on the
|
||||
* spinner's **container**. Setting the `display` attribute to `block` will set
|
||||
* a height of `100px` by default.
|
||||
* spinner's **container**.
|
||||
*
|
||||
* To apply a custom size to the loading indicator, set the `--size` and
|
||||
* `--thickness` CSS custom properties on the loading indicator container.
|
||||
* `--thickness` custom properties on the loading indicator itself.
|
||||
*
|
||||
* If you *really* want to change how this looks as part of your custom theme,
|
||||
* If you really want to change how this looks as part of your custom theme,
|
||||
* you can override the `border-radius` and `border` then set either a
|
||||
* background image, or use `content: "\<glyph>"` (e.g. `content: "\f1ce"`)
|
||||
* and `font-family: 'Font Awesome 5 Free'` to set an FA icon if you'd rather.
|
||||
@@ -47,33 +40,21 @@ export interface LoadingIndicatorAttrs extends ComponentAttrs {
|
||||
*
|
||||
* - `containerClassName` Class name(s) to apply to the indicator's parent
|
||||
* - `className` Class name(s) to apply to the indicator itself
|
||||
* - `display` Determines how the spinner should be displayed (`inline`, `block` (default) or `unset`)
|
||||
* - `size` Size of the loading indicator (`small`, `medium` or `large`)
|
||||
* - `size` Size of the loading indicator
|
||||
* - `containerAttrs` Optional attrs to be applied to the container DOM element
|
||||
*
|
||||
* All other attrs will be assigned as attributes on the DOM element.
|
||||
*/
|
||||
export default class LoadingIndicator extends Component<LoadingIndicatorAttrs> {
|
||||
view() {
|
||||
const { display = 'block', size = 'medium', containerClassName, className, ...attrs } = this.attrs;
|
||||
const { size, ...attrs } = this.attrs;
|
||||
|
||||
const completeClassName = classList('LoadingIndicator', className);
|
||||
const completeContainerClassName = classList(
|
||||
'LoadingIndicator-container',
|
||||
display !== 'unset' && `LoadingIndicator-container--${display}`,
|
||||
size && `LoadingIndicator-container--${size}`,
|
||||
containerClassName
|
||||
);
|
||||
attrs.className = classList({ LoadingIndicator: true, [attrs.className || '']: true });
|
||||
attrs.containerClassName = classList({ 'LoadingIndicator-container': true, [attrs.containerClassName || '']: true });
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-label={app.translator.trans('core.lib.loading_indicator.accessible_label')}
|
||||
role="status"
|
||||
{...attrs.containerAttrs}
|
||||
data-size={size}
|
||||
className={completeContainerClassName}
|
||||
>
|
||||
<div aria-hidden className={completeClassName} {...attrs} />
|
||||
<div {...attrs.containerAttrs} data-size={size} className={attrs.containerClassName}>
|
||||
<div {...attrs}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import Mithril from 'mithril';
|
||||
import ErrorPage from '../components/ErrorPage';
|
||||
|
||||
/**
|
||||
* Generates a route resolver for a given component.
|
||||
@@ -32,6 +33,12 @@ export default class DefaultResolver {
|
||||
}
|
||||
|
||||
onmatch(args, requestedPath, route) {
|
||||
const errorCode = app.data.errorCode;
|
||||
if (errorCode) {
|
||||
delete app.data.errorCode;
|
||||
return { view: () => ErrorPage.component({ errorCode }) };
|
||||
}
|
||||
|
||||
return this.component;
|
||||
}
|
||||
|
||||
|
@@ -52,13 +52,7 @@ export default class AvatarEditor extends Component {
|
||||
ondragend={this.disableDragover.bind(this)}
|
||||
ondrop={this.dropUpload.bind(this)}
|
||||
>
|
||||
{this.loading ? (
|
||||
<LoadingIndicator display="unset" size="large" />
|
||||
) : user.avatarUrl() ? (
|
||||
icon('fas fa-pencil-alt')
|
||||
) : (
|
||||
icon('fas fa-plus-circle')
|
||||
)}
|
||||
{this.loading ? <LoadingIndicator /> : user.avatarUrl() ? icon('fas fa-pencil-alt') : icon('fas fa-plus-circle')}
|
||||
</a>
|
||||
<ul className="Dropdown-menu Menu">{listItems(this.controlItems().toArray())}</ul>
|
||||
</div>
|
||||
|
@@ -5,7 +5,6 @@ import TextEditor from '../../common/components/TextEditor';
|
||||
import avatar from '../../common/helpers/avatar';
|
||||
import listItems from '../../common/helpers/listItems';
|
||||
import ItemList from '../../common/utils/ItemList';
|
||||
import classList from '../../common/utils/classList';
|
||||
|
||||
/**
|
||||
* The `ComposerBody` component handles the body, or the content, of the
|
||||
@@ -67,7 +66,7 @@ export default class ComposerBody extends Component {
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<LoadingIndicator display="unset" containerClassName={classList('ComposerBody-loading', this.loading && 'active')} size="large" />
|
||||
{LoadingIndicator.component({ className: 'ComposerBody-loading' + (this.loading ? ' active' : '') })}
|
||||
</div>
|
||||
</ConfirmDocumentUnload>
|
||||
);
|
||||
|
@@ -19,7 +19,7 @@ export default class DiscussionList extends Component {
|
||||
let loading;
|
||||
|
||||
if (state.isLoading()) {
|
||||
loading = <LoadingIndicator />;
|
||||
loading = LoadingIndicator.component();
|
||||
} else if (state.moreResults) {
|
||||
loading = Button.component(
|
||||
{
|
||||
|
@@ -73,25 +73,23 @@ export default class DiscussionPage extends Page {
|
||||
<div className="DiscussionPage">
|
||||
<DiscussionListPane state={app.discussions} />
|
||||
<div className="DiscussionPage-discussion">
|
||||
{discussion ? (
|
||||
[
|
||||
DiscussionHero.component({ discussion }),
|
||||
<div className="container">
|
||||
<nav className="DiscussionPage-nav">
|
||||
<ul>{listItems(this.sidebarItems().toArray())}</ul>
|
||||
</nav>
|
||||
<div className="DiscussionPage-stream">
|
||||
{PostStream.component({
|
||||
discussion,
|
||||
stream: this.stream,
|
||||
onPositionChange: this.positionChanged.bind(this),
|
||||
})}
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
) : (
|
||||
<LoadingIndicator />
|
||||
)}
|
||||
{discussion
|
||||
? [
|
||||
DiscussionHero.component({ discussion }),
|
||||
<div className="container">
|
||||
<nav className="DiscussionPage-nav">
|
||||
<ul>{listItems(this.sidebarItems().toArray())}</ul>
|
||||
</nav>
|
||||
<div className="DiscussionPage-stream">
|
||||
{PostStream.component({
|
||||
discussion,
|
||||
stream: this.stream,
|
||||
onPositionChange: this.positionChanged.bind(this),
|
||||
})}
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
: LoadingIndicator.component({ className: 'LoadingIndicator--block' })}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -84,7 +84,7 @@ export default class NotificationList extends Component {
|
||||
})
|
||||
: ''}
|
||||
{state.isLoading() ? (
|
||||
<LoadingIndicator />
|
||||
<LoadingIndicator className="LoadingIndicator--block" />
|
||||
) : pages.length ? (
|
||||
''
|
||||
) : (
|
||||
|
@@ -98,7 +98,7 @@ export default class Search extends Component {
|
||||
onblur={() => (this.hasFocus = false)}
|
||||
/>
|
||||
{this.loadingSources ? (
|
||||
<LoadingIndicator size="small" display="inline" containerClassName="Button Button--icon Button--link" />
|
||||
LoadingIndicator.component({ size: 'tiny', className: 'Button Button--icon Button--link' })
|
||||
) : currentSearch ? (
|
||||
<button className="Search-clear Button Button--icon Button--link" onclick={this.clear.bind(this)}>
|
||||
{icon('fas fa-times-circle')}
|
||||
@@ -114,15 +114,6 @@ export default class Search extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
updateMaxHeight() {
|
||||
// Since extensions might add elements above the search box on mobile,
|
||||
// we need to calculate and set the max height dynamically.
|
||||
const resultsElementMargin = 14;
|
||||
const maxHeight =
|
||||
window.innerHeight - this.element.querySelector('.Search-input>.FormControl').getBoundingClientRect().bottom - resultsElementMargin;
|
||||
this.element.querySelector('.Search-results').style['max-height'] = `${maxHeight}px`;
|
||||
}
|
||||
|
||||
onupdate() {
|
||||
// Highlight the item that is currently selected.
|
||||
this.setIndex(this.getCurrentNumericIndex());
|
||||
@@ -130,7 +121,12 @@ export default class Search extends Component {
|
||||
// If there are no sources, the search view is not shown.
|
||||
if (!this.sources.length) return;
|
||||
|
||||
this.updateMaxHeight();
|
||||
// Since extensions might add elements above the search box on mobile,
|
||||
// we need to calculate and set the max height dynamically.
|
||||
const resultsElementMargin = 14;
|
||||
const maxHeight =
|
||||
window.innerHeight - this.element.querySelector('.Search-input>.FormControl').getBoundingClientRect().bottom - resultsElementMargin;
|
||||
this.element.querySelector('.Search-results').style['max-height'] = `${maxHeight}px`;
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
@@ -195,13 +191,6 @@ export default class Search extends Component {
|
||||
.one('mouseup', (e) => e.preventDefault())
|
||||
.select();
|
||||
});
|
||||
|
||||
this.updateMaxHeightHandler = this.updateMaxHeight.bind(this);
|
||||
window.addEventListener('resize', this.updateMaxHeightHandler);
|
||||
}
|
||||
|
||||
onremove() {
|
||||
window.removeEventListener('resize', this.updateMaxHeightHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -51,7 +51,7 @@ export default class UserPage extends Page {
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
: [<LoadingIndicator display="block" />]}
|
||||
: [<LoadingIndicator className="LoadingIndicator--block" />]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -90,11 +90,14 @@
|
||||
.Button-label {
|
||||
.transition(margin-right 0.1s);
|
||||
}
|
||||
|
||||
.LoadingIndicator-container {
|
||||
.LoadingIndicator {
|
||||
color: inherit;
|
||||
margin-top: -0.175em;
|
||||
margin-left: 4px;
|
||||
margin: 0 -5px 0 -15px;
|
||||
}
|
||||
&.loading {
|
||||
.Button-label {
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,17 @@
|
||||
|
||||
.LoadingIndicator {
|
||||
@spin-time: 750ms;
|
||||
--size: 24px;
|
||||
--thickness: 2px;
|
||||
|
||||
&-container[data-size="large"] & {
|
||||
--size: 32px;
|
||||
--thickness: 3px;
|
||||
}
|
||||
|
||||
&-container[data-size="tiny"] & {
|
||||
--size: 18px;
|
||||
}
|
||||
|
||||
// Use the value of `color` to maintain backwards compatibility
|
||||
border-color: currentColor;
|
||||
@@ -19,9 +30,6 @@
|
||||
// <div> container around the spinner
|
||||
// Used for positioning
|
||||
&-container {
|
||||
--size: 24px;
|
||||
--thickness: 2px;
|
||||
|
||||
color: @muted-color;
|
||||
|
||||
// Center vertically and horizontally
|
||||
@@ -30,26 +38,12 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
// Size
|
||||
|
||||
&--large {
|
||||
--size: 32px;
|
||||
--thickness: 3px;
|
||||
}
|
||||
|
||||
&--small {
|
||||
--size: 18px;
|
||||
}
|
||||
|
||||
// Display types
|
||||
|
||||
&--block {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
&--inline {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,8 +8,7 @@
|
||||
&.focused {
|
||||
margin-left: -400px;
|
||||
|
||||
input,
|
||||
.Search-results {
|
||||
input, .Search-results {
|
||||
width: 400px;
|
||||
}
|
||||
}
|
||||
@@ -62,21 +61,11 @@
|
||||
.transition(all 0.4s);
|
||||
box-sizing: inherit !important;
|
||||
}
|
||||
|
||||
.LoadingIndicator-container {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.Button {
|
||||
float: left;
|
||||
margin-left: -36px;
|
||||
outline: none;
|
||||
width: 36px !important;
|
||||
|
||||
&.LoadingIndicator {
|
||||
width: var(--size) !important;
|
||||
padding: 0;
|
||||
}
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -23,7 +23,7 @@
|
||||
&.dragover .Dropdown-toggle {
|
||||
opacity: 1;
|
||||
}
|
||||
.LoadingIndicator-container {
|
||||
.LoadingIndicator {
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
@@ -92,7 +92,7 @@
|
||||
border-radius: @border-radius @border-radius 0 0;
|
||||
|
||||
&.active {
|
||||
display: flex;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.ComposerBody-editor {
|
||||
|
@@ -303,6 +303,15 @@ core:
|
||||
username_heading: => core.ref.username
|
||||
username_label: => core.ref.username
|
||||
|
||||
error:
|
||||
401_text: You do not have permission to access this page. Try again after logging in.
|
||||
403_text: You do not have permission to access this page.
|
||||
404_text: The page you requested could not be found.
|
||||
401_title: Not Authenticated
|
||||
403_title: Permission Denied
|
||||
404_title: Not Found
|
||||
404_link_text: "Return to {forum}"
|
||||
|
||||
# These translations are used in the Forgot Password modal dialog.
|
||||
forgot_password:
|
||||
dismiss_button: => core.ref.okay
|
||||
@@ -483,10 +492,6 @@ core:
|
||||
permission_denied_message: You do not have permission to do that.
|
||||
rate_limit_exceeded_message: You're going a little too quickly. Please try again in a few seconds.
|
||||
|
||||
# These translations are used in the loading indicator component.
|
||||
loading_indicator:
|
||||
accessible_label: => core.ref.loading
|
||||
|
||||
# These translations are used as suffixes when abbreviating numbers.
|
||||
number_suffix:
|
||||
kilo_text: K
|
||||
@@ -509,7 +514,7 @@ core:
|
||||
content:
|
||||
javascript_disabled_message: This site is best viewed in a modern browser with JavaScript enabled.
|
||||
load_error_message: Something went wrong while trying to load the full version of this site. Try hard-refreshing this page to fix the error.
|
||||
loading_text: => core.ref.loading
|
||||
loading_text: Loading...
|
||||
|
||||
# Translations in this namespace are displayed in the basic HTML discussion view.
|
||||
discussion:
|
||||
@@ -521,10 +526,6 @@ core:
|
||||
csrf_token_mismatch: You have been inactive for too long.
|
||||
csrf_token_mismatch_return_link: Go back, to try again
|
||||
invalid_confirmation_token: This confirmation link has already been used or is invalid.
|
||||
not_authenticated: You do not have permission to access this page. Try again after logging in.
|
||||
not_found: The page you requested could not be found.
|
||||
not_found_return_link: "Return to {forum}"
|
||||
permission_denied: You do not have permission to access this page.
|
||||
unknown: An error occurred while trying to load this page.
|
||||
|
||||
# Translations in this namespace are displayed by the basic HTML discussion index.
|
||||
@@ -628,7 +629,6 @@ core:
|
||||
icon: Icon
|
||||
icon_text: "Enter the name of any <a>FontAwesome</a> icon class, <em>including</em> the <code>fas fa-</code> prefix."
|
||||
load_more: Load More
|
||||
loading: Loading...
|
||||
log_in: Log In
|
||||
log_out: Log Out
|
||||
mark_all_as_read: Mark All as Read
|
||||
|
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
|
||||
return [
|
||||
'up' => function (Builder $schema) {
|
||||
if (! $schema->hasColumn('migrations', 'id')) {
|
||||
$schema->table('migrations', function (Blueprint $table) {
|
||||
$table->increments('id')->first();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
'down' => function (Builder $schema) {
|
||||
$schema->table('migrations', function (Blueprint $table) {
|
||||
$table->dropColumn('id');
|
||||
});
|
||||
}
|
||||
];
|
@@ -12,6 +12,7 @@ namespace Flarum\Admin;
|
||||
use Flarum\Extension\Event\Disabled;
|
||||
use Flarum\Extension\Event\Enabled;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\ErrorHandling\FrontendFormatter;
|
||||
use Flarum\Foundation\ErrorHandling\Registry;
|
||||
use Flarum\Foundation\ErrorHandling\Reporter;
|
||||
use Flarum\Foundation\ErrorHandling\ViewFormatter;
|
||||
@@ -66,6 +67,7 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
return new HttpMiddleware\HandleErrors(
|
||||
$this->container->make(Registry::class),
|
||||
$this->container['flarum.config']->inDebugMode() ? $this->container->make(WhoopsFormatter::class) : $this->container->make(ViewFormatter::class),
|
||||
$this->container->make(FrontendFormatter::class),
|
||||
$this->container->tagged(Reporter::class)
|
||||
);
|
||||
});
|
||||
|
@@ -73,9 +73,12 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
});
|
||||
|
||||
$this->container->bind('flarum.api.error_handler', function () {
|
||||
$jsonFormatter = new JsonApiFormatter($this->container['flarum.config']->inDebugMode());
|
||||
|
||||
return new HttpMiddleware\HandleErrors(
|
||||
$this->container->make(Registry::class),
|
||||
new JsonApiFormatter($this->container['flarum.config']->inDebugMode()),
|
||||
$jsonFormatter,
|
||||
$jsonFormatter, // This won't be used.
|
||||
$this->container->tagged(Reporter::class)
|
||||
);
|
||||
});
|
||||
|
@@ -11,9 +11,8 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Filesystem\Factory;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use League\Flysystem\FilesystemInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class DeleteFaviconController extends AbstractDeleteController
|
||||
@@ -24,18 +23,18 @@ class DeleteFaviconController extends AbstractDeleteController
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @var Filesystem
|
||||
* @var FilesystemInterface
|
||||
*/
|
||||
protected $uploadDir;
|
||||
|
||||
/**
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
* @param Factory $filesystemFactory
|
||||
* @param FilesystemInterface $uploadDir
|
||||
*/
|
||||
public function __construct(SettingsRepositoryInterface $settings, Factory $filesystemFactory)
|
||||
public function __construct(SettingsRepositoryInterface $settings, FilesystemInterface $uploadDir)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
$this->uploadDir = $filesystemFactory->disk('flarum-assets');
|
||||
$this->uploadDir = $uploadDir;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +48,7 @@ class DeleteFaviconController extends AbstractDeleteController
|
||||
|
||||
$this->settings->set('favicon_path', null);
|
||||
|
||||
if ($this->uploadDir->exists($path)) {
|
||||
if ($this->uploadDir->has($path)) {
|
||||
$this->uploadDir->delete($path);
|
||||
}
|
||||
|
||||
|
@@ -11,9 +11,8 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Filesystem\Factory;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use League\Flysystem\FilesystemInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class DeleteLogoController extends AbstractDeleteController
|
||||
@@ -24,18 +23,18 @@ class DeleteLogoController extends AbstractDeleteController
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @var Filesystem
|
||||
* @var FilesystemInterface
|
||||
*/
|
||||
protected $uploadDir;
|
||||
|
||||
/**
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
* @param Factory $filesystemFactory
|
||||
* @param FilesystemInterface $uploadDir
|
||||
*/
|
||||
public function __construct(SettingsRepositoryInterface $settings, Factory $filesystemFactory)
|
||||
public function __construct(SettingsRepositoryInterface $settings, FilesystemInterface $uploadDir)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
$this->uploadDir = $filesystemFactory->disk('flarum-assets');
|
||||
$this->uploadDir = $uploadDir;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +48,7 @@ class DeleteLogoController extends AbstractDeleteController
|
||||
|
||||
$this->settings->set('logo_path', null);
|
||||
|
||||
if ($this->uploadDir->exists($path)) {
|
||||
if ($this->uploadDir->has($path)) {
|
||||
$this->uploadDir->delete($path);
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Illuminate\Container\Container;
|
||||
use Illuminate\Contracts\Mail\Mailer;
|
||||
use Illuminate\Mail\Message;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
@@ -20,12 +21,15 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class SendTestMailController implements RequestHandlerInterface
|
||||
{
|
||||
protected $container;
|
||||
|
||||
protected $mailer;
|
||||
|
||||
protected $translator;
|
||||
|
||||
public function __construct(Mailer $mailer, TranslatorInterface $translator)
|
||||
public function __construct(Container $container, Mailer $mailer, TranslatorInterface $translator)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->mailer = $mailer;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
@@ -11,11 +11,10 @@ namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Filesystem\Factory;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Intervention\Image\Image;
|
||||
use League\Flysystem\FilesystemInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
@@ -28,7 +27,7 @@ abstract class UploadImageController extends ShowForumController
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @var Filesystem
|
||||
* @var FilesystemInterface
|
||||
*/
|
||||
protected $uploadDir;
|
||||
|
||||
@@ -49,12 +48,12 @@ abstract class UploadImageController extends ShowForumController
|
||||
|
||||
/**
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
* @param Factory $filesystemFactory
|
||||
* @param FilesystemInterface $uploadDir
|
||||
*/
|
||||
public function __construct(SettingsRepositoryInterface $settings, Factory $filesystemFactory)
|
||||
public function __construct(SettingsRepositoryInterface $settings, FilesystemInterface $uploadDir)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
$this->uploadDir = $filesystemFactory->disk('flarum-assets');
|
||||
$this->uploadDir = $uploadDir;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +73,7 @@ abstract class UploadImageController extends ShowForumController
|
||||
|
||||
$uploadName = $this->filenamePrefix.'-'.Str::lower(Str::random(8)).'.'.$this->fileExtension;
|
||||
|
||||
$this->uploadDir->put($uploadName, $encodedImage);
|
||||
$this->uploadDir->write($uploadName, $encodedImage);
|
||||
|
||||
$this->settings->set($this->filePathSettingKey, $uploadName);
|
||||
|
||||
|
@@ -13,8 +13,6 @@ use Flarum\Foundation\Application;
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Filesystem\Cloud;
|
||||
use Illuminate\Contracts\Filesystem\Factory;
|
||||
|
||||
class ForumSerializer extends AbstractSerializer
|
||||
{
|
||||
@@ -38,21 +36,14 @@ class ForumSerializer extends AbstractSerializer
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @var Cloud
|
||||
*/
|
||||
protected $assetsFilesystem;
|
||||
|
||||
/**
|
||||
* @param Config $config
|
||||
* @param Factory $filesystemFactory
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
* @param UrlGenerator $url
|
||||
*/
|
||||
public function __construct(Config $config, Factory $filesystemFactory, SettingsRepositoryInterface $settings, UrlGenerator $url)
|
||||
public function __construct(Config $config, SettingsRepositoryInterface $settings, UrlGenerator $url)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->assetsFilesystem = $filesystemFactory->disk('flarum-assets');
|
||||
$this->settings = $settings;
|
||||
$this->url = $url;
|
||||
}
|
||||
@@ -116,7 +107,7 @@ class ForumSerializer extends AbstractSerializer
|
||||
{
|
||||
$logoPath = $this->settings->get('logo_path');
|
||||
|
||||
return $logoPath ? $this->getAssetUrl($logoPath) : null;
|
||||
return $logoPath ? $this->url->to('forum')->path('assets/'.$logoPath) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,11 +117,6 @@ class ForumSerializer extends AbstractSerializer
|
||||
{
|
||||
$faviconPath = $this->settings->get('favicon_path');
|
||||
|
||||
return $faviconPath ? $this->getAssetUrl($faviconPath) : null;
|
||||
}
|
||||
|
||||
public function getAssetUrl($assetPath): string
|
||||
{
|
||||
return $this->assetsFilesystem->url($assetPath);
|
||||
return $faviconPath ? $this->url->to('forum')->path('assets/'.$faviconPath) : null;
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,6 @@ namespace Flarum\Console;
|
||||
use Flarum\Database\Console\MigrateCommand;
|
||||
use Flarum\Database\Console\ResetCommand;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\Console\AssetsPublishCommand;
|
||||
use Flarum\Foundation\Console\CacheClearCommand;
|
||||
use Flarum\Foundation\Console\InfoCommand;
|
||||
use Illuminate\Console\Scheduling\Schedule as LaravelSchedule;
|
||||
@@ -38,7 +37,6 @@ class ConsoleServiceProvider extends AbstractServiceProvider
|
||||
|
||||
$this->container->singleton('flarum.console.commands', function () {
|
||||
return [
|
||||
AssetsPublishCommand::class,
|
||||
CacheClearCommand::class,
|
||||
InfoCommand::class,
|
||||
MigrateCommand::class,
|
||||
|
@@ -88,5 +88,12 @@ class MigrateCommand extends AbstractCommand
|
||||
}
|
||||
|
||||
$this->container->make(SettingsRepositoryInterface::class)->set('version', Application::VERSION);
|
||||
|
||||
$this->info('Publishing assets...');
|
||||
|
||||
$this->container->make('files')->copyDirectory(
|
||||
$this->paths->vendor.'/components/font-awesome/webfonts',
|
||||
$this->paths->public.'/assets/fonts'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -98,7 +98,6 @@ class DatabaseMigrationRepository implements MigrationRepositoryInterface
|
||||
$schema = $this->connection->getSchemaBuilder();
|
||||
|
||||
$schema->create($this->table, function ($table) {
|
||||
$table->increments('id');
|
||||
$table->string('migration');
|
||||
$table->string('extension')->nullable();
|
||||
});
|
||||
|
@@ -9,7 +9,6 @@
|
||||
|
||||
namespace Flarum\Discussion\Search\Gambit;
|
||||
|
||||
use Flarum\Discussion\Discussion;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Search\GambitInterface;
|
||||
use Flarum\Search\SearchState;
|
||||
@@ -30,11 +29,6 @@ class FulltextGambit implements GambitInterface
|
||||
$query = $search->getQuery();
|
||||
$grammar = $query->getGrammar();
|
||||
|
||||
$discussionSubquery = Discussion::select('id')
|
||||
->selectRaw('NULL as score')
|
||||
->selectRaw('first_post_id as most_relevant_post_id')
|
||||
->whereRaw('MATCH('.$grammar->wrap('discussions.title').') AGAINST (? IN BOOLEAN MODE)', [$bit]);
|
||||
|
||||
// Construct a subquery to fetch discussions which contain relevant
|
||||
// posts. Retrieve the collective relevance of each discussion's posts,
|
||||
// which we will use later in the order by clause, and also retrieve
|
||||
@@ -45,8 +39,7 @@ class FulltextGambit implements GambitInterface
|
||||
->selectRaw('SUBSTRING_INDEX(GROUP_CONCAT('.$grammar->wrap('posts.id').' ORDER BY MATCH('.$grammar->wrap('posts.content').') AGAINST (?) DESC, '.$grammar->wrap('posts.number').'), \',\', 1) as most_relevant_post_id', [$bit])
|
||||
->where('posts.type', 'comment')
|
||||
->whereRaw('MATCH('.$grammar->wrap('posts.content').') AGAINST (? IN BOOLEAN MODE)', [$bit])
|
||||
->groupBy('posts.discussion_id')
|
||||
->union($discussionSubquery);
|
||||
->groupBy('posts.discussion_id');
|
||||
|
||||
// Join the subquery into the main search query and scope results to
|
||||
// discussions that have a relevant title or that contain relevant posts.
|
||||
@@ -58,8 +51,11 @@ class FulltextGambit implements GambitInterface
|
||||
'=',
|
||||
'discussions.id'
|
||||
)
|
||||
->groupBy('discussions.id')
|
||||
->addBinding($subquery->getBindings(), 'join');
|
||||
->addBinding($subquery->getBindings(), 'join')
|
||||
->where(function ($query) use ($grammar, $bit) {
|
||||
$query->whereRaw('MATCH('.$grammar->wrap('discussions.title').') AGAINST (? IN BOOLEAN MODE)', [$bit])
|
||||
->orWhereNotNull('posts_ft.score');
|
||||
});
|
||||
|
||||
$search->setDefaultSort(function ($query) use ($grammar, $bit) {
|
||||
$query->orderByRaw('MATCH('.$grammar->wrap('discussions.title').') AGAINST (?) desc', [$bit]);
|
||||
|
@@ -42,7 +42,7 @@ class Event implements ExtenderInterface
|
||||
* Event subscribers are classes that may subscribe to multiple events from within the subscriber class itself,
|
||||
* allowing you to define several event handlers within a single class.
|
||||
*
|
||||
* @see https://laravel.com/docs/8.x/events#writing-event-subscribers
|
||||
* @see https://laravel.com/docs/6.x/events#writing-event-subscribers
|
||||
*
|
||||
* @param string $subscriber: The class attribute of the subscriber class
|
||||
*/
|
||||
|
@@ -1,85 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Filesystem implements ExtenderInterface
|
||||
{
|
||||
private $disks = [];
|
||||
private $drivers = [];
|
||||
|
||||
/**
|
||||
* Declare a new filesystem disk.
|
||||
* Disks represent storage locations, and are backed by storage drivers.
|
||||
* Flarum core uses disks for storing assets and avatars.
|
||||
*
|
||||
* By default, the "local" driver will be used for disks.
|
||||
* The "local" driver represents the filesystem where your Flarum installation is running.
|
||||
*
|
||||
* To declare a new disk, you must provide default configuration a "local" driver.
|
||||
*
|
||||
* @param string $name: The name of the disk
|
||||
* @param string|callable $callback: A callback or invokable class name with parameters:
|
||||
* - \Flarum\Foundation\Paths $paths
|
||||
* - \Flarum\Http\UrlGenerator $url
|
||||
* which returns a Laravel disk config array.
|
||||
* The `driver` key is not necessary for this array, and will be ignored.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* ->disk('flarum-uploads', function (Paths $paths, UrlGenerator $url) {
|
||||
* return [
|
||||
* 'root' => "$paths->public/assets/uploads",
|
||||
* 'url' => $url->to('forum')->path('assets/uploads')
|
||||
* ];
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @see https://laravel.com/docs/8.x/filesystem#configuration
|
||||
*/
|
||||
public function disk(string $name, $callback)
|
||||
{
|
||||
$this->disks[$name] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new filesystem driver.
|
||||
* Drivers must implement `\Flarum\Filesystem\DriverInterface`.
|
||||
*
|
||||
* @param string $name: The name of the driver
|
||||
* @param string $driverClass: The ::class attribute of the driver.
|
||||
*/
|
||||
public function driver(string $name, string $driverClass)
|
||||
{
|
||||
$this->drivers[$name] = $driverClass;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->extend('flarum.filesystem.disks', function ($existingDisks) use ($container) {
|
||||
foreach ($this->disks as $name => $disk) {
|
||||
$existingDisks[$name] = ContainerUtil::wrapCallback($disk, $container);
|
||||
}
|
||||
|
||||
return $existingDisks;
|
||||
});
|
||||
|
||||
$container->extend('flarum.filesystem.drivers', function ($existingDrivers) {
|
||||
return array_merge($existingDrivers, $this->drivers);
|
||||
});
|
||||
}
|
||||
}
|
@@ -18,7 +18,6 @@ class Formatter implements ExtenderInterface, LifecycleInterface
|
||||
{
|
||||
private $configurationCallbacks = [];
|
||||
private $parsingCallbacks = [];
|
||||
private $unparsingCallbacks = [];
|
||||
private $renderingCallbacks = [];
|
||||
|
||||
/**
|
||||
@@ -59,28 +58,6 @@ class Formatter implements ExtenderInterface, LifecycleInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the system for unparsing. This can be used to modify the text that was parsed.
|
||||
* Please note that the parsed text must be returned, regardless of whether it's changed.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callback can be a closure or invokable class, and should accept:
|
||||
* - mixed $context
|
||||
* - string $xml: The parsed text.
|
||||
*
|
||||
* The callback should return:
|
||||
* - string $xml: The text to be unparsed.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function unparse($callback)
|
||||
{
|
||||
$this->unparsingCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the system for rendering. This can be used to modify the xml that will be rendered, or to modify the renderer.
|
||||
* Please note that the xml to be rendered must be returned, regardless of whether it's changed.
|
||||
@@ -114,10 +91,6 @@ class Formatter implements ExtenderInterface, LifecycleInterface
|
||||
$formatter->addParsingCallback(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
|
||||
foreach ($this->unparsingCallbacks as $callback) {
|
||||
$formatter->addUnparsingCallback(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
|
||||
foreach ($this->renderingCallbacks as $callback) {
|
||||
$formatter->addRenderingCallback(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
|
@@ -73,7 +73,7 @@ class SimpleFlarumSearch implements ExtenderInterface
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
if (! is_null($this->fullTextGambit)) {
|
||||
$container->extend('flarum.simple_search.fulltext_gambits', function ($oldFulltextGambits) {
|
||||
$container->resolving('flarum.simple_search.fulltext_gambits', function ($oldFulltextGambits) {
|
||||
$oldFulltextGambits[$this->searcher] = $this->fullTextGambit;
|
||||
|
||||
return $oldFulltextGambits;
|
||||
|
@@ -27,7 +27,7 @@ class View implements ExtenderInterface, LifecycleInterface
|
||||
*
|
||||
* Views can then be used in your extension by injecting an instance of `Illuminate\Contracts\View\Factory`,
|
||||
* and calling its `make` method. The `make` method takes the view parameter in the format NAMESPACE::VIEW_NAME.
|
||||
* You can also pass variables into a view: for more information, see https://laravel.com/api/8.x/Illuminate/View/Factory.html#method_make
|
||||
* You can also pass variables into a view: for more information, see https://laravel.com/api/6.x/Illuminate/View/Factory.html#method_make
|
||||
*
|
||||
* @param string $namespace: The name of the namespace.
|
||||
* @param string|array $hints: This is a path (or an array of paths) to the folder(s)
|
||||
|
@@ -12,12 +12,15 @@ namespace Flarum\Extension;
|
||||
use Flarum\Database\Migrator;
|
||||
use Flarum\Extend\LifecycleInterface;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem as FilesystemInterface;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use League\Flysystem\Adapter\Local;
|
||||
use League\Flysystem\Filesystem;
|
||||
use League\Flysystem\FilesystemInterface;
|
||||
use League\Flysystem\MountManager;
|
||||
use League\Flysystem\Plugin\ListFiles;
|
||||
|
||||
/**
|
||||
* @property string $name
|
||||
@@ -154,7 +157,7 @@ class Extension implements Arrayable
|
||||
/**
|
||||
* Dot notation getter for composer.json attributes.
|
||||
*
|
||||
* @see https://laravel.com/docs/8.x/helpers#arrays
|
||||
* @see https://laravel.com/docs/5.1/helpers#arrays
|
||||
*
|
||||
* @param $name
|
||||
* @return mixed
|
||||
@@ -425,13 +428,16 @@ class Extension implements Arrayable
|
||||
return;
|
||||
}
|
||||
|
||||
$source = new Filesystem();
|
||||
$mount = new MountManager([
|
||||
'source' => $source = new Filesystem(new Local($this->getPath().'/assets')),
|
||||
'target' => $target,
|
||||
]);
|
||||
|
||||
$assetFiles = $source->allFiles("$this->path/assets");
|
||||
$source->addPlugin(new ListFiles);
|
||||
$assetFiles = $source->listFiles('/', true);
|
||||
|
||||
foreach ($assetFiles as $fullPath) {
|
||||
$relPath = substr($fullPath, strlen("$this->path/assets"));
|
||||
$target->put("extensions/$this->id/$relPath", $source->get($fullPath));
|
||||
foreach ($assetFiles as $file) {
|
||||
$mount->copy("source://$file[path]", "target://extensions/$this->id/$file[path]");
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -19,7 +19,6 @@ use Flarum\Foundation\Paths;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Contracts\Filesystem\Cloud;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
@@ -253,7 +252,12 @@ class ExtensionManager
|
||||
*/
|
||||
protected function publishAssets(Extension $extension)
|
||||
{
|
||||
$extension->copyAssetsTo($this->getAssetsFilesystem());
|
||||
if ($extension->hasAssets()) {
|
||||
$this->filesystem->copyDirectory(
|
||||
$extension->getPath().'/assets',
|
||||
$this->paths->public.'/assets/extensions/'.$extension->getId()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,7 +267,7 @@ class ExtensionManager
|
||||
*/
|
||||
protected function unpublishAssets(Extension $extension)
|
||||
{
|
||||
$this->getAssetsFilesystem()->deleteDirectory('extensions/'.$extension->getId());
|
||||
$this->filesystem->deleteDirectory($this->paths->public.'/assets/extensions/'.$extension->getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,17 +279,7 @@ class ExtensionManager
|
||||
*/
|
||||
public function getAsset(Extension $extension, $path)
|
||||
{
|
||||
return $this->getAssetsFilesystem()->url($extension->getId()."/$path");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the assets filesystem.
|
||||
* This is resolved dynamically because Flarum's filesystem configuration
|
||||
* might not be booted yet when the ExtensionManager singleton initializes.
|
||||
*/
|
||||
protected function getAssetsFilesystem(): Cloud
|
||||
{
|
||||
return resolve('filesystem')->disk('flarum-assets');
|
||||
return $this->paths->public.'/assets/extensions/'.$extension->getId().$path;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filesystem;
|
||||
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Filesystem\Cloud;
|
||||
|
||||
interface DriverInterface
|
||||
{
|
||||
/**
|
||||
* Construct a Laravel Cloud filesystem for this filesystem driver.
|
||||
* Settings and configuration can either be pulled from the Flarum settings repository
|
||||
* or the config.php file.
|
||||
*
|
||||
* Typically, this is done by wrapping a Flysystem adapter in Laravel's
|
||||
* `Illuminate\Filesystem\FilesystemAdapter` class.
|
||||
* You should ensure that the Flysystem adapter you use has a `getUrl` method.
|
||||
* If it doesn't, you should create a subclass implementing that method.
|
||||
* Otherwise, this driver won't work for public-facing disks
|
||||
* like `flarum-assets` or `flarum-avatars`.
|
||||
*
|
||||
* @param string $diskName: The name of a disk this driver is being used for.
|
||||
* This is generally used to locate disk-specific settings.
|
||||
* @param SettingsRepositoryInterface $settings: An instance of the Flarum settings repository.
|
||||
* @param Config $config: An instance of the wrapper class around `config.php`.
|
||||
* @param array $localConfig: The configuration array that would have been used
|
||||
* if this disk were using the 'local' filesystem driver.
|
||||
* Some of these settings might be useful (e.g. visibility, )
|
||||
*/
|
||||
public function build(string $diskName, SettingsRepositoryInterface $settings, Config $config, array $localConfig): Cloud;
|
||||
}
|
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filesystem;
|
||||
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Foundation\Paths;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Filesystem\FilesystemManager as LaravelFilesystemManager;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class FilesystemManager extends LaravelFilesystemManager
|
||||
{
|
||||
protected $diskLocalConfig = [];
|
||||
protected $drivers = [];
|
||||
|
||||
public function __construct(Container $app, array $diskLocalConfig, array $drivers)
|
||||
{
|
||||
parent::__construct($app);
|
||||
|
||||
$this->diskLocalConfig = $diskLocalConfig;
|
||||
$this->drivers = $drivers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function resolve($name): Filesystem
|
||||
{
|
||||
$driver = $this->getDriver($name);
|
||||
|
||||
$localConfig = $this->getLocalConfig($name);
|
||||
|
||||
if (empty($localConfig)) {
|
||||
throw new InvalidArgumentException("Disk [{$name}] has not been declared. Use the Filesystem extender to do this.");
|
||||
}
|
||||
|
||||
if ($driver === 'local') {
|
||||
return $this->createLocalDriver($localConfig);
|
||||
}
|
||||
|
||||
$settings = $this->app->make(SettingsRepositoryInterface::class);
|
||||
$config = $this->app->make(Config::class);
|
||||
|
||||
return $driver->build($name, $settings, $config, $localConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|DriverInterface
|
||||
*/
|
||||
protected function getDriver(string $name)
|
||||
{
|
||||
$config = $this->app->make(Config::class);
|
||||
$settings = $this->app->make(SettingsRepositoryInterface::class);
|
||||
|
||||
$key = "disk_driver.$name";
|
||||
$configuredDriver = Arr::get($config, $key, $settings->get($key, 'local'));
|
||||
|
||||
return Arr::get($this->drivers, $configuredDriver, 'local');
|
||||
}
|
||||
|
||||
protected function getLocalConfig(string $name): array
|
||||
{
|
||||
if (! array_key_exists($name, $this->diskLocalConfig)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$paths = $this->app->make(Paths::class);
|
||||
$url = $this->app->make(UrlGenerator::class);
|
||||
|
||||
return $this->diskLocalConfig[$name]($paths, $url);
|
||||
}
|
||||
}
|
@@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Filesystem;
|
||||
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\Paths;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
|
||||
class FilesystemServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->container->singleton('files', function () {
|
||||
return new Filesystem;
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.filesystem.disks', function () {
|
||||
return [
|
||||
'flarum-assets' => function (Paths $paths, UrlGenerator $url) {
|
||||
return [
|
||||
'root' => "$paths->public/assets",
|
||||
'url' => $url->to('forum')->path('assets')
|
||||
];
|
||||
},
|
||||
'flarum-avatars' => function (Paths $paths, UrlGenerator $url) {
|
||||
return [
|
||||
'root' => "$paths->public/assets/avatars",
|
||||
'url' => $url->to('forum')->path('assets/avatars')
|
||||
];
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.filesystem.drivers', function () {
|
||||
return [];
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.filesystem.resolved_drivers', function () {
|
||||
return array_map(function ($driverClass) {
|
||||
return $this->container->make($driverClass);
|
||||
}, $this->container->make('flarum.filesystem.drivers'));
|
||||
});
|
||||
|
||||
$this->container->singleton('filesystem', function () {
|
||||
return new FilesystemManager(
|
||||
$this->container,
|
||||
$this->container->make('flarum.filesystem.disks'),
|
||||
$this->container->make('flarum.filesystem.resolved_drivers')
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
@@ -9,24 +9,17 @@
|
||||
|
||||
namespace Flarum\Formatter;
|
||||
|
||||
use __PHP_Incomplete_Class;
|
||||
use ArrayObject;
|
||||
use Flarum\Frontend\Compiler\RevisionCompiler;
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use s9e\TextFormatter\Configurator;
|
||||
use s9e\TextFormatter\Unparser;
|
||||
|
||||
class Formatter extends RevisionCompiler
|
||||
class Formatter
|
||||
{
|
||||
protected $configurationCallbacks = [];
|
||||
|
||||
protected $parsingCallbacks = [];
|
||||
|
||||
protected $unparsingCallbacks = [];
|
||||
|
||||
protected $renderingCallbacks = [];
|
||||
|
||||
/**
|
||||
@@ -40,17 +33,13 @@ class Formatter extends RevisionCompiler
|
||||
protected $cacheDir;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
* @param Repository $cache
|
||||
* @param string $cacheDir
|
||||
*/
|
||||
protected static $formatter;
|
||||
|
||||
public function __construct(Repository $cache, string $cacheDir, Filesystem $assetsDir)
|
||||
public function __construct(Repository $cache, $cacheDir)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
$this->cacheDir = $cacheDir;
|
||||
$this->assetsDir = $assetsDir;
|
||||
|
||||
$this->filename = 'formatter';
|
||||
}
|
||||
|
||||
public function addConfigurationCallback($callback)
|
||||
@@ -63,11 +52,6 @@ class Formatter extends RevisionCompiler
|
||||
$this->parsingCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
public function addUnparsingCallback($callback)
|
||||
{
|
||||
$this->unparsingCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
public function addRenderingCallback($callback)
|
||||
{
|
||||
$this->renderingCallbacks[] = $callback;
|
||||
@@ -114,15 +98,10 @@ class Formatter extends RevisionCompiler
|
||||
* Unparse XML.
|
||||
*
|
||||
* @param string $xml
|
||||
* @param mixed $context
|
||||
* @return string
|
||||
*/
|
||||
public function unparse($xml, $context = null)
|
||||
public function unparse($xml)
|
||||
{
|
||||
foreach ($this->unparsingCallbacks as $callback) {
|
||||
$xml = $callback($context, $xml);
|
||||
}
|
||||
|
||||
return Unparser::unparse($xml);
|
||||
}
|
||||
|
||||
@@ -187,27 +166,13 @@ class Formatter extends RevisionCompiler
|
||||
* @param string $name "renderer" or "parser" or "js"
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getComponent(string $name)
|
||||
protected function getComponent($name)
|
||||
{
|
||||
if (! static::$formatter) {
|
||||
static::$formatter = $this->cache->rememberForever('flarum.formatter', function () {
|
||||
return $this->finalize();
|
||||
});
|
||||
}
|
||||
$formatter = $this->cache->rememberForever('flarum.formatter', function () {
|
||||
return $this->getConfigurator()->finalize();
|
||||
});
|
||||
|
||||
// We will now execute a check on disk, to see whether the requested renderer
|
||||
// is written to disk. In case cache is not a local file-based driver the below
|
||||
// `getRenderer()` method won't be able to autoload the file.
|
||||
if ($name === 'renderer') {
|
||||
$this->ensureRendererExists();
|
||||
}
|
||||
|
||||
// We will now check revisions and do a sanity check.
|
||||
if ($this->requiresRefresh()) {
|
||||
$this->finalize();
|
||||
}
|
||||
|
||||
return Arr::get(static::$formatter, $name);
|
||||
return $formatter[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,69 +215,4 @@ class Formatter extends RevisionCompiler
|
||||
{
|
||||
return $this->getComponent('js');
|
||||
}
|
||||
|
||||
protected function ensureRendererExists()
|
||||
{
|
||||
if (! static::$formatter) {
|
||||
return;
|
||||
}
|
||||
|
||||
$revision = $this->getRevision();
|
||||
|
||||
if (file_exists($this->cacheDir."/Renderer_$revision.php")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$renderer = Arr::get(static::$formatter, 'renderer');
|
||||
|
||||
if (! empty($renderer)) {
|
||||
file_put_contents($this->cacheDir."/Renderer_$revision.php", $renderer);
|
||||
} else {
|
||||
$this->finalize();
|
||||
}
|
||||
|
||||
// Reload because finalizing might have generated a new one.
|
||||
$renderer = Arr::get(static::$formatter, 'renderer');
|
||||
|
||||
if ($renderer && static::$formatter['renderer'] instanceof __PHP_Incomplete_Class) {
|
||||
// Autoload the file from disk using a simple include, while suppressing errors.
|
||||
@include $this->cacheDir."/Renderer_$revision.php";
|
||||
|
||||
// Reload the formatter again from cache to resolve the __PHP_Incomplete_Class
|
||||
static::$formatter = $this->cache->get('flarum.formatter');
|
||||
}
|
||||
}
|
||||
|
||||
protected function finalize()
|
||||
{
|
||||
$formatter = $this->getConfigurator()->finalize();
|
||||
|
||||
preg_match('~^Renderer\_(?<revision>[^\.]+)$~', get_class($formatter['renderer']), $m);
|
||||
$revision = $m['revision'];
|
||||
|
||||
$this->putRevision($revision);
|
||||
|
||||
return $formatter;
|
||||
}
|
||||
|
||||
protected function requiresRefresh(): bool
|
||||
{
|
||||
if (! $this->getRevision()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! Arr::get(static::$formatter, 'renderer')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$renderer = static::$formatter['renderer'] instanceof __PHP_Incomplete_Class
|
||||
? (new ArrayObject(static::$formatter['renderer']))['__PHP_Incomplete_Class_Name']
|
||||
: get_class(static::$formatter['renderer']);
|
||||
|
||||
if (preg_match('~^Renderer_(?<revision>[^\.]+)$~', $renderer, $m)) {
|
||||
return $this->getRevision() !== $m['revision'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -24,8 +24,7 @@ class FormatterServiceProvider extends AbstractServiceProvider
|
||||
$this->container->singleton('flarum.formatter', function (Container $container) {
|
||||
return new Formatter(
|
||||
new Repository($container->make('cache.filestore')),
|
||||
$container[Paths::class]->storage.'/formatter',
|
||||
$container->make('filesystem')->disk('flarum-assets')
|
||||
$this->container[Paths::class]->storage.'/formatter'
|
||||
);
|
||||
});
|
||||
|
||||
|
@@ -13,6 +13,8 @@ use Flarum\Extension\Event\Disabled;
|
||||
use Flarum\Extension\Event\Enabled;
|
||||
use Flarum\Formatter\Formatter;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\ErrorHandling\FrontendFormatter;
|
||||
use Flarum\Foundation\ErrorHandling\Middleware\ExecuteErrorToFrontend;
|
||||
use Flarum\Foundation\ErrorHandling\Registry;
|
||||
use Flarum\Foundation\ErrorHandling\Reporter;
|
||||
use Flarum\Foundation\ErrorHandling\ViewFormatter;
|
||||
@@ -32,6 +34,7 @@ use Flarum\Settings\Event\Saved;
|
||||
use Flarum\Settings\Event\Saving;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Laminas\Stratigility\MiddlewarePipe;
|
||||
use Laminas\Stratigility\MiddlewarePipeInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ForumServiceProvider extends AbstractServiceProvider
|
||||
@@ -77,6 +80,7 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
return new HttpMiddleware\HandleErrors(
|
||||
$this->container->make(Registry::class),
|
||||
$this->container['flarum.config']->inDebugMode() ? $this->container->make(WhoopsFormatter::class) : $this->container->make(ViewFormatter::class),
|
||||
$this->container->make(FrontendFormatter::class),
|
||||
$this->container->tagged(Reporter::class)
|
||||
);
|
||||
});
|
||||
@@ -124,6 +128,30 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
$this->container->bind('flarum.frontend.forum', function () {
|
||||
return $this->container->make('flarum.frontend.factory')('forum');
|
||||
});
|
||||
|
||||
$this->container->when(FrontendFormatter::class)
|
||||
->needs(MiddlewarePipeInterface::class)
|
||||
->give(function () {
|
||||
$middleware = $this->container->make('flarum.forum.middleware');
|
||||
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
foreach ($middleware as $middlewareClass) {
|
||||
if (! in_array($middlewareClass, [
|
||||
'flarum.forum.error_handler',
|
||||
HttpMiddleware\InjectActorReference::class,
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
'flarum.forum.route_resolver',
|
||||
])) {
|
||||
$pipe->pipe($this->container->make($middlewareClass));
|
||||
}
|
||||
}
|
||||
|
||||
$pipe->pipe(new ExecuteErrorToFrontend('forum', $this->container->make(RouteHandlerFactory::class)));
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,82 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Foundation\Console;
|
||||
|
||||
use Flarum\Console\AbstractCommand;
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\Foundation\Paths;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
|
||||
class AssetsPublishCommand extends AbstractCommand
|
||||
{
|
||||
/**
|
||||
* @var Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* @var Paths
|
||||
*/
|
||||
protected $paths;
|
||||
|
||||
/**
|
||||
* @param Container $container
|
||||
* @param Paths $paths
|
||||
*/
|
||||
public function __construct(Container $container, Paths $paths)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->paths = $paths;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('assets:publish')
|
||||
->setDescription('Publish core and extension assets.');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function fire()
|
||||
{
|
||||
$this->info('Publishing core assets...');
|
||||
|
||||
$target = $this->container->make('filesystem')->disk('flarum-assets');
|
||||
$local = new Filesystem();
|
||||
|
||||
$pathPrefix = $this->paths->vendor.'/components/font-awesome/webfonts';
|
||||
$assetFiles = $local->allFiles($pathPrefix);
|
||||
|
||||
foreach ($assetFiles as $fullPath) {
|
||||
$relPath = substr($fullPath, strlen($pathPrefix));
|
||||
$target->put("fonts/$relPath", $local->get($fullPath));
|
||||
}
|
||||
|
||||
$this->info('Publishing extension assets...');
|
||||
|
||||
$extensions = $this->container->make(ExtensionManager::class);
|
||||
$extensions->getMigrator()->setOutput($this->output);
|
||||
|
||||
foreach ($extensions->getEnabledExtensions() as $name => $extension) {
|
||||
if ($extension->hasAssets()) {
|
||||
$this->info('Publishing for extension: '.$name);
|
||||
$extension->copyAssetsTo($target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
src/Foundation/ErrorHandling/FrontendFormatter.php
Normal file
35
src/Foundation/ErrorHandling/FrontendFormatter.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Foundation\ErrorHandling;
|
||||
|
||||
use Laminas\Stratigility\MiddlewarePipeInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
/**
|
||||
* This formatter will route errors to the SPA frontend.
|
||||
*/
|
||||
class FrontendFormatter implements HttpFormatter
|
||||
{
|
||||
/**
|
||||
* @var MiddlewarePipeInterface
|
||||
*/
|
||||
protected $pipe;
|
||||
|
||||
public function __construct(MiddlewarePipeInterface $pipe)
|
||||
{
|
||||
$this->pipe = $pipe;
|
||||
}
|
||||
|
||||
public function format(HandledError $error, Request $request): Response
|
||||
{
|
||||
return $this->pipe->handle($request->withAttribute('error', $error));
|
||||
}
|
||||
}
|
@@ -23,6 +23,7 @@ class HandledError
|
||||
private $error;
|
||||
private $type;
|
||||
private $statusCode;
|
||||
private $frontendContentClass;
|
||||
|
||||
private $details = [];
|
||||
|
||||
@@ -31,11 +32,12 @@ class HandledError
|
||||
return new static($error, 'unknown', 500);
|
||||
}
|
||||
|
||||
public function __construct(Throwable $error, $type, $statusCode)
|
||||
public function __construct(Throwable $error, $type, $statusCode, $frontendContentClass = null)
|
||||
{
|
||||
$this->error = $error;
|
||||
$this->type = $type;
|
||||
$this->statusCode = $statusCode;
|
||||
$this->frontendContentClass = $frontendContentClass;
|
||||
}
|
||||
|
||||
public function withDetails(array $details): self
|
||||
@@ -65,6 +67,11 @@ class HandledError
|
||||
return $this->type === 'unknown';
|
||||
}
|
||||
|
||||
public function contentClass()
|
||||
{
|
||||
return $this->frontendContentClass;
|
||||
}
|
||||
|
||||
public function getDetails(): array
|
||||
{
|
||||
return $this->details;
|
||||
|
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Foundation\ErrorHandling\Middleware;
|
||||
|
||||
use Flarum\Http\RouteHandlerFactory;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
class ExecuteErrorToFrontend implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $frontend;
|
||||
|
||||
/**
|
||||
* @var RouteHandlerFactory
|
||||
*/
|
||||
protected $handlerFactory;
|
||||
|
||||
public function __construct(string $frontend, RouteHandlerFactory $handlerFactory)
|
||||
{
|
||||
$this->frontend = $frontend;
|
||||
$this->handlerFactory = $handlerFactory;
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$error = $request->getAttribute('error');
|
||||
|
||||
$contentClass = $error->contentClass();
|
||||
$controller = $this->handlerFactory->toFrontend($this->frontend, new $contentClass);
|
||||
|
||||
return $controller($request, [])->withStatus($error->getStatusCode());
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Foundation\ErrorHandling;
|
||||
|
||||
use Flarum\Foundation\KnownError;
|
||||
use Illuminate\Support\Arr;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@@ -24,12 +25,14 @@ class Registry
|
||||
private $statusMap;
|
||||
private $classMap;
|
||||
private $handlerMap;
|
||||
private $contentMap;
|
||||
|
||||
public function __construct(array $statusMap, array $classMap, array $handlerMap)
|
||||
public function __construct(array $statusMap, array $classMap, array $handlerMap, array $contentMap)
|
||||
{
|
||||
$this->statusMap = $statusMap;
|
||||
$this->classMap = $classMap;
|
||||
$this->handlerMap = $handlerMap;
|
||||
$this->contentMap = $contentMap;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,20 +58,23 @@ class Registry
|
||||
{
|
||||
$errorType = null;
|
||||
|
||||
$errorClass = get_class($error);
|
||||
if ($error instanceof KnownError) {
|
||||
$errorType = $error->getType();
|
||||
} else {
|
||||
$errorClass = get_class($error);
|
||||
if (isset($this->classMap[$errorClass])) {
|
||||
$errorType = $this->classMap[$errorClass];
|
||||
}
|
||||
}
|
||||
|
||||
$errorContent = Arr::get($this->contentMap, $errorClass);
|
||||
|
||||
if ($errorType) {
|
||||
return new HandledError(
|
||||
$error,
|
||||
$errorType,
|
||||
$this->statusMap[$errorType] ?? 500
|
||||
$this->statusMap[$errorType] ?? 500,
|
||||
$errorContent
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -17,6 +17,12 @@ use Flarum\Foundation\ErrorHandling\ExceptionHandler;
|
||||
use Flarum\Foundation\ErrorHandling\LogReporter;
|
||||
use Flarum\Foundation\ErrorHandling\Registry;
|
||||
use Flarum\Foundation\ErrorHandling\Reporter;
|
||||
use Flarum\Http\Content\NotAuthenticated;
|
||||
use Flarum\Http\Content\NotFound;
|
||||
use Flarum\Http\Content\PermissionDenied;
|
||||
use Flarum\Http\Exception\RouteNotFoundException;
|
||||
use Flarum\User\Exception\NotAuthenticatedException;
|
||||
use Flarum\User\Exception\PermissionDeniedException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Validation\ValidationException as IlluminateValidationException;
|
||||
use Tobscure\JsonApi\Exception\InvalidParameterException;
|
||||
@@ -57,6 +63,15 @@ class ErrorServiceProvider extends AbstractServiceProvider
|
||||
];
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.error.contents', function () {
|
||||
return [
|
||||
NotAuthenticatedException::class => NotAuthenticated::class,
|
||||
PermissionDeniedException::class => PermissionDenied::class,
|
||||
ModelNotFoundException::class => NotFound::class,
|
||||
RouteNotFoundException::class => NotFound::class,
|
||||
];
|
||||
});
|
||||
|
||||
$this->container->singleton('flarum.error.handlers', function () {
|
||||
return [
|
||||
IlluminateValidationException::class => ExceptionHandler\IlluminateValidationExceptionHandler::class,
|
||||
@@ -70,7 +85,8 @@ class ErrorServiceProvider extends AbstractServiceProvider
|
||||
return new Registry(
|
||||
$this->container->make('flarum.error.statuses'),
|
||||
$this->container->make('flarum.error.classes'),
|
||||
$this->container->make('flarum.error.handlers')
|
||||
$this->container->make('flarum.error.handlers'),
|
||||
$this->container->make('flarum.error.contents'),
|
||||
);
|
||||
});
|
||||
|
||||
|
@@ -16,7 +16,6 @@ use Flarum\Console\ConsoleServiceProvider;
|
||||
use Flarum\Database\DatabaseServiceProvider;
|
||||
use Flarum\Discussion\DiscussionServiceProvider;
|
||||
use Flarum\Extension\ExtensionServiceProvider;
|
||||
use Flarum\Filesystem\FilesystemServiceProvider;
|
||||
use Flarum\Filter\FilterServiceProvider;
|
||||
use Flarum\Formatter\FormatterServiceProvider;
|
||||
use Flarum\Forum\ForumServiceProvider;
|
||||
@@ -40,6 +39,7 @@ use Illuminate\Contracts\Cache\Repository;
|
||||
use Illuminate\Contracts\Cache\Store;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Filesystem\FilesystemServiceProvider;
|
||||
use Illuminate\Hashing\HashServiceProvider;
|
||||
use Illuminate\Validation\ValidationServiceProvider;
|
||||
use Illuminate\View\ViewServiceProvider;
|
||||
@@ -166,6 +166,21 @@ class InstalledSite implements SiteInterface
|
||||
'mail' => [
|
||||
'driver' => 'mail',
|
||||
],
|
||||
'filesystems' => [
|
||||
'default' => 'local',
|
||||
'cloud' => 's3',
|
||||
'disks' => [
|
||||
'flarum-assets' => [
|
||||
'driver' => 'local',
|
||||
'root' => $this->paths->public.'/assets',
|
||||
'url' => $app->url('assets')
|
||||
],
|
||||
'flarum-avatars' => [
|
||||
'driver' => 'local',
|
||||
'root' => $this->paths->public.'/assets/avatars'
|
||||
]
|
||||
]
|
||||
],
|
||||
'session' => [
|
||||
'lifetime' => 120,
|
||||
'files' => $this->paths->storage.'/sessions',
|
||||
|
24
src/Http/Content/NotAuthenticated.php
Normal file
24
src/Http/Content/NotAuthenticated.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Http\Content;
|
||||
|
||||
use Flarum\Frontend\Document;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class NotAuthenticated
|
||||
{
|
||||
public function __invoke(Document $document, Request $request)
|
||||
{
|
||||
$document->title = 'Not Authenticated';
|
||||
$document->payload['errorCode'] = 401;
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
24
src/Http/Content/NotFound.php
Normal file
24
src/Http/Content/NotFound.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Http\Content;
|
||||
|
||||
use Flarum\Frontend\Document;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class NotFound
|
||||
{
|
||||
public function __invoke(Document $document, Request $request)
|
||||
{
|
||||
$document->title = 'Not Found';
|
||||
$document->payload['errorCode'] = 404;
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
24
src/Http/Content/PermissionDenied.php
Normal file
24
src/Http/Content/PermissionDenied.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Http\Content;
|
||||
|
||||
use Flarum\Frontend\Document;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class PermissionDenied
|
||||
{
|
||||
public function __invoke(Document $document, Request $request)
|
||||
{
|
||||
$document->title = 'Permission Denied';
|
||||
$document->payload['errorCode'] = 403;
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
@@ -13,7 +13,6 @@ use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\IdWithTransliteratedSlugDriver;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\IdSlugDriver;
|
||||
use Flarum\User\User;
|
||||
use Flarum\User\UsernameSlugDriver;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -39,8 +38,7 @@ class HttpServiceProvider extends AbstractServiceProvider
|
||||
'default' => IdWithTransliteratedSlugDriver::class
|
||||
],
|
||||
User::class => [
|
||||
'default' => UsernameSlugDriver::class,
|
||||
'id' => IdSlugDriver::class
|
||||
'default' => UsernameSlugDriver::class
|
||||
],
|
||||
];
|
||||
});
|
||||
@@ -60,6 +58,7 @@ class HttpServiceProvider extends AbstractServiceProvider
|
||||
|
||||
return $compiledDrivers;
|
||||
});
|
||||
|
||||
$this->container->bind(SlugManager::class, function () {
|
||||
return new SlugManager($this->container->make('flarum.http.selectedSlugDrivers'));
|
||||
});
|
||||
|
@@ -26,6 +26,8 @@ class AuthenticateWithSession implements Middleware
|
||||
|
||||
$actor = $this->getActor($session, $request);
|
||||
|
||||
$actor->setSession($session);
|
||||
|
||||
$request = RequestUtil::withActor($request, $actor);
|
||||
|
||||
return $handler->handle($request);
|
||||
|
@@ -36,15 +36,21 @@ class HandleErrors implements Middleware
|
||||
*/
|
||||
protected $formatter;
|
||||
|
||||
/**
|
||||
* @var HttpFormatter
|
||||
*/
|
||||
protected $frontendFormatter;
|
||||
|
||||
/**
|
||||
* @var \Flarum\Foundation\ErrorHandling\Reporter[]
|
||||
*/
|
||||
protected $reporters;
|
||||
|
||||
public function __construct(Registry $registry, HttpFormatter $formatter, iterable $reporters)
|
||||
public function __construct(Registry $registry, HttpFormatter $formatter, HttpFormatter $frontendFormatter, iterable $reporters)
|
||||
{
|
||||
$this->registry = $registry;
|
||||
$this->formatter = $formatter;
|
||||
$this->frontendFormatter = $frontendFormatter;
|
||||
$this->reporters = $reporters;
|
||||
}
|
||||
|
||||
@@ -64,6 +70,10 @@ class HandleErrors implements Middleware
|
||||
}
|
||||
}
|
||||
|
||||
if ($error->contentClass()) {
|
||||
return $this->frontendFormatter->format($error, $request);
|
||||
}
|
||||
|
||||
return $this->formatter->format($error, $request);
|
||||
}
|
||||
}
|
||||
|
@@ -15,7 +15,6 @@ use Flarum\Extension\Extension;
|
||||
use Flarum\Install\Step;
|
||||
use Flarum\Settings\DatabaseSettingsRepository;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use League\Flysystem\Adapter\Local;
|
||||
@@ -80,7 +79,7 @@ class EnableBundledExtensions implements Step
|
||||
foreach ($extensions as $extension) {
|
||||
$extension->migrate($this->getMigrator());
|
||||
$extension->copyAssetsTo(
|
||||
new FilesystemAdapter(new Filesystem(new Local($this->assetPath)))
|
||||
new Filesystem(new Local($this->assetPath))
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -128,7 +128,7 @@ class CommentPost extends Post
|
||||
*/
|
||||
public function getContentAttribute($value)
|
||||
{
|
||||
return static::$formatter->unparse($value, $this);
|
||||
return static::$formatter->unparse($value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -12,7 +12,7 @@ namespace Flarum\Search;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @todo This whole gambits thing needs way better documentation.
|
||||
*/
|
||||
class GambitManager
|
||||
{
|
||||
@@ -26,20 +26,12 @@ class GambitManager
|
||||
*/
|
||||
protected $fulltextGambit;
|
||||
|
||||
/**
|
||||
* @param GambitInterface $gambit
|
||||
*/
|
||||
public function __construct(GambitInterface $fulltextGambit)
|
||||
{
|
||||
$this->fulltextGambit = $fulltextGambit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a gambit.
|
||||
*
|
||||
* @param GambitInterface $gambit
|
||||
*/
|
||||
public function add(GambitInterface $gambit)
|
||||
public function add($gambit)
|
||||
{
|
||||
$this->gambits[] = $gambit;
|
||||
}
|
||||
@@ -59,6 +51,16 @@ class GambitManager
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gambit to handle fulltext searching.
|
||||
*
|
||||
* @param GambitInterface $gambit
|
||||
*/
|
||||
public function setFulltextGambit($gambit)
|
||||
{
|
||||
$this->fulltextGambit = $gambit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explode a search query into an array of bits.
|
||||
*
|
||||
@@ -108,6 +110,10 @@ class GambitManager
|
||||
*/
|
||||
protected function applyFulltext(SearchState $search, $query)
|
||||
{
|
||||
if (! $this->fulltextGambit) {
|
||||
return;
|
||||
}
|
||||
|
||||
$search->addActiveGambit($this->fulltextGambit);
|
||||
$this->fulltextGambit->apply($search, $query);
|
||||
}
|
||||
|
@@ -58,6 +58,9 @@ class SearchServiceProvider extends AbstractServiceProvider
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
// The rest of these we can resolve in the when->needs->give callback,
|
||||
// but we need to resolve at least one regardless so we know which
|
||||
// searchers we need to register gambits for.
|
||||
$fullTextGambits = $this->container->make('flarum.simple_search.fulltext_gambits');
|
||||
|
||||
foreach ($fullTextGambits as $searcher => $fullTextGambitClass) {
|
||||
@@ -65,7 +68,8 @@ class SearchServiceProvider extends AbstractServiceProvider
|
||||
->when($searcher)
|
||||
->needs(GambitManager::class)
|
||||
->give(function () use ($searcher, $fullTextGambitClass) {
|
||||
$gambitManager = new GambitManager($this->container->make($fullTextGambitClass));
|
||||
$gambitManager = new GambitManager();
|
||||
$gambitManager->setFulltextGambit($this->container->make($fullTextGambitClass));
|
||||
foreach (Arr::get($this->container->make('flarum.simple_search.gambits'), $searcher, []) as $gambit) {
|
||||
$gambitManager->add($this->container->make($gambit));
|
||||
}
|
||||
|
@@ -9,8 +9,15 @@
|
||||
|
||||
namespace Flarum\Settings;
|
||||
|
||||
use Flarum\Api\Controller\DeleteFaviconController;
|
||||
use Flarum\Api\Controller\DeleteLogoController;
|
||||
use Flarum\Api\Controller\UploadFaviconController;
|
||||
use Flarum\Api\Controller\UploadLogoController;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Filesystem\Factory;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use League\Flysystem\FilesystemInterface;
|
||||
|
||||
class SettingsServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
@@ -28,5 +35,18 @@ class SettingsServiceProvider extends AbstractServiceProvider
|
||||
});
|
||||
|
||||
$this->container->alias(SettingsRepositoryInterface::class, 'flarum.settings');
|
||||
|
||||
$assets = function (Container $container) {
|
||||
return $container->make(Factory::class)->disk('flarum-assets')->getDriver();
|
||||
};
|
||||
|
||||
$this->container->when([
|
||||
DeleteFaviconController::class,
|
||||
DeleteLogoController::class,
|
||||
UploadFaviconController::class,
|
||||
UploadLogoController::class,
|
||||
])
|
||||
->needs(FilesystemInterface::class)
|
||||
->give($assets);
|
||||
}
|
||||
}
|
||||
|
@@ -9,21 +9,17 @@
|
||||
|
||||
namespace Flarum\User;
|
||||
|
||||
use Illuminate\Contracts\Filesystem\Factory;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Support\Str;
|
||||
use Intervention\Image\Image;
|
||||
use League\Flysystem\FilesystemInterface;
|
||||
|
||||
class AvatarUploader
|
||||
{
|
||||
/**
|
||||
* @var Filesystem
|
||||
*/
|
||||
protected $uploadDir;
|
||||
|
||||
public function __construct(Factory $filesystemFactory)
|
||||
public function __construct(FilesystemInterface $uploadDir)
|
||||
{
|
||||
$this->uploadDir = $filesystemFactory->disk('flarum-avatars');
|
||||
$this->uploadDir = $uploadDir;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +52,7 @@ class AvatarUploader
|
||||
$avatarPath = $user->getRawOriginal('avatar_url');
|
||||
|
||||
$user->afterSave(function () use ($avatarPath) {
|
||||
if ($this->uploadDir->exists($avatarPath)) {
|
||||
if ($this->uploadDir->has($avatarPath)) {
|
||||
$this->uploadDir->delete($avatarPath);
|
||||
}
|
||||
});
|
||||
|
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User;
|
||||
|
||||
use Flarum\Database\AbstractModel;
|
||||
use Flarum\Http\SlugDriverInterface;
|
||||
|
||||
class IdSlugDriver implements SlugDriverInterface
|
||||
{
|
||||
/**
|
||||
* @var UserRepository
|
||||
*/
|
||||
protected $users;
|
||||
|
||||
public function __construct(UserRepository $users)
|
||||
{
|
||||
$this->users = $users;
|
||||
}
|
||||
|
||||
public function toSlug(AbstractModel $instance): string
|
||||
{
|
||||
return $instance->id;
|
||||
}
|
||||
|
||||
public function fromSlug(string $slug, User $actor): AbstractModel
|
||||
{
|
||||
return $this->users->findOrFail($slug, $actor);
|
||||
}
|
||||
}
|
@@ -18,6 +18,7 @@ use Flarum\Foundation\EventGeneratorTrait;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Group\Permission;
|
||||
use Flarum\Http\AccessToken;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Notification\Notification;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\User\DisplayName\DriverInterface;
|
||||
@@ -31,8 +32,8 @@ use Flarum\User\Event\Registered;
|
||||
use Flarum\User\Event\Renamed;
|
||||
use Flarum\User\Exception\NotAuthenticatedException;
|
||||
use Flarum\User\Exception\PermissionDeniedException;
|
||||
use Illuminate\Contracts\Filesystem\Factory;
|
||||
use Illuminate\Contracts\Hashing\Hasher;
|
||||
use Illuminate\Contracts\Session\Session;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
/**
|
||||
@@ -75,6 +76,11 @@ class User extends AbstractModel
|
||||
*/
|
||||
protected $permissions = null;
|
||||
|
||||
/**
|
||||
* @var Session
|
||||
*/
|
||||
protected $session;
|
||||
|
||||
/**
|
||||
* An array of callables, through each of which the user's list of groups is passed
|
||||
* before being returned.
|
||||
@@ -312,7 +318,7 @@ class User extends AbstractModel
|
||||
public function getAvatarUrlAttribute(string $value = null)
|
||||
{
|
||||
if ($value && strpos($value, '://') === false) {
|
||||
return resolve(Factory::class)->disk('flarum-avatars')->url($value);
|
||||
return app(UrlGenerator::class)->to('forum')->path('assets/avatars/'.$value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
@@ -780,6 +786,22 @@ class User extends AbstractModel
|
||||
return ! $this->can($ability, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Session
|
||||
*/
|
||||
public function getSession()
|
||||
{
|
||||
return $this->session;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Session $session
|
||||
*/
|
||||
public function setSession(Session $session)
|
||||
{
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the hasher with which to hash passwords.
|
||||
*
|
||||
|
@@ -24,7 +24,10 @@ use Flarum\User\DisplayName\UsernameDriver;
|
||||
use Flarum\User\Event\EmailChangeRequested;
|
||||
use Flarum\User\Event\Registered;
|
||||
use Flarum\User\Event\Saving;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Filesystem\Factory;
|
||||
use Illuminate\Support\Arr;
|
||||
use League\Flysystem\FilesystemInterface;
|
||||
|
||||
class UserServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
@@ -33,6 +36,7 @@ class UserServiceProvider extends AbstractServiceProvider
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->registerAvatarsFilesystem();
|
||||
$this->registerDisplayNameDrivers();
|
||||
$this->registerPasswordCheckers();
|
||||
|
||||
@@ -74,6 +78,17 @@ class UserServiceProvider extends AbstractServiceProvider
|
||||
$this->container->alias('flarum.user.display_name.driver', DriverInterface::class);
|
||||
}
|
||||
|
||||
protected function registerAvatarsFilesystem()
|
||||
{
|
||||
$avatarsFilesystem = function (Container $container) {
|
||||
return $container->make(Factory::class)->disk('flarum-avatars')->getDriver();
|
||||
};
|
||||
|
||||
$this->container->when(AvatarUploader::class)
|
||||
->needs(FilesystemInterface::class)
|
||||
->give($avatarsFilesystem);
|
||||
}
|
||||
|
||||
protected function registerPasswordCheckers()
|
||||
{
|
||||
$this->container->singleton('flarum.user.password_checkers', function () {
|
||||
|
@@ -31,9 +31,8 @@ class ListWithFulltextSearchTest extends TestCase
|
||||
// We clean it up explcitly at the end.
|
||||
$this->database()->table('discussions')->insert([
|
||||
['id' => 1, 'title' => 'lightsail in title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'comment_count' => 1],
|
||||
['id' => 2, 'title' => 'lightsail in title too', 'created_at' => Carbon::createFromDate(2020, 01, 01)->toDateTimeString(), 'user_id' => 1, 'comment_count' => 1],
|
||||
['id' => 2, 'title' => 'not in title and older', 'created_at' => Carbon::createFromDate(2020, 01, 01)->toDateTimeString(), 'user_id' => 1, 'comment_count' => 1],
|
||||
['id' => 3, 'title' => 'not in title either', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'comment_count' => 1],
|
||||
['id' => 4, 'title' => 'not in title or text', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'comment_count' => 1],
|
||||
]);
|
||||
|
||||
$this->database()->table('posts')->insert([
|
||||
@@ -41,7 +40,6 @@ class ListWithFulltextSearchTest extends TestCase
|
||||
['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>lightsail in text</p></t>'],
|
||||
['id' => 3, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>another lightsail for discussion 2!</p></t>'],
|
||||
['id' => 4, 'discussion_id' => 3, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>just one lightsail for discussion 3.</p></t>'],
|
||||
['id' => 5, 'discussion_id' => 4, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'type' => 'comment', 'content' => '<t><p>not in title or text</p></t>'],
|
||||
]);
|
||||
|
||||
// We need to call these again, since we rolled back the transaction started by `::app()`.
|
||||
@@ -57,14 +55,14 @@ class ListWithFulltextSearchTest extends TestCase
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
$this->database()->table('discussions')->delete();
|
||||
$this->database()->table('posts')->delete();
|
||||
$this->database()->table('discussions')->whereIn('id', [1, 2, 3])->delete();
|
||||
$this->database()->table('posts')->whereIn('id', [1, 2, 3, 4])->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function can_search_for_word_or_title_in_post()
|
||||
public function can_search_for_word_in_post()
|
||||
{
|
||||
$response = $this->send(
|
||||
$this->request('GET', '/api/discussions')
|
||||
@@ -79,7 +77,7 @@ class ListWithFulltextSearchTest extends TestCase
|
||||
return $row['id'];
|
||||
}, $data['data']);
|
||||
|
||||
$this->assertEquals(['2', '1', '3'], $ids, 'IDs do not match');
|
||||
$this->assertEquals(['2', '3'], $ids, 'IDs do not match');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,7 +98,7 @@ class ListWithFulltextSearchTest extends TestCase
|
||||
return $row['id'];
|
||||
}, $data['data']);
|
||||
|
||||
$this->assertEquals(['2', '1', '3'], $ids, 'IDs do not match');
|
||||
$this->assertEquals(['2', '3'], $ids, 'IDs do not match');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,156 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Filesystem\DriverInterface;
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Foundation\Paths;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Illuminate\Contracts\Filesystem\Cloud;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use InvalidArgumentException;
|
||||
use League\Flysystem\Adapter\Local;
|
||||
use League\Flysystem\Adapter\NullAdapter;
|
||||
use League\Flysystem\Filesystem as LeagueFilesystem;
|
||||
|
||||
class FilesystemTest extends TestCase
|
||||
{
|
||||
use RetrievesAuthorizedUsers;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_disk_doesnt_exist_by_default()
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->app()->getContainer()->make('filesystem')->disk('flarum-uploads');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_disk_exists_if_added_and_uses_local_adapter_by_default()
|
||||
{
|
||||
$this->extend((new Extend\Filesystem)->disk('flarum-uploads', function (Paths $paths, UrlGenerator $url) {
|
||||
return [
|
||||
'root' => "$paths->public/assets/uploads",
|
||||
'url' => $url->to('forum')->path('assets/uploads')
|
||||
];
|
||||
}));
|
||||
|
||||
$uploadsDisk = $this->app()->getContainer()->make('filesystem')->disk('flarum-uploads');
|
||||
|
||||
$this->assertEquals(get_class($uploadsDisk->getDriver()->getAdapter()), Local::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_disk_exists_if_added_via_invokable_class_and_uses_local_adapter_by_default()
|
||||
{
|
||||
$this->extend((new Extend\Filesystem)->disk('flarum-uploads', UploadsDisk::class));
|
||||
|
||||
$uploadsDisk = $this->app()->getContainer()->make('filesystem')->disk('flarum-uploads');
|
||||
|
||||
$this->assertEquals(get_class($uploadsDisk->getDriver()->getAdapter()), Local::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function disk_uses_local_adapter_if_configured_adapter_unavailable()
|
||||
{
|
||||
$this->app()->getContainer()->make(SettingsRepositoryInterface::class)->set('disk_driver.flarum-assets', 'nonexistent_driver');
|
||||
|
||||
$assetsDisk = $this->app()->getContainer()->make('filesystem')->disk('flarum-assets');
|
||||
|
||||
$this->assertEquals(get_class($assetsDisk->getDriver()->getAdapter()), Local::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function disk_uses_local_adapter_if_configured_adapter_from_config_file_unavailable()
|
||||
{
|
||||
$this->overrideConfigWithDiskDriver();
|
||||
|
||||
$assetsDisk = $this->app()->getContainer()->make('filesystem')->disk('flarum-assets');
|
||||
|
||||
$this->assertEquals(get_class($assetsDisk->getDriver()->getAdapter()), Local::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function disk_uses_custom_adapter_if_configured_and_available()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\Filesystem)->driver('null', NullFilesystemDriver::class)
|
||||
);
|
||||
|
||||
$this->app()->getContainer()->make(SettingsRepositoryInterface::class)->set('disk_driver.flarum-assets', 'null');
|
||||
|
||||
$assetsDisk = $this->app()->getContainer()->make('filesystem')->disk('flarum-assets');
|
||||
|
||||
$this->assertEquals(get_class($assetsDisk->getDriver()->getAdapter()), NullAdapter::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function disk_uses_custom_adapter_from_config_file_if_configured_and_available()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\Filesystem)->driver('null', NullFilesystemDriver::class)
|
||||
);
|
||||
|
||||
$this->overrideConfigWithDiskDriver();
|
||||
|
||||
$assetsDisk = $this->app()->getContainer()->make('filesystem')->disk('flarum-assets');
|
||||
|
||||
$this->assertEquals(get_class($assetsDisk->getDriver()->getAdapter()), NullAdapter::class);
|
||||
}
|
||||
|
||||
protected function overrideConfigWithDiskDriver()
|
||||
{
|
||||
$tmp = $this->tmpDir();
|
||||
$configArr = include "$tmp/config.php";
|
||||
$configArr = array_merge($configArr, ['disk_driver' => [
|
||||
'flarum-assets' => 'null'
|
||||
]]);
|
||||
|
||||
$config = new Config($configArr);
|
||||
|
||||
$this->app()->getContainer()->instance('flarum.config', $config);
|
||||
}
|
||||
}
|
||||
|
||||
class NullFilesystemDriver implements DriverInterface
|
||||
{
|
||||
public function build(string $diskName, SettingsRepositoryInterface $settings, Config $config, array $localConfig): Cloud
|
||||
{
|
||||
return new FilesystemAdapter(new LeagueFilesystem(new NullAdapter()));
|
||||
}
|
||||
}
|
||||
|
||||
class UploadsDisk
|
||||
{
|
||||
public function __invoke(Paths $paths, UrlGenerator $url)
|
||||
{
|
||||
return [
|
||||
'root' => "$paths->public/assets/uploads",
|
||||
'url' => $url->to('forum')->path('assets/uploads')
|
||||
];
|
||||
}
|
||||
}
|
@@ -89,36 +89,6 @@ class FormatterTest extends TestCase
|
||||
$this->assertEquals('<t>ReplacedText<a></t>', $this->getFormatter()->parse('Text<a>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_unparsing_doesnt_work_by_default()
|
||||
{
|
||||
$this->assertEquals('Text<a>', $this->getFormatter()->unparse('<t>Text<a></t>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_unparsing_works_if_added_with_closure()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->unparse(function ($context, $xml) {
|
||||
return '<t>ReplacedText<a></t>';
|
||||
}));
|
||||
|
||||
$this->assertEquals('ReplacedText<a>', $this->getFormatter()->unparse('<t>Text<a></t>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_unparsing_works_if_added_with_invokable_class()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->unparse(InvokableUnparsing::class));
|
||||
|
||||
$this->assertEquals('ReplacedText<a>', $this->getFormatter()->unparse('<t>Text<a></t>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
@@ -166,14 +136,6 @@ class InvokableParsing
|
||||
}
|
||||
}
|
||||
|
||||
class InvokableUnparsing
|
||||
{
|
||||
public function __invoke($context, $xml)
|
||||
{
|
||||
return '<t>ReplacedText<a></t>';
|
||||
}
|
||||
}
|
||||
|
||||
class InvokableRendering
|
||||
{
|
||||
public function __invoke($renderer, $context, $xml, $request)
|
||||
|
@@ -12,17 +12,13 @@ namespace Flarum\Tests\integration\extenders;
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Discussion\Search\DiscussionSearcher;
|
||||
use Flarum\Extend;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Query\QueryCriteria;
|
||||
use Flarum\Search\AbstractRegexGambit;
|
||||
use Flarum\Search\AbstractSearcher;
|
||||
use Flarum\Search\GambitInterface;
|
||||
use Flarum\Search\SearchState;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class SimpleFlarumSearchTest extends TestCase
|
||||
{
|
||||
@@ -139,36 +135,6 @@ class SimpleFlarumSearchTest extends TestCase
|
||||
|
||||
$this->assertEquals('[]', json_encode($this->searchDiscussions('in text', 5)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function cant_resolve_custom_searcher_without_fulltext_gambit()
|
||||
{
|
||||
$this->expectException(BindingResolutionException::class);
|
||||
|
||||
$this->app()->getContainer()->make(CustomSearcher::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function can_resolve_custom_searcher_with_fulltext_gambit()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\SimpleFlarumSearch(CustomSearcher::class))->setFullTextGambit(CustomFullTextGambit::class)
|
||||
);
|
||||
|
||||
$anExceptionWasThrown = false;
|
||||
|
||||
try {
|
||||
$this->app()->getContainer()->make(CustomSearcher::class);
|
||||
} catch (BindingResolutionException $e) {
|
||||
$anExceptionWasThrown = true;
|
||||
}
|
||||
|
||||
$this->assertFalse($anExceptionWasThrown);
|
||||
}
|
||||
}
|
||||
|
||||
class NoResultFullTextGambit implements GambitInterface
|
||||
@@ -213,19 +179,3 @@ class CustomSearchMutator
|
||||
$search->getQuery()->whereRaw('1=0');
|
||||
}
|
||||
}
|
||||
|
||||
class CustomSearcher extends AbstractSearcher
|
||||
{
|
||||
// This isn't actually used, we just need it to implement the abstract method.
|
||||
protected function getQuery(User $actor): Builder
|
||||
{
|
||||
return Group::query();
|
||||
}
|
||||
}
|
||||
|
||||
class CustomFullTextGambit implements GambitInterface
|
||||
{
|
||||
public function apply(SearchState $search, $bit)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@@ -13,17 +13,15 @@ use Flarum\Testing\unit\TestCase;
|
||||
use Flarum\User\AvatarUploader;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Contracts\Filesystem\Factory;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Intervention\Image\ImageManagerStatic;
|
||||
use League\Flysystem\FilesystemInterface;
|
||||
use Mockery as m;
|
||||
|
||||
class AvatarUploaderTest extends TestCase
|
||||
{
|
||||
private $dispatcher;
|
||||
private $filesystem;
|
||||
private $filesystemFactory;
|
||||
private $uploader;
|
||||
|
||||
/**
|
||||
@@ -35,15 +33,13 @@ class AvatarUploaderTest extends TestCase
|
||||
$this->dispatcher->shouldIgnoreMissing();
|
||||
Model::setEventDispatcher($this->dispatcher);
|
||||
|
||||
$this->filesystem = m::mock(Filesystem::class);
|
||||
$this->filesystemFactory = m::mock(Factory::class);
|
||||
$this->filesystemFactory->shouldReceive('disk')->with('flarum-avatars')->andReturn($this->filesystem);
|
||||
$this->uploader = new AvatarUploader($this->filesystemFactory);
|
||||
$this->filesystem = m::mock(FilesystemInterface::class);
|
||||
$this->uploader = new AvatarUploader($this->filesystem);
|
||||
}
|
||||
|
||||
public function test_removing_avatar_removes_file()
|
||||
{
|
||||
$this->filesystem->shouldReceive('exists')->with('ABCDEFGHabcdefgh.png')->andReturn(true);
|
||||
$this->filesystem->shouldReceive('has')->with('ABCDEFGHabcdefgh.png')->andReturn(true);
|
||||
$this->filesystem->shouldReceive('delete')->with('ABCDEFGHabcdefgh.png')->once();
|
||||
|
||||
$user = new User();
|
||||
@@ -65,7 +61,7 @@ class AvatarUploaderTest extends TestCase
|
||||
|
||||
public function test_removing_url_avatar_removes_no_file()
|
||||
{
|
||||
$this->filesystem->shouldReceive('exists')->with('https://example.com/avatar.png')->andReturn(false)->once();
|
||||
$this->filesystem->shouldReceive('has')->with('https://example.com/avatar.png')->andReturn(false)->once();
|
||||
$this->filesystem->shouldNotReceive('delete');
|
||||
|
||||
$user = new User();
|
||||
@@ -86,7 +82,7 @@ class AvatarUploaderTest extends TestCase
|
||||
public function test_changing_avatar_removes_file()
|
||||
{
|
||||
$this->filesystem->shouldReceive('put')->once();
|
||||
$this->filesystem->shouldReceive('exists')->with('ABCDEFGHabcdefgh.png')->andReturn(true);
|
||||
$this->filesystem->shouldReceive('has')->with('ABCDEFGHabcdefgh.png')->andReturn(true);
|
||||
$this->filesystem->shouldReceive('delete')->with('ABCDEFGHabcdefgh.png')->once();
|
||||
|
||||
$user = new User();
|
||||
|
@@ -1,12 +0,0 @@
|
||||
@extends('flarum.forum::layouts.basic')
|
||||
|
||||
@section('content')
|
||||
<p>
|
||||
{{ $message }}
|
||||
</p>
|
||||
<p>
|
||||
<a href="{{ $url->to('forum')->base() }}">
|
||||
{{ $translator->trans('core.views.error.not_found_return_link', ['{forum}' => $settings->get('forum_title')]) }}
|
||||
</a>
|
||||
</p>
|
||||
@endsection
|
Reference in New Issue
Block a user