mirror of
https://github.com/flarum/core.git
synced 2025-01-17 22:29:15 +01:00
Merge branch 'master' into 1236-database-changes
# Conflicts: # src/Forum/Controller/IndexController.php # src/User/UserMetadataUpdater.php
This commit is contained in:
commit
677a7dd2d3
2
.github/ISSUE_TEMPLATE/feature-request.md
vendored
2
.github/ISSUE_TEMPLATE/feature-request.md
vendored
@ -5,7 +5,7 @@ about: "I have a suggestion (and may want to implement it!)"
|
||||
---
|
||||
|
||||
<!--
|
||||
Note: The issue tracker is only for serious requests which have had some thought and planning put into them. If you just have an idea you would like to discuss, please post in the Flarum Community: https://discuss.flarum.org/t/feedback
|
||||
IMPORTANT: Feature requests on this GitHub issue tracker are only accepted in case they have been approved by a core developer or contain extensive argumentation and directions for implementation. For all other feature requests, ideas and feedback please post in the Flarum Community: https://discuss.flarum.org/t/feedback.
|
||||
-->
|
||||
|
||||
## Feature Request
|
||||
|
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,10 +1,24 @@
|
||||
<!--
|
||||
IMPORTANT: We applaud pull requests, they excite us every single time. As we have an obligation to maintain a healthy code standard and quality, we take sufficient time for reviews.
|
||||
-->
|
||||
|
||||
**Fixes #0000**
|
||||
|
||||
**Changes proposed in this pull request:**
|
||||
<!-- fill this out -->
|
||||
<!-- fill this out, mention the pages and/or components which have been impacted -->
|
||||
|
||||
**Reviewers should focus on:**
|
||||
<!-- fill this out -->
|
||||
<!-- fill this out, ask for feedback on specific changes you are unsure about -->
|
||||
|
||||
**Screenshot**
|
||||
<!-- include an image of the most relevant user-facing change, if any -->
|
||||
|
||||
**Confirmed**
|
||||
|
||||
- [ ] Frontend changes: tested on a local Flarum installation.
|
||||
- [ ] Backend changes: tests are green (run `php vendor/bin/phpunit`).
|
||||
|
||||
**Required changes:**
|
||||
|
||||
- [ ] Related documentation PR: (Remove if irrelevant)
|
||||
- [ ] Related core extension PRs: (Remove if irrelevant)
|
||||
|
@ -21,9 +21,10 @@
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"axy/sourcemap": "^0.1.4",
|
||||
"components/font-awesome": "^5.0.6",
|
||||
"dflydev/fig-cookies": "^1.0.2",
|
||||
"doctrine/dbal": "^2.5",
|
||||
"doctrine/dbal": "^2.7",
|
||||
"franzl/whoops-middleware": "^0.4.0",
|
||||
"illuminate/bus": "5.5.*",
|
||||
"illuminate/cache": "5.5.*",
|
||||
@ -45,11 +46,11 @@
|
||||
"matthiasmullie/minify": "^1.3",
|
||||
"monolog/monolog": "^1.16.0",
|
||||
"nikic/fast-route": "^0.6",
|
||||
"oyejorge/less.php": "~1.5",
|
||||
"oyejorge/less.php": "^1.7",
|
||||
"psr/http-message": "^1.0",
|
||||
"psr/http-server-handler": "^1.0",
|
||||
"psr/http-server-middleware": "^1.0",
|
||||
"s9e/text-formatter": "^0.8.1",
|
||||
"s9e/text-formatter": "^1.2.0",
|
||||
"symfony/config": "^3.3",
|
||||
"symfony/console": "^3.3",
|
||||
"symfony/http-foundation": "^3.3",
|
||||
|
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
4
js/dist/forum.js
vendored
4
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
@ -55,7 +55,7 @@ export default class AdminApplication extends Application {
|
||||
required.push('discussion.hide');
|
||||
}
|
||||
if (permission === 'discussion.deletePosts') {
|
||||
required.push('discussion.editPosts');
|
||||
required.push('discussion.hidePosts');
|
||||
}
|
||||
|
||||
return required;
|
||||
|
@ -206,10 +206,16 @@ export default class PermissionGrid extends Component {
|
||||
|
||||
items.add('editPosts', {
|
||||
icon: 'fas fa-pencil-alt',
|
||||
label: app.translator.trans('core.admin.permissions.edit_and_delete_posts_label'),
|
||||
label: app.translator.trans('core.admin.permissions.edit_posts_label'),
|
||||
permission: 'discussion.editPosts'
|
||||
}, 70);
|
||||
|
||||
items.add('hidePosts', {
|
||||
icon: 'far fa-trash-alt',
|
||||
label: app.translator.trans('core.admin.permissions.delete_posts_label'),
|
||||
permission: 'discussion.hidePosts'
|
||||
}, 60);
|
||||
|
||||
items.add('deletePosts', {
|
||||
icon: 'fas fa-times',
|
||||
label: app.translator.trans('core.admin.permissions.delete_posts_forever_label'),
|
||||
|
@ -1,6 +1,5 @@
|
||||
import User from './models/User';
|
||||
import username from './helpers/username';
|
||||
import extractText from './utils/extractText';
|
||||
import extract from './utils/extract';
|
||||
|
||||
/**
|
||||
@ -23,6 +22,10 @@ export default class Translator {
|
||||
this.locale = null;
|
||||
}
|
||||
|
||||
addTranslations(translations) {
|
||||
Object.assign(this.translations, translations);
|
||||
}
|
||||
|
||||
trans(id, parameters) {
|
||||
const translation = this.translations[id];
|
||||
|
||||
|
@ -24,5 +24,6 @@ Object.assign(Post.prototype, {
|
||||
isHidden: computed('hideTime', hideTime => !!hideTime),
|
||||
|
||||
canEdit: Model.attribute('canEdit'),
|
||||
canHide: Model.attribute('canHide'),
|
||||
canDelete: Model.attribute('canDelete')
|
||||
});
|
||||
|
@ -71,15 +71,17 @@ export default class IndexPage extends Page {
|
||||
<div className="IndexPage">
|
||||
{this.hero()}
|
||||
<div className="container">
|
||||
<nav className="IndexPage-nav sideNav">
|
||||
<ul>{listItems(this.sidebarItems().toArray())}</ul>
|
||||
</nav>
|
||||
<div className="IndexPage-results sideNavOffset">
|
||||
<div className="IndexPage-toolbar">
|
||||
<ul className="IndexPage-toolbar-view">{listItems(this.viewItems().toArray())}</ul>
|
||||
<ul className="IndexPage-toolbar-action">{listItems(this.actionItems().toArray())}</ul>
|
||||
<div className="sideNavContainer">
|
||||
<nav className="IndexPage-nav sideNav">
|
||||
<ul>{listItems(this.sidebarItems().toArray())}</ul>
|
||||
</nav>
|
||||
<div className="IndexPage-results sideNavOffset">
|
||||
<div className="IndexPage-toolbar">
|
||||
<ul className="IndexPage-toolbar-view">{listItems(this.viewItems().toArray())}</ul>
|
||||
<ul className="IndexPage-toolbar-action">{listItems(this.actionItems().toArray())}</ul>
|
||||
</div>
|
||||
{app.cache.discussionList.render()}
|
||||
</div>
|
||||
{app.cache.discussionList.render()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -40,11 +40,13 @@ export default class UserPage extends Page {
|
||||
controlsButtonClassName: 'Button'
|
||||
}),
|
||||
<div className="container">
|
||||
<nav className="sideNav UserPage-nav" config={affixSidebar}>
|
||||
<ul>{listItems(this.sidebarItems().toArray())}</ul>
|
||||
</nav>
|
||||
<div className="sideNavOffset UserPage-content">
|
||||
{this.content()}
|
||||
<div className="sideNavContainer">
|
||||
<nav className="sideNav UserPage-nav" config={affixSidebar}>
|
||||
<ul>{listItems(this.sidebarItems().toArray())}</ul>
|
||||
</nav>
|
||||
<div className="sideNavOffset UserPage-content">
|
||||
{this.content()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
] : [
|
||||
|
@ -82,7 +82,7 @@ export default {
|
||||
const items = new ItemList();
|
||||
|
||||
if (post.contentType() === 'comment' && !post.isHidden()) {
|
||||
if (post.canEdit()) {
|
||||
if (post.canHide()) {
|
||||
items.add('hide', Button.component({
|
||||
icon: 'far fa-trash-alt',
|
||||
children: app.translator.trans('core.forum.post_controls.delete_button'),
|
||||
@ -90,7 +90,7 @@ export default {
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
if (post.contentType() === 'comment' && post.canEdit()) {
|
||||
if (post.contentType() === 'comment' && post.canHide()) {
|
||||
items.add('restore', Button.component({
|
||||
icon: 'fas fa-reply',
|
||||
children: app.translator.trans('core.forum.post_controls.restore_button'),
|
||||
|
@ -171,8 +171,6 @@
|
||||
.Header-logo {
|
||||
max-height: 30px;
|
||||
vertical-align: middle;
|
||||
// Prevent blurriness in Chrome
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
|
||||
// On phones, the header is displayed inside of the drawer. We lay its
|
||||
|
@ -14,8 +14,6 @@
|
||||
height: 100%;
|
||||
border-radius: 100%;
|
||||
vertical-align: top;
|
||||
// Prevent blurriness in Chrome
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@
|
||||
// .dropdown-select into a plain list.
|
||||
@media @expand-side-nav {
|
||||
.sideNav {
|
||||
// Expand the dropdown-select component into a normal nav list.
|
||||
& .Dropdown--select {
|
||||
display: block;
|
||||
|
||||
@ -48,56 +47,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sideNav--horizontal {}
|
||||
|
||||
@media @expand-side-nav {
|
||||
.sideNav--horizontal {
|
||||
padding: 15px 0;
|
||||
white-space: nowrap;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
&:after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-top: 15px;
|
||||
border-bottom: 1px solid @control-bg;
|
||||
}
|
||||
|
||||
> ul > li, .Dropdown-menu > li {
|
||||
display: inline-block;
|
||||
margin: 0 20px 0 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
.Dropdown-separator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.Dropdown--select .Dropdown-menu > li > a {
|
||||
padding-left: 25px;
|
||||
|
||||
.icon {
|
||||
margin-left: -25px;
|
||||
}
|
||||
}
|
||||
|
||||
.affix {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
|
||||
@media @tablet {
|
||||
.sideNav {
|
||||
.sideNav--horizontal();
|
||||
}
|
||||
@media @tablet {
|
||||
.sideNav {
|
||||
.sideNav--horizontal();
|
||||
}
|
||||
}
|
||||
|
||||
@media @desktop-up {
|
||||
.sideNavContainer {
|
||||
display: flex;
|
||||
}
|
||||
.sideNav {
|
||||
float: left;
|
||||
flex-shrink: 0;
|
||||
margin-right: 50px;
|
||||
|
||||
&, > ul {
|
||||
width: 190px;
|
||||
@ -115,6 +77,43 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sideNav--horizontal {
|
||||
padding: 15px 0;
|
||||
white-space: nowrap;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
&:after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin-top: 15px;
|
||||
border-bottom: 1px solid @control-bg;
|
||||
}
|
||||
|
||||
> ul > li, .Dropdown-menu > li {
|
||||
display: inline-block;
|
||||
margin: 0 20px 0 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
.Dropdown-separator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.Dropdown--select .Dropdown-menu > li > a {
|
||||
padding-left: 25px;
|
||||
|
||||
.icon {
|
||||
margin-left: -25px;
|
||||
}
|
||||
}
|
||||
|
||||
.affix {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
|
||||
@media @phone, @tablet {
|
||||
.sideNavOffset {
|
||||
margin-top: 15px;
|
||||
@ -124,6 +123,6 @@
|
||||
@media @desktop-up {
|
||||
.sideNavOffset {
|
||||
margin-top: 30px;
|
||||
margin-left: 240px;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
> li {
|
||||
border-bottom: 1px solid @control-bg;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.Post {
|
||||
@ -20,7 +21,6 @@
|
||||
}
|
||||
.PostsUserPage-discussion {
|
||||
font-weight: bold;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
@ -29,8 +29,8 @@
|
||||
|
||||
.NotificationsDropdown-unread {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 17px;
|
||||
top: 2px;
|
||||
left: 18px;
|
||||
background: @header-control-color;
|
||||
color: @header-bg;
|
||||
font-size: 11px;
|
||||
@ -38,9 +38,9 @@
|
||||
padding: 2px 4px 3px;
|
||||
line-height: 1em;
|
||||
border-radius: 10px;
|
||||
border: 1px solid @header-bg;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
.box-shadow(0 0 0 1px @header-bg);
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
text-align: center;
|
||||
|
||||
@media @phone {
|
||||
|
30
migrations/2015_02_25_000000_setup_default_permissions.php
Normal file
30
migrations/2015_02_25_000000_setup_default_permissions.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Flarum\Database\Migration;
|
||||
use Flarum\Group\Group;
|
||||
|
||||
return Migration::addPermissions([
|
||||
// Guests can view the forum
|
||||
'viewDiscussions' => Group::GUEST_ID,
|
||||
|
||||
// Members can create and reply to discussions, and view the user list
|
||||
'startDiscussion' => Group::MEMBER_ID,
|
||||
'discussion.reply' => Group::MEMBER_ID,
|
||||
'viewUserList' => Group::MEMBER_ID,
|
||||
|
||||
// Moderators can edit + delete stuff
|
||||
'discussion.hide' => Group::MODERATOR_ID,
|
||||
'discussion.editPosts' => Group::MODERATOR_ID,
|
||||
'discussion.hidePosts' => Group::MODERATOR_ID,
|
||||
'discussion.rename' => Group::MODERATOR_ID,
|
||||
'discussion.viewIpsPosts' => Group::MODERATOR_ID,
|
||||
]);
|
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Flarum\Database\Migration;
|
||||
use Flarum\Group\Group;
|
||||
|
||||
return Migration::addPermissions([
|
||||
'discussion.hidePosts' => Group::MODERATOR_ID
|
||||
]);
|
@ -13,9 +13,8 @@ namespace Flarum\Admin;
|
||||
|
||||
use Flarum\Admin\Middleware\RequireAdministrateAbility;
|
||||
use Flarum\Event\ConfigureMiddleware;
|
||||
use Flarum\Extension\Event\Disabled;
|
||||
use Flarum\Extension\Event\Enabled;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Frontend\RecompileFrontendAssets;
|
||||
use Flarum\Http\Middleware\AuthenticateWithSession;
|
||||
use Flarum\Http\Middleware\DispatchRoute;
|
||||
use Flarum\Http\Middleware\HandleErrors;
|
||||
@ -26,7 +25,6 @@ use Flarum\Http\Middleware\StartSession;
|
||||
use Flarum\Http\RouteCollection;
|
||||
use Flarum\Http\RouteHandlerFactory;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Settings\Event\Saved;
|
||||
use Zend\Stratigility\MiddlewarePipe;
|
||||
|
||||
class AdminServiceProvider extends AbstractServiceProvider
|
||||
@ -64,6 +62,19 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.admin.assets', function () {
|
||||
return $this->app->make('flarum.frontend.assets.defaults')('admin');
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.admin.frontend', function () {
|
||||
$view = $this->app->make('flarum.frontend.view.defaults')('admin');
|
||||
|
||||
$view->setAssets($this->app->make('flarum.admin.assets'));
|
||||
$view->add($this->app->make(Content\AdminPayload::class));
|
||||
|
||||
return $view;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,12 +86,15 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
|
||||
$this->loadViewsFrom(__DIR__.'/../../views', 'flarum.admin');
|
||||
|
||||
$this->registerListeners();
|
||||
$this->app->make('events')->subscribe(
|
||||
new RecompileFrontendAssets(
|
||||
$this->app->make('flarum.admin.assets'),
|
||||
$this->app->make('flarum.locales')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the forum client routes.
|
||||
*
|
||||
* @param RouteCollection $routes
|
||||
*/
|
||||
protected function populateRoutes(RouteCollection $routes)
|
||||
@ -90,36 +104,4 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
$callback = include __DIR__.'/routes.php';
|
||||
$callback($routes, $factory);
|
||||
}
|
||||
|
||||
protected function registerListeners()
|
||||
{
|
||||
$dispatcher = $this->app->make('events');
|
||||
|
||||
// Flush web app assets when the theme is changed
|
||||
$dispatcher->listen(Saved::class, function (Saved $event) {
|
||||
if (preg_match('/^theme_|^custom_less$/i', $event->key)) {
|
||||
$this->getWebAppAssets()->flushCss();
|
||||
}
|
||||
});
|
||||
|
||||
// Flush web app assets when extensions are changed
|
||||
$dispatcher->listen(Enabled::class, [$this, 'flushWebAppAssets']);
|
||||
$dispatcher->listen(Disabled::class, [$this, 'flushWebAppAssets']);
|
||||
|
||||
// Check the format of custom LESS code
|
||||
$dispatcher->subscribe(CheckCustomLessFormat::class);
|
||||
}
|
||||
|
||||
public function flushWebAppAssets()
|
||||
{
|
||||
$this->getWebAppAssets()->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Flarum\Frontend\FrontendAssets
|
||||
*/
|
||||
protected function getWebAppAssets()
|
||||
{
|
||||
return $this->app->make(Frontend::class)->getAssets();
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Admin;
|
||||
|
||||
use Flarum\Foundation\ValidationException;
|
||||
use Flarum\Settings\Event\Serializing;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Less_Exception_Parser;
|
||||
use Less_Parser;
|
||||
|
||||
class CheckCustomLessFormat
|
||||
{
|
||||
public function subscribe(Dispatcher $events)
|
||||
{
|
||||
$events->listen(Serializing::class, [$this, 'check']);
|
||||
}
|
||||
|
||||
public function check(Serializing $event)
|
||||
{
|
||||
if ($event->key === 'custom_less') {
|
||||
$parser = new Less_Parser();
|
||||
|
||||
try {
|
||||
// Check the custom less format before saving
|
||||
// Variables names are not checked, we would have to set them and call getCss() to check them
|
||||
$parser->parse($event->value);
|
||||
} catch (Less_Exception_Parser $e) {
|
||||
throw new ValidationException([
|
||||
'custom_less' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
src/Admin/Content/AdminPayload.php
Normal file
60
src/Admin/Content/AdminPayload.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Admin\Content;
|
||||
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\Frontend\Content\ContentInterface;
|
||||
use Flarum\Frontend\HtmlDocument;
|
||||
use Flarum\Group\Permission;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class AdminPayload implements ContentInterface
|
||||
{
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @var ExtensionManager
|
||||
*/
|
||||
protected $extensions;
|
||||
|
||||
/**
|
||||
* @var ConnectionInterface
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
* @param ExtensionManager $extensions
|
||||
* @param ConnectionInterface $db
|
||||
*/
|
||||
public function __construct(SettingsRepositoryInterface $settings, ExtensionManager $extensions, ConnectionInterface $db)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
$this->extensions = $extensions;
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function populate(HtmlDocument $document, Request $request)
|
||||
{
|
||||
$document->payload['settings'] = $this->settings->all();
|
||||
$document->payload['permissions'] = Permission::map();
|
||||
$document->payload['extensions'] = $this->extensions->getExtensions()->toArray();
|
||||
|
||||
$document->payload['phpVersion'] = PHP_VERSION;
|
||||
$document->payload['mysqlVersion'] = $this->db->selectOne('select version() as version')->version;
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Admin\Controller;
|
||||
|
||||
use Flarum\Admin\Frontend;
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\Frontend\AbstractFrontendController;
|
||||
use Flarum\Group\Permission;
|
||||
use Flarum\Settings\Event\Deserializing;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class FrontendController extends AbstractFrontendController
|
||||
{
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @var ExtensionManager
|
||||
*/
|
||||
protected $extensions;
|
||||
|
||||
/**
|
||||
* @var ConnectionInterface
|
||||
*/
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* @param Frontend $webApp
|
||||
* @param Dispatcher $events
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
* @param ExtensionManager $extensions
|
||||
* @param ConnectionInterface $db
|
||||
*/
|
||||
public function __construct(Frontend $webApp, Dispatcher $events, SettingsRepositoryInterface $settings, ExtensionManager $extensions, ConnectionInterface $db)
|
||||
{
|
||||
$this->webApp = $webApp;
|
||||
$this->events = $events;
|
||||
$this->settings = $settings;
|
||||
$this->extensions = $extensions;
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getView(ServerRequestInterface $request)
|
||||
{
|
||||
$view = parent::getView($request);
|
||||
|
||||
$settings = $this->settings->all();
|
||||
|
||||
$this->events->dispatch(
|
||||
new Deserializing($settings)
|
||||
);
|
||||
|
||||
$view->setVariable('settings', $settings);
|
||||
$view->setVariable('permissions', Permission::map());
|
||||
$view->setVariable('extensions', $this->extensions->getExtensions()->toArray());
|
||||
|
||||
$view->setVariable('phpVersion', PHP_VERSION);
|
||||
$view->setVariable('mysqlVersion', $this->db->selectOne('select version() as version')->version);
|
||||
|
||||
return $view;
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Flarum\Admin\Controller;
|
||||
use Flarum\Http\RouteCollection;
|
||||
use Flarum\Http\RouteHandlerFactory;
|
||||
|
||||
@ -17,6 +16,6 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
$map->get(
|
||||
'/',
|
||||
'index',
|
||||
$route->toController(Controller\FrontendController::class)
|
||||
$route->toAdmin()
|
||||
);
|
||||
};
|
||||
|
@ -11,8 +11,7 @@
|
||||
|
||||
namespace Flarum\Api\Controller;
|
||||
|
||||
use Flarum\Settings\Event\Saved;
|
||||
use Flarum\Settings\Event\Serializing;
|
||||
use Flarum\Settings\Event;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\AssertPermissionTrait;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
@ -53,14 +52,16 @@ class SetSettingsController implements RequestHandlerInterface
|
||||
|
||||
$settings = $request->getParsedBody();
|
||||
|
||||
$this->dispatcher->dispatch(new Event\Saving($settings));
|
||||
|
||||
foreach ($settings as $k => $v) {
|
||||
$this->dispatcher->dispatch(new Serializing($k, $v));
|
||||
$this->dispatcher->dispatch(new Event\Serializing($k, $v));
|
||||
|
||||
$this->settings->set($k, $v);
|
||||
|
||||
$this->dispatcher->dispatch(new Saved($k, $v));
|
||||
}
|
||||
|
||||
$this->dispatcher->dispatch(new Event\Saved($settings));
|
||||
|
||||
return new EmptyResponse(204);
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,8 @@ class PostSerializer extends BasicPostSerializer
|
||||
|
||||
$attributes += [
|
||||
'canEdit' => $canEdit,
|
||||
'canDelete' => $gate->allows('delete', $post)
|
||||
'canDelete' => $gate->allows('delete', $post),
|
||||
'canHide' => $gate->allows('hide', $post)
|
||||
];
|
||||
|
||||
return $attributes;
|
||||
|
@ -12,29 +12,37 @@
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Frontend\Event\Rendering;
|
||||
use Flarum\Frontend\Asset\ExtensionAssets;
|
||||
use Flarum\Frontend\CompilerFactory;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
|
||||
class Assets implements ExtenderInterface
|
||||
{
|
||||
protected $appName;
|
||||
protected $frontend;
|
||||
|
||||
protected $assets = [];
|
||||
protected $css = [];
|
||||
protected $js;
|
||||
|
||||
public function __construct($appName)
|
||||
public function __construct($frontend)
|
||||
{
|
||||
$this->appName = $appName;
|
||||
$this->frontend = $frontend;
|
||||
}
|
||||
|
||||
public function asset($path)
|
||||
public function css($path)
|
||||
{
|
||||
$this->assets[] = $path;
|
||||
$this->css[] = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function asset($path)
|
||||
{
|
||||
return $this->css($path);
|
||||
}
|
||||
|
||||
public function js($path)
|
||||
{
|
||||
$this->js = $path;
|
||||
@ -44,35 +52,13 @@ class Assets implements ExtenderInterface
|
||||
|
||||
public function __invoke(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->make(Dispatcher::class)->listen(
|
||||
Rendering::class,
|
||||
function (Rendering $event) use ($extension) {
|
||||
if (! $this->matches($event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->addAssets($this->assets);
|
||||
|
||||
if ($this->js) {
|
||||
$event->view->getJs()->addString(function () use ($extension) {
|
||||
$name = $extension->getId();
|
||||
|
||||
return 'var module={};'.file_get_contents($this->js).";\nflarum.extensions['$name']=module.exports";
|
||||
});
|
||||
}
|
||||
$container->resolving(
|
||||
"flarum.$this->frontend.assets",
|
||||
function (CompilerFactory $assets) use ($extension) {
|
||||
$assets->add(function () use ($extension) {
|
||||
return new ExtensionAssets($extension, $this->css, $this->js);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function matches(Rendering $event)
|
||||
{
|
||||
switch ($this->appName) {
|
||||
case 'admin':
|
||||
return $event->isAdmin();
|
||||
case 'forum':
|
||||
return $event->isForum();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ class Formatter
|
||||
*/
|
||||
public function render($xml, $context = null)
|
||||
{
|
||||
$renderer = $this->getRenderer($context);
|
||||
$renderer = $this->getRenderer();
|
||||
|
||||
$this->events->dispatch(new Rendering($renderer, $context, $xml));
|
||||
|
||||
@ -96,8 +96,7 @@ class Formatter
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
$this->cache->forget('flarum.formatter.parser');
|
||||
$this->cache->forget('flarum.formatter.renderer');
|
||||
$this->cache->forget('flarum.formatter');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,6 +111,12 @@ class Formatter
|
||||
$configurator->rendering->engine = 'PHP';
|
||||
$configurator->rendering->engine->cacheDir = $this->cacheDir;
|
||||
|
||||
$configurator->enableJavaScript();
|
||||
$configurator->javascript->exportMethods = ['preview'];
|
||||
|
||||
$configurator->javascript->setMinifier('MatthiasMullieMinify')
|
||||
->keepGoing = true;
|
||||
|
||||
$configurator->Escaper;
|
||||
$configurator->Autoemail;
|
||||
$configurator->Autolink;
|
||||
@ -142,16 +147,16 @@ class Formatter
|
||||
/**
|
||||
* Get a TextFormatter component.
|
||||
*
|
||||
* @param string $name "renderer" or "parser"
|
||||
* @param string $name "renderer" or "parser" or "js"
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getComponent($name)
|
||||
{
|
||||
$cacheKey = 'flarum.formatter.'.$name;
|
||||
|
||||
return $this->cache->rememberForever($cacheKey, function () use ($name) {
|
||||
return $this->getConfigurator()->finalize()[$name];
|
||||
$formatter = $this->cache->rememberForever('flarum.formatter', function () {
|
||||
return $this->getConfigurator()->finalize();
|
||||
});
|
||||
|
||||
return $formatter[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,10 +177,9 @@ class Formatter
|
||||
/**
|
||||
* Get the renderer.
|
||||
*
|
||||
* @param mixed $context
|
||||
* @return \s9e\TextFormatter\Renderer
|
||||
*/
|
||||
protected function getRenderer($context = null)
|
||||
protected function getRenderer()
|
||||
{
|
||||
spl_autoload_register(function ($class) {
|
||||
if (file_exists($file = $this->cacheDir.'/'.$class.'.php')) {
|
||||
@ -193,13 +197,6 @@ class Formatter
|
||||
*/
|
||||
public function getJs()
|
||||
{
|
||||
$configurator = $this->getConfigurator();
|
||||
$configurator->enableJavaScript();
|
||||
$configurator->javascript->exportMethods = ['preview'];
|
||||
|
||||
return $configurator->finalize([
|
||||
'returnParser' => false,
|
||||
'returnRenderer' => false
|
||||
])['js'];
|
||||
return $this->getComponent('js');
|
||||
}
|
||||
}
|
||||
|
51
src/Forum/Asset/CustomCss.php
Normal file
51
src/Forum/Asset/CustomCss.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Forum\Asset;
|
||||
|
||||
use Flarum\Frontend\Asset\AssetInterface;
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
|
||||
class CustomCss implements AssetInterface
|
||||
{
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
*/
|
||||
public function __construct(SettingsRepositoryInterface $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function css(SourceCollector $sources)
|
||||
{
|
||||
$sources->addString(function () {
|
||||
return $this->settings->get('custom_less');
|
||||
});
|
||||
}
|
||||
|
||||
public function js(SourceCollector $sources)
|
||||
{
|
||||
}
|
||||
|
||||
public function localeJs(SourceCollector $sources, string $locale)
|
||||
{
|
||||
}
|
||||
|
||||
public function localeCss(SourceCollector $sources, string $locale)
|
||||
{
|
||||
}
|
||||
}
|
51
src/Forum/Asset/FormatterJs.php
Normal file
51
src/Forum/Asset/FormatterJs.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Forum\Asset;
|
||||
|
||||
use Flarum\Formatter\Formatter;
|
||||
use Flarum\Frontend\Asset\AssetInterface;
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
|
||||
class FormatterJs implements AssetInterface
|
||||
{
|
||||
/**
|
||||
* @var Formatter
|
||||
*/
|
||||
protected $formatter;
|
||||
|
||||
/**
|
||||
* @param Formatter $formatter
|
||||
*/
|
||||
public function __construct(Formatter $formatter)
|
||||
{
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
public function js(SourceCollector $sources)
|
||||
{
|
||||
$sources->addString(function () {
|
||||
return $this->formatter->getJs();
|
||||
});
|
||||
}
|
||||
|
||||
public function css(SourceCollector $sources)
|
||||
{
|
||||
}
|
||||
|
||||
public function localeJs(SourceCollector $sources, string $locale)
|
||||
{
|
||||
}
|
||||
|
||||
public function localeCss(SourceCollector $sources, string $locale)
|
||||
{
|
||||
}
|
||||
}
|
27
src/Forum/Content/AssertRegistered.php
Normal file
27
src/Forum/Content/AssertRegistered.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Forum\Content;
|
||||
|
||||
use Flarum\Frontend\Content\ContentInterface;
|
||||
use Flarum\Frontend\HtmlDocument;
|
||||
use Flarum\User\AssertPermissionTrait;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class AssertRegistered implements ContentInterface
|
||||
{
|
||||
use AssertPermissionTrait;
|
||||
|
||||
public function populate(HtmlDocument $document, Request $request)
|
||||
{
|
||||
$this->assertRegistered($request->getAttribute('actor'));
|
||||
}
|
||||
}
|
@ -9,17 +9,18 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Forum\Controller;
|
||||
namespace Flarum\Forum\Content;
|
||||
|
||||
use Flarum\Api\Client;
|
||||
use Flarum\Forum\Frontend;
|
||||
use Flarum\Frontend\Content\ContentInterface;
|
||||
use Flarum\Frontend\HtmlDocument;
|
||||
use Flarum\Http\Exception\RouteNotFoundException;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class DiscussionController extends FrontendController
|
||||
class Discussion implements ContentInterface
|
||||
{
|
||||
/**
|
||||
* @var Client
|
||||
@ -32,23 +33,24 @@ class DiscussionController extends FrontendController
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @var Factory
|
||||
*/
|
||||
public function __construct(Frontend $webApp, Dispatcher $events, Client $api, UrlGenerator $url)
|
||||
{
|
||||
parent::__construct($webApp, $events);
|
||||
|
||||
$this->api = $api;
|
||||
$this->url = $url;
|
||||
}
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @param Client $api
|
||||
* @param UrlGenerator $url
|
||||
* @param Factory $view
|
||||
*/
|
||||
protected function getView(Request $request)
|
||||
public function __construct(Client $api, UrlGenerator $url, Factory $view)
|
||||
{
|
||||
$view = parent::getView($request);
|
||||
$this->api = $api;
|
||||
$this->url = $url;
|
||||
$this->view = $view;
|
||||
}
|
||||
|
||||
public function populate(HtmlDocument $document, Request $request)
|
||||
{
|
||||
$queryParams = $request->getQueryParams();
|
||||
$page = max(1, array_get($queryParams, 'page'));
|
||||
|
||||
@ -61,35 +63,39 @@ class DiscussionController extends FrontendController
|
||||
]
|
||||
];
|
||||
|
||||
$document = $this->getDocument($request->getAttribute('actor'), $params);
|
||||
$apiDocument = $this->getApiDocument($request->getAttribute('actor'), $params);
|
||||
|
||||
$getResource = function ($link) use ($document) {
|
||||
return array_first($document->included, function ($value, $key) use ($link) {
|
||||
$getResource = function ($link) use ($apiDocument) {
|
||||
return array_first($apiDocument->included, function ($value) use ($link) {
|
||||
return $value->type === $link->type && $value->id === $link->id;
|
||||
});
|
||||
};
|
||||
|
||||
$url = function ($newQueryParams) use ($queryParams, $document) {
|
||||
$url = function ($newQueryParams) use ($queryParams, $apiDocument) {
|
||||
$newQueryParams = array_merge($queryParams, $newQueryParams);
|
||||
unset($newQueryParams['id']);
|
||||
$queryString = http_build_query($newQueryParams);
|
||||
|
||||
return $this->url->to('forum')->route('discussion', ['id' => $document->data->id]).
|
||||
$idWithSlug = $apiDocument->data->id.(trim($apiDocument->data->attributes->slug) ? '-'.$apiDocument->data->attributes->slug : '');
|
||||
|
||||
return $this->url->to('forum')->route('discussion', ['id' => $idWithSlug]).
|
||||
($queryString ? '?'.$queryString : '');
|
||||
};
|
||||
|
||||
$posts = [];
|
||||
|
||||
foreach ($document->included as $resource) {
|
||||
foreach ($apiDocument->included as $resource) {
|
||||
if ($resource->type === 'posts' && isset($resource->relationships->discussion) && isset($resource->attributes->contentHtml)) {
|
||||
$posts[] = $resource;
|
||||
}
|
||||
}
|
||||
|
||||
$view->title = $document->data->attributes->title;
|
||||
$view->document = $document;
|
||||
$view->content = app('view')->make('flarum.forum::frontend.content.discussion', compact('document', 'page', 'getResource', 'posts', 'url'));
|
||||
$document->title = $apiDocument->data->attributes->title;
|
||||
$document->canonicalUrl = $url([]);
|
||||
$document->content = $this->view->make('flarum.forum::frontend.content.discussion', compact('apiDocument', 'page', 'getResource', 'posts', 'url'));
|
||||
$document->payload['apiDocument'] = $apiDocument;
|
||||
|
||||
return $view;
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,7 +106,7 @@ class DiscussionController extends FrontendController
|
||||
* @return object
|
||||
* @throws RouteNotFoundException
|
||||
*/
|
||||
protected function getDocument(User $actor, array $params)
|
||||
protected function getApiDocument(User $actor, array $params)
|
||||
{
|
||||
$response = $this->api->send('Flarum\Api\Controller\ShowDiscussionController', $actor, $params);
|
||||
$statusCode = $response->getStatusCode();
|
97
src/Forum/Content/Index.php
Normal file
97
src/Forum/Content/Index.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Forum\Content;
|
||||
|
||||
use Flarum\Api\Client;
|
||||
use Flarum\Api\Controller\ListDiscussionsController;
|
||||
use Flarum\Frontend\Content\ContentInterface;
|
||||
use Flarum\Frontend\HtmlDocument;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class Index implements ContentInterface
|
||||
{
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
protected $api;
|
||||
|
||||
/**
|
||||
* @var Factory
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* @param Client $api
|
||||
* @param Factory $view
|
||||
*/
|
||||
public function __construct(Client $api, Factory $view)
|
||||
{
|
||||
$this->api = $api;
|
||||
$this->view = $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function populate(HtmlDocument $document, Request $request)
|
||||
{
|
||||
$queryParams = $request->getQueryParams();
|
||||
|
||||
$sort = array_pull($queryParams, 'sort');
|
||||
$q = array_pull($queryParams, 'q');
|
||||
$page = array_pull($queryParams, 'page', 1);
|
||||
|
||||
$sortMap = $this->getSortMap();
|
||||
|
||||
$params = [
|
||||
'sort' => $sort && isset($sortMap[$sort]) ? $sortMap[$sort] : '',
|
||||
'filter' => compact('q'),
|
||||
'page' => ['offset' => ($page - 1) * 20, 'limit' => 20]
|
||||
];
|
||||
|
||||
$apiDocument = $this->getApiDocument($request->getAttribute('actor'), $params);
|
||||
|
||||
$document->content = $this->view->make('flarum.forum::frontend.content.index', compact('apiDocument', 'page', 'forum'));
|
||||
$document->payload['apiDocument'] = $apiDocument;
|
||||
|
||||
return $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map of sort query param values and their API sort params.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getSortMap()
|
||||
{
|
||||
return [
|
||||
'latest' => '-lastPostedAt',
|
||||
'top' => '-commentCount',
|
||||
'newest' => '-createdAt',
|
||||
'oldest' => 'createdAt'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result of an API request to list discussions.
|
||||
*
|
||||
* @param User $actor
|
||||
* @param array $params
|
||||
* @return object
|
||||
*/
|
||||
private function getApiDocument(User $actor, array $params)
|
||||
{
|
||||
return json_decode($this->api->send(ListDiscussionsController::class, $actor, $params)->getBody());
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Forum\Controller;
|
||||
|
||||
use Flarum\User\Exception\PermissionDeniedException;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class AuthorizedWebAppController extends FrontendController
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render(Request $request)
|
||||
{
|
||||
if (! $request->getAttribute('session')->get('user_id')) {
|
||||
throw new PermissionDeniedException;
|
||||
}
|
||||
|
||||
return parent::render($request);
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Forum\Controller;
|
||||
|
||||
use Flarum\Forum\Frontend;
|
||||
use Flarum\Frontend\AbstractFrontendController;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
class FrontendController extends AbstractFrontendController
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(Frontend $webApp, Dispatcher $events)
|
||||
{
|
||||
$this->webApp = $webApp;
|
||||
$this->events = $events;
|
||||
}
|
||||
}
|
@ -13,8 +13,6 @@ namespace Flarum\Forum;
|
||||
|
||||
use Flarum\Event\ConfigureForumRoutes;
|
||||
use Flarum\Event\ConfigureMiddleware;
|
||||
use Flarum\Extension\Event\Disabled;
|
||||
use Flarum\Extension\Event\Enabled;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Http\Middleware\AuthenticateWithSession;
|
||||
use Flarum\Http\Middleware\CollectGarbage;
|
||||
@ -28,7 +26,6 @@ use Flarum\Http\Middleware\StartSession;
|
||||
use Flarum\Http\RouteCollection;
|
||||
use Flarum\Http\RouteHandlerFactory;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\Settings\Event\Saved;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
use Zend\Stratigility\MiddlewarePipe;
|
||||
@ -69,6 +66,27 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.forum.assets', function () {
|
||||
$assets = $this->app->make('flarum.frontend.assets.defaults')('forum');
|
||||
|
||||
$assets->add(function () {
|
||||
return [
|
||||
$this->app->make(Asset\FormatterJs::class),
|
||||
$this->app->make(Asset\CustomCss::class)
|
||||
];
|
||||
});
|
||||
|
||||
return $assets;
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.forum.frontend', function () {
|
||||
$view = $this->app->make('flarum.frontend.view.defaults')('forum');
|
||||
|
||||
$view->setAssets($this->app->make('flarum.forum.assets'));
|
||||
|
||||
return $view;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,9 +103,13 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
'settings' => $this->app->make(SettingsRepositoryInterface::class)
|
||||
]);
|
||||
|
||||
$this->flushWebAppAssetsWhenThemeChanged();
|
||||
|
||||
$this->flushWebAppAssetsWhenExtensionsChanged();
|
||||
$this->app->make('events')->subscribe(
|
||||
new RecompileFrontendAssets(
|
||||
$this->app->make('flarum.forum.assets'),
|
||||
$this->app->make('flarum.locales'),
|
||||
$this->app
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,7 +133,7 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
if (isset($routes->getRouteData()[0]['GET'][$defaultRoute])) {
|
||||
$toDefaultController = $routes->getRouteData()[0]['GET'][$defaultRoute];
|
||||
} else {
|
||||
$toDefaultController = $factory->toController(Controller\IndexController::class);
|
||||
$toDefaultController = $factory->toForum(Content\Index::class);
|
||||
}
|
||||
|
||||
$routes->get(
|
||||
@ -120,34 +142,4 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
$toDefaultController
|
||||
);
|
||||
}
|
||||
|
||||
protected function flushWebAppAssetsWhenThemeChanged()
|
||||
{
|
||||
$this->app->make('events')->listen(Saved::class, function (Saved $event) {
|
||||
if (preg_match('/^theme_|^custom_less$/i', $event->key)) {
|
||||
$this->getWebAppAssets()->flushCss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function flushWebAppAssetsWhenExtensionsChanged()
|
||||
{
|
||||
$events = $this->app->make('events');
|
||||
|
||||
$events->listen(Enabled::class, [$this, 'flushWebAppAssets']);
|
||||
$events->listen(Disabled::class, [$this, 'flushWebAppAssets']);
|
||||
}
|
||||
|
||||
public function flushWebAppAssets()
|
||||
{
|
||||
$this->getWebAppAssets()->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Flarum\Frontend\FrontendAssets
|
||||
*/
|
||||
protected function getWebAppAssets()
|
||||
{
|
||||
return $this->app->make(Frontend::class)->getAssets();
|
||||
}
|
||||
}
|
||||
|
@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Forum;
|
||||
|
||||
use Flarum\Formatter\Formatter;
|
||||
use Flarum\Frontend\AbstractFrontend;
|
||||
use Flarum\Frontend\FrontendAssetsFactory;
|
||||
use Flarum\Frontend\FrontendViewFactory;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
|
||||
class Frontend extends AbstractFrontend
|
||||
{
|
||||
/**
|
||||
* @var Formatter
|
||||
*/
|
||||
protected $formatter;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(
|
||||
FrontendAssetsFactory $assets,
|
||||
FrontendViewFactory $view,
|
||||
SettingsRepositoryInterface $settings,
|
||||
LocaleManager $locales,
|
||||
Formatter $formatter
|
||||
) {
|
||||
parent::__construct($assets, $view, $settings, $locales);
|
||||
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getView()
|
||||
{
|
||||
$view = parent::getView();
|
||||
|
||||
$view->getJs()->addString(function () {
|
||||
return $this->formatter->getJs();
|
||||
});
|
||||
|
||||
$view->getCss()->addString(function () {
|
||||
return $this->settings->get('custom_less');
|
||||
});
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getName()
|
||||
{
|
||||
return 'forum';
|
||||
}
|
||||
}
|
110
src/Forum/RecompileFrontendAssets.php
Normal file
110
src/Forum/RecompileFrontendAssets.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Forum;
|
||||
|
||||
use Flarum\Foundation\ValidationException;
|
||||
use Flarum\Frontend\CompilerFactory;
|
||||
use Flarum\Frontend\RecompileFrontendAssets as BaseListener;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Flarum\Settings\Event\Saved;
|
||||
use Flarum\Settings\Event\Saving;
|
||||
use Flarum\Settings\OverrideSettingsRepository;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
use League\Flysystem\Adapter\NullAdapter;
|
||||
use League\Flysystem\Filesystem;
|
||||
use Less_Exception_Parser;
|
||||
|
||||
class RecompileFrontendAssets extends BaseListener
|
||||
{
|
||||
/**
|
||||
* @var Container
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* @param CompilerFactory $assets
|
||||
* @param LocaleManager $locales
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(CompilerFactory $assets, LocaleManager $locales, Container $container)
|
||||
{
|
||||
parent::__construct($assets, $locales);
|
||||
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
*/
|
||||
public function subscribe(Dispatcher $events)
|
||||
{
|
||||
parent::subscribe($events);
|
||||
|
||||
$events->listen(Saving::class, [$this, 'whenSettingsSaving']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Saving $event
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function whenSettingsSaving(Saving $event)
|
||||
{
|
||||
if (isset($event->settings['custom_less'])) {
|
||||
// We haven't saved the settings yet, but we want to trial a full
|
||||
// recompile of the CSS to see if this custom LESS will break
|
||||
// anything. In order to do that, we will temporarily override the
|
||||
// settings repository with the new settings so that the recompile
|
||||
// is effective. We will also use a dummy filesystem so that nothing
|
||||
// is actually written yet.
|
||||
|
||||
$settings = $this->container->make(SettingsRepositoryInterface::class);
|
||||
|
||||
$this->container->extend(
|
||||
SettingsRepositoryInterface::class,
|
||||
function ($settings) use ($event) {
|
||||
return new OverrideSettingsRepository($settings, $event->settings);
|
||||
}
|
||||
);
|
||||
|
||||
$assetsDir = $this->assets->getAssetsDir();
|
||||
$this->assets->setAssetsDir(new FilesystemAdapter(new Filesystem(new NullAdapter)));
|
||||
|
||||
try {
|
||||
$this->assets->makeCss()->commit();
|
||||
|
||||
foreach ($this->locales->getLocales() as $locale => $name) {
|
||||
$this->assets->makeLocaleCss($locale)->commit();
|
||||
}
|
||||
} catch (Less_Exception_Parser $e) {
|
||||
throw new ValidationException(['custom_less' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
$this->assets->setAssetsDir($assetsDir);
|
||||
$this->container->instance(SettingsRepositoryInterface::class, $settings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Saved $event
|
||||
*/
|
||||
public function whenSettingsSaved(Saved $event)
|
||||
{
|
||||
parent::whenSettingsSaved($event);
|
||||
|
||||
if (isset($event->settings['custom_less'])) {
|
||||
$this->flushCss();
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Flarum\Forum\Content;
|
||||
use Flarum\Forum\Controller;
|
||||
use Flarum\Http\RouteCollection;
|
||||
use Flarum\Http\RouteHandlerFactory;
|
||||
@ -17,31 +18,31 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
||||
$map->get(
|
||||
'/all',
|
||||
'index',
|
||||
$route->toController(Controller\IndexController::class)
|
||||
$route->toForum(Content\Index::class)
|
||||
);
|
||||
|
||||
$map->get(
|
||||
'/d/{id:\d+(?:-[^/]*)?}[/{near:[^/]*}]',
|
||||
'discussion',
|
||||
$route->toController(Controller\DiscussionController::class)
|
||||
$route->toForum(Content\Discussion::class)
|
||||
);
|
||||
|
||||
$map->get(
|
||||
'/u/{username}[/{filter:[^/]*}]',
|
||||
'user',
|
||||
$route->toController(Controller\FrontendController::class)
|
||||
$route->toForum()
|
||||
);
|
||||
|
||||
$map->get(
|
||||
'/settings',
|
||||
'settings',
|
||||
$route->toController(Controller\AuthorizedWebAppController::class)
|
||||
$route->toForum(Content\AssertRegistered::class)
|
||||
);
|
||||
|
||||
$map->get(
|
||||
'/notifications',
|
||||
'notifications',
|
||||
$route->toController(Controller\AuthorizedWebAppController::class)
|
||||
$route->toForum(Content\AssertRegistered::class)
|
||||
);
|
||||
|
||||
$map->get(
|
||||
|
@ -11,10 +11,9 @@
|
||||
|
||||
namespace Flarum\Foundation\Console;
|
||||
|
||||
use Flarum\Admin\Frontend as AdminWebApp;
|
||||
use Flarum\Console\AbstractCommand;
|
||||
use Flarum\Forum\Frontend as ForumWebApp;
|
||||
use Flarum\Foundation\Application;
|
||||
use Flarum\Foundation\Event\ClearingCache;
|
||||
use Illuminate\Contracts\Cache\Store;
|
||||
|
||||
class CacheClearCommand extends AbstractCommand
|
||||
@ -24,16 +23,6 @@ class CacheClearCommand extends AbstractCommand
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @var ForumWebApp
|
||||
*/
|
||||
protected $forum;
|
||||
|
||||
/**
|
||||
* @var AdminWebApp
|
||||
*/
|
||||
protected $admin;
|
||||
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
@ -41,15 +30,11 @@ class CacheClearCommand extends AbstractCommand
|
||||
|
||||
/**
|
||||
* @param Store $cache
|
||||
* @param ForumWebApp $forum
|
||||
* @param AdminWebApp $admin
|
||||
* @param Application $app
|
||||
*/
|
||||
public function __construct(Store $cache, ForumWebApp $forum, AdminWebApp $admin, Application $app)
|
||||
public function __construct(Store $cache, Application $app)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
$this->forum = $forum;
|
||||
$this->admin = $admin;
|
||||
$this->app = $app;
|
||||
|
||||
parent::__construct();
|
||||
@ -72,13 +57,12 @@ class CacheClearCommand extends AbstractCommand
|
||||
{
|
||||
$this->info('Clearing the cache...');
|
||||
|
||||
$this->forum->getAssets()->flush();
|
||||
$this->admin->getAssets()->flush();
|
||||
|
||||
$this->cache->flush();
|
||||
|
||||
$storagePath = $this->app->storagePath();
|
||||
array_map('unlink', glob($storagePath.'/formatter/*'));
|
||||
array_map('unlink', glob($storagePath.'/locale/*'));
|
||||
|
||||
event(new ClearingCache);
|
||||
}
|
||||
}
|
||||
|
@ -9,13 +9,8 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Event;
|
||||
namespace Flarum\Foundation\Event;
|
||||
|
||||
use Flarum\Frontend\Event\Rendering;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
class ConfigureClientView extends Rendering
|
||||
class ClearingCache
|
||||
{
|
||||
}
|
@ -20,6 +20,7 @@ use Flarum\Discussion\DiscussionServiceProvider;
|
||||
use Flarum\Extension\ExtensionServiceProvider;
|
||||
use Flarum\Formatter\FormatterServiceProvider;
|
||||
use Flarum\Forum\ForumServiceProvider;
|
||||
use Flarum\Frontend\FrontendServiceProvider;
|
||||
use Flarum\Group\GroupServiceProvider;
|
||||
use Flarum\Locale\LocaleServiceProvider;
|
||||
use Flarum\Notification\NotificationServiceProvider;
|
||||
@ -188,6 +189,7 @@ class Site
|
||||
|
||||
$app->register(DiscussionServiceProvider::class);
|
||||
$app->register(FormatterServiceProvider::class);
|
||||
$app->register(FrontendServiceProvider::class);
|
||||
$app->register(GroupServiceProvider::class);
|
||||
$app->register(NotificationServiceProvider::class);
|
||||
$app->register(PostServiceProvider::class);
|
||||
@ -231,6 +233,11 @@ class Site
|
||||
'default' => 'local',
|
||||
'cloud' => 's3',
|
||||
'disks' => [
|
||||
'flarum-assets' => [
|
||||
'driver' => 'local',
|
||||
'root' => $app->publicPath().'/assets',
|
||||
'url' => $app->url('assets')
|
||||
],
|
||||
'flarum-avatars' => [
|
||||
'driver' => 'local',
|
||||
'root' => $app->publicPath().'/assets/avatars'
|
||||
|
@ -1,184 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend;
|
||||
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
|
||||
abstract class AbstractFrontend
|
||||
{
|
||||
/**
|
||||
* @var FrontendAssetsFactory
|
||||
*/
|
||||
protected $assets;
|
||||
|
||||
/**
|
||||
* @var FrontendViewFactory
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @var LocaleManager
|
||||
*/
|
||||
protected $locales;
|
||||
|
||||
/**
|
||||
* @param FrontendAssetsFactory $assets
|
||||
* @param FrontendViewFactory $view
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
* @param LocaleManager $locales
|
||||
*/
|
||||
public function __construct(FrontendAssetsFactory $assets, FrontendViewFactory $view, SettingsRepositoryInterface $settings, LocaleManager $locales)
|
||||
{
|
||||
$this->assets = $assets;
|
||||
$this->view = $view;
|
||||
$this->settings = $settings;
|
||||
$this->locales = $locales;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FrontendView
|
||||
*/
|
||||
public function getView()
|
||||
{
|
||||
$view = $this->view->make($this->getLayout(), $this->getAssets());
|
||||
|
||||
$this->addDefaultAssets($view);
|
||||
$this->addCustomLess($view);
|
||||
$this->addTranslations($view);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FrontendAssets
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
return $this->assets->make($this->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the client.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getName();
|
||||
|
||||
/**
|
||||
* Get the path to the client layout view.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getLayout()
|
||||
{
|
||||
return 'flarum.forum::frontend.'.$this->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a regular expression to match against translation keys.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getTranslationFilter()
|
||||
{
|
||||
return '/^.+(?:\.|::)(?:'.$this->getName().'|lib)\./';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FrontendView $view
|
||||
*/
|
||||
private function addDefaultAssets(FrontendView $view)
|
||||
{
|
||||
$root = __DIR__.'/../..';
|
||||
$name = $this->getName();
|
||||
|
||||
$view->getJs()->addFile("$root/js/dist/$name.js");
|
||||
$view->getCss()->addFile("$root/less/$name.less");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FrontendView $view
|
||||
*/
|
||||
private function addCustomLess(FrontendView $view)
|
||||
{
|
||||
$css = $view->getCss();
|
||||
$localeCss = $view->getLocaleCss();
|
||||
|
||||
$lessVariables = function () {
|
||||
$less = '';
|
||||
|
||||
foreach ($this->getLessVariables() as $name => $value) {
|
||||
$less .= "@$name: $value;";
|
||||
}
|
||||
|
||||
return $less;
|
||||
};
|
||||
|
||||
$css->addString($lessVariables);
|
||||
$localeCss->addString($lessVariables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the values of any LESS variables to compile into the CSS, based on
|
||||
* the forum's configuration.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getLessVariables()
|
||||
{
|
||||
return [
|
||||
'config-primary-color' => $this->settings->get('theme_primary_color') ?: '#000',
|
||||
'config-secondary-color' => $this->settings->get('theme_secondary_color') ?: '#000',
|
||||
'config-dark-mode' => $this->settings->get('theme_dark_mode') ? 'true' : 'false',
|
||||
'config-colored-header' => $this->settings->get('theme_colored_header') ? 'true' : 'false'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FrontendView $view
|
||||
*/
|
||||
private function addTranslations(FrontendView $view)
|
||||
{
|
||||
$translations = array_get($this->locales->getTranslator()->getCatalogue()->all(), 'messages', []);
|
||||
|
||||
$translations = $this->filterTranslations($translations);
|
||||
|
||||
$view->getLocaleJs()->setTranslations($translations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a selection of keys from a collection of translations.
|
||||
*
|
||||
* @param array $translations
|
||||
* @return array
|
||||
*/
|
||||
private function filterTranslations(array $translations)
|
||||
{
|
||||
$filter = $this->getTranslationFilter();
|
||||
|
||||
if (! $filter) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$filtered = array_filter(array_keys($translations), function ($id) use ($filter) {
|
||||
return preg_match($filter, $id);
|
||||
});
|
||||
|
||||
return array_only($translations, $filtered);
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend;
|
||||
|
||||
use Flarum\Event\ConfigureClientView;
|
||||
use Flarum\Frontend\Event\Rendering;
|
||||
use Flarum\Http\Controller\AbstractHtmlController;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
abstract class AbstractFrontendController extends AbstractHtmlController
|
||||
{
|
||||
/**
|
||||
* @var AbstractFrontend
|
||||
*/
|
||||
protected $webApp;
|
||||
|
||||
/**
|
||||
* @var Dispatcher
|
||||
*/
|
||||
protected $events;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render(Request $request)
|
||||
{
|
||||
$view = $this->getView($request);
|
||||
|
||||
$this->events->dispatch(
|
||||
new ConfigureClientView($this, $view, $request)
|
||||
);
|
||||
$this->events->dispatch(
|
||||
new Rendering($this, $view, $request)
|
||||
);
|
||||
|
||||
return $view->render($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return \Flarum\Frontend\FrontendView
|
||||
*/
|
||||
protected function getView(Request $request)
|
||||
{
|
||||
return $this->webApp->getView();
|
||||
}
|
||||
}
|
39
src/Frontend/Asset/AssetInterface.php
Normal file
39
src/Frontend/Asset/AssetInterface.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Asset;
|
||||
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
|
||||
interface AssetInterface
|
||||
{
|
||||
/**
|
||||
* @param SourceCollector $sources
|
||||
*/
|
||||
public function js(SourceCollector $sources);
|
||||
|
||||
/**
|
||||
* @param SourceCollector $sources
|
||||
*/
|
||||
public function css(SourceCollector $sources);
|
||||
|
||||
/**
|
||||
* @param SourceCollector $sources
|
||||
* @param string $locale
|
||||
*/
|
||||
public function localeJs(SourceCollector $sources, string $locale);
|
||||
|
||||
/**
|
||||
* @param SourceCollector $sources
|
||||
* @param string $locale
|
||||
*/
|
||||
public function localeCss(SourceCollector $sources, string $locale);
|
||||
}
|
48
src/Frontend/Asset/CoreAssets.php
Normal file
48
src/Frontend/Asset/CoreAssets.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Asset;
|
||||
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
|
||||
class CoreAssets implements AssetInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function js(SourceCollector $sources)
|
||||
{
|
||||
$sources->addFile(__DIR__."/../../../js/dist/$this->name.js");
|
||||
}
|
||||
|
||||
public function css(SourceCollector $sources)
|
||||
{
|
||||
$sources->addFile(__DIR__."/../../../less/$this->name.less");
|
||||
}
|
||||
|
||||
public function localeJs(SourceCollector $sources, string $locale)
|
||||
{
|
||||
}
|
||||
|
||||
public function localeCss(SourceCollector $sources, string $locale)
|
||||
{
|
||||
}
|
||||
}
|
80
src/Frontend/Asset/ExtensionAssets.php
Normal file
80
src/Frontend/Asset/ExtensionAssets.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Asset;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
|
||||
class ExtensionAssets implements AssetInterface
|
||||
{
|
||||
/**
|
||||
* @var Extension
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $css;
|
||||
|
||||
/**
|
||||
* @var string|callable|null
|
||||
*/
|
||||
protected $js;
|
||||
|
||||
/**
|
||||
* @param Extension $extension
|
||||
* @param array $css
|
||||
* @param string|callable|null $js
|
||||
*/
|
||||
public function __construct(Extension $extension, array $css, $js = null)
|
||||
{
|
||||
$this->extension = $extension;
|
||||
$this->css = $css;
|
||||
$this->js = $js;
|
||||
}
|
||||
|
||||
public function js(SourceCollector $sources)
|
||||
{
|
||||
if ($this->js) {
|
||||
$sources->addString(function () {
|
||||
$name = $this->extension->getId();
|
||||
|
||||
return 'var module={};'.$this->getContent($this->js).";flarum.extensions['$name']=module.exports";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function css(SourceCollector $sources)
|
||||
{
|
||||
foreach ($this->css as $asset) {
|
||||
if (is_callable($asset)) {
|
||||
$sources->addString($asset);
|
||||
} else {
|
||||
$sources->addFile($asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getContent($asset)
|
||||
{
|
||||
return is_callable($asset) ? $asset() : file_get_contents($asset);
|
||||
}
|
||||
|
||||
public function localeJs(SourceCollector $sources, string $locale)
|
||||
{
|
||||
}
|
||||
|
||||
public function localeCss(SourceCollector $sources, string $locale)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Asset;
|
||||
|
||||
use Illuminate\Cache\Repository;
|
||||
|
||||
class JsCompiler extends RevisionCompiler
|
||||
{
|
||||
/**
|
||||
* @var Repository
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $filename
|
||||
* @param bool $watch
|
||||
* @param Repository $cache
|
||||
*/
|
||||
public function __construct($path, $filename, $watch = false, Repository $cache = null)
|
||||
{
|
||||
parent::__construct($path, $filename, $watch);
|
||||
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function format($string)
|
||||
{
|
||||
return $string.";\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getCacheDifferentiator()
|
||||
{
|
||||
return $this->watch;
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Asset;
|
||||
|
||||
use Less_Exception_Parser;
|
||||
use Less_Parser;
|
||||
|
||||
class LessCompiler extends RevisionCompiler
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $cachePath;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $filename
|
||||
* @param bool $watch
|
||||
* @param string $cachePath
|
||||
*/
|
||||
public function __construct($path, $filename, $watch, $cachePath)
|
||||
{
|
||||
parent::__construct($path, $filename, $watch);
|
||||
|
||||
$this->cachePath = $cachePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function compile()
|
||||
{
|
||||
if (! count($this->files) || ! count($this->strings)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ini_set('xdebug.max_nesting_level', 200);
|
||||
|
||||
$parser = new Less_Parser([
|
||||
'compress' => true,
|
||||
'cache_dir' => $this->cachePath,
|
||||
'import_dirs' => [
|
||||
base_path('vendor/components/font-awesome/less') => '',
|
||||
],
|
||||
]);
|
||||
|
||||
try {
|
||||
foreach ($this->files as $file) {
|
||||
$parser->parseFile($file);
|
||||
}
|
||||
|
||||
foreach ($this->strings as $callback) {
|
||||
$parser->parse($callback());
|
||||
}
|
||||
|
||||
return $parser->getCss();
|
||||
} catch (Less_Exception_Parser $e) {
|
||||
// TODO: log an error somewhere?
|
||||
}
|
||||
}
|
||||
}
|
65
src/Frontend/Asset/LessVariables.php
Normal file
65
src/Frontend/Asset/LessVariables.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Asset;
|
||||
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
|
||||
class LessVariables implements AssetInterface
|
||||
{
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @param SettingsRepositoryInterface $settings
|
||||
*/
|
||||
public function __construct(SettingsRepositoryInterface $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function css(SourceCollector $sources)
|
||||
{
|
||||
$this->addLessVariables($sources);
|
||||
}
|
||||
|
||||
public function localeCss(SourceCollector $sources, string $locale)
|
||||
{
|
||||
$this->addLessVariables($sources);
|
||||
}
|
||||
|
||||
private function addLessVariables(SourceCollector $compiler)
|
||||
{
|
||||
$vars = [
|
||||
'config-primary-color' => $this->settings->get('theme_primary_color', '#000'),
|
||||
'config-secondary-color' => $this->settings->get('theme_secondary_color', '#000'),
|
||||
'config-dark-mode' => $this->settings->get('theme_dark_mode') ? 'true' : 'false',
|
||||
'config-colored-header' => $this->settings->get('theme_colored_header') ? 'true' : 'false'
|
||||
];
|
||||
|
||||
$compiler->addString(function () use ($vars) {
|
||||
return array_reduce(array_keys($vars), function ($string, $name) use ($vars) {
|
||||
return $string."@$name: {$vars[$name]};";
|
||||
}, '');
|
||||
});
|
||||
}
|
||||
|
||||
public function js(SourceCollector $sources)
|
||||
{
|
||||
}
|
||||
|
||||
public function localeJs(SourceCollector $sources, string $locale)
|
||||
{
|
||||
}
|
||||
}
|
53
src/Frontend/Asset/LocaleAssets.php
Normal file
53
src/Frontend/Asset/LocaleAssets.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Asset;
|
||||
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
|
||||
class LocaleAssets implements AssetInterface
|
||||
{
|
||||
/**
|
||||
* @var LocaleManager
|
||||
*/
|
||||
protected $locales;
|
||||
|
||||
/**
|
||||
* @param LocaleManager $locales
|
||||
*/
|
||||
public function __construct(LocaleManager $locales)
|
||||
{
|
||||
$this->locales = $locales;
|
||||
}
|
||||
|
||||
public function localeJs(SourceCollector $sources, string $locale)
|
||||
{
|
||||
foreach ($this->locales->getJsFiles($locale) as $file) {
|
||||
$sources->addFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
public function localeCss(SourceCollector $sources, string $locale)
|
||||
{
|
||||
foreach ($this->locales->getCssFiles($locale) as $file) {
|
||||
$sources->addFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
public function js(SourceCollector $sources)
|
||||
{
|
||||
}
|
||||
|
||||
public function css(SourceCollector $sources)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Asset;
|
||||
|
||||
class LocaleJsCompiler extends JsCompiler
|
||||
{
|
||||
protected $translations = [];
|
||||
|
||||
public function setTranslations(array $translations)
|
||||
{
|
||||
$this->translations = $translations;
|
||||
}
|
||||
|
||||
public function compile()
|
||||
{
|
||||
$output = 'flarum.core.app.translator.translations='.json_encode($this->translations).";\n";
|
||||
|
||||
foreach ($this->files as $filename) {
|
||||
$output .= file_get_contents($filename);
|
||||
}
|
||||
|
||||
return $this->format($output);
|
||||
}
|
||||
}
|
@ -1,207 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Asset;
|
||||
|
||||
class RevisionCompiler implements CompilerInterface
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected $files = [];
|
||||
|
||||
/**
|
||||
* @var callable[]
|
||||
*/
|
||||
protected $strings = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $watch;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $filename
|
||||
* @param bool $watch
|
||||
*/
|
||||
public function __construct($path, $filename, $watch = false)
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->filename = $filename;
|
||||
$this->watch = $watch;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFilename($filename)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addFile($file)
|
||||
{
|
||||
$this->files[] = $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addString(callable $callback)
|
||||
{
|
||||
$this->strings[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFile()
|
||||
{
|
||||
$old = $current = $this->getRevision();
|
||||
|
||||
$ext = pathinfo($this->filename, PATHINFO_EXTENSION);
|
||||
$file = $this->path.'/'.substr_replace($this->filename, '-'.$old, -strlen($ext) - 1, 0);
|
||||
|
||||
if ($this->watch || ! $old) {
|
||||
$cacheDifferentiator = [$this->getCacheDifferentiator()];
|
||||
|
||||
foreach ($this->files as $source) {
|
||||
$cacheDifferentiator[] = [$source, filemtime($source)];
|
||||
}
|
||||
|
||||
foreach ($this->strings as $callback) {
|
||||
$cacheDifferentiator[] = $callback();
|
||||
}
|
||||
|
||||
$current = hash('crc32b', serialize($cacheDifferentiator));
|
||||
}
|
||||
|
||||
$exists = file_exists($file);
|
||||
|
||||
if (! $exists || $old !== $current) {
|
||||
if ($exists) {
|
||||
unlink($file);
|
||||
}
|
||||
|
||||
$file = $this->path.'/'.substr_replace($this->filename, '-'.$current, -strlen($ext) - 1, 0);
|
||||
|
||||
if ($content = $this->compile()) {
|
||||
$this->putRevision($current);
|
||||
|
||||
file_put_contents($file, $content);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getCacheDifferentiator()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
protected function format($string)
|
||||
{
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function compile()
|
||||
{
|
||||
$output = '';
|
||||
|
||||
foreach ($this->files as $file) {
|
||||
$output .= $this->formatFile($file);
|
||||
}
|
||||
|
||||
foreach ($this->strings as $callback) {
|
||||
$output .= $this->format($callback());
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @return string
|
||||
*/
|
||||
protected function formatFile($file)
|
||||
{
|
||||
return $this->format(file_get_contents($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getRevisionFile()
|
||||
{
|
||||
return $this->path.'/rev-manifest.json';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getRevision()
|
||||
{
|
||||
if (file_exists($file = $this->getRevisionFile())) {
|
||||
$manifest = json_decode(file_get_contents($file), true);
|
||||
|
||||
return array_get($manifest, $this->filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $revision
|
||||
* @return int
|
||||
*/
|
||||
protected function putRevision($revision)
|
||||
{
|
||||
if (file_exists($file = $this->getRevisionFile())) {
|
||||
$manifest = json_decode(file_get_contents($file), true);
|
||||
} else {
|
||||
$manifest = [];
|
||||
}
|
||||
|
||||
$manifest[$this->filename] = $revision;
|
||||
|
||||
return file_put_contents($this->getRevisionFile(), json_encode($manifest));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
$revision = $this->getRevision();
|
||||
|
||||
$ext = pathinfo($this->filename, PATHINFO_EXTENSION);
|
||||
|
||||
$file = $this->path.'/'.substr_replace($this->filename, '-'.$revision, -strlen($ext) - 1, 0);
|
||||
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
87
src/Frontend/Asset/Translations.php
Normal file
87
src/Frontend/Asset/Translations.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Asset;
|
||||
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
|
||||
class Translations implements AssetInterface
|
||||
{
|
||||
/**
|
||||
* @var LocaleManager
|
||||
*/
|
||||
protected $locales;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
protected $filter;
|
||||
|
||||
/**
|
||||
* @param LocaleManager $locales
|
||||
*/
|
||||
public function __construct(LocaleManager $locales)
|
||||
{
|
||||
$this->locales = $locales;
|
||||
|
||||
$this->filter = function () {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
public function localeJs(SourceCollector $sources, string $locale)
|
||||
{
|
||||
$sources->addString(function () use ($locale) {
|
||||
$translations = $this->getTranslations($locale);
|
||||
|
||||
return 'flarum.core.app.translator.addTranslations('.json_encode($translations).')';
|
||||
});
|
||||
}
|
||||
|
||||
private function getTranslations(string $locale)
|
||||
{
|
||||
$translations = $this->locales->getTranslator()->getCatalogue($locale)->all('messages');
|
||||
|
||||
return array_only(
|
||||
$translations,
|
||||
array_filter(array_keys($translations), $this->filter)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable
|
||||
*/
|
||||
public function getFilter(): callable
|
||||
{
|
||||
return $this->filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $filter
|
||||
*/
|
||||
public function setFilter(callable $filter)
|
||||
{
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
public function js(SourceCollector $sources)
|
||||
{
|
||||
}
|
||||
|
||||
public function css(SourceCollector $sources)
|
||||
{
|
||||
}
|
||||
|
||||
public function localeCss(SourceCollector $sources, string $locale)
|
||||
{
|
||||
}
|
||||
}
|
@ -9,34 +9,31 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Asset;
|
||||
namespace Flarum\Frontend\Compiler;
|
||||
|
||||
interface CompilerInterface
|
||||
{
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
public function setFilename($filename);
|
||||
public function getFilename(): string;
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param string $filename
|
||||
*/
|
||||
public function addFile($file);
|
||||
public function setFilename(string $filename);
|
||||
|
||||
/**
|
||||
* @param callable $callback
|
||||
*/
|
||||
public function addString(callable $callback);
|
||||
public function addSources(callable $callback);
|
||||
|
||||
public function commit();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getFile();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function compile();
|
||||
public function getUrl(): ?string;
|
||||
|
||||
public function flush();
|
||||
}
|
84
src/Frontend/Compiler/JsCompiler.php
Normal file
84
src/Frontend/Compiler/JsCompiler.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Compiler;
|
||||
|
||||
use axy\sourcemap\SourceMap;
|
||||
use Flarum\Frontend\Compiler\Source\FileSource;
|
||||
|
||||
class JsCompiler extends RevisionCompiler
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function save(string $file, array $sources): bool
|
||||
{
|
||||
$mapFile = $file.'.map';
|
||||
|
||||
$map = new SourceMap();
|
||||
$map->file = $mapFile;
|
||||
$output = [];
|
||||
$line = 0;
|
||||
|
||||
// For each of the sources, get their content and add it to the
|
||||
// output. For file sources, if a sourcemap is present, add it to
|
||||
// the output sourcemap.
|
||||
foreach ($sources as $source) {
|
||||
$content = $source->getContent();
|
||||
|
||||
if ($source instanceof FileSource) {
|
||||
$sourceMap = $source->getPath().'.map';
|
||||
|
||||
if (file_exists($sourceMap)) {
|
||||
$map->concat($sourceMap, $line);
|
||||
}
|
||||
}
|
||||
|
||||
$content = $this->format($content);
|
||||
$output[] = $content;
|
||||
$line += substr_count($content, "\n") + 1;
|
||||
}
|
||||
|
||||
// Add a comment to the end of our file to point to the sourcemap
|
||||
// we just constructed. We will then write the JS file, save the
|
||||
// map to a temporary location, and then move it to the asset dir.
|
||||
$output[] = '//# sourceMappingURL='.$this->assetsDir->url($mapFile);
|
||||
|
||||
$this->assetsDir->put($file, implode("\n", $output));
|
||||
|
||||
$mapTemp = tempnam(sys_get_temp_dir(), $mapFile);
|
||||
$map->save($mapTemp);
|
||||
$this->assetsDir->put($mapFile, file_get_contents($mapTemp));
|
||||
@unlink($mapTemp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function format(string $string): string
|
||||
{
|
||||
return preg_replace('~//# sourceMappingURL.*$~s', '', $string).";\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function delete(string $file)
|
||||
{
|
||||
parent::delete($file);
|
||||
|
||||
if ($this->assetsDir->has($mapFile = $file.'.map')) {
|
||||
$this->assetsDir->delete($mapFile);
|
||||
}
|
||||
}
|
||||
}
|
96
src/Frontend/Compiler/LessCompiler.php
Normal file
96
src/Frontend/Compiler/LessCompiler.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Compiler;
|
||||
|
||||
use Flarum\Frontend\Compiler\Source\FileSource;
|
||||
use Less_Parser;
|
||||
|
||||
class LessCompiler extends RevisionCompiler
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $cacheDir;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $importDirs = [];
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheDir(): string
|
||||
{
|
||||
return $this->cacheDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cacheDir
|
||||
*/
|
||||
public function setCacheDir(string $cacheDir)
|
||||
{
|
||||
$this->cacheDir = $cacheDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getImportDirs(): array
|
||||
{
|
||||
return $this->importDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $importDirs
|
||||
*/
|
||||
public function setImportDirs(array $importDirs)
|
||||
{
|
||||
$this->importDirs = $importDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function compile(array $sources): string
|
||||
{
|
||||
if (! count($sources)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
ini_set('xdebug.max_nesting_level', 200);
|
||||
|
||||
$parser = new Less_Parser([
|
||||
'compress' => true,
|
||||
'cache_dir' => $this->cacheDir,
|
||||
'import_dirs' => $this->importDirs
|
||||
]);
|
||||
|
||||
foreach ($sources as $source) {
|
||||
if ($source instanceof FileSource) {
|
||||
$parser->parseFile($source->getPath());
|
||||
} else {
|
||||
$parser->parse($source->getContent());
|
||||
}
|
||||
}
|
||||
|
||||
return $parser->getCss();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getCacheDifferentiator()
|
||||
{
|
||||
return time();
|
||||
}
|
||||
}
|
276
src/Frontend/Compiler/RevisionCompiler.php
Normal file
276
src/Frontend/Compiler/RevisionCompiler.php
Normal file
@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Compiler;
|
||||
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
use Flarum\Frontend\Compiler\Source\SourceInterface;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
|
||||
class RevisionCompiler implements CompilerInterface
|
||||
{
|
||||
const REV_MANIFEST = 'rev-manifest.json';
|
||||
|
||||
const EMPTY_REVISION = 'empty';
|
||||
|
||||
/**
|
||||
* @var FilesystemAdapter
|
||||
*/
|
||||
protected $assetsDir;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $filename;
|
||||
|
||||
/**
|
||||
* @var callable[]
|
||||
*/
|
||||
protected $sourcesCallbacks = [];
|
||||
|
||||
/**
|
||||
* @param FilesystemAdapter $assetsDir
|
||||
* @param string $filename
|
||||
*/
|
||||
public function __construct(FilesystemAdapter $assetsDir, string $filename)
|
||||
{
|
||||
$this->assetsDir = $assetsDir;
|
||||
$this->filename = $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilename(): string
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFilename(string $filename)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
$sources = $this->getSources();
|
||||
|
||||
$oldRevision = $this->getRevision();
|
||||
|
||||
$newRevision = $this->calculateRevision($sources);
|
||||
|
||||
$oldFile = $oldRevision ? $this->getFilenameForRevision($oldRevision) : null;
|
||||
|
||||
if ($oldRevision !== $newRevision || ($oldFile && ! $this->assetsDir->has($oldFile))) {
|
||||
$newFile = $this->getFilenameForRevision($newRevision);
|
||||
|
||||
if (! $this->save($newFile, $sources)) {
|
||||
// If no file was written (because the sources were empty), we
|
||||
// will set the revision to a special value so that we can tell
|
||||
// that this file does not have a URL.
|
||||
$newRevision = static::EMPTY_REVISION;
|
||||
}
|
||||
|
||||
$this->putRevision($newRevision);
|
||||
|
||||
if ($oldFile) {
|
||||
$this->delete($oldFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addSources(callable $callback)
|
||||
{
|
||||
$this->sourcesCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SourceInterface[]
|
||||
*/
|
||||
protected function getSources()
|
||||
{
|
||||
$sources = new SourceCollector;
|
||||
|
||||
foreach ($this->sourcesCallbacks as $callback) {
|
||||
$callback($sources);
|
||||
}
|
||||
|
||||
return $sources->getSources();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUrl(): ?string
|
||||
{
|
||||
$revision = $this->getRevision();
|
||||
|
||||
if (! $revision) {
|
||||
$this->commit();
|
||||
|
||||
$revision = $this->getRevision();
|
||||
|
||||
if (! $revision) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($revision === static::EMPTY_REVISION) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$file = $this->getFilenameForRevision($revision);
|
||||
|
||||
return $this->assetsDir->url($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param SourceInterface[] $sources
|
||||
* @return bool true if the file was written, false if there was nothing to write
|
||||
*/
|
||||
protected function save(string $file, array $sources): bool
|
||||
{
|
||||
if ($content = $this->compile($sources)) {
|
||||
$this->assetsDir->put($file, $content);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SourceInterface[] $sources
|
||||
* @return string
|
||||
*/
|
||||
protected function compile(array $sources): string
|
||||
{
|
||||
$output = '';
|
||||
|
||||
foreach ($sources as $source) {
|
||||
$output .= $this->format($source->getContent());
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
protected function format(string $string): string
|
||||
{
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename for the given revision.
|
||||
*
|
||||
* @param string $revision
|
||||
* @return string
|
||||
*/
|
||||
protected function getFilenameForRevision(string $revision): string
|
||||
{
|
||||
$ext = pathinfo($this->filename, PATHINFO_EXTENSION);
|
||||
|
||||
return substr_replace($this->filename, '-'.$revision, -strlen($ext) - 1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getRevision(): ?string
|
||||
{
|
||||
if ($this->assetsDir->has(static::REV_MANIFEST)) {
|
||||
$manifest = json_decode($this->assetsDir->read(static::REV_MANIFEST), true);
|
||||
|
||||
return array_get($manifest, $this->filename);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $revision
|
||||
*/
|
||||
protected function putRevision(?string $revision)
|
||||
{
|
||||
if ($this->assetsDir->has(static::REV_MANIFEST)) {
|
||||
$manifest = json_decode($this->assetsDir->read(static::REV_MANIFEST), true);
|
||||
} else {
|
||||
$manifest = [];
|
||||
}
|
||||
|
||||
if ($revision) {
|
||||
$manifest[$this->filename] = $revision;
|
||||
} else {
|
||||
unset($manifest[$this->filename]);
|
||||
}
|
||||
|
||||
$this->assetsDir->put(static::REV_MANIFEST, json_encode($manifest));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SourceInterface[] $sources
|
||||
* @return string
|
||||
*/
|
||||
protected function calculateRevision(array $sources): string
|
||||
{
|
||||
$cacheDifferentiator = [$this->getCacheDifferentiator()];
|
||||
|
||||
foreach ($sources as $source) {
|
||||
$cacheDifferentiator[] = $source->getCacheDifferentiator();
|
||||
}
|
||||
|
||||
return hash('crc32b', serialize($cacheDifferentiator));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getCacheDifferentiator()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
if ($revision = $this->getRevision()) {
|
||||
$file = $this->getFilenameForRevision($revision);
|
||||
|
||||
$this->delete($file);
|
||||
|
||||
$this->putRevision(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*/
|
||||
protected function delete(string $file)
|
||||
{
|
||||
if ($this->assetsDir->has($file)) {
|
||||
$this->assetsDir->delete($file);
|
||||
}
|
||||
}
|
||||
}
|
52
src/Frontend/Compiler/Source/FileSource.php
Normal file
52
src/Frontend/Compiler/Source/FileSource.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Compiler\Source;
|
||||
|
||||
class FileSource implements SourceInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
*/
|
||||
public function __construct(string $path)
|
||||
{
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getContent(): string
|
||||
{
|
||||
return file_get_contents($this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCacheDifferentiator()
|
||||
{
|
||||
return [$this->path, filemtime($this->path)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
}
|
50
src/Frontend/Compiler/Source/SourceCollector.php
Normal file
50
src/Frontend/Compiler/Source/SourceCollector.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Compiler\Source;
|
||||
|
||||
class SourceCollector
|
||||
{
|
||||
/**
|
||||
* @var SourceInterface[]
|
||||
*/
|
||||
protected $sources = [];
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @return $this
|
||||
*/
|
||||
public function addFile(string $file)
|
||||
{
|
||||
$this->sources[] = new FileSource($file);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function addString(callable $callback)
|
||||
{
|
||||
$this->sources[] = new StringSource($callback);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SourceInterface[]
|
||||
*/
|
||||
public function getSources()
|
||||
{
|
||||
return $this->sources;
|
||||
}
|
||||
}
|
25
src/Frontend/Compiler/Source/SourceInterface.php
Normal file
25
src/Frontend/Compiler/Source/SourceInterface.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Compiler\Source;
|
||||
|
||||
interface SourceInterface
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getContent(): string;
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCacheDifferentiator();
|
||||
}
|
50
src/Frontend/Compiler/Source/StringSource.php
Normal file
50
src/Frontend/Compiler/Source/StringSource.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Compiler\Source;
|
||||
|
||||
class StringSource implements SourceInterface
|
||||
{
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
protected $callback;
|
||||
|
||||
private $content;
|
||||
|
||||
/**
|
||||
* @param callable $callback
|
||||
*/
|
||||
public function __construct(callable $callback)
|
||||
{
|
||||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getContent(): string
|
||||
{
|
||||
if (is_null($this->content)) {
|
||||
$this->content = call_user_func($this->callback);
|
||||
}
|
||||
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCacheDifferentiator()
|
||||
{
|
||||
return $this->getContent();
|
||||
}
|
||||
}
|
239
src/Frontend/CompilerFactory.php
Normal file
239
src/Frontend/CompilerFactory.php
Normal file
@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend;
|
||||
|
||||
use Flarum\Frontend\Asset\AssetInterface;
|
||||
use Flarum\Frontend\Compiler\CompilerInterface;
|
||||
use Flarum\Frontend\Compiler\JsCompiler;
|
||||
use Flarum\Frontend\Compiler\LessCompiler;
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
use Illuminate\Filesystem\FilesystemAdapter;
|
||||
|
||||
/**
|
||||
* A factory class for creating frontend asset compilers.
|
||||
*/
|
||||
class CompilerFactory
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var FilesystemAdapter
|
||||
*/
|
||||
protected $assetsDir;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $cacheDir;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $lessImportDirs;
|
||||
|
||||
/**
|
||||
* @var AssetInterface[]
|
||||
*/
|
||||
protected $assets = [];
|
||||
|
||||
/**
|
||||
* @var callable[]
|
||||
*/
|
||||
protected $addCallbacks = [];
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param FilesystemAdapter $assetsDir
|
||||
* @param string $cacheDir
|
||||
* @param array|null $lessImportDirs
|
||||
*/
|
||||
public function __construct(string $name, FilesystemAdapter $assetsDir, string $cacheDir = null, array $lessImportDirs = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->assetsDir = $assetsDir;
|
||||
$this->cacheDir = $cacheDir;
|
||||
$this->lessImportDirs = $lessImportDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callback
|
||||
*/
|
||||
public function add(callable $callback)
|
||||
{
|
||||
$this->addCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JsCompiler
|
||||
*/
|
||||
public function makeJs(): JsCompiler
|
||||
{
|
||||
$compiler = new JsCompiler($this->assetsDir, $this->name.'.js');
|
||||
|
||||
$this->addSources($compiler, function (AssetInterface $asset, SourceCollector $sources) {
|
||||
$asset->js($sources);
|
||||
});
|
||||
|
||||
return $compiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LessCompiler
|
||||
*/
|
||||
public function makeCss(): LessCompiler
|
||||
{
|
||||
$compiler = $this->makeLessCompiler($this->name.'.css');
|
||||
|
||||
$this->addSources($compiler, function (AssetInterface $asset, SourceCollector $sources) {
|
||||
$asset->css($sources);
|
||||
});
|
||||
|
||||
return $compiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $locale
|
||||
* @return JsCompiler
|
||||
*/
|
||||
public function makeLocaleJs(string $locale): JsCompiler
|
||||
{
|
||||
$compiler = new JsCompiler($this->assetsDir, $this->name.'-'.$locale.'.js');
|
||||
|
||||
$this->addSources($compiler, function (AssetInterface $asset, SourceCollector $sources) use ($locale) {
|
||||
$asset->localeJs($sources, $locale);
|
||||
});
|
||||
|
||||
return $compiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $locale
|
||||
* @return LessCompiler
|
||||
*/
|
||||
public function makeLocaleCss(string $locale): LessCompiler
|
||||
{
|
||||
$compiler = $this->makeLessCompiler($this->name.'-'.$locale.'.css');
|
||||
|
||||
$this->addSources($compiler, function (AssetInterface $asset, SourceCollector $sources) use ($locale) {
|
||||
$asset->localeCss($sources, $locale);
|
||||
});
|
||||
|
||||
return $compiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return LessCompiler
|
||||
*/
|
||||
protected function makeLessCompiler(string $filename): LessCompiler
|
||||
{
|
||||
$compiler = new LessCompiler($this->assetsDir, $filename);
|
||||
|
||||
if ($this->cacheDir) {
|
||||
$compiler->setCacheDir($this->cacheDir.'/less');
|
||||
}
|
||||
|
||||
if ($this->lessImportDirs) {
|
||||
$compiler->setImportDirs($this->lessImportDirs);
|
||||
}
|
||||
|
||||
return $compiler;
|
||||
}
|
||||
|
||||
protected function fireAddCallbacks()
|
||||
{
|
||||
foreach ($this->addCallbacks as $callback) {
|
||||
$assets = $callback($this);
|
||||
$this->assets = array_merge($this->assets, is_array($assets) ? $assets : [$assets]);
|
||||
}
|
||||
|
||||
$this->addCallbacks = [];
|
||||
}
|
||||
|
||||
private function addSources(CompilerInterface $compiler, callable $callback)
|
||||
{
|
||||
$compiler->addSources(function ($sources) use ($callback) {
|
||||
$this->fireAddCallbacks();
|
||||
|
||||
foreach ($this->assets as $asset) {
|
||||
$callback($asset, $sources);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FilesystemAdapter
|
||||
*/
|
||||
public function getAssetsDir(): FilesystemAdapter
|
||||
{
|
||||
return $this->assetsDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FilesystemAdapter $assetsDir
|
||||
*/
|
||||
public function setAssetsDir(FilesystemAdapter $assetsDir)
|
||||
{
|
||||
$this->assetsDir = $assetsDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheDir(): ?string
|
||||
{
|
||||
return $this->cacheDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cacheDir
|
||||
*/
|
||||
public function setCacheDir(?string $cacheDir)
|
||||
{
|
||||
$this->cacheDir = $cacheDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getLessImportDirs(): array
|
||||
{
|
||||
return $this->lessImportDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $lessImportDirs
|
||||
*/
|
||||
public function setLessImportDirs(array $lessImportDirs)
|
||||
{
|
||||
$this->lessImportDirs = $lessImportDirs;
|
||||
}
|
||||
}
|
24
src/Frontend/Content/ContentInterface.php
Normal file
24
src/Frontend/Content/ContentInterface.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Content;
|
||||
|
||||
use Flarum\Frontend\HtmlDocument;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
interface ContentInterface
|
||||
{
|
||||
/**
|
||||
* @param HtmlDocument $document
|
||||
* @param Request $request
|
||||
*/
|
||||
public function populate(HtmlDocument $document, Request $request);
|
||||
}
|
99
src/Frontend/Content/CorePayload.php
Normal file
99
src/Frontend/Content/CorePayload.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Content;
|
||||
|
||||
use Flarum\Api\Client;
|
||||
use Flarum\Api\Controller\ShowUserController;
|
||||
use Flarum\Frontend\HtmlDocument;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Flarum\User\User;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class CorePayload implements ContentInterface
|
||||
{
|
||||
/**
|
||||
* @var LocaleManager
|
||||
*/
|
||||
private $locales;
|
||||
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
private $api;
|
||||
|
||||
/**
|
||||
* @param LocaleManager $locales
|
||||
* @param Client $api
|
||||
*/
|
||||
public function __construct(LocaleManager $locales, Client $api)
|
||||
{
|
||||
$this->locales = $locales;
|
||||
$this->api = $api;
|
||||
}
|
||||
|
||||
public function populate(HtmlDocument $document, Request $request)
|
||||
{
|
||||
$document->payload = array_merge(
|
||||
$document->payload,
|
||||
$this->buildPayload($document, $request)
|
||||
);
|
||||
}
|
||||
|
||||
private function buildPayload(HtmlDocument $document, Request $request)
|
||||
{
|
||||
$data = $this->getDataFromApiDocument($document->getForumApiDocument());
|
||||
|
||||
$actor = $request->getAttribute('actor');
|
||||
|
||||
if ($actor->exists) {
|
||||
$user = $this->getUserApiDocument($actor);
|
||||
$data = array_merge($data, $this->getDataFromApiDocument($user));
|
||||
}
|
||||
|
||||
return [
|
||||
'resources' => $data,
|
||||
'session' => [
|
||||
'userId' => $actor->id,
|
||||
'csrfToken' => $request->getAttribute('session')->token()
|
||||
],
|
||||
'locales' => $this->locales->getLocales(),
|
||||
'locale' => $request->getAttribute('locale')
|
||||
];
|
||||
}
|
||||
|
||||
private function getDataFromApiDocument(array $apiDocument): array
|
||||
{
|
||||
$data[] = $apiDocument['data'];
|
||||
|
||||
if (isset($apiDocument['included'])) {
|
||||
$data = array_merge($data, $apiDocument['included']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function getUserApiDocument(User $user): array
|
||||
{
|
||||
// TODO: to avoid an extra query, something like
|
||||
// $controller = new ShowUserController(new PreloadedUserRepository($user));
|
||||
|
||||
return $this->getResponseBody(
|
||||
$this->api->send(ShowUserController::class, $user, ['id' => $user->id])
|
||||
);
|
||||
}
|
||||
|
||||
private function getResponseBody(ResponseInterface $response)
|
||||
{
|
||||
return json_decode($response->getBody(), true);
|
||||
}
|
||||
}
|
36
src/Frontend/Content/Layout.php
Normal file
36
src/Frontend/Content/Layout.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Content;
|
||||
|
||||
use Flarum\Frontend\HtmlDocument;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class Layout implements ContentInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $layoutView;
|
||||
|
||||
/**
|
||||
* @param string $layoutView
|
||||
*/
|
||||
public function __construct(string $layoutView)
|
||||
{
|
||||
$this->layoutView = $layoutView;
|
||||
}
|
||||
|
||||
public function populate(HtmlDocument $document, Request $request)
|
||||
{
|
||||
$document->layoutView = $this->layoutView;
|
||||
}
|
||||
}
|
50
src/Frontend/Content/Meta.php
Normal file
50
src/Frontend/Content/Meta.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Content;
|
||||
|
||||
use Flarum\Frontend\HtmlDocument;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class Meta implements ContentInterface
|
||||
{
|
||||
public function populate(HtmlDocument $document, Request $request)
|
||||
{
|
||||
$document->meta = array_merge($document->meta, $this->buildMeta($document));
|
||||
$document->head = array_merge($document->head, $this->buildHead($document));
|
||||
}
|
||||
|
||||
private function buildMeta(HtmlDocument $document)
|
||||
{
|
||||
$forumApiDocument = $document->getForumApiDocument();
|
||||
|
||||
$meta = [
|
||||
'viewport' => 'width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1',
|
||||
'description' => array_get($forumApiDocument, 'data.attributes.forumDescription'),
|
||||
'theme-color' => array_get($forumApiDocument, 'data.attributes.themePrimaryColor')
|
||||
];
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
private function buildHead(HtmlDocument $document)
|
||||
{
|
||||
$head = [
|
||||
'font' => '<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700,600">'
|
||||
];
|
||||
|
||||
if ($faviconUrl = array_get($document->getForumApiDocument(), 'data.attributes.faviconUrl')) {
|
||||
$head['favicon'] = '<link rel="shortcut icon" href="'.e($faviconUrl).'">';
|
||||
}
|
||||
|
||||
return $head;
|
||||
}
|
||||
}
|
43
src/Frontend/Controller.php
Normal file
43
src/Frontend/Controller.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Zend\Diactoros\Response\HtmlResponse;
|
||||
|
||||
class Controller implements RequestHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @var HtmlDocumentFactory
|
||||
*/
|
||||
protected $document;
|
||||
|
||||
/**
|
||||
* @param HtmlDocumentFactory $document
|
||||
*/
|
||||
public function __construct(HtmlDocumentFactory $document)
|
||||
{
|
||||
$this->document = $document;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(Request $request): Response
|
||||
{
|
||||
return new HtmlResponse(
|
||||
$this->document->make($request)->render()
|
||||
);
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend\Event;
|
||||
|
||||
use Flarum\Admin\Controller\FrontendController as AdminFrontendController;
|
||||
use Flarum\Forum\Controller\FrontendController as ForumFrontendController;
|
||||
use Flarum\Frontend\AbstractFrontendController;
|
||||
use Flarum\Frontend\FrontendView;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class Rendering
|
||||
{
|
||||
/**
|
||||
* @var AbstractFrontendController
|
||||
*/
|
||||
public $controller;
|
||||
|
||||
/**
|
||||
* @var FrontendView
|
||||
*/
|
||||
public $view;
|
||||
|
||||
/**
|
||||
* @var ServerRequestInterface
|
||||
*/
|
||||
public $request;
|
||||
|
||||
/**
|
||||
* @param AbstractFrontendController $controller
|
||||
* @param FrontendView $view
|
||||
* @param ServerRequestInterface $request
|
||||
*/
|
||||
public function __construct(AbstractFrontendController $controller, FrontendView $view, ServerRequestInterface $request)
|
||||
{
|
||||
$this->controller = $controller;
|
||||
$this->view = $view;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function isForum()
|
||||
{
|
||||
return $this->controller instanceof ForumFrontendController;
|
||||
}
|
||||
|
||||
public function isAdmin()
|
||||
{
|
||||
return $this->controller instanceof AdminFrontendController;
|
||||
}
|
||||
|
||||
public function addAssets($files)
|
||||
{
|
||||
foreach ((array) $files as $file) {
|
||||
$ext = pathinfo($file, PATHINFO_EXTENSION);
|
||||
|
||||
switch ($ext) {
|
||||
case 'js':
|
||||
$this->view->getJs()->addFile($file);
|
||||
break;
|
||||
|
||||
case 'css':
|
||||
case 'less':
|
||||
$this->view->getCss()->addFile($file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function addBootstrapper($bootstrapper)
|
||||
{
|
||||
$this->view->loadModule($bootstrapper);
|
||||
}
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend;
|
||||
|
||||
use Flarum\Foundation\Application;
|
||||
use Flarum\Frontend\Asset\JsCompiler;
|
||||
use Flarum\Frontend\Asset\LessCompiler;
|
||||
use Flarum\Frontend\Asset\LocaleJsCompiler as LocaleJsCompiler;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
|
||||
class FrontendAssets
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* @var Repository
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @var LocaleManager
|
||||
*/
|
||||
protected $locales;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param Application $app
|
||||
* @param Repository $cache
|
||||
* @param LocaleManager $locales
|
||||
*/
|
||||
public function __construct($name, Application $app, Repository $cache, LocaleManager $locales)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->app = $app;
|
||||
$this->cache = $cache;
|
||||
$this->locales = $locales;
|
||||
}
|
||||
|
||||
public function flush()
|
||||
{
|
||||
$this->flushJs();
|
||||
$this->flushCss();
|
||||
}
|
||||
|
||||
public function flushJs()
|
||||
{
|
||||
$this->getJs()->flush();
|
||||
$this->flushLocaleJs();
|
||||
}
|
||||
|
||||
public function flushLocaleJs()
|
||||
{
|
||||
foreach ($this->locales->getLocales() as $locale => $info) {
|
||||
$this->getLocaleJs($locale)->flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function flushCss()
|
||||
{
|
||||
$this->getCss()->flush();
|
||||
$this->flushLocaleCss();
|
||||
}
|
||||
|
||||
public function flushLocaleCss()
|
||||
{
|
||||
foreach ($this->locales->getLocales() as $locale => $info) {
|
||||
$this->getLocaleCss($locale)->flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JsCompiler
|
||||
*/
|
||||
public function getJs()
|
||||
{
|
||||
return new JsCompiler(
|
||||
$this->getDestination(),
|
||||
"$this->name.js",
|
||||
$this->shouldWatch(),
|
||||
$this->cache
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LessCompiler
|
||||
*/
|
||||
public function getCss()
|
||||
{
|
||||
return new LessCompiler(
|
||||
$this->getDestination(),
|
||||
"$this->name.css",
|
||||
$this->shouldWatch(),
|
||||
$this->getLessStorage()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $locale
|
||||
* @return LocaleJsCompiler
|
||||
*/
|
||||
public function getLocaleJs($locale)
|
||||
{
|
||||
return new LocaleJsCompiler(
|
||||
$this->getDestination(),
|
||||
"$this->name-$locale.js",
|
||||
$this->shouldWatch(),
|
||||
$this->cache
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $locale
|
||||
* @return LessCompiler
|
||||
*/
|
||||
public function getLocaleCss($locale)
|
||||
{
|
||||
return new LessCompiler(
|
||||
$this->getDestination(),
|
||||
"$this->name-$locale.css",
|
||||
$this->shouldWatch(),
|
||||
$this->getLessStorage()
|
||||
);
|
||||
}
|
||||
|
||||
protected function getDestination()
|
||||
{
|
||||
return $this->app->publicPath().'/assets';
|
||||
}
|
||||
|
||||
protected function shouldWatch()
|
||||
{
|
||||
return $this->app->config('debug');
|
||||
}
|
||||
|
||||
protected function getLessStorage()
|
||||
{
|
||||
return $this->app->storagePath().'/less';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend;
|
||||
|
||||
use Flarum\Foundation\Application;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
|
||||
class FrontendAssetsFactory
|
||||
{
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* @var Repository
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @var LocaleManager
|
||||
*/
|
||||
protected $locales;
|
||||
|
||||
/**
|
||||
* @param Application $app
|
||||
* @param Repository $cache
|
||||
* @param LocaleManager $locales
|
||||
*/
|
||||
public function __construct(Application $app, Repository $cache, LocaleManager $locales)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->cache = $cache;
|
||||
$this->locales = $locales;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return FrontendAssets
|
||||
*/
|
||||
public function make($name)
|
||||
{
|
||||
return new FrontendAssets($name, $this->app, $this->cache, $this->locales);
|
||||
}
|
||||
}
|
@ -12,14 +12,73 @@
|
||||
namespace Flarum\Frontend;
|
||||
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Illuminate\Contracts\View\Factory as ViewFactory;
|
||||
|
||||
class FrontendServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
// Yo dawg, I heard you like factories, so I made you a factory to
|
||||
// create your factory. We expose a couple of factory functions that
|
||||
// will create frontend factories and configure them with some default
|
||||
// settings common to both the forum and admin frontends.
|
||||
|
||||
$this->app->singleton('flarum.frontend.assets.defaults', function () {
|
||||
return function (string $name) {
|
||||
$assets = new CompilerFactory(
|
||||
$name,
|
||||
$this->app->make('filesystem')->disk('flarum-assets'),
|
||||
$this->app->storagePath()
|
||||
);
|
||||
|
||||
$assets->setLessImportDirs([
|
||||
$this->app->basePath().'/vendor/components/font-awesome/less' => ''
|
||||
]);
|
||||
|
||||
$assets->add(function () use ($name) {
|
||||
$translations = $this->app->make(Asset\Translations::class);
|
||||
$translations->setFilter(function (string $id) use ($name) {
|
||||
return preg_match('/^.+(?:\.|::)(?:'.$name.'|lib)\./', $id);
|
||||
});
|
||||
|
||||
return [
|
||||
new Asset\CoreAssets($name),
|
||||
$this->app->make(Asset\LessVariables::class),
|
||||
$translations,
|
||||
$this->app->make(Asset\LocaleAssets::class)
|
||||
];
|
||||
});
|
||||
|
||||
return $assets;
|
||||
};
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.frontend.view.defaults', function () {
|
||||
return function (string $name) {
|
||||
$view = $this->app->make(HtmlDocumentFactory::class);
|
||||
|
||||
$view->setCommitAssets($this->app->inDebugMode());
|
||||
|
||||
$view->add(new Content\Layout('flarum::frontend.'.$name));
|
||||
$view->add($this->app->make(Content\CorePayload::class));
|
||||
$view->add($this->app->make(Content\Meta::class));
|
||||
|
||||
return $view;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->loadViewsFrom(__DIR__.'/../../views', 'flarum');
|
||||
|
||||
$this->app->make(ViewFactory::class)->share([
|
||||
'translator' => $this->app->make('translator'),
|
||||
'url' => $this->app->make(UrlGenerator::class)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -1,488 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend;
|
||||
|
||||
use Flarum\Api\Client;
|
||||
use Flarum\Api\Serializer\AbstractSerializer;
|
||||
use Flarum\Foundation\Application;
|
||||
use Flarum\Frontend\Asset\CompilerInterface;
|
||||
use Flarum\Frontend\Asset\LocaleJsCompiler;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Illuminate\View\Factory;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Tobscure\JsonApi\Document;
|
||||
use Tobscure\JsonApi\Resource;
|
||||
|
||||
/**
|
||||
* This class represents a view which boots up Flarum's client.
|
||||
*/
|
||||
class FrontendView
|
||||
{
|
||||
/**
|
||||
* The title of the document, displayed in the <title> tag.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* The description of the document, displayed in a <meta> tag.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
public $description;
|
||||
|
||||
/**
|
||||
* The language of the document, displayed as the value of the attribute `dir` in the <html> tag.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
public $language;
|
||||
|
||||
/**
|
||||
* The text direction of the document, displayed as the value of the attribute `dir` in the <html> tag.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
public $direction;
|
||||
|
||||
/**
|
||||
* The path to the client layout view to display.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $layout;
|
||||
|
||||
/**
|
||||
* The SEO content of the page, displayed within the layout in <noscript>
|
||||
* tags.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $content;
|
||||
|
||||
/**
|
||||
* An API response to be preloaded into the page.
|
||||
*
|
||||
* This should be a JSON-API document.
|
||||
*
|
||||
* @var null|array|object
|
||||
*/
|
||||
public $document;
|
||||
|
||||
/**
|
||||
* Other variables to preload into the page.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $variables = [];
|
||||
|
||||
/**
|
||||
* An array of JS modules to load before booting the app.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $modules = ['locale'];
|
||||
|
||||
/**
|
||||
* An array of strings to append to the page's <head>.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $head = [];
|
||||
|
||||
/**
|
||||
* An array of strings to prepend before the page's </body>.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $foot = [];
|
||||
|
||||
/**
|
||||
* A map of <link> tags to be generated.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $links = [];
|
||||
|
||||
/**
|
||||
* @var CompilerInterface
|
||||
*/
|
||||
protected $js;
|
||||
|
||||
/**
|
||||
* @var CompilerInterface
|
||||
*/
|
||||
protected $css;
|
||||
|
||||
/**
|
||||
* @var CompilerInterface
|
||||
*/
|
||||
protected $localeJs;
|
||||
|
||||
/**
|
||||
* @var CompilerInterface
|
||||
*/
|
||||
protected $localeCss;
|
||||
|
||||
/**
|
||||
* @var FrontendAssets
|
||||
*/
|
||||
protected $assets;
|
||||
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
protected $api;
|
||||
|
||||
/**
|
||||
* @var Factory
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* @var LocaleManager
|
||||
*/
|
||||
protected $locales;
|
||||
|
||||
/**
|
||||
* @var AbstractSerializer
|
||||
*/
|
||||
protected $userSerializer;
|
||||
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* @param string $layout
|
||||
* @param FrontendAssets $assets
|
||||
* @param Client $api
|
||||
* @param Factory $view
|
||||
* @param LocaleManager $locales
|
||||
* @param AbstractSerializer $userSerializer
|
||||
* @param Application $app
|
||||
*/
|
||||
public function __construct($layout, FrontendAssets $assets, Client $api, Factory $view, LocaleManager $locales, AbstractSerializer $userSerializer, Application $app)
|
||||
{
|
||||
$this->layout = $layout;
|
||||
$this->api = $api;
|
||||
$this->assets = $assets;
|
||||
$this->view = $view;
|
||||
$this->locales = $locales;
|
||||
$this->userSerializer = $userSerializer;
|
||||
$this->app = $app;
|
||||
|
||||
$this->addHeadString('<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700,600">', 'font');
|
||||
|
||||
$this->js = $this->assets->getJs();
|
||||
$this->css = $this->assets->getCss();
|
||||
|
||||
$locale = $this->locales->getLocale();
|
||||
$this->localeJs = $this->assets->getLocaleJs($locale);
|
||||
$this->localeCss = $this->assets->getLocaleCss($locale);
|
||||
|
||||
foreach ($this->locales->getJsFiles($locale) as $file) {
|
||||
$this->localeJs->addFile($file);
|
||||
}
|
||||
|
||||
foreach ($this->locales->getCssFiles($locale) as $file) {
|
||||
$this->localeCss->addFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a string to be appended to the page's <head>.
|
||||
*
|
||||
* @param string $string
|
||||
* @param null|string $name
|
||||
*/
|
||||
public function addHeadString($string, $name = null)
|
||||
{
|
||||
if ($name) {
|
||||
$this->head[$name] = $string;
|
||||
} else {
|
||||
$this->head[] = $string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a string to be prepended before the page's </body>.
|
||||
*
|
||||
* @param string $string
|
||||
*/
|
||||
public function addFootString($string)
|
||||
{
|
||||
$this->foot[] = $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a <link> tag.
|
||||
*
|
||||
* @param string $relation
|
||||
* @param string $target
|
||||
*/
|
||||
public function link($relation, $target)
|
||||
{
|
||||
$this->links[$relation] = $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the canonical URL for this page.
|
||||
*
|
||||
* This will signal to search engines what URL should be used for this
|
||||
* content, if it can be found under multiple addresses. This is an
|
||||
* important tool to tackle duplicate content.
|
||||
*
|
||||
* @param string $url
|
||||
*/
|
||||
public function setCanonicalUrl($url)
|
||||
{
|
||||
$this->link('canonical', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a variable to be preloaded into the app.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setVariable($name, $value)
|
||||
{
|
||||
$this->variables[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a JavaScript module to be imported before the app is booted.
|
||||
*
|
||||
* @param string $module
|
||||
*/
|
||||
public function loadModule($module)
|
||||
{
|
||||
$this->modules[] = $module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string contents of the view.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return string
|
||||
*/
|
||||
public function render(Request $request)
|
||||
{
|
||||
$forum = $this->getForumDocument($request);
|
||||
|
||||
$this->view->share('translator', $this->locales->getTranslator());
|
||||
$this->view->share('allowJs', ! array_get($request->getQueryParams(), 'nojs'));
|
||||
$this->view->share('forum', array_get($forum, 'data'));
|
||||
$this->view->share('debug', $this->app->inDebugMode());
|
||||
|
||||
$view = $this->view->make('flarum.forum::frontend.app');
|
||||
|
||||
$view->title = $this->buildTitle(array_get($forum, 'data.attributes.title'));
|
||||
$view->description = $this->description ?: array_get($forum, 'data.attributes.description');
|
||||
$view->language = $this->language ?: $this->locales->getLocale();
|
||||
$view->direction = $this->direction ?: 'ltr';
|
||||
|
||||
$view->modules = $this->modules;
|
||||
$view->payload = $this->buildPayload($request, $forum);
|
||||
|
||||
$view->layout = $this->buildLayout();
|
||||
|
||||
$baseUrl = array_get($forum, 'data.attributes.baseUrl');
|
||||
$view->cssUrls = $this->buildCssUrls($baseUrl);
|
||||
$view->jsUrls = $this->buildJsUrls($baseUrl);
|
||||
|
||||
$view->head = $this->buildHeadContent();
|
||||
$view->foot = $this->buildFootContent(array_get($forum, 'data.attributes.footerHtml'));
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
protected function buildTitle($forumTitle)
|
||||
{
|
||||
return ($this->title ? $this->title.' - ' : '').$forumTitle;
|
||||
}
|
||||
|
||||
protected function buildPayload(Request $request, $forum)
|
||||
{
|
||||
$data = $this->getDataFromDocument($forum);
|
||||
|
||||
if ($request->getAttribute('actor')->exists) {
|
||||
$user = $this->getUserDocument($request);
|
||||
$data = array_merge($data, $this->getDataFromDocument($user));
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'resources' => $data,
|
||||
'session' => $this->buildSession($request),
|
||||
'document' => $this->document,
|
||||
'locales' => $this->locales->getLocales(),
|
||||
'locale' => $this->locales->getLocale()
|
||||
];
|
||||
|
||||
return array_merge($payload, $this->variables);
|
||||
}
|
||||
|
||||
protected function buildLayout()
|
||||
{
|
||||
$view = $this->view->make($this->layout);
|
||||
|
||||
$view->content = $this->buildContent();
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
protected function buildContent()
|
||||
{
|
||||
$view = $this->view->make('flarum.forum::frontend.content');
|
||||
|
||||
$view->content = $this->content;
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
protected function buildCssUrls($baseUrl)
|
||||
{
|
||||
return $this->buildAssetUrls($baseUrl, [$this->css->getFile(), $this->localeCss->getFile()]);
|
||||
}
|
||||
|
||||
protected function buildJsUrls($baseUrl)
|
||||
{
|
||||
return $this->buildAssetUrls($baseUrl, [$this->js->getFile(), $this->localeJs->getFile()]);
|
||||
}
|
||||
|
||||
protected function buildAssetUrls($baseUrl, $files)
|
||||
{
|
||||
return array_map(function ($file) use ($baseUrl) {
|
||||
return $baseUrl.str_replace(public_path(), '', $file);
|
||||
}, array_filter($files));
|
||||
}
|
||||
|
||||
protected function buildHeadContent()
|
||||
{
|
||||
$html = implode("\n", $this->head);
|
||||
|
||||
foreach ($this->links as $rel => $href) {
|
||||
$html .= "\n<link rel=\"$rel\" href=\"$href\" />";
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
protected function buildFootContent($customFooterHtml)
|
||||
{
|
||||
return implode("\n", $this->foot)."\n".$customFooterHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CompilerInterface
|
||||
*/
|
||||
public function getJs()
|
||||
{
|
||||
return $this->js;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CompilerInterface
|
||||
*/
|
||||
public function getCss()
|
||||
{
|
||||
return $this->css;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LocaleJsCompiler
|
||||
*/
|
||||
public function getLocaleJs()
|
||||
{
|
||||
return $this->localeJs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CompilerInterface
|
||||
*/
|
||||
public function getLocaleCss()
|
||||
{
|
||||
return $this->localeCss;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result of an API request to show the forum.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
protected function getForumDocument(Request $request)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
|
||||
$response = $this->api->send('Flarum\Api\Controller\ShowForumController', $actor);
|
||||
|
||||
return json_decode($response->getBody(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result of an API request to show the current user.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
protected function getUserDocument(Request $request)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
|
||||
$this->userSerializer->setActor($actor);
|
||||
|
||||
$resource = new Resource($actor, $this->userSerializer);
|
||||
|
||||
$document = new Document($resource->with('groups'));
|
||||
|
||||
return $document->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information about the current session.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
protected function buildSession(Request $request)
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$session = $request->getAttribute('session');
|
||||
|
||||
return [
|
||||
'userId' => $actor->id,
|
||||
'csrfToken' => $session->token()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of data by merging the 'data' and 'included' keys of a
|
||||
* JSON-API document.
|
||||
*
|
||||
* @param array $document
|
||||
* @return array
|
||||
*/
|
||||
private function getDataFromDocument(array $document)
|
||||
{
|
||||
$data[] = $document['data'];
|
||||
|
||||
if (isset($document['included'])) {
|
||||
$data = array_merge($data, $document['included']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend;
|
||||
|
||||
use Flarum\Api\Client;
|
||||
use Flarum\Api\Serializer\CurrentUserSerializer;
|
||||
use Flarum\Foundation\Application;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
|
||||
class FrontendViewFactory
|
||||
{
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
protected $api;
|
||||
|
||||
/**
|
||||
* @var Factory
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* @var LocaleManager
|
||||
*/
|
||||
protected $locales;
|
||||
|
||||
/**
|
||||
* @var CurrentUserSerializer
|
||||
*/
|
||||
protected $userSerializer;
|
||||
|
||||
/**
|
||||
* @var Application
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* @param Client $api
|
||||
* @param Factory $view
|
||||
* @param LocaleManager $locales
|
||||
* @param CurrentUserSerializer $userSerializer
|
||||
* @param Application $app
|
||||
*/
|
||||
public function __construct(Client $api, Factory $view, LocaleManager $locales, CurrentUserSerializer $userSerializer, Application $app)
|
||||
{
|
||||
$this->api = $api;
|
||||
$this->view = $view;
|
||||
$this->locales = $locales;
|
||||
$this->userSerializer = $userSerializer;
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $layout
|
||||
* @param FrontendAssets $assets
|
||||
* @return FrontendView
|
||||
*/
|
||||
public function make($layout, FrontendAssets $assets)
|
||||
{
|
||||
return new FrontendView($layout, $assets, $this->api, $this->view, $this->locales, $this->userSerializer, $this->app);
|
||||
}
|
||||
}
|
251
src/Frontend/HtmlDocument.php
Normal file
251
src/Frontend/HtmlDocument.php
Normal file
@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend;
|
||||
|
||||
use Illuminate\Contracts\Support\Renderable;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
|
||||
/**
|
||||
* A view which renders a HTML skeleton for Flarum's frontend app.
|
||||
*/
|
||||
class HtmlDocument implements Renderable
|
||||
{
|
||||
/**
|
||||
* The title of the document, displayed in the <title> tag.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* The language of the document, displayed as the value of the attribute `lang` in the <html> tag.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
public $language;
|
||||
|
||||
/**
|
||||
* The text direction of the document, displayed as the value of the attribute `dir` in the <html> tag.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
public $direction;
|
||||
|
||||
/**
|
||||
* The name of the frontend app view to display.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $appView = 'flarum::frontend.app';
|
||||
|
||||
/**
|
||||
* The name of the frontend layout view to display.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $layoutView;
|
||||
|
||||
/**
|
||||
* The name of the frontend content view to display.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $contentView = 'flarum::frontend.content';
|
||||
|
||||
/**
|
||||
* The SEO content of the page, displayed within the layout in <noscript> tags.
|
||||
*
|
||||
* @var string|Renderable
|
||||
*/
|
||||
public $content;
|
||||
|
||||
/**
|
||||
* Other variables to preload into the Flarum JS.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $payload = [];
|
||||
|
||||
/**
|
||||
* An array of meta tags to append to the page's <head>.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $meta = [];
|
||||
|
||||
/**
|
||||
* The canonical URL for this page.
|
||||
*
|
||||
* This will signal to search engines what URL should be used for this
|
||||
* content, if it can be found under multiple addresses. This is an
|
||||
* important tool to tackle duplicate content.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
public $canonicalUrl;
|
||||
|
||||
/**
|
||||
* An array of strings to append to the page's <head>.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $head = [];
|
||||
|
||||
/**
|
||||
* An array of strings to prepend before the page's </body>.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $foot = [];
|
||||
|
||||
/**
|
||||
* An array of JavaScript URLs to load.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $js = [];
|
||||
|
||||
/**
|
||||
* An array of CSS URLs to load.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $css = [];
|
||||
|
||||
/**
|
||||
* @var Factory
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $forumApiDocument;
|
||||
|
||||
/**
|
||||
* @param Factory $view
|
||||
* @param array $forumApiDocument
|
||||
*/
|
||||
public function __construct(Factory $view, array $forumApiDocument)
|
||||
{
|
||||
$this->view = $view;
|
||||
$this->forumApiDocument = $forumApiDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render(): string
|
||||
{
|
||||
$this->view->share('forum', array_get($this->forumApiDocument, 'data.attributes'));
|
||||
|
||||
return $this->makeView()->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
*/
|
||||
protected function makeView(): View
|
||||
{
|
||||
return $this->view->make($this->appView)->with([
|
||||
'title' => $this->makeTitle(),
|
||||
'payload' => $this->payload,
|
||||
'layout' => $this->makeLayout(),
|
||||
'language' => $this->language,
|
||||
'direction' => $this->direction,
|
||||
'js' => $this->makeJs(),
|
||||
'head' => $this->makeHead(),
|
||||
'foot' => $this->makeFoot(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function makeTitle(): string
|
||||
{
|
||||
return ($this->title ? $this->title.' - ' : '').array_get($this->forumApiDocument, 'data.attributes.title');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
*/
|
||||
protected function makeLayout(): View
|
||||
{
|
||||
if ($this->layoutView) {
|
||||
return $this->view->make($this->layoutView)->with('content', $this->makeContent());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
*/
|
||||
protected function makeContent(): View
|
||||
{
|
||||
return $this->view->make($this->contentView)->with('content', $this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function makeHead(): string
|
||||
{
|
||||
$head = array_map(function ($url) {
|
||||
return '<link rel="stylesheet" href="'.e($url).'">';
|
||||
}, $this->css);
|
||||
|
||||
if ($this->canonicalUrl) {
|
||||
$head[] = '<link rel="canonical" href="'.e($this->canonicalUrl).'">';
|
||||
}
|
||||
|
||||
$head = array_merge($head, array_map(function ($content, $name) {
|
||||
return '<meta name="'.e($name).'" content="'.e($content).'">';
|
||||
}, $this->meta, array_keys($this->meta)));
|
||||
|
||||
return implode("\n", array_merge($head, $this->head));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function makeJs(): string
|
||||
{
|
||||
return implode("\n", array_map(function ($url) {
|
||||
return '<script src="'.e($url).'"></script>';
|
||||
}, $this->js));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function makeFoot(): string
|
||||
{
|
||||
return implode("\n", $this->foot);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getForumApiDocument(): array
|
||||
{
|
||||
return $this->forumApiDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $forumApiDocument
|
||||
*/
|
||||
public function setForumApiDocument(array $forumApiDocument)
|
||||
{
|
||||
$this->forumApiDocument = $forumApiDocument;
|
||||
}
|
||||
}
|
180
src/Frontend/HtmlDocumentFactory.php
Normal file
180
src/Frontend/HtmlDocumentFactory.php
Normal file
@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend;
|
||||
|
||||
use Flarum\Api\Client;
|
||||
use Flarum\Api\Controller\ShowForumController;
|
||||
use Flarum\Frontend\Compiler\CompilerInterface;
|
||||
use Flarum\Frontend\Content\ContentInterface;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class HtmlDocumentFactory
|
||||
{
|
||||
/**
|
||||
* @var Factory
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
protected $api;
|
||||
|
||||
/**
|
||||
* @var CompilerFactory
|
||||
*/
|
||||
protected $assets;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $commitAssets;
|
||||
|
||||
/**
|
||||
* @var ContentInterface[]
|
||||
*/
|
||||
protected $content = [];
|
||||
|
||||
/**
|
||||
* @param Factory $view
|
||||
* @param Client $api
|
||||
* @param CompilerFactory|null $assets
|
||||
* @param bool $commitAssets
|
||||
*/
|
||||
public function __construct(Factory $view, Client $api, CompilerFactory $assets = null, bool $commitAssets = false)
|
||||
{
|
||||
$this->view = $view;
|
||||
$this->api = $api;
|
||||
$this->assets = $assets;
|
||||
$this->commitAssets = $commitAssets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContentInterface $content
|
||||
*/
|
||||
public function add($content)
|
||||
{
|
||||
$this->content[] = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return HtmlDocument
|
||||
*/
|
||||
public function make(Request $request): HtmlDocument
|
||||
{
|
||||
$forumDocument = $this->getForumDocument($request);
|
||||
|
||||
$view = new HtmlDocument($this->view, $forumDocument);
|
||||
|
||||
$locale = $request->getAttribute('locale');
|
||||
|
||||
$js = [$this->assets->makeJs(), $this->assets->makeLocaleJs($locale)];
|
||||
$css = [$this->assets->makeCss(), $this->assets->makeLocaleCss($locale)];
|
||||
|
||||
$this->maybeCommitAssets(array_merge($js, $css));
|
||||
|
||||
$view->js = array_merge($view->js, $this->getUrls($js));
|
||||
$view->css = array_merge($view->css, $this->getUrls($css));
|
||||
|
||||
$this->populate($view, $request);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CompilerFactory
|
||||
*/
|
||||
public function getAssets(): CompilerFactory
|
||||
{
|
||||
return $this->assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CompilerFactory $assets
|
||||
*/
|
||||
public function setAssets(CompilerFactory $assets)
|
||||
{
|
||||
$this->assets = $assets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HtmlDocument $view
|
||||
* @param Request $request
|
||||
*/
|
||||
protected function populate(HtmlDocument $view, Request $request)
|
||||
{
|
||||
foreach ($this->content as $content) {
|
||||
$content->populate($view, $request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
private function getForumDocument(Request $request): array
|
||||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
|
||||
return $this->getResponseBody(
|
||||
$this->api->send(ShowForumController::class, $actor)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @return array
|
||||
*/
|
||||
private function getResponseBody(Response $response)
|
||||
{
|
||||
return json_decode($response->getBody(), true);
|
||||
}
|
||||
|
||||
private function maybeCommitAssets(array $compilers)
|
||||
{
|
||||
if ($this->commitAssets) {
|
||||
foreach ($compilers as $compiler) {
|
||||
$compiler->commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CompilerInterface[] $compilers
|
||||
* @return string[]
|
||||
*/
|
||||
private function getUrls(array $compilers)
|
||||
{
|
||||
return array_filter(array_map(function (CompilerInterface $compiler) {
|
||||
return $compiler->getUrl();
|
||||
}, $compilers));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getCommitAssets(): bool
|
||||
{
|
||||
return $this->commitAssets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $commitAssets
|
||||
*/
|
||||
public function setCommitAssets(bool $commitAssets)
|
||||
{
|
||||
$this->commitAssets = $commitAssets;
|
||||
}
|
||||
}
|
84
src/Frontend/RecompileFrontendAssets.php
Normal file
84
src/Frontend/RecompileFrontendAssets.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Frontend;
|
||||
|
||||
use Flarum\Extension\Event\Disabled;
|
||||
use Flarum\Extension\Event\Enabled;
|
||||
use Flarum\Foundation\Event\ClearingCache;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Flarum\Settings\Event\Saved;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
class RecompileFrontendAssets
|
||||
{
|
||||
/**
|
||||
* @var CompilerFactory
|
||||
*/
|
||||
protected $assets;
|
||||
|
||||
/**
|
||||
* @var LocaleManager
|
||||
*/
|
||||
protected $locales;
|
||||
|
||||
/**
|
||||
* @param CompilerFactory $assets
|
||||
* @param LocaleManager $locales
|
||||
*/
|
||||
public function __construct(CompilerFactory $assets, LocaleManager $locales)
|
||||
{
|
||||
$this->assets = $assets;
|
||||
$this->locales = $locales;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
*/
|
||||
public function subscribe(Dispatcher $events)
|
||||
{
|
||||
$events->listen(Saved::class, [$this, 'whenSettingsSaved']);
|
||||
$events->listen(Enabled::class, [$this, 'flush']);
|
||||
$events->listen(Disabled::class, [$this, 'flush']);
|
||||
$events->listen(ClearingCache::class, [$this, 'flush']);
|
||||
}
|
||||
|
||||
public function whenSettingsSaved(Saved $event)
|
||||
{
|
||||
if (preg_grep('/^theme_/i', array_keys($event->settings))) {
|
||||
$this->flushCss();
|
||||
}
|
||||
}
|
||||
|
||||
public function flush()
|
||||
{
|
||||
$this->flushCss();
|
||||
$this->flushJs();
|
||||
}
|
||||
|
||||
protected function flushCss()
|
||||
{
|
||||
$this->assets->makeCss()->flush();
|
||||
|
||||
foreach ($this->locales->getLocales() as $locale => $name) {
|
||||
$this->assets->makeLocaleCss($locale)->flush();
|
||||
}
|
||||
}
|
||||
|
||||
protected function flushJs()
|
||||
{
|
||||
$this->assets->makeJs()->flush();
|
||||
|
||||
foreach ($this->locales->getLocales() as $locale => $name) {
|
||||
$this->assets->makeLocaleJs($locale)->flush();
|
||||
}
|
||||
}
|
||||
}
|
@ -25,13 +25,13 @@ class ControllerRouteHandler
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @var string|callable
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* @param Container $container
|
||||
* @param string $controller
|
||||
* @param string|callable $controller
|
||||
*/
|
||||
public function __construct(Container $container, $controller)
|
||||
{
|
||||
@ -54,12 +54,16 @@ class ControllerRouteHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string|callable $class
|
||||
* @return RequestHandlerInterface
|
||||
*/
|
||||
protected function resolveController($class)
|
||||
{
|
||||
$controller = $this->container->make($class);
|
||||
if (is_callable($class)) {
|
||||
$controller = $this->container->call($class);
|
||||
} else {
|
||||
$controller = $this->container->make($class);
|
||||
}
|
||||
|
||||
if (! ($controller instanceof RequestHandlerInterface)) {
|
||||
throw new InvalidArgumentException(
|
||||
|
@ -46,6 +46,8 @@ class SetLocale implements Middleware
|
||||
$this->locales->setLocale($locale);
|
||||
}
|
||||
|
||||
$request = $request->withAttribute('locale', $this->locales->getLocale());
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Flarum\Http;
|
||||
|
||||
use Flarum\Frontend\Controller as FrontendController;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class RouteHandlerFactory
|
||||
@ -29,11 +30,47 @@ class RouteHandlerFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $controller
|
||||
* @param string|callable $controller
|
||||
* @return ControllerRouteHandler
|
||||
*/
|
||||
public function toController($controller)
|
||||
{
|
||||
return new ControllerRouteHandler($this->container, $controller);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $frontend
|
||||
* @param string|null $content
|
||||
* @return ControllerRouteHandler
|
||||
*/
|
||||
public function toFrontend(string $frontend, string $content = null)
|
||||
{
|
||||
return $this->toController(function (Container $container) use ($frontend, $content) {
|
||||
$frontend = $container->make($frontend);
|
||||
|
||||
if ($content) {
|
||||
$frontend->add($container->make($content));
|
||||
}
|
||||
|
||||
return new FrontendController($frontend);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $content
|
||||
* @return ControllerRouteHandler
|
||||
*/
|
||||
public function toForum(string $content = null)
|
||||
{
|
||||
return $this->toFrontend('flarum.forum.frontend', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $content
|
||||
* @return ControllerRouteHandler
|
||||
*/
|
||||
public function toAdmin(string $content = null)
|
||||
{
|
||||
return $this->toFrontend('flarum.admin.frontend', $content);
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\Formatter\FormatterServiceProvider;
|
||||
use Flarum\Group\Group;
|
||||
use Flarum\Group\GroupServiceProvider;
|
||||
use Flarum\Group\Permission;
|
||||
use Flarum\Install\Prerequisite\PrerequisiteInterface;
|
||||
use Flarum\Notification\NotificationServiceProvider;
|
||||
use Flarum\Post\PostServiceProvider;
|
||||
@ -138,7 +137,6 @@ class InstallCommand extends AbstractCommand
|
||||
protected function install()
|
||||
{
|
||||
try {
|
||||
$this->debug = $this->dataSource->isDebugMode();
|
||||
$this->dbConfig = $this->dataSource->getDatabaseConfiguration();
|
||||
|
||||
$validation = $this->getValidator()->make(
|
||||
@ -177,7 +175,7 @@ class InstallCommand extends AbstractCommand
|
||||
throw new Exception('Username can only contain letters, numbers, underscores, and dashes.');
|
||||
}
|
||||
|
||||
$this->storeConfiguration();
|
||||
$this->storeConfiguration($this->dataSource->isDebugMode());
|
||||
|
||||
$resolver = $this->application->make(ConnectionResolverInterface::class);
|
||||
AbstractModel::setConnectionResolver($resolver);
|
||||
@ -196,7 +194,6 @@ class InstallCommand extends AbstractCommand
|
||||
$this->application->register(PostServiceProvider::class);
|
||||
|
||||
$this->seedGroups();
|
||||
$this->seedPermissions();
|
||||
|
||||
$this->createAdminUser();
|
||||
|
||||
@ -210,12 +207,12 @@ class InstallCommand extends AbstractCommand
|
||||
}
|
||||
}
|
||||
|
||||
protected function storeConfiguration()
|
||||
protected function storeConfiguration(bool $debugMode)
|
||||
{
|
||||
$dbConfig = $this->dbConfig;
|
||||
|
||||
$config = [
|
||||
'debug' => $this->debug,
|
||||
'debug' => $debugMode,
|
||||
'database' => [
|
||||
'driver' => $dbConfig['driver'],
|
||||
'host' => $dbConfig['host'],
|
||||
@ -305,34 +302,6 @@ class InstallCommand extends AbstractCommand
|
||||
}
|
||||
}
|
||||
|
||||
protected function seedPermissions()
|
||||
{
|
||||
$permissions = [
|
||||
// Guests can view the forum
|
||||
[Group::GUEST_ID, 'viewDiscussions'],
|
||||
|
||||
// Members can create and reply to discussions, and view the user list
|
||||
[Group::MEMBER_ID, 'startDiscussion'],
|
||||
[Group::MEMBER_ID, 'discussion.reply'],
|
||||
[Group::MEMBER_ID, 'viewUserList'],
|
||||
|
||||
// Moderators can edit + delete stuff
|
||||
[Group::MODERATOR_ID, 'discussion.hide'],
|
||||
[Group::MODERATOR_ID, 'discussion.editPosts'],
|
||||
[Group::MODERATOR_ID, 'discussion.rename'],
|
||||
[Group::MODERATOR_ID, 'discussion.viewIpsPosts'],
|
||||
];
|
||||
|
||||
foreach ($permissions as &$permission) {
|
||||
$permission = [
|
||||
'group_id' => $permission[0],
|
||||
'permission' => $permission[1]
|
||||
];
|
||||
}
|
||||
|
||||
Permission::insert($permissions);
|
||||
}
|
||||
|
||||
protected function createAdminUser()
|
||||
{
|
||||
$admin = $this->adminUser;
|
||||
|
@ -31,7 +31,7 @@ class InstallServiceProvider extends AbstractServiceProvider
|
||||
PrerequisiteInterface::class,
|
||||
function () {
|
||||
return new Composite(
|
||||
new PhpVersion('5.5.0'),
|
||||
new PhpVersion('7.1.0'),
|
||||
new PhpExtensions([
|
||||
'dom',
|
||||
'fileinfo',
|
||||
|
@ -22,7 +22,7 @@ class PhpVersion extends AbstractPrerequisite
|
||||
|
||||
public function check()
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '5.5.0', '<')) {
|
||||
if (version_compare(PHP_VERSION, $this->minVersion, '<')) {
|
||||
$this->errors[] = [
|
||||
'message' => "PHP $this->minVersion is required.",
|
||||
'detail' => 'You are running version '.PHP_VERSION.'. Talk to your hosting provider about upgrading to the latest PHP version.',
|
||||
|
@ -25,7 +25,7 @@ class LocaleServiceProvider extends AbstractServiceProvider
|
||||
*/
|
||||
public function boot(Dispatcher $events)
|
||||
{
|
||||
$locales = $this->app->make('flarum.localeManager');
|
||||
$locales = $this->app->make('flarum.locales');
|
||||
|
||||
$locales->addLocale($this->getDefaultLocale(), 'Default');
|
||||
|
||||
@ -38,7 +38,7 @@ class LocaleServiceProvider extends AbstractServiceProvider
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton(LocaleManager::class);
|
||||
$this->app->alias(LocaleManager::class, 'flarum.localeManager');
|
||||
$this->app->alias(LocaleManager::class, 'flarum.locales');
|
||||
|
||||
$this->app->singleton('translator', function () {
|
||||
$translator = new Translator($this->getDefaultLocale(), new MessageSelector());
|
||||
|
@ -68,7 +68,7 @@ class EditPostHandler
|
||||
}
|
||||
|
||||
if (isset($attributes['isHidden'])) {
|
||||
$this->assertCan($actor, 'edit', $post);
|
||||
$this->assertCan($actor, 'hide', $post);
|
||||
|
||||
if ($attributes['isHidden']) {
|
||||
$post->hide($actor);
|
||||
|
@ -166,10 +166,9 @@ class CommentPost extends Post
|
||||
/**
|
||||
* Get the content rendered as HTML.
|
||||
*
|
||||
* @param string $value
|
||||
* @return string
|
||||
*/
|
||||
public function getContentHtmlAttribute($value)
|
||||
public function getContentHtmlAttribute()
|
||||
{
|
||||
return static::$formatter->render($this->attributes['content'], $this);
|
||||
}
|
||||
|
@ -121,9 +121,13 @@ class Post extends AbstractModel
|
||||
|
||||
// Make sure the post's discussion is visible as well
|
||||
$query->whereExists(function ($query) use ($actor) {
|
||||
$grammar = $query->getGrammar();
|
||||
$column1 = $grammar->wrap('discussions.id');
|
||||
$column2 = $grammar->wrap('posts.discussion_id');
|
||||
|
||||
$query->selectRaw('1')
|
||||
->from('discussions')
|
||||
->whereRaw('discussions.id = posts.discussion_id');
|
||||
->whereRaw("$column1 = $column2");
|
||||
|
||||
static::$dispatcher->dispatch(
|
||||
new ScopeModelVisibility(Discussion::query()->setQuery($query), $actor, 'view')
|
||||
|
@ -79,7 +79,7 @@ class PostPolicy extends AbstractPolicy
|
||||
// Hide hidden posts, unless they are authored by the current user, or
|
||||
// the current user has permission to view hidden posts in the
|
||||
// discussion.
|
||||
if (! $actor->hasPermission('discussion.editPosts')) {
|
||||
if (! $actor->hasPermission('discussion.hidePosts')) {
|
||||
$query->where(function ($query) use ($actor) {
|
||||
$query->whereNull('posts.hidden_at')
|
||||
->orWhere('posts.user_id', $actor->id)
|
||||
@ -89,7 +89,7 @@ class PostPolicy extends AbstractPolicy
|
||||
->whereRaw('discussions.id = posts.discussion_id')
|
||||
->where(function ($query) use ($actor) {
|
||||
$this->events->dispatch(
|
||||
new ScopeModelVisibility(Discussion::query()->setQuery($query), $actor, 'editPosts')
|
||||
new ScopeModelVisibility(Discussion::query()->setQuery($query), $actor, 'hidePosts')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -14,26 +14,15 @@ namespace Flarum\Settings\Event;
|
||||
class Saved
|
||||
{
|
||||
/**
|
||||
* The setting key that was set.
|
||||
*
|
||||
* @var string
|
||||
* @var array
|
||||
*/
|
||||
public $key;
|
||||
public $settings;
|
||||
|
||||
/**
|
||||
* The setting value that was set.
|
||||
*
|
||||
* @var string
|
||||
* @param array $settings
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* @param string $key The setting key that was set.
|
||||
* @param string $value The setting value that was set.
|
||||
*/
|
||||
public function __construct($key, $value)
|
||||
public function __construct(array $settings)
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->value = $value;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
}
|
||||
|
@ -9,17 +9,20 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Admin;
|
||||
namespace Flarum\Settings\Event;
|
||||
|
||||
use Flarum\Frontend\AbstractFrontend;
|
||||
|
||||
class Frontend extends AbstractFrontend
|
||||
class Saving
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @var array
|
||||
*/
|
||||
protected function getName()
|
||||
public $settings;
|
||||
|
||||
/**
|
||||
* @param array $settings
|
||||
*/
|
||||
public function __construct(array &$settings)
|
||||
{
|
||||
return 'admin';
|
||||
$this->settings = &$settings;
|
||||
}
|
||||
}
|
61
src/Settings/OverrideSettingsRepository.php
Normal file
61
src/Settings/OverrideSettingsRepository.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Settings;
|
||||
|
||||
/**
|
||||
* A settings repository decorator that allows overriding certain values.
|
||||
*
|
||||
* The `OverrideSettingsRepository` class decorates another
|
||||
* `SettingsRepositoryInterface` instance but allows certain settings to be
|
||||
* overridden with predefined values. It does not affect writing methods.
|
||||
*
|
||||
* Within Flarum, this can be used to test out new setting values in a system
|
||||
* before they are committed to the database.
|
||||
*
|
||||
* @see \Flarum\Forum\RecompileFrontendAssets For an example usage.
|
||||
*/
|
||||
class OverrideSettingsRepository implements SettingsRepositoryInterface
|
||||
{
|
||||
protected $inner;
|
||||
|
||||
protected $overrides = [];
|
||||
|
||||
public function __construct(SettingsRepositoryInterface $inner, array $overrides)
|
||||
{
|
||||
$this->inner = $inner;
|
||||
$this->overrides = $overrides;
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
return array_merge($this->inner->all(), $this->overrides);
|
||||
}
|
||||
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
if (array_key_exists($key, $this->overrides)) {
|
||||
return $this->overrides[$key];
|
||||
}
|
||||
|
||||
return array_get($this->all(), $key, $default);
|
||||
}
|
||||
|
||||
public function set($key, $value)
|
||||
{
|
||||
$this->inner->set($key, $value);
|
||||
}
|
||||
|
||||
public function delete($key)
|
||||
{
|
||||
$this->inner->delete($key);
|
||||
}
|
||||
}
|
@ -584,6 +584,16 @@ class User extends AbstractModel
|
||||
return $this->hasMany('Flarum\Post\Post');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the user's discussions.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function discussions()
|
||||
{
|
||||
return $this->hasMany('Flarum\Discussion\Discussion');
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the relationship with the user's read discussions.
|
||||
*
|
||||
@ -727,4 +737,28 @@ class User extends AbstractModel
|
||||
{
|
||||
return 'notify_'.$type.'_'.$method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the user's comments count.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function refreshCommentsCount()
|
||||
{
|
||||
$this->comment_count = $this->posts()->count();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the user's comments count.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function refreshDiscussionsCount()
|
||||
{
|
||||
$this->discussion_count = $this->discussions()->count();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,7 @@ use Flarum\Discussion\Discussion;
|
||||
use Flarum\Discussion\Event\Deleted as DiscussionDeleted;
|
||||
use Flarum\Discussion\Event\Started;
|
||||
use Flarum\Post\Event\Deleted as PostDeleted;
|
||||
use Flarum\Post\Event\Hidden;
|
||||
use Flarum\Post\Event\Posted;
|
||||
use Flarum\Post\Event\Restored;
|
||||
use Flarum\Post\Post;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
@ -30,8 +28,6 @@ class UserMetadataUpdater
|
||||
{
|
||||
$events->listen(Posted::class, [$this, 'whenPostWasPosted']);
|
||||
$events->listen(PostDeleted::class, [$this, 'whenPostWasDeleted']);
|
||||
$events->listen(Hidden::class, [$this, 'whenPostWasHidden']);
|
||||
$events->listen(Restored::class, [$this, 'whenPostWasRestored']);
|
||||
$events->listen(Started::class, [$this, 'whenDiscussionWasStarted']);
|
||||
$events->listen(DiscussionDeleted::class, [$this, 'whenDiscussionWasDeleted']);
|
||||
}
|
||||
@ -41,7 +37,7 @@ class UserMetadataUpdater
|
||||
*/
|
||||
public function whenPostWasPosted(Posted $event)
|
||||
{
|
||||
$this->updateCommentsCount($event->post, 1);
|
||||
$this->updateCommentsCount($event->post);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -49,23 +45,7 @@ class UserMetadataUpdater
|
||||
*/
|
||||
public function whenPostWasDeleted(PostDeleted $event)
|
||||
{
|
||||
$this->updateCommentsCount($event->post, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Flarum\Post\Event\Hidden $event
|
||||
*/
|
||||
public function whenPostWasHidden(Hidden $event)
|
||||
{
|
||||
$this->updateCommentsCount($event->post, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Flarum\Post\Event\Restored $event
|
||||
*/
|
||||
public function whenPostWasRestored(Restored $event)
|
||||
{
|
||||
$this->updateCommentsCount($event->post, 1);
|
||||
$this->updateCommentsCount($event->post);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,7 +53,7 @@ class UserMetadataUpdater
|
||||
*/
|
||||
public function whenDiscussionWasStarted(Started $event)
|
||||
{
|
||||
$this->updateDiscussionsCount($event->discussion, 1);
|
||||
$this->updateDiscussionsCount($event->discussion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,34 +61,24 @@ class UserMetadataUpdater
|
||||
*/
|
||||
public function whenDiscussionWasDeleted(DiscussionDeleted $event)
|
||||
{
|
||||
$this->updateDiscussionsCount($event->discussion, -1);
|
||||
$this->updateDiscussionsCount($event->discussion);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Post $post
|
||||
* @param int $amount
|
||||
*/
|
||||
protected function updateCommentsCount(Post $post, $amount)
|
||||
private function updateCommentsCount(Post $post)
|
||||
{
|
||||
$user = $post->user;
|
||||
|
||||
if ($user && $user->exists) {
|
||||
$user->comment_count += $amount;
|
||||
$user->save();
|
||||
$user->refreshCommentsCount()->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Flarum\Discussion\Discussion $discussion
|
||||
* @param int $amount
|
||||
*/
|
||||
protected function updateDiscussionsCount(Discussion $discussion, $amount)
|
||||
private function updateDiscussionsCount(Discussion $discussion)
|
||||
{
|
||||
$user = $discussion->startUser;
|
||||
|
||||
if ($user && $user->exists) {
|
||||
$user->discussion_count += $amount;
|
||||
$user->save();
|
||||
$user->refreshDiscussionsCount()->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,37 @@
|
||||
<div id="app" class="App">
|
||||
|
||||
<div id="app-navigation" class="App-navigation"></div>
|
||||
<div id="app-navigation" class="App-navigation"></div>
|
||||
|
||||
<div id="drawer" class="App-drawer">
|
||||
<div id="drawer" class="App-drawer">
|
||||
|
||||
<header id="header" class="App-header">
|
||||
<div id="header-navigation" class="Header-navigation"></div>
|
||||
<div class="container">
|
||||
<h1 class="Header-title">
|
||||
<a href="{{ array_get($forum, 'attributes.baseUrl') }}">
|
||||
<?php $title = array_get($forum, 'attributes.title'); ?>
|
||||
@if ($logo = array_get($forum, 'attributes.logoUrl'))
|
||||
<img src="{{ $logo }}" alt="{{ $title }}" class="Header-logo">
|
||||
@else
|
||||
{{ $title }}
|
||||
@endif
|
||||
</a>
|
||||
</h1>
|
||||
<div id="header-primary" class="Header-primary"></div>
|
||||
<div id="header-secondary" class="Header-secondary"></div>
|
||||
</div>
|
||||
</header>
|
||||
<header id="header" class="App-header">
|
||||
<div id="header-navigation" class="Header-navigation"></div>
|
||||
<div class="container">
|
||||
<h1 class="Header-title">
|
||||
<a href="{{ array_get($forum, 'baseUrl') }}">
|
||||
<?php $title = array_get($forum, 'title'); ?>
|
||||
@if ($logo = array_get($forum, 'logoUrl'))
|
||||
<img src="{{ $logo }}" alt="{{ $title }}" class="Header-logo">
|
||||
@else
|
||||
{{ $title }}
|
||||
@endif
|
||||
</a>
|
||||
</h1>
|
||||
<div id="header-primary" class="Header-primary"></div>
|
||||
<div id="header-secondary" class="Header-secondary"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
</div>
|
||||
|
||||
<main class="App-content">
|
||||
<div class="container">
|
||||
<div id="admin-navigation" class="App-nav sideNav"></div>
|
||||
</div>
|
||||
|
||||
<div id="content" class="sideNavOffset"></div>
|
||||
<main class="App-content">
|
||||
<div class="container">
|
||||
<div id="admin-navigation" class="App-nav sideNav"></div>
|
||||
</div>
|
||||
|
||||
{!! $content !!}
|
||||
</main>
|
||||
<div id="content" class="sideNavOffset"></div>
|
||||
|
||||
{!! $content !!}
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
@ -1,63 +1,40 @@
|
||||
<!doctype html>
|
||||
<html dir="{{ $direction }}" lang="{{ $language }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ $title }}</title>
|
||||
<meta name="description" content="{{ $description }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
|
||||
<meta name="theme-color" content="{{ array_get($forum, 'attributes.themePrimaryColor') }}">
|
||||
@if (! $allowJs)
|
||||
<meta name="robots" content="noindex" />
|
||||
@endif
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ $title }}</title>
|
||||
|
||||
@foreach ($cssUrls as $url)
|
||||
<link rel="stylesheet" href="{{ $url }}">
|
||||
@endforeach
|
||||
{!! $head !!}
|
||||
</head>
|
||||
|
||||
@if ($faviconUrl = array_get($forum, 'attributes.faviconUrl'))
|
||||
<link href="{{ $faviconUrl }}" rel="shortcut icon">
|
||||
@endif
|
||||
<body>
|
||||
{!! $layout !!}
|
||||
|
||||
{!! $head !!}
|
||||
</head>
|
||||
<div id="modal"></div>
|
||||
<div id="alerts"></div>
|
||||
|
||||
<body>
|
||||
{!! $layout !!}
|
||||
<script>
|
||||
document.getElementById('flarum-loading').style.display = 'block';
|
||||
var flarum = {extensions: {}};
|
||||
</script>
|
||||
|
||||
<div id="modal"></div>
|
||||
<div id="alerts"></div>
|
||||
{!! $js !!}
|
||||
|
||||
@if ($allowJs)
|
||||
<script>
|
||||
document.getElementById('flarum-loading').style.display = 'block';
|
||||
var flarum = {extensions: {}};
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById('flarum-loading').style.display = 'none';
|
||||
|
||||
@foreach ($jsUrls as $url)
|
||||
<script src="{{ $url }}"></script>
|
||||
@endforeach
|
||||
try {
|
||||
flarum.core.app.load(@json($payload));
|
||||
flarum.core.app.bootExtensions(flarum.extensions);
|
||||
flarum.core.app.boot();
|
||||
} catch (e) {
|
||||
var error = document.getElementById('flarum-loading-error');
|
||||
error.innerHTML += document.getElementById('flarum-content').textContent;
|
||||
error.style.display = 'block';
|
||||
throw e;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
document.getElementById('flarum-loading').style.display = 'none';
|
||||
@if (! $debug)
|
||||
try {
|
||||
@endif
|
||||
flarum.core.app.load(@json($payload));
|
||||
flarum.core.app.bootExtensions(flarum.extensions);
|
||||
flarum.core.app.boot();
|
||||
@if (! $debug)
|
||||
} catch (e) {
|
||||
window.location += (window.location.search ? '&' : '?') + 'nojs=1';
|
||||
throw e;
|
||||
}
|
||||
@endif
|
||||
</script>
|
||||
@else
|
||||
<script>
|
||||
window.history.replaceState(null, null, window.location.toString().replace(/([&?]nojs=1$|nojs=1&)/, ''));
|
||||
</script>
|
||||
@endif
|
||||
|
||||
{!! $foot !!}
|
||||
</body>
|
||||
{!! $foot !!}
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,23 +1,23 @@
|
||||
<div id="flarum-loading" style="display: none">
|
||||
{{ $translator->trans('core.views.content.loading_text') }}
|
||||
{{ $translator->trans('core.views.content.loading_text') }}
|
||||
</div>
|
||||
|
||||
@if ($allowJs)
|
||||
<noscript>
|
||||
<noscript>
|
||||
<div class="Alert">
|
||||
<div class="container">
|
||||
{{ $translator->trans('core.views.content.javascript_disabled_message') }}
|
||||
</div>
|
||||
<div class="container">
|
||||
{{ $translator->trans('core.views.content.javascript_disabled_message') }}
|
||||
</div>
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
<div id="flarum-loading-error" style="display: none">
|
||||
<div class="Alert">
|
||||
<div class="container">
|
||||
{{ $translator->trans('core.views.content.load_error_message') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<noscript id="flarum-content">
|
||||
{!! $content !!}
|
||||
</noscript>
|
||||
@else
|
||||
<div class="Alert Alert--error">
|
||||
<div class="container">
|
||||
{{ $translator->trans('core.views.content.load_error_message') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!! $content !!}
|
||||
@endif
|
||||
</noscript>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div class="container">
|
||||
<h2>{{ $document->data->attributes->title }}</h2>
|
||||
<h2>{{ $apiDocument->data->attributes->title }}</h2>
|
||||
|
||||
<div>
|
||||
@foreach ($posts as $post)
|
||||
@ -15,11 +15,11 @@
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@if (isset($document->links->prev))
|
||||
@if (isset($apiDocument->links->prev))
|
||||
<a href="{{ $url(['page' => $page - 1]) }}">« {{ $translator->trans('core.views.discussion.previous_page_button') }}</a>
|
||||
@endif
|
||||
|
||||
@if (isset($document->links->next))
|
||||
@if (isset($apiDocument->links->next))
|
||||
<a href="{{ $url(['page' => $page + 1]) }}">{{ $translator->trans('core.views.discussion.next_page_button') }} »</a>
|
||||
@endif
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<h2>{{ $translator->trans('core.views.index.all_discussions_heading') }}</h2>
|
||||
|
||||
<ul>
|
||||
@foreach ($document->data as $discussion)
|
||||
@foreach ($apiDocument->data as $discussion)
|
||||
<li>
|
||||
<a href="{{ $url->to('forum')->route('discussion', [
|
||||
'id' => $discussion->id . (trim($discussion->attributes->slug) ? '-' . $discussion->attributes->slug : '')
|
||||
@ -15,11 +15,11 @@
|
||||
@endforeach
|
||||
</ul>
|
||||
|
||||
@if (isset($document->links->prev))
|
||||
@if (isset($apiDocument->links->prev))
|
||||
<a href="{{ $url->to('forum')->route('index') }}?page={{ $page - 1 }}">« {{ $translator->trans('core.views.index.previous_page_button') }}</a>
|
||||
@endif
|
||||
|
||||
@if (isset($document->links->next))
|
||||
@if (isset($apiDocument->links->next))
|
||||
<a href="{{ $url->to('forum')->route('index') }}?page={{ $page + 1 }}">{{ $translator->trans('core.views.index.next_page_button') }} »</a>
|
||||
@endif
|
||||
</div>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user