From d2bce5d756e338abf422ee39d36d3f05834a08e5 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Thu, 26 May 2016 19:04:24 +0930 Subject: [PATCH] Refactor the web app bootstrapping code - All custom JS variables are now preloaded into the `app.data` object, rather than directly on the `app` object. This means that admin settings are available in `app.data.settings` rather than `app.settings`, etc. - Cleaner route handler generation - Renamed ConfigureClientView to ConfigureWebApp, though the former still exists and is deprecated - Partial fix for #881 (strips ?nojs=1 from URL if possible, so that refreshing will attempt to load JS version again) --- framework/core/js/admin/dist/app.js | 82 ++-- .../js/admin/src/components/AppearancePage.js | 8 +- .../js/admin/src/components/BasicsPage.js | 6 +- .../src/components/EditCustomCssModal.js | 2 +- .../js/admin/src/components/ExtensionsPage.js | 6 +- .../core/js/admin/src/components/MailPage.js | 4 +- .../src/components/PermissionDropdown.js | 6 +- .../js/admin/src/components/PermissionGrid.js | 4 +- .../admin/src/components/SettingDropdown.js | 2 +- .../js/admin/src/components/SettingsModal.js | 4 +- .../core/js/admin/src/utils/saveSettings.js | 6 +- framework/core/js/forum/dist/app.js | 26 +- .../forum/src/components/HeaderSecondary.js | 10 +- framework/core/js/lib/App.js | 30 +- framework/core/js/lib/initializers/preload.js | 6 +- .../core/src/Admin/AdminServiceProvider.php | 32 +- ...entController.php => WebAppController.php} | 44 +- framework/core/src/Admin/WebApp.php | 24 + framework/core/src/Api/ApiServiceProvider.php | 72 ++- framework/core/src/Asset/AssetManager.php | 117 ----- .../src/Debug/Console/CacheClearCommand.php | 19 +- .../src/Event/AbstractConfigureRoutes.php | 13 +- .../core/src/Event/ConfigureClientView.php | 49 +- framework/core/src/Event/ConfigureWebApp.php | 72 +++ ...ler.php => AuthorizedWebAppController.php} | 2 +- .../src/Forum/Controller/ClientController.php | 70 --- .../Forum/Controller/DiscussionController.php | 40 +- .../src/Forum/Controller/IndexController.php | 28 +- .../src/Forum/Controller/WebAppController.php | 27 ++ .../core/src/Forum/ForumServiceProvider.php | 54 +-- framework/core/src/Forum/WebApp.php | 63 +++ .../Controller/AbstractClientController.php | 316 ------------- .../Controller/AbstractHtmlController.php | 2 +- .../Controller/AbstractWebAppController.php | 52 ++ .../core/src/Http/Controller/ClientView.php | 371 --------------- .../src/Http/GenerateRouteHandlerTrait.php | 41 -- .../Http/Handler/ControllerRouteHandler.php | 71 +++ .../src/Http/Handler/RouteHandlerFactory.php | 38 ++ .../core/src/Http/WebApp/AbstractWebApp.php | 188 ++++++++ .../core/src/Http/WebApp/WebAppAssets.php | 164 +++++++ .../src/Http/WebApp/WebAppAssetsFactory.php | 54 +++ framework/core/src/Http/WebApp/WebAppView.php | 446 ++++++++++++++++++ .../src/Http/WebApp/WebAppViewFactory.php | 63 +++ framework/core/src/Locale/JsCompiler.php | 10 +- .../src/Listener/AddClientAssets.php | 6 +- framework/core/views/admin.blade.php | 4 +- framework/core/views/app.blade.php | 39 +- framework/core/views/content.blade.php | 30 +- framework/core/views/forum.blade.php | 6 +- 49 files changed, 1575 insertions(+), 1254 deletions(-) rename framework/core/src/Admin/Controller/{ClientController.php => WebAppController.php} (53%) create mode 100644 framework/core/src/Admin/WebApp.php delete mode 100644 framework/core/src/Asset/AssetManager.php create mode 100644 framework/core/src/Event/ConfigureWebApp.php rename framework/core/src/Forum/Controller/{AuthorizedClientController.php => AuthorizedWebAppController.php} (91%) delete mode 100644 framework/core/src/Forum/Controller/ClientController.php create mode 100644 framework/core/src/Forum/Controller/WebAppController.php create mode 100644 framework/core/src/Forum/WebApp.php delete mode 100644 framework/core/src/Http/Controller/AbstractClientController.php create mode 100644 framework/core/src/Http/Controller/AbstractWebAppController.php delete mode 100644 framework/core/src/Http/Controller/ClientView.php delete mode 100644 framework/core/src/Http/GenerateRouteHandlerTrait.php create mode 100644 framework/core/src/Http/Handler/ControllerRouteHandler.php create mode 100644 framework/core/src/Http/Handler/RouteHandlerFactory.php create mode 100644 framework/core/src/Http/WebApp/AbstractWebApp.php create mode 100644 framework/core/src/Http/WebApp/WebAppAssets.php create mode 100644 framework/core/src/Http/WebApp/WebAppAssetsFactory.php create mode 100644 framework/core/src/Http/WebApp/WebAppView.php create mode 100644 framework/core/src/Http/WebApp/WebAppViewFactory.php diff --git a/framework/core/js/admin/dist/app.js b/framework/core/js/admin/dist/app.js index 7568d5d04..3f666d9e4 100644 --- a/framework/core/js/admin/dist/app.js +++ b/framework/core/js/admin/dist/app.js @@ -16669,24 +16669,6 @@ System.register('flarum/App', ['flarum/utils/ItemList', 'flarum/components/Alert */ this.routes = {}; - /** - * An object containing data to preload into the application. - * - * @type {Object} - * @property {Object} preload.data An array of resource objects to preload - * into the data store. - * @property {Object} preload.document An API response document to be used - * by the route that is first activated. - * @property {Object} preload.session A response from the /api/token - * endpoint containing the session's authentication token and user ID. - * @public - */ - this.preload = { - data: null, - document: null, - session: null - }; - /** * An ordered list of initializers to bootstrap the application. * @@ -16758,10 +16740,12 @@ System.register('flarum/App', ['flarum/utils/ItemList', 'flarum/components/Alert babelHelpers.createClass(App, [{ key: 'boot', - value: function boot() { + value: function boot(data) { var _this = this; - this.translator.locale = this.locale; + this.data = data; + + this.translator.locale = data.locale; this.initializers.toArray().forEach(function (initializer) { return initializer(_this); @@ -16770,9 +16754,9 @@ System.register('flarum/App', ['flarum/utils/ItemList', 'flarum/components/Alert }, { key: 'preloadedDocument', value: function preloadedDocument() { - if (app.preload.document) { - var results = app.store.pushPayload(app.preload.document); - app.preload.document = null; + if (this.data.document) { + var results = this.store.pushPayload(this.data.document); + this.data.document = null; return results; } @@ -17500,10 +17484,10 @@ System.register('flarum/components/AppearancePage', ['flarum/components/Page', ' value: function init() { babelHelpers.get(Object.getPrototypeOf(AppearancePage.prototype), 'init', this).call(this); - this.primaryColor = m.prop(app.settings.theme_primary_color); - this.secondaryColor = m.prop(app.settings.theme_secondary_color); - this.darkMode = m.prop(app.settings.theme_dark_mode === '1'); - this.coloredHeader = m.prop(app.settings.theme_colored_header === '1'); + this.primaryColor = m.prop(app.data.settings.theme_primary_color); + this.secondaryColor = m.prop(app.data.settings.theme_secondary_color); + this.darkMode = m.prop(app.data.settings.theme_dark_mode === '1'); + this.coloredHeader = m.prop(app.data.settings.theme_colored_header === '1'); } }, { key: 'view', @@ -17702,13 +17686,13 @@ System.register('flarum/components/BasicsPage', ['flarum/components/Page', 'flar this.fields = ['forum_title', 'forum_description', 'default_locale', 'default_route', 'welcome_title', 'welcome_message']; this.values = {}; - var settings = app.settings; + var settings = app.data.settings; this.fields.forEach(function (key) { return _this2.values[key] = m.prop(settings[key]); }); this.localeOptions = {}; - var locales = app.locales; + var locales = app.data.locales; for (var i in locales) { this.localeOptions[i] = locales[i] + ' (' + i + ')'; } @@ -17795,7 +17779,7 @@ System.register('flarum/components/BasicsPage', ['flarum/components/Page', 'flar var _this4 = this; return this.fields.some(function (key) { - return _this4.values[key]() !== app.settings[key]; + return _this4.values[key]() !== app.data.settings[key]; }); } }, { @@ -18212,7 +18196,7 @@ System.register('flarum/components/EditCustomCssModal', ['flarum/components/Moda babelHelpers.createClass(EditCustomCssModal, [{ key: 'init', value: function init() { - this.customLess = m.prop(app.settings.custom_less || ''); + this.customLess = m.prop(app.data.settings.custom_less || ''); } }, { key: 'className', @@ -18495,8 +18479,8 @@ System.register('flarum/components/ExtensionsPage', ['flarum/components/Page', ' m( 'ul', { className: 'ExtensionList' }, - Object.keys(app.extensions).map(function (id) { - var extension = app.extensions[id]; + Object.keys(app.data.extensions).map(function (id) { + var extension = app.data.extensions[id]; var controls = _this2.controlItems(extension.id).toArray(); return m( @@ -18576,7 +18560,7 @@ System.register('flarum/components/ExtensionsPage', ['flarum/components/Page', ' }, { key: 'isEnabled', value: function isEnabled(name) { - var enabled = JSON.parse(app.settings.extensions_enabled); + var enabled = JSON.parse(app.data.settings.extensions_enabled); return enabled.indexOf(name) !== -1; } @@ -18970,7 +18954,7 @@ System.register('flarum/components/MailPage', ['flarum/components/Page', 'flarum this.fields = ['mail_driver', 'mail_host', 'mail_from', 'mail_port', 'mail_username', 'mail_password', 'mail_encryption']; this.values = {}; - var settings = app.settings; + var settings = app.data.settings; this.fields.forEach(function (key) { return _this2.values[key] = m.prop(settings[key]); }); @@ -19086,7 +19070,7 @@ System.register('flarum/components/MailPage', ['flarum/components/Page', 'flarum var _this3 = this; return this.fields.some(function (key) { - return _this3.values[key]() !== app.settings[key]; + return _this3.values[key]() !== app.data.settings[key]; }); } }, { @@ -19567,7 +19551,7 @@ System.register('flarum/components/PermissionDropdown', ['flarum/components/Drop this.props.children = []; - var groupIds = app.permissions[this.props.permission] || []; + var groupIds = app.data.permissions[this.props.permission] || []; var everyone = groupIds.indexOf(Group.GUEST_ID) !== -1; var members = groupIds.indexOf(Group.MEMBER_ID) !== -1; var adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID); @@ -19626,7 +19610,7 @@ System.register('flarum/components/PermissionDropdown', ['flarum/components/Drop value: function save(groupIds) { var permission = this.props.permission; - app.permissions[permission] = groupIds; + app.data.permissions[permission] = groupIds; app.request({ method: 'POST', @@ -19639,7 +19623,7 @@ System.register('flarum/components/PermissionDropdown', ['flarum/components/Drop value: function toggle(groupId) { var permission = this.props.permission; - var groupIds = app.permissions[permission] || []; + var groupIds = app.data.permissions[permission] || []; var index = groupIds.indexOf(groupId); @@ -19843,7 +19827,7 @@ System.register('flarum/components/PermissionGrid', ['flarum/Component', 'flarum icon: 'i-cursor', label: app.translator.trans('core.admin.permissions.allow_renaming_label'), setting: function setting() { - var minutes = parseInt(app.settings.allow_renaming, 10); + var minutes = parseInt(app.data.settings.allow_renaming, 10); return SettingDropdown.component({ defaultLabel: minutes ? app.translator.transChoice('core.admin.permissions_controls.allow_some_minutes_button', minutes, { count: minutes }) : app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'), @@ -19870,7 +19854,7 @@ System.register('flarum/components/PermissionGrid', ['flarum/Component', 'flarum icon: 'pencil', label: app.translator.trans('core.admin.permissions.allow_post_editing_label'), setting: function setting() { - var minutes = parseInt(app.settings.allow_post_editing, 10); + var minutes = parseInt(app.data.settings.allow_post_editing, 10); return SettingDropdown.component({ defaultLabel: minutes ? app.translator.transChoice('core.admin.permissions_controls.allow_some_minutes_button', minutes, { count: minutes }) : app.translator.trans('core.admin.permissions_controls.allow_indefinitely_button'), @@ -20399,7 +20383,7 @@ System.register('flarum/components/SettingDropdown', ['flarum/components/SelectD var value = _ref.value; var label = _ref.label; - var active = app.settings[props.key] === value; + var active = app.data.settings[props.key] === value; return Button.component({ children: label, @@ -20485,7 +20469,7 @@ System.register('flarum/components/SettingsModal', ['flarum/components/Modal', ' value: function setting(key) { var fallback = arguments.length <= 1 || arguments[1] === undefined ? '' : arguments[1]; - this.settings[key] = this.settings[key] || m.prop(app.settings[key] || fallback); + this.settings[key] = this.settings[key] || m.prop(app.data.settings[key] || fallback); return this.settings[key]; } @@ -20499,7 +20483,7 @@ System.register('flarum/components/SettingsModal', ['flarum/components/Modal', ' Object.keys(this.settings).forEach(function (key) { var value = _this2.settings[key](); - if (value !== app.settings[key]) { + if (value !== app.data.settings[key]) { dirty[key] = value; } }); @@ -21130,11 +21114,11 @@ System.register('flarum/initializers/humanTime', ['flarum/utils/humanTime'], fun System.register('flarum/initializers/preload', ['flarum/Session'], function (_export, _context) { var Session; function preload(app) { - app.store.pushPayload({ data: app.preload.data }); + app.store.pushPayload({ data: app.data.resources }); app.forum = app.store.getById('forums', 1); - app.session = new Session(app.store.getById('users', app.preload.session.userId), app.preload.session.csrfToken); + app.session = new Session(app.store.getById('users', app.data.session.userId), app.data.session.csrfToken); } _export('default', preload); @@ -22977,16 +22961,16 @@ System.register("flarum/utils/RequestError", [], function (_export, _context) { System.register('flarum/utils/saveSettings', [], function (_export, _context) { function saveSettings(settings) { - var oldSettings = JSON.parse(JSON.stringify(app.settings)); + var oldSettings = JSON.parse(JSON.stringify(app.data.settings)); - babelHelpers.extends(app.settings, settings); + babelHelpers.extends(app.data.settings, settings); return app.request({ method: 'POST', url: app.forum.attribute('apiUrl') + '/settings', data: settings }).catch(function (error) { - app.settings = oldSettings; + app.data.settings = oldSettings; throw error; }); } diff --git a/framework/core/js/admin/src/components/AppearancePage.js b/framework/core/js/admin/src/components/AppearancePage.js index 48659da1f..a6baa7c7a 100644 --- a/framework/core/js/admin/src/components/AppearancePage.js +++ b/framework/core/js/admin/src/components/AppearancePage.js @@ -8,10 +8,10 @@ export default class AppearancePage extends Page { init() { super.init(); - this.primaryColor = m.prop(app.settings.theme_primary_color); - this.secondaryColor = m.prop(app.settings.theme_secondary_color); - this.darkMode = m.prop(app.settings.theme_dark_mode === '1'); - this.coloredHeader = m.prop(app.settings.theme_colored_header === '1'); + this.primaryColor = m.prop(app.data.settings.theme_primary_color); + this.secondaryColor = m.prop(app.data.settings.theme_secondary_color); + this.darkMode = m.prop(app.data.settings.theme_dark_mode === '1'); + this.coloredHeader = m.prop(app.data.settings.theme_colored_header === '1'); } view() { diff --git a/framework/core/js/admin/src/components/BasicsPage.js b/framework/core/js/admin/src/components/BasicsPage.js index 63c44635c..6ed272a8f 100644 --- a/framework/core/js/admin/src/components/BasicsPage.js +++ b/framework/core/js/admin/src/components/BasicsPage.js @@ -22,11 +22,11 @@ export default class BasicsPage extends Page { ]; this.values = {}; - const settings = app.settings; + const settings = app.data.settings; this.fields.forEach(key => this.values[key] = m.prop(settings[key])); this.localeOptions = {}; - const locales = app.locales; + const locales = app.data.locales; for (const i in locales) { this.localeOptions[i] = `${locales[i]} (${i})`; } @@ -110,7 +110,7 @@ export default class BasicsPage extends Page { } changed() { - return this.fields.some(key => this.values[key]() !== app.settings[key]); + return this.fields.some(key => this.values[key]() !== app.data.settings[key]); } /** diff --git a/framework/core/js/admin/src/components/EditCustomCssModal.js b/framework/core/js/admin/src/components/EditCustomCssModal.js index a73d2b9bc..c15d188bb 100644 --- a/framework/core/js/admin/src/components/EditCustomCssModal.js +++ b/framework/core/js/admin/src/components/EditCustomCssModal.js @@ -4,7 +4,7 @@ import saveSettings from 'flarum/utils/saveSettings'; export default class EditCustomCssModal extends Modal { init() { - this.customLess = m.prop(app.settings.custom_less || ''); + this.customLess = m.prop(app.data.settings.custom_less || ''); } className() { diff --git a/framework/core/js/admin/src/components/ExtensionsPage.js b/framework/core/js/admin/src/components/ExtensionsPage.js index 125d477a4..b0a9e51d8 100644 --- a/framework/core/js/admin/src/components/ExtensionsPage.js +++ b/framework/core/js/admin/src/components/ExtensionsPage.js @@ -27,9 +27,9 @@ export default class ExtensionsPage extends Page {
    - {Object.keys(app.extensions) + {Object.keys(app.data.extensions) .map(id => { - const extension = app.extensions[id]; + const extension = app.data.extensions[id]; const controls = this.controlItems(extension.id).toArray(); return
  • @@ -92,7 +92,7 @@ export default class ExtensionsPage extends Page { } isEnabled(name) { - const enabled = JSON.parse(app.settings.extensions_enabled); + const enabled = JSON.parse(app.data.settings.extensions_enabled); return enabled.indexOf(name) !== -1; } diff --git a/framework/core/js/admin/src/components/MailPage.js b/framework/core/js/admin/src/components/MailPage.js index 1e29d3565..cffe92db6 100644 --- a/framework/core/js/admin/src/components/MailPage.js +++ b/framework/core/js/admin/src/components/MailPage.js @@ -21,7 +21,7 @@ export default class MailPage extends Page { ]; this.values = {}; - const settings = app.settings; + const settings = app.data.settings; this.fields.forEach(key => this.values[key] = m.prop(settings[key])); this.localeOptions = {}; @@ -96,7 +96,7 @@ export default class MailPage extends Page { } changed() { - return this.fields.some(key => this.values[key]() !== app.settings[key]); + return this.fields.some(key => this.values[key]() !== app.data.settings[key]); } onsubmit(e) { diff --git a/framework/core/js/admin/src/components/PermissionDropdown.js b/framework/core/js/admin/src/components/PermissionDropdown.js index ea19a1ae3..d61674392 100644 --- a/framework/core/js/admin/src/components/PermissionDropdown.js +++ b/framework/core/js/admin/src/components/PermissionDropdown.js @@ -21,7 +21,7 @@ export default class PermissionDropdown extends Dropdown { view() { this.props.children = []; - const groupIds = app.permissions[this.props.permission] || []; + const groupIds = app.data.permissions[this.props.permission] || []; const everyone = groupIds.indexOf(Group.GUEST_ID) !== -1; const members = groupIds.indexOf(Group.MEMBER_ID) !== -1; const adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID); @@ -87,7 +87,7 @@ export default class PermissionDropdown extends Dropdown { save(groupIds) { const permission = this.props.permission; - app.permissions[permission] = groupIds; + app.data.permissions[permission] = groupIds; app.request({ method: 'POST', @@ -99,7 +99,7 @@ export default class PermissionDropdown extends Dropdown { toggle(groupId) { const permission = this.props.permission; - let groupIds = app.permissions[permission] || []; + let groupIds = app.data.permissions[permission] || []; const index = groupIds.indexOf(groupId); diff --git a/framework/core/js/admin/src/components/PermissionGrid.js b/framework/core/js/admin/src/components/PermissionGrid.js index b34261565..b20e2143d 100644 --- a/framework/core/js/admin/src/components/PermissionGrid.js +++ b/framework/core/js/admin/src/components/PermissionGrid.js @@ -119,7 +119,7 @@ export default class PermissionGrid extends Component { icon: 'i-cursor', label: app.translator.trans('core.admin.permissions.allow_renaming_label'), setting: () => { - const minutes = parseInt(app.settings.allow_renaming, 10); + const minutes = parseInt(app.data.settings.allow_renaming, 10); return SettingDropdown.component({ defaultLabel: minutes @@ -151,7 +151,7 @@ export default class PermissionGrid extends Component { icon: 'pencil', label: app.translator.trans('core.admin.permissions.allow_post_editing_label'), setting: () => { - const minutes = parseInt(app.settings.allow_post_editing, 10); + const minutes = parseInt(app.data.settings.allow_post_editing, 10); return SettingDropdown.component({ defaultLabel: minutes diff --git a/framework/core/js/admin/src/components/SettingDropdown.js b/framework/core/js/admin/src/components/SettingDropdown.js index d736a5b53..5f00b1754 100644 --- a/framework/core/js/admin/src/components/SettingDropdown.js +++ b/framework/core/js/admin/src/components/SettingDropdown.js @@ -12,7 +12,7 @@ export default class SettingDropdown extends SelectDropdown { props.defaultLabel = 'Custom'; props.children = props.options.map(({value, label}) => { - const active = app.settings[props.key] === value; + const active = app.data.settings[props.key] === value; return Button.component({ children: label, diff --git a/framework/core/js/admin/src/components/SettingsModal.js b/framework/core/js/admin/src/components/SettingsModal.js index 3a7dfaee8..8dce58a57 100644 --- a/framework/core/js/admin/src/components/SettingsModal.js +++ b/framework/core/js/admin/src/components/SettingsModal.js @@ -39,7 +39,7 @@ export default class SettingsModal extends Modal { } setting(key, fallback = '') { - this.settings[key] = this.settings[key] || m.prop(app.settings[key] || fallback); + this.settings[key] = this.settings[key] || m.prop(app.data.settings[key] || fallback); return this.settings[key]; } @@ -50,7 +50,7 @@ export default class SettingsModal extends Modal { Object.keys(this.settings).forEach(key => { const value = this.settings[key](); - if (value !== app.settings[key]) { + if (value !== app.data.settings[key]) { dirty[key] = value; } }); diff --git a/framework/core/js/admin/src/utils/saveSettings.js b/framework/core/js/admin/src/utils/saveSettings.js index 853a3b467..b0d81c5eb 100644 --- a/framework/core/js/admin/src/utils/saveSettings.js +++ b/framework/core/js/admin/src/utils/saveSettings.js @@ -1,14 +1,14 @@ export default function saveSettings(settings) { - const oldSettings = JSON.parse(JSON.stringify(app.settings)); + const oldSettings = JSON.parse(JSON.stringify(app.data.settings)); - Object.assign(app.settings, settings); + Object.assign(app.data.settings, settings); return app.request({ method: 'POST', url: app.forum.attribute('apiUrl') + '/settings', data: settings }).catch(error => { - app.settings = oldSettings; + app.data.settings = oldSettings; throw error; }); } diff --git a/framework/core/js/forum/dist/app.js b/framework/core/js/forum/dist/app.js index 8ad5ceb06..5f1cddcfb 100644 --- a/framework/core/js/forum/dist/app.js +++ b/framework/core/js/forum/dist/app.js @@ -18418,10 +18418,12 @@ System.register('flarum/App', ['flarum/utils/ItemList', 'flarum/components/Alert babelHelpers.createClass(App, [{ key: 'boot', - value: function boot() { + value: function boot(data) { var _this = this; - this.translator.locale = this.locale; + this.data = data; + + this.translator.locale = data.locale; this.initializers.toArray().forEach(function (initializer) { return initializer(_this); @@ -18430,9 +18432,9 @@ System.register('flarum/App', ['flarum/utils/ItemList', 'flarum/components/Alert }, { key: 'preloadedDocument', value: function preloadedDocument() { - if (app.preload.document) { - var results = app.store.pushPayload(app.preload.document); - app.preload.document = null; + if (this.data.document) { + var results = this.store.pushPayload(this.data.document); + this.data.document = null; return results; } @@ -22305,14 +22307,14 @@ System.register('flarum/components/HeaderSecondary', ['flarum/Component', 'flaru items.add('search', app.search.render(), 30); - if (Object.keys(app.locales).length > 1) { + if (Object.keys(app.data.locales).length > 1) { var locales = []; var _loop = function _loop(locale) { locales.push(Button.component({ - active: app.locale === locale, - children: app.locales[locale], - icon: app.locale === locale ? 'check' : true, + active: app.data.locale === locale, + children: app.data.locales[locale], + icon: app.data.locale === locale ? 'check' : true, onclick: function onclick() { if (app.session.user) { app.session.user.savePreferences({ locale: locale }).then(function () { @@ -22326,7 +22328,7 @@ System.register('flarum/components/HeaderSecondary', ['flarum/Component', 'flaru })); }; - for (var locale in app.locales) { + for (var locale in app.data.locales) { _loop(locale); } @@ -28664,11 +28666,11 @@ System.register('flarum/initializers/humanTime', ['flarum/utils/humanTime'], fun System.register('flarum/initializers/preload', ['flarum/Session'], function (_export, _context) { var Session; function preload(app) { - app.store.pushPayload({ data: app.preload.data }); + app.store.pushPayload({ data: app.data.resources }); app.forum = app.store.getById('forums', 1); - app.session = new Session(app.store.getById('users', app.preload.session.userId), app.preload.session.csrfToken); + app.session = new Session(app.store.getById('users', app.data.session.userId), app.data.session.csrfToken); } _export('default', preload); diff --git a/framework/core/js/forum/src/components/HeaderSecondary.js b/framework/core/js/forum/src/components/HeaderSecondary.js index 750f868d9..912281989 100644 --- a/framework/core/js/forum/src/components/HeaderSecondary.js +++ b/framework/core/js/forum/src/components/HeaderSecondary.js @@ -39,14 +39,14 @@ export default class HeaderSecondary extends Component { items.add('search', app.search.render(), 30); - if (Object.keys(app.locales).length > 1) { + if (Object.keys(app.data.locales).length > 1) { const locales = []; - for (const locale in app.locales) { + for (const locale in app.data.locales) { locales.push(Button.component({ - active: app.locale === locale, - children: app.locales[locale], - icon: app.locale === locale ? 'check' : true, + active: app.data.locale === locale, + children: app.data.locales[locale], + icon: app.data.locale === locale ? 'check' : true, onclick: () => { if (app.session.user) { app.session.user.savePreferences({locale}).then(() => window.location.reload()); diff --git a/framework/core/js/lib/App.js b/framework/core/js/lib/App.js index 1c8345441..aae82c416 100644 --- a/framework/core/js/lib/App.js +++ b/framework/core/js/lib/App.js @@ -40,24 +40,6 @@ export default class App { */ this.routes = {}; - /** - * An object containing data to preload into the application. - * - * @type {Object} - * @property {Object} preload.data An array of resource objects to preload - * into the data store. - * @property {Object} preload.document An API response document to be used - * by the route that is first activated. - * @property {Object} preload.session A response from the /api/token - * endpoint containing the session's authentication token and user ID. - * @public - */ - this.preload = { - data: null, - document: null, - session: null - }; - /** * An ordered list of initializers to bootstrap the application. * @@ -125,8 +107,10 @@ export default class App { * * @public */ - boot() { - this.translator.locale = this.locale; + boot(data) { + this.data = data; + + this.translator.locale = data.locale; this.initializers.toArray().forEach(initializer => initializer(this)); } @@ -138,9 +122,9 @@ export default class App { * @public */ preloadedDocument() { - if (app.preload.document) { - const results = app.store.pushPayload(app.preload.document); - app.preload.document = null; + if (this.data.document) { + const results = this.store.pushPayload(this.data.document); + this.data.document = null; return results; } diff --git a/framework/core/js/lib/initializers/preload.js b/framework/core/js/lib/initializers/preload.js index 31d2d814f..a8d977898 100644 --- a/framework/core/js/lib/initializers/preload.js +++ b/framework/core/js/lib/initializers/preload.js @@ -13,12 +13,12 @@ import Session from 'flarum/Session'; * @param {App} app */ export default function preload(app) { - app.store.pushPayload({data: app.preload.data}); + app.store.pushPayload({data: app.data.resources}); app.forum = app.store.getById('forums', 1); app.session = new Session( - app.store.getById('users', app.preload.session.userId), - app.preload.session.csrfToken + app.store.getById('users', app.data.session.userId), + app.data.session.csrfToken ); } diff --git a/framework/core/src/Admin/AdminServiceProvider.php b/framework/core/src/Admin/AdminServiceProvider.php index 5b2b340dc..34c2046ab 100644 --- a/framework/core/src/Admin/AdminServiceProvider.php +++ b/framework/core/src/Admin/AdminServiceProvider.php @@ -14,13 +14,11 @@ use Flarum\Event\ExtensionWasDisabled; use Flarum\Event\ExtensionWasEnabled; use Flarum\Event\SettingWasSet; use Flarum\Foundation\AbstractServiceProvider; -use Flarum\Http\GenerateRouteHandlerTrait; +use Flarum\Http\Handler\RouteHandlerFactory; use Flarum\Http\RouteCollection; class AdminServiceProvider extends AbstractServiceProvider { - use GenerateRouteHandlerTrait; - /** * {@inheritdoc} */ @@ -44,9 +42,9 @@ class AdminServiceProvider extends AbstractServiceProvider $this->loadViewsFrom(__DIR__.'/../../views', 'flarum.admin'); - $this->flushAssetsWhenThemeChanged(); + $this->flushWebAppAssetsWhenThemeChanged(); - $this->flushAssetsWhenExtensionsChanged(); + $this->flushWebAppAssetsWhenExtensionsChanged(); } /** @@ -56,42 +54,42 @@ class AdminServiceProvider extends AbstractServiceProvider */ protected function populateRoutes(RouteCollection $routes) { - $toController = $this->getHandlerGenerator($this->app); + $route = $this->app->make(RouteHandlerFactory::class); $routes->get( '/', 'index', - $toController('Flarum\Admin\Controller\ClientController') + $route->toController(Controller\WebAppController::class) ); } - protected function flushAssetsWhenThemeChanged() + protected function flushWebAppAssetsWhenThemeChanged() { $this->app->make('events')->listen(SettingWasSet::class, function (SettingWasSet $event) { if (preg_match('/^theme_|^custom_less$/i', $event->key)) { - $this->getClientController()->flushCss(); + $this->getWebAppAssets()->flushCss(); } }); } - protected function flushAssetsWhenExtensionsChanged() + protected function flushWebAppAssetsWhenExtensionsChanged() { $events = $this->app->make('events'); - $events->listen(ExtensionWasEnabled::class, [$this, 'flushAssets']); - $events->listen(ExtensionWasDisabled::class, [$this, 'flushAssets']); + $events->listen(ExtensionWasEnabled::class, [$this, 'flushWebAppAssets']); + $events->listen(ExtensionWasDisabled::class, [$this, 'flushWebAppAssets']); } - public function flushAssets() + public function flushWebAppAssets() { - $this->getClientController()->flushAssets(); + $this->getWebAppAssets()->flush(); } /** - * @return \Flarum\Admin\Controller\ClientController + * @return \Flarum\Http\WebApp\WebAppAssets */ - protected function getClientController() + protected function getWebAppAssets() { - return $this->app->make('Flarum\Admin\Controller\ClientController'); + return $this->app->make(WebApp::class)->getAssets(); } } diff --git a/framework/core/src/Admin/Controller/ClientController.php b/framework/core/src/Admin/Controller/WebAppController.php similarity index 53% rename from framework/core/src/Admin/Controller/ClientController.php rename to framework/core/src/Admin/Controller/WebAppController.php index df8ce812e..5cf89f66c 100644 --- a/framework/core/src/Admin/Controller/ClientController.php +++ b/framework/core/src/Admin/Controller/WebAppController.php @@ -10,29 +10,23 @@ namespace Flarum\Admin\Controller; -use Flarum\Api\Client; +use Flarum\Admin\WebApp; use Flarum\Core\Permission; use Flarum\Event\PrepareUnserializedSettings; use Flarum\Extension\ExtensionManager; use Flarum\Foundation\Application; -use Flarum\Http\Controller\AbstractClientController as BaseClientController; +use Flarum\Http\Controller\AbstractWebAppController; use Flarum\Locale\LocaleManager; use Flarum\Settings\SettingsRepositoryInterface; -use Illuminate\Contracts\Cache\Repository; use Illuminate\Contracts\Events\Dispatcher; -use Psr\Http\Message\ServerRequestInterface as Request; +use Psr\Http\Message\ServerRequestInterface; -class ClientController extends BaseClientController +class WebAppController extends AbstractWebAppController { /** - * {@inheritdoc} + * @var SettingsRepositoryInterface */ - protected $clientName = 'admin'; - - /** - * {@inheritdoc} - */ - protected $translations = '/^[^\.]+\.(?:admin|lib)\./'; + protected $settings; /** * @var ExtensionManager @@ -40,29 +34,25 @@ class ClientController extends BaseClientController protected $extensions; /** - * {@inheritdoc} + * @param WebApp $webApp + * @param Dispatcher $events + * @param SettingsRepositoryInterface $settings + * @param ExtensionManager $extensions */ - public function __construct( - Application $app, - Client $apiClient, - LocaleManager $locales, - SettingsRepositoryInterface $settings, - Dispatcher $events, - Repository $cache, - ExtensionManager $extensions - ) { - BaseClientController::__construct($app, $apiClient, $locales, $settings, $events, $cache); - - $this->layout = __DIR__.'/../../../views/admin.blade.php'; + public function __construct(WebApp $webApp, Dispatcher $events, SettingsRepositoryInterface $settings, ExtensionManager $extensions) + { + $this->webApp = $webApp; + $this->events = $events; + $this->settings = $settings; $this->extensions = $extensions; } /** * {@inheritdoc} */ - public function render(Request $request) + protected function getView(ServerRequestInterface $request) { - $view = BaseClientController::render($request); + $view = parent::getView($request); $settings = $this->settings->all(); diff --git a/framework/core/src/Admin/WebApp.php b/framework/core/src/Admin/WebApp.php new file mode 100644 index 000000000..e1e97ed2a --- /dev/null +++ b/framework/core/src/Admin/WebApp.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Admin; + +use Flarum\Http\WebApp\AbstractWebApp; + +class WebApp extends AbstractWebApp +{ + /** + * {@inheritdoc} + */ + protected function getName() + { + return 'admin'; + } +} diff --git a/framework/core/src/Api/ApiServiceProvider.php b/framework/core/src/Api/ApiServiceProvider.php index 7cd77bc39..87004deec 100644 --- a/framework/core/src/Api/ApiServiceProvider.php +++ b/framework/core/src/Api/ApiServiceProvider.php @@ -16,7 +16,7 @@ use Flarum\Api\Serializer\NotificationSerializer; use Flarum\Event\ConfigureApiRoutes; use Flarum\Event\ConfigureNotificationTypes; use Flarum\Foundation\AbstractServiceProvider; -use Flarum\Http\GenerateRouteHandlerTrait; +use Flarum\Http\Handler\RouteHandlerFactory; use Flarum\Http\RouteCollection; use Tobscure\JsonApi\ErrorHandler; use Tobscure\JsonApi\Exception\Handler\FallbackExceptionHandler; @@ -24,8 +24,6 @@ use Tobscure\JsonApi\Exception\Handler\InvalidParameterExceptionHandler; class ApiServiceProvider extends AbstractServiceProvider { - use GenerateRouteHandlerTrait; - /** * {@inheritdoc} */ @@ -101,27 +99,27 @@ class ApiServiceProvider extends AbstractServiceProvider */ protected function populateRoutes(RouteCollection $routes) { - $toController = $this->getHandlerGenerator($this->app); + $route = $this->app->make(RouteHandlerFactory::class); // Get forum information $routes->get( '/forum', 'forum.show', - $toController('Flarum\Api\Controller\ShowForumController') + $route->toController(Controller\ShowForumController::class) ); // Retrieve authentication token $routes->post( '/token', 'token', - $toController('Flarum\Api\Controller\TokenController') + $route->toController(Controller\TokenController::class) ); // Send forgot password email $routes->post( '/forgot', 'forgot', - $toController('Flarum\Api\Controller\ForgotPasswordController') + $route->toController(Controller\ForgotPasswordController::class) ); /* @@ -134,56 +132,56 @@ class ApiServiceProvider extends AbstractServiceProvider $routes->get( '/users', 'users.index', - $toController('Flarum\Api\Controller\ListUsersController') + $route->toController(Controller\ListUsersController::class) ); // Register a user $routes->post( '/users', 'users.create', - $toController('Flarum\Api\Controller\CreateUserController') + $route->toController(Controller\CreateUserController::class) ); // Get a single user $routes->get( '/users/{id}', 'users.show', - $toController('Flarum\Api\Controller\ShowUserController') + $route->toController(Controller\ShowUserController::class) ); // Edit a user $routes->patch( '/users/{id}', 'users.update', - $toController('Flarum\Api\Controller\UpdateUserController') + $route->toController(Controller\UpdateUserController::class) ); // Delete a user $routes->delete( '/users/{id}', 'users.delete', - $toController('Flarum\Api\Controller\DeleteUserController') + $route->toController(Controller\DeleteUserController::class) ); // Upload avatar $routes->post( '/users/{id}/avatar', 'users.avatar.upload', - $toController('Flarum\Api\Controller\UploadAvatarController') + $route->toController(Controller\UploadAvatarController::class) ); // Remove avatar $routes->delete( '/users/{id}/avatar', 'users.avatar.delete', - $toController('Flarum\Api\Controller\DeleteAvatarController') + $route->toController(Controller\DeleteAvatarController::class) ); // send confirmation email $routes->post( '/users/{id}/send-confirmation', 'users.confirmation.send', - $toController('Flarum\Api\Controller\SendConfirmationEmailController') + $route->toController(Controller\SendConfirmationEmailController::class) ); /* @@ -196,21 +194,21 @@ class ApiServiceProvider extends AbstractServiceProvider $routes->get( '/notifications', 'notifications.index', - $toController('Flarum\Api\Controller\ListNotificationsController') + $route->toController(Controller\ListNotificationsController::class) ); // Mark all notifications as read $routes->post( '/notifications/read', 'notifications.readAll', - $toController('Flarum\Api\Controller\ReadAllNotificationsController') + $route->toController(Controller\ReadAllNotificationsController::class) ); // Mark a single notification as read $routes->patch( '/notifications/{id}', 'notifications.update', - $toController('Flarum\Api\Controller\UpdateNotificationController') + $route->toController(Controller\UpdateNotificationController::class) ); /* @@ -223,35 +221,35 @@ class ApiServiceProvider extends AbstractServiceProvider $routes->get( '/discussions', 'discussions.index', - $toController('Flarum\Api\Controller\ListDiscussionsController') + $route->toController(Controller\ListDiscussionsController::class) ); // Create a discussion $routes->post( '/discussions', 'discussions.create', - $toController('Flarum\Api\Controller\CreateDiscussionController') + $route->toController(Controller\CreateDiscussionController::class) ); // Show a single discussion $routes->get( '/discussions/{id}', 'discussions.show', - $toController('Flarum\Api\Controller\ShowDiscussionController') + $route->toController(Controller\ShowDiscussionController::class) ); // Edit a discussion $routes->patch( '/discussions/{id}', 'discussions.update', - $toController('Flarum\Api\Controller\UpdateDiscussionController') + $route->toController(Controller\UpdateDiscussionController::class) ); // Delete a discussion $routes->delete( '/discussions/{id}', 'discussions.delete', - $toController('Flarum\Api\Controller\DeleteDiscussionController') + $route->toController(Controller\DeleteDiscussionController::class) ); /* @@ -264,35 +262,35 @@ class ApiServiceProvider extends AbstractServiceProvider $routes->get( '/posts', 'posts.index', - $toController('Flarum\Api\Controller\ListPostsController') + $route->toController(Controller\ListPostsController::class) ); // Create a post $routes->post( '/posts', 'posts.create', - $toController('Flarum\Api\Controller\CreatePostController') + $route->toController(Controller\CreatePostController::class) ); // Show a single or multiple posts by ID $routes->get( '/posts/{id}', 'posts.show', - $toController('Flarum\Api\Controller\ShowPostController') + $route->toController(Controller\ShowPostController::class) ); // Edit a post $routes->patch( '/posts/{id}', 'posts.update', - $toController('Flarum\Api\Controller\UpdatePostController') + $route->toController(Controller\UpdatePostController::class) ); // Delete a post $routes->delete( '/posts/{id}', 'posts.delete', - $toController('Flarum\Api\Controller\DeletePostController') + $route->toController(Controller\DeletePostController::class) ); /* @@ -305,28 +303,28 @@ class ApiServiceProvider extends AbstractServiceProvider $routes->get( '/groups', 'groups.index', - $toController('Flarum\Api\Controller\ListGroupsController') + $route->toController(Controller\ListGroupsController::class) ); // Create a group $routes->post( '/groups', 'groups.create', - $toController('Flarum\Api\Controller\CreateGroupController') + $route->toController(Controller\CreateGroupController::class) ); // Edit a group $routes->patch( '/groups/{id}', 'groups.update', - $toController('Flarum\Api\Controller\UpdateGroupController') + $route->toController(Controller\UpdateGroupController::class) ); // Delete a group $routes->delete( '/groups/{id}', 'groups.delete', - $toController('Flarum\Api\Controller\DeleteGroupController') + $route->toController(Controller\DeleteGroupController::class) ); /* @@ -339,32 +337,32 @@ class ApiServiceProvider extends AbstractServiceProvider $routes->patch( '/extensions/{name}', 'extensions.update', - $toController('Flarum\Api\Controller\UpdateExtensionController') + $route->toController(Controller\UpdateExtensionController::class) ); // Uninstall an extension $routes->delete( '/extensions/{name}', 'extensions.delete', - $toController('Flarum\Api\Controller\UninstallExtensionController') + $route->toController(Controller\UninstallExtensionController::class) ); // Update settings $routes->post( '/settings', 'settings', - $toController('Flarum\Api\Controller\SetSettingsController') + $route->toController(Controller\SetSettingsController::class) ); // Update a permission $routes->post( '/permission', 'permission', - $toController('Flarum\Api\Controller\SetPermissionController') + $route->toController(Controller\SetPermissionController::class) ); $this->app->make('events')->fire( - new ConfigureApiRoutes($routes, $toController) + new ConfigureApiRoutes($routes, $route) ); } } diff --git a/framework/core/src/Asset/AssetManager.php b/framework/core/src/Asset/AssetManager.php deleted file mode 100644 index fad3af170..000000000 --- a/framework/core/src/Asset/AssetManager.php +++ /dev/null @@ -1,117 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Asset; - -use DomainException; - -class AssetManager -{ - /** - * @var CompilerInterface - */ - protected $less; - - /** - * @var CompilerInterface - */ - protected $js; - - /** - * @param CompilerInterface $js - * @param CompilerInterface $less - */ - public function __construct(CompilerInterface $js, CompilerInterface $less) - { - $this->js = $js; - $this->less = $less; - } - - /** - * @param $filename - */ - public function setFilename($filename) - { - $this->js->setFilename($filename.'.js'); - $this->less->setFilename($filename.'.css'); - } - - /** - * @param string $file - */ - public function addFile($file) - { - $ext = pathinfo($file, PATHINFO_EXTENSION); - - switch ($ext) { - case 'js': - $this->js->addFile($file); - break; - - case 'css': - case 'less': - $this->less->addFile($file); - break; - - default: - throw new DomainException('Unsupported asset type: '.$ext); - } - } - - /** - * @param string[] $files - */ - public function addFiles(array $files) - { - array_walk($files, [$this, 'addFile']); - } - - /** - * @param callable $callback - */ - public function addLess(callable $callback) - { - $this->less->addString($callback); - } - - /** - * @param callable $callback - */ - public function addJs(callable $callback) - { - $this->js->addString($callback); - } - - /** - * @return string - */ - public function getCssFile() - { - return $this->less->getFile(); - } - - /** - * @return string - */ - public function getJsFile() - { - return $this->js->getFile(); - } - - public function flushCss() - { - $this->less->flush(); - } - - public function flushJs() - { - $this->js->flush(); - } -} diff --git a/framework/core/src/Debug/Console/CacheClearCommand.php b/framework/core/src/Debug/Console/CacheClearCommand.php index bbd34b5a7..3cb55a8a8 100644 --- a/framework/core/src/Debug/Console/CacheClearCommand.php +++ b/framework/core/src/Debug/Console/CacheClearCommand.php @@ -10,9 +10,9 @@ namespace Flarum\Debug\Console; -use Flarum\Admin\Controller\ClientController as AdminClient; +use Flarum\Admin\WebApp as AdminWebApp; use Flarum\Console\Command\AbstractCommand; -use Flarum\Forum\Controller\ClientController as ForumClient; +use Flarum\Forum\WebApp as ForumWebApp; use Illuminate\Contracts\Cache\Store; class CacheClearCommand extends AbstractCommand @@ -23,16 +23,21 @@ class CacheClearCommand extends AbstractCommand protected $cache; /** - * @var \Flarum\Forum\Controller\ClientController + * @var \Flarum\Forum\WebApp */ protected $forum; /** - * @var \Flarum\Admin\Controller\ClientController + * @var \Flarum\Admin\WebApp */ protected $admin; - public function __construct(Store $cache, ForumClient $forum, AdminClient $admin) + /** + * @param Store $cache + * @param ForumWebApp $forum + * @param AdminWebApp $admin + */ + public function __construct(Store $cache, ForumWebApp $forum, AdminWebApp $admin) { $this->cache = $cache; $this->forum = $forum; @@ -58,8 +63,8 @@ class CacheClearCommand extends AbstractCommand { $this->info('Clearing the cache...'); - $this->forum->flushAssets(); - $this->admin->flushAssets(); + $this->forum->getAssets()->flush(); + $this->admin->getAssets()->flush(); $this->cache->flush(); } diff --git a/framework/core/src/Event/AbstractConfigureRoutes.php b/framework/core/src/Event/AbstractConfigureRoutes.php index 33460507d..dfebfacaa 100644 --- a/framework/core/src/Event/AbstractConfigureRoutes.php +++ b/framework/core/src/Event/AbstractConfigureRoutes.php @@ -10,6 +10,7 @@ namespace Flarum\Event; +use Flarum\Http\Handler\RouteHandlerFactory; use Flarum\Http\RouteCollection; abstract class AbstractConfigureRoutes @@ -20,18 +21,18 @@ abstract class AbstractConfigureRoutes public $routes; /** - * @var callable + * @var RouteHandlerFactory */ - protected $handlerGenerator; + protected $route; /** * @param RouteCollection $routes - * @param callable $handlerGenerator + * @param RouteHandlerFactory $route */ - public function __construct(RouteCollection $routes, callable $handlerGenerator) + public function __construct(RouteCollection $routes, RouteHandlerFactory $route) { $this->routes = $routes; - $this->handlerGenerator = $handlerGenerator; + $this->route = $route; } /** @@ -91,6 +92,6 @@ abstract class AbstractConfigureRoutes */ protected function toController($controller) { - return call_user_func($this->handlerGenerator, $controller); + return $this->route->toController($controller); } } diff --git a/framework/core/src/Event/ConfigureClientView.php b/framework/core/src/Event/ConfigureClientView.php index 4b623ff2c..63c45801e 100644 --- a/framework/core/src/Event/ConfigureClientView.php +++ b/framework/core/src/Event/ConfigureClientView.php @@ -10,50 +10,9 @@ namespace Flarum\Event; -use Flarum\Admin\Controller\ClientController as AdminClientAction; -use Flarum\Forum\Controller\ClientController as ForumClientAction; -use Flarum\Http\Controller\AbstractClientController; -use Flarum\Http\Controller\ClientView; - -class ConfigureClientView +/** + * @deprecated + */ +class ConfigureClientView extends ConfigureWebApp { - /** - * @var AbstractClientController - */ - public $action; - - /** - * @var ClientView - */ - public $view; - - /** - * @param AbstractClientController $action - * @param ClientView $view - */ - public function __construct(AbstractClientController $action, ClientView $view) - { - $this->action = $action; - $this->view = $view; - } - - public function isForum() - { - return $this->action instanceof ForumClientAction; - } - - public function isAdmin() - { - return $this->action instanceof AdminClientAction; - } - - public function addAssets($files) - { - $this->view->getAssets()->addFiles((array) $files); - } - - public function addBootstrapper($bootstrapper) - { - $this->view->addBootstrapper($bootstrapper); - } } diff --git a/framework/core/src/Event/ConfigureWebApp.php b/framework/core/src/Event/ConfigureWebApp.php new file mode 100644 index 000000000..f7e723439 --- /dev/null +++ b/framework/core/src/Event/ConfigureWebApp.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Event; + +use Flarum\Admin\Controller\WebAppController as AdminClientController; +use Flarum\Forum\Controller\WebAppController as ForumClientController; +use Flarum\Http\Controller\AbstractWebAppController; +use Flarum\Http\WebApp\WebAppView; + +class ConfigureWebApp +{ + /** + * @var AbstractClientController + */ + public $controller; + + /** + * @var ClientView + */ + public $view; + + /** + * @param AbstractClientController $controller + * @param ClientView $view + */ + public function __construct(AbstractWebAppController $controller, WebAppView $view) + { + $this->controller = $controller; + $this->view = $view; + } + + public function isForum() + { + return $this->controller instanceof ForumClientController; + } + + public function isAdmin() + { + return $this->controller instanceof AdminClientController; + } + + 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); + } +} diff --git a/framework/core/src/Forum/Controller/AuthorizedClientController.php b/framework/core/src/Forum/Controller/AuthorizedWebAppController.php similarity index 91% rename from framework/core/src/Forum/Controller/AuthorizedClientController.php rename to framework/core/src/Forum/Controller/AuthorizedWebAppController.php index 0b81f7171..cd39f32bc 100644 --- a/framework/core/src/Forum/Controller/AuthorizedClientController.php +++ b/framework/core/src/Forum/Controller/AuthorizedWebAppController.php @@ -13,7 +13,7 @@ namespace Flarum\Forum\Controller; use Flarum\Core\Exception\PermissionDeniedException; use Psr\Http\Message\ServerRequestInterface as Request; -class AuthorizedClientController extends ClientController +class AuthorizedWebAppController extends WebAppController { /** * {@inheritdoc} diff --git a/framework/core/src/Forum/Controller/ClientController.php b/framework/core/src/Forum/Controller/ClientController.php deleted file mode 100644 index 08056be1e..000000000 --- a/framework/core/src/Forum/Controller/ClientController.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * 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\Api\Client; -use Flarum\Formatter\Formatter; -use Flarum\Foundation\Application; -use Flarum\Http\Controller\AbstractClientController; -use Flarum\Locale\LocaleManager; -use Flarum\Settings\SettingsRepositoryInterface; -use Illuminate\Contracts\Cache\Repository; -use Illuminate\Contracts\Events\Dispatcher; - -class ClientController extends AbstractClientController -{ - /** - * {@inheritdoc} - */ - protected $clientName = 'forum'; - - /** - * {@inheritdoc} - */ - protected $translations = '/^[^\.]+\.(?:forum|lib)\./'; - - /** - * @var Formatter - */ - protected $formatter; - - /** - * {@inheritdoc} - */ - public function __construct( - Application $app, - Client $api, - LocaleManager $locales, - SettingsRepositoryInterface $settings, - Dispatcher $events, - Repository $cache, - Formatter $formatter - ) { - parent::__construct($app, $api, $locales, $settings, $events, $cache); - - $this->layout = __DIR__.'/../../../views/forum.blade.php'; - $this->formatter = $formatter; - } - - /** - * {@inheritdoc} - */ - protected function getAssets() - { - $assets = parent::getAssets(); - - $assets->addJs(function () { - return $this->formatter->getJs(); - }); - - return $assets; - } -} diff --git a/framework/core/src/Forum/Controller/DiscussionController.php b/framework/core/src/Forum/Controller/DiscussionController.php index 0454dc751..1db44accc 100644 --- a/framework/core/src/Forum/Controller/DiscussionController.php +++ b/framework/core/src/Forum/Controller/DiscussionController.php @@ -10,18 +10,43 @@ namespace Flarum\Forum\Controller; +use Flarum\Api\Client as ApiClient; use Flarum\Core\User; +use Flarum\Forum\WebApp; +use Flarum\Forum\UrlGenerator; use Flarum\Http\Exception\RouteNotFoundException; +use Illuminate\Contracts\Events\Dispatcher; use Psr\Http\Message\ServerRequestInterface as Request; -class DiscussionController extends ClientController +class DiscussionController extends WebAppController { + /** + * @var ApiClient + */ + protected $api; + + /** + * @var UrlGenerator + */ + protected $url; + /** * {@inheritdoc} */ - public function render(Request $request) + public function __construct(WebApp $webApp, Dispatcher $events, ApiClient $api, UrlGenerator $url) { - $view = parent::render($request); + parent::__construct($webApp, $events); + + $this->api = $api; + $this->url = $url; + } + + /** + * {@inheritdoc} + */ + protected function getView(Request $request) + { + $view = parent::getView($request); $queryParams = $request->getQueryParams(); $page = max(1, array_get($queryParams, 'page')); @@ -35,7 +60,7 @@ class DiscussionController extends ClientController ] ]; - $document = $this->preload($request->getAttribute('actor'), $params); + $document = $this->getDocument($request->getAttribute('actor'), $params); $getResource = function ($link) use ($document) { return array_first($document->included, function ($key, $value) use ($link) { @@ -47,9 +72,8 @@ class DiscussionController extends ClientController $newQueryParams = array_merge($queryParams, $newQueryParams); $queryString = http_build_query($newQueryParams); - return app('Flarum\Forum\UrlGenerator') - ->toRoute('discussion', ['id' => $document->data->id]). - ($queryString ? '?'.$queryString : ''); + return $this->url->toRoute('discussion', ['id' => $document->data->id]). + ($queryString ? '?'.$queryString : ''); }; $posts = []; @@ -75,7 +99,7 @@ class DiscussionController extends ClientController * @return object * @throws RouteNotFoundException */ - protected function preload(User $actor, array $params) + protected function getDocument(User $actor, array $params) { $response = $this->api->send('Flarum\Api\Controller\ShowDiscussionController', $actor, $params); $statusCode = $response->getStatusCode(); diff --git a/framework/core/src/Forum/Controller/IndexController.php b/framework/core/src/Forum/Controller/IndexController.php index 28f445e5e..a88de30aa 100644 --- a/framework/core/src/Forum/Controller/IndexController.php +++ b/framework/core/src/Forum/Controller/IndexController.php @@ -10,11 +10,19 @@ namespace Flarum\Forum\Controller; +use Flarum\Api\Client as ApiClient; use Flarum\Core\User; +use Flarum\Forum\WebApp; +use Illuminate\Contracts\Events\Dispatcher; use Psr\Http\Message\ServerRequestInterface as Request; -class IndexController extends ClientController +class IndexController extends WebAppController { + /** + * @var ApiClient + */ + protected $api; + /** * A map of sort query param values to their API sort param. * @@ -30,9 +38,19 @@ class IndexController extends ClientController /** * {@inheritdoc} */ - public function render(Request $request) + public function __construct(WebApp $webApp, Dispatcher $events, ApiClient $api) { - $view = parent::render($request); + parent::__construct($webApp, $events); + + $this->api = $api; + } + + /** + * {@inheritdoc} + */ + protected function getView(Request $request) + { + $view = parent::getView($request); $queryParams = $request->getQueryParams(); @@ -46,7 +64,7 @@ class IndexController extends ClientController 'page' => ['offset' => ($page - 1) * 20, 'limit' => 20] ]; - $document = $this->preload($request->getAttribute('actor'), $params); + $document = $this->getDocument($request->getAttribute('actor'), $params); $view->setDocument($document); $view->setContent(app('view')->make('flarum.forum::index', compact('document', 'page', 'forum'))); @@ -61,7 +79,7 @@ class IndexController extends ClientController * @param array $params * @return object */ - protected function preload(User $actor, array $params) + private function getDocument(User $actor, array $params) { return json_decode($this->api->send('Flarum\Api\Controller\ListDiscussionsController', $actor, $params)->getBody()); } diff --git a/framework/core/src/Forum/Controller/WebAppController.php b/framework/core/src/Forum/Controller/WebAppController.php new file mode 100644 index 000000000..441200e41 --- /dev/null +++ b/framework/core/src/Forum/Controller/WebAppController.php @@ -0,0 +1,27 @@ + + * + * 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\WebApp; +use Flarum\Http\Controller\AbstractWebAppController; +use Illuminate\Contracts\Events\Dispatcher; + +class WebAppController extends AbstractWebAppController +{ + /** + * {@inheritdoc} + */ + public function __construct(WebApp $webApp, Dispatcher $events) + { + $this->webApp = $webApp; + $this->events = $events; + } +} diff --git a/framework/core/src/Forum/ForumServiceProvider.php b/framework/core/src/Forum/ForumServiceProvider.php index 75dafe658..d044b6de9 100644 --- a/framework/core/src/Forum/ForumServiceProvider.php +++ b/framework/core/src/Forum/ForumServiceProvider.php @@ -15,13 +15,11 @@ use Flarum\Event\ExtensionWasDisabled; use Flarum\Event\ExtensionWasEnabled; use Flarum\Event\SettingWasSet; use Flarum\Foundation\AbstractServiceProvider; -use Flarum\Http\GenerateRouteHandlerTrait; +use Flarum\Http\Handler\RouteHandlerFactory; use Flarum\Http\RouteCollection; class ForumServiceProvider extends AbstractServiceProvider { - use GenerateRouteHandlerTrait; - /** * {@inheritdoc} */ @@ -45,9 +43,9 @@ class ForumServiceProvider extends AbstractServiceProvider $this->loadViewsFrom(__DIR__.'/../../views', 'flarum.forum'); - $this->flushAssetsWhenThemeChanged(); + $this->flushWebAppAssetsWhenThemeChanged(); - $this->flushAssetsWhenExtensionsChanged(); + $this->flushWebAppAssetsWhenExtensionsChanged(); } /** @@ -57,76 +55,76 @@ class ForumServiceProvider extends AbstractServiceProvider */ protected function populateRoutes(RouteCollection $routes) { - $toController = $this->getHandlerGenerator($this->app); + $route = $this->app->make(RouteHandlerFactory::class); $routes->get( '/all', 'index', - $toDefaultController = $toController('Flarum\Forum\Controller\IndexController') + $toDefaultController = $route->toController(Controller\IndexController::class) ); $routes->get( '/d/{id:\d+(?:-[^/]*)?}[/{near:[^/]*}]', 'discussion', - $toController('Flarum\Forum\Controller\DiscussionController') + $route->toController(Controller\DiscussionController::class) ); $routes->get( '/u/{username}[/{filter:[^/]*}]', 'user', - $toController('Flarum\Forum\Controller\ClientController') + $route->toController(Controller\WebAppController::class) ); $routes->get( '/settings', 'settings', - $toController('Flarum\Forum\Controller\AuthorizedClientController') + $route->toController(Controller\AuthorizedWebAppController::class) ); $routes->get( '/notifications', 'notifications', - $toController('Flarum\Forum\Controller\AuthorizedClientController') + $route->toController(Controller\AuthorizedWebAppController::class) ); $routes->get( '/logout', 'logout', - $toController('Flarum\Forum\Controller\LogOutController') + $route->toController(Controller\LogOutController::class) ); $routes->post( '/login', 'login', - $toController('Flarum\Forum\Controller\LogInController') + $route->toController(Controller\LogInController::class) ); $routes->post( '/register', 'register', - $toController('Flarum\Forum\Controller\RegisterController') + $route->toController(Controller\RegisterController::class) ); $routes->get( '/confirm/{token}', 'confirmEmail', - $toController('Flarum\Forum\Controller\ConfirmEmailController') + $route->toController(Controller\ConfirmEmailController::class) ); $routes->get( '/reset/{token}', 'resetPassword', - $toController('Flarum\Forum\Controller\ResetPasswordController') + $route->toController(Controller\ResetPasswordController::class) ); $routes->post( '/reset', 'savePassword', - $toController('Flarum\Forum\Controller\SavePasswordController') + $route->toController(Controller\SavePasswordController::class) ); $this->app->make('events')->fire( - new ConfigureForumRoutes($routes, $toController) + new ConfigureForumRoutes($routes, $route) ); $defaultRoute = $this->app->make('flarum.settings')->get('default_route'); @@ -142,33 +140,33 @@ class ForumServiceProvider extends AbstractServiceProvider ); } - protected function flushAssetsWhenThemeChanged() + protected function flushWebAppAssetsWhenThemeChanged() { $this->app->make('events')->listen(SettingWasSet::class, function (SettingWasSet $event) { if (preg_match('/^theme_|^custom_less$/i', $event->key)) { - $this->getClientController()->flushCss(); + $this->getWebAppAssets()->flushCss(); } }); } - protected function flushAssetsWhenExtensionsChanged() + protected function flushWebAppAssetsWhenExtensionsChanged() { $events = $this->app->make('events'); - $events->listen(ExtensionWasEnabled::class, [$this, 'flushAssets']); - $events->listen(ExtensionWasDisabled::class, [$this, 'flushAssets']); + $events->listen(ExtensionWasEnabled::class, [$this, 'flushWebAppAssets']); + $events->listen(ExtensionWasDisabled::class, [$this, 'flushWebAppAssets']); } - public function flushAssets() + public function flushWebAppAssets() { - $this->getClientController()->flushAssets(); + $this->getWebAppAssets()->flush(); } /** - * @return \Flarum\Forum\Controller\ClientController + * @return \Flarum\Http\WebApp\WebAppAssets */ - protected function getClientController() + protected function getWebAppAssets() { - return $this->app->make('Flarum\Forum\Controller\ClientController'); + return $this->app->make(WebApp::class)->getAssets(); } } diff --git a/framework/core/src/Forum/WebApp.php b/framework/core/src/Forum/WebApp.php new file mode 100644 index 000000000..4f8667090 --- /dev/null +++ b/framework/core/src/Forum/WebApp.php @@ -0,0 +1,63 @@ + + * + * 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\Http\WebApp\AbstractWebApp; +use Flarum\Http\WebApp\WebAppAssetsFactory; +use Flarum\Http\WebApp\WebAppViewFactory; +use Flarum\Locale\LocaleManager; +use Flarum\Settings\SettingsRepositoryInterface; + +class WebApp extends AbstractWebApp +{ + /** + * @var Formatter + */ + protected $formatter; + + /** + * {@inheritdoc} + */ + public function __construct( + WebAppAssetsFactory $assets, + WebAppViewFactory $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(); + }); + + return $view; + } + + /** + * {@inheritdoc} + */ + protected function getName() + { + return 'forum'; + } +} diff --git a/framework/core/src/Http/Controller/AbstractClientController.php b/framework/core/src/Http/Controller/AbstractClientController.php deleted file mode 100644 index a11442793..000000000 --- a/framework/core/src/Http/Controller/AbstractClientController.php +++ /dev/null @@ -1,316 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Http\Controller; - -use Flarum\Api\Client; -use Flarum\Asset\AssetManager; -use Flarum\Asset\JsCompiler; -use Flarum\Asset\LessCompiler; -use Flarum\Event\ConfigureClientView; -use Flarum\Foundation\Application; -use Flarum\Locale\JsCompiler as LocaleJsCompiler; -use Flarum\Locale\LocaleManager; -use Flarum\Settings\SettingsRepositoryInterface; -use Illuminate\Contracts\Cache\Repository; -use Illuminate\Contracts\Events\Dispatcher; -use Psr\Http\Message\ServerRequestInterface as Request; - -/** - * This action sets up a ClientView, and preloads it with the assets necessary - * to boot a Flarum client. - * - * Subclasses should set a $clientName, $layout, and $translationKeys. The - * client name will be used to locate the client assets (or alternatively, - * subclasses can overwrite the addAssets method), and set up asset compilers - * which write to the assets directory. Configured LESS customizations will be - * appended. - * - * A locale compiler is set up for the actor's locale, including the - * translations specified in $translationKeys. Additionally, an event is fired - * before the ClientView is returned, giving extensions an opportunity to add - * assets, translations, or alter the view. - */ -abstract class AbstractClientController extends AbstractHtmlController -{ - /** - * The name of the client. This is used to locate assets within the js/ - * and less/ directories. It is also used as the filename of the compiled - * asset files. - * - * @var string - */ - protected $clientName; - - /** - * The name of the view to include as the page layout. - * - * @var string - */ - protected $layout; - - /** - * A regex matching the keys of the translations that should be included in - * the compiled locale file. - * - * @var string - */ - protected $translations; - - /** - * @var \Flarum\Foundation\Application - */ - protected $app; - - /** - * @var Client - */ - protected $api; - - /** - * @var LocaleManager - */ - protected $locales; - - /** - * @var \Flarum\Settings\SettingsRepositoryInterface - */ - protected $settings; - - /** - * @var Dispatcher - */ - protected $events; - - /** - * @var Repository - */ - protected $cache; - - /** - * @param \Flarum\Foundation\Application $app - * @param Client $api - * @param LocaleManager $locales - * @param \Flarum\Settings\SettingsRepositoryInterface $settings - * @param Dispatcher $events - * @param Repository $cache - */ - public function __construct( - Application $app, - Client $api, - LocaleManager $locales, - SettingsRepositoryInterface $settings, - Dispatcher $events, - Repository $cache - ) { - $this->app = $app; - $this->api = $api; - $this->locales = $locales; - $this->settings = $settings; - $this->events = $events; - $this->cache = $cache; - } - - /** - * {@inheritdoc} - * - * @return ClientView - */ - public function render(Request $request) - { - $actor = $request->getAttribute('actor'); - $assets = $this->getAssets(); - $locale = $this->locales->getLocale(); - $localeCompiler = $locale ? $this->getLocaleCompiler($locale) : null; - - $view = new ClientView( - $this->api, - $request, - $actor, - $assets, - $this->layout, - $localeCompiler - ); - - $view->setVariable('locales', $this->locales->getLocales()); - $view->setVariable('locale', $locale); - - $this->events->fire( - new ConfigureClientView($this, $view) - ); - - if ($localeCompiler) { - $translations = array_get($this->locales->getTranslator()->getMessages(), 'messages', []); - - $translations = $this->filterTranslations($translations); - - $localeCompiler->setTranslations($translations); - } - - app('view')->share('translator', $this->locales->getTranslator()); - - return $view; - } - - /** - * Flush the client's assets so that they will be regenerated from scratch - * on the next render. - */ - public function flushAssets() - { - $this->flushCss(); - $this->flushJs(); - } - - public function flushCss() - { - $this->getAssets()->flushCss(); - } - - public function flushJs() - { - $this->getAssets()->flushJs(); - - $this->flushLocales(); - } - - public function flushLocales() - { - $locales = array_keys($this->locales->getLocales()); - - foreach ($locales as $locale) { - $this->getLocaleCompiler($locale)->flush(); - } - } - - /** - * Set up the asset manager, preloaded with a JavaScript compiler and a LESS - * compiler. Automatically add the files necessary to boot a Flarum client, - * as well as any configured LESS customizations. - * - * @return AssetManager - */ - protected function getAssets() - { - $public = $this->getAssetDirectory(); - $watch = $this->app->config('debug'); - - $assets = new AssetManager( - new JsCompiler($public, "$this->clientName.js", $watch, $this->cache), - new LessCompiler($public, "$this->clientName.css", $watch, $this->app->storagePath().'/less') - ); - - $this->addAssets($assets); - $this->addCustomizations($assets); - - return $assets; - } - - /** - * Add the assets necessary to boot a Flarum client, found within the - * directory specified by the $clientName property. - * - * @param AssetManager $assets - */ - protected function addAssets(AssetManager $assets) - { - $root = __DIR__.'/../../..'; - - $assets->addFile("$root/js/$this->clientName/dist/app.js"); - $assets->addFile("$root/less/$this->clientName/app.less"); - } - - /** - * Add any configured JS/LESS customizations to the asset manager. - * - * @param AssetManager $assets - */ - protected function addCustomizations(AssetManager $assets) - { - $assets->addLess(function () { - $less = ''; - - foreach ($this->getLessVariables() as $name => $value) { - $less .= "@$name: $value;"; - } - - $less .= $this->settings->get('custom_less'); - - return $less; - }); - } - - /** - * Get the values of any LESS variables to compile into the CSS, based on - * the forum's configuration. - * - * @return array - */ - protected 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' - ]; - } - - /** - * Set up the locale compiler for the given locale. - * - * @param string $locale - * @return LocaleJsCompiler - */ - protected function getLocaleCompiler($locale) - { - $compiler = new LocaleJsCompiler( - $this->getAssetDirectory(), - "$this->clientName-$locale.js", - $this->app->config('debug'), - $this->cache - ); - - foreach ($this->locales->getJsFiles($locale) as $file) { - $compiler->addFile($file); - } - - return $compiler; - } - - /** - * Get the path to the directory where assets should be written. - * - * @return string - */ - protected function getAssetDirectory() - { - return public_path().'/assets'; - } - - /** - * Take a selection of keys from a collection of translations. - * - * @param array $translations - * @return array - */ - protected function filterTranslations(array $translations) - { - if (! $this->translations) { - return []; - } - - $filtered = array_filter(array_keys($translations), function ($id) { - return preg_match($this->translations, $id); - }); - - return array_only($translations, $filtered); - } -} diff --git a/framework/core/src/Http/Controller/AbstractHtmlController.php b/framework/core/src/Http/Controller/AbstractHtmlController.php index c5c722d73..49e548d0b 100644 --- a/framework/core/src/Http/Controller/AbstractHtmlController.php +++ b/framework/core/src/Http/Controller/AbstractHtmlController.php @@ -24,7 +24,7 @@ abstract class AbstractHtmlController implements ControllerInterface $view = $this->render($request); $response = new Response; - $response->getBody()->write($view->render()); + $response->getBody()->write($view); return $response; } diff --git a/framework/core/src/Http/Controller/AbstractWebAppController.php b/framework/core/src/Http/Controller/AbstractWebAppController.php new file mode 100644 index 000000000..0038f8087 --- /dev/null +++ b/framework/core/src/Http/Controller/AbstractWebAppController.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Flarum\Http\Controller; + +use Flarum\Event\ConfigureWebApp; +use Flarum\Http\WebApp\AbstractWebApp; +use Illuminate\Contracts\Events\Dispatcher; +use Psr\Http\Message\ServerRequestInterface as Request; + +abstract class AbstractWebAppController extends AbstractHtmlController +{ + /** + * @var AbstractWebApp + */ + protected $webApp; + + /** + * @var Dispatcher + */ + protected $events; + + /** + * {@inheritdoc} + */ + public function render(Request $request) + { + $view = $this->getView($request); + + $this->events->fire( + new ConfigureWebApp($this, $view, $request) + ); + + return $view->render($request); + } + + /** + * @param Request $request + * @return \Flarum\Http\WebApp\WebAppView + */ + protected function getView(Request $request) + { + return $this->webApp->getView(); + } +} diff --git a/framework/core/src/Http/Controller/ClientView.php b/framework/core/src/Http/Controller/ClientView.php deleted file mode 100644 index 7b9875af5..000000000 --- a/framework/core/src/Http/Controller/ClientView.php +++ /dev/null @@ -1,371 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Flarum\Http\Controller; - -use Flarum\Api\Client; -use Flarum\Asset\AssetManager; -use Flarum\Core\User; -use Flarum\Locale\JsCompiler; -use Illuminate\Contracts\Support\Renderable; -use Psr\Http\Message\ServerRequestInterface as Request; - -/** - * This class represents a view which boots up Flarum's client. - */ -class ClientView implements Renderable -{ - /** - * The user who is using the client. - * - * @var User - */ - protected $actor; - - /** - * The title of the document, displayed in the tag. - * - * @var null|string - */ - protected $title; - - /** - * The SEO content of the page, displayed in <noscript> tags. - * - * @var string - */ - protected $content; - - /** - * The path to the client layout view to display. - * - * @var string - */ - protected $layout; - - /** - * An API response that should be preloaded into the page. - * - * @var null|array|object - */ - protected $document; - - /** - * Other variables to preload into the page. - * - * @var array - */ - protected $variables = []; - - /** - * An array of JS modules to import before booting the app. - * - * @var array - */ - protected $bootstrappers = ['locale']; - - /** - * An array of strings to append to the page's <head>. - * - * @var array - */ - protected $headStrings = []; - - /** - * An array of strings to prepend before the page's </body>. - * - * @var array - */ - protected $footStrings = []; - - /** - * @var Client - */ - protected $api; - - /** - * @var Request - */ - protected $request; - - /** - * @var AssetManager - */ - protected $assets; - - /** - * @var JsCompiler - */ - protected $localeJs; - - /** - * @param Client $api - * @param Request $request - * @param User $actor - * @param AssetManager $assets - * @param string $layout - * @param JsCompiler $localeJs - */ - public function __construct( - Client $api, - Request $request, - User $actor, - AssetManager $assets, - $layout, - JsCompiler $localeJs = null - ) { - $this->api = $api; - $this->request = $request; - $this->actor = $actor; - $this->assets = $assets; - $this->layout = $layout; - $this->localeJs = $localeJs; - - $this->addHeadString('<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,400,700,600">', 'font'); - } - - /** - * The title of the document, to be displayed in the <title> tag. - * - * @param null|string $title - */ - public function setTitle($title) - { - $this->title = $title; - } - - /** - * Set the SEO content of the page, to be displayed in <noscript> tags. - * - * @param null|string $content - */ - public function setContent($content) - { - $this->content = $content; - } - - /** - * Set the name of the client layout view to display. - * - * @param string $layout - */ - public function setLayout($layout) - { - $this->layout = $layout; - } - - /** - * Add a string to be appended to the page's <head>. - * - * @param string $string - */ - public function addHeadString($string, $name = null) - { - if ($name) { - $this->headStrings[$name] = $string; - } else { - $this->headStrings[] = $string; - } - } - - /** - * Add a string to be prepended before the page's </body>. - * - * @param string $string - */ - public function addFootString($string) - { - $this->footStrings[] = $string; - } - - /** - * Set an API response to be preloaded into the page. This should be a - * JSON-API document. - * - * @param null|array|object $document - */ - public function setDocument($document) - { - $this->document = $document; - } - - /** - * 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 $string - */ - public function addBootstrapper($string) - { - $this->bootstrappers[] = $string; - } - - /** - * Get the view's asset manager. - * - * @return AssetManager - */ - public function getAssets() - { - return $this->assets; - } - - /** - * Get the string contents of the view. - * - * @return string - */ - public function render() - { - $view = app('view')->file(__DIR__.'/../../../views/app.blade.php'); - - $forum = $this->getForumDocument(); - $data = $this->getDataFromDocument($forum); - - if ($this->actor->exists) { - $user = $this->getUserDocument(); - $data = array_merge($data, $this->getDataFromDocument($user)); - } - - $view->app = [ - 'preload' => [ - 'data' => $data, - 'session' => $this->getSession(), - 'document' => $this->document - ] - ] + $this->variables; - $view->bootstrappers = $this->bootstrappers; - - $noJs = array_get($this->request->getQueryParams(), 'nojs'); - - $view->title = ($this->title ? $this->title.' - ' : '').$forum->data->attributes->title; - $view->forum = $forum->data; - $view->layout = app('view')->file($this->layout, [ - 'forum' => $forum->data, - 'content' => app('view')->file(__DIR__.'/../../../views/content.blade.php', [ - 'content' => $this->content, - 'noJs' => $noJs, - 'forum' => $forum->data - ]) - ]); - $view->noJs = $noJs; - - $view->styles = [$this->assets->getCssFile()]; - $view->scripts = [$this->assets->getJsFile()]; - - if ($this->localeJs) { - $view->scripts[] = $this->localeJs->getFile(); - } - - $view->head = implode("\n", $this->headStrings); - $view->foot = implode("\n", $this->footStrings); - - return $view->render(); - } - - /** - * Get the string contents of the view. - * - * @return string - */ - public function __toString() - { - return $this->render(); - } - - /** - * Get the result of an API request to show the forum. - * - * @return object - */ - protected function getForumDocument() - { - return json_decode($this->api->send('Flarum\Api\Controller\ShowForumController', $this->actor)->getBody()); - } - - /** - * Get the result of an API request to show the current user. - * - * @return object - */ - protected function getUserDocument() - { - // TODO: calling on the API here results in an extra query to get - // the user + their groups, when we already have this information on - // $this->actor. Can we simply run the CurrentUserSerializer - // manually? Or can we somehow inject this data into the ShowDiscussionController? - $document = json_decode($this->api->send( - 'Flarum\Api\Controller\ShowUserController', - $this->actor, - ['id' => $this->actor->id] - )->getBody()); - - return $document; - } - - /** - * Get an array of data by merging the 'data' and 'included' keys of a - * JSON-API document. - * - * @param object $document - * @return array - */ - protected function getDataFromDocument($document) - { - $data[] = $document->data; - - if (isset($document->included)) { - $data = array_merge($data, $document->included); - } - - return $data; - } - - /** - * Get information about the current session. - * - * @return array - */ - protected function getSession() - { - $session = $this->request->getAttribute('session'); - - return [ - 'userId' => $this->actor->id, - 'csrfToken' => $session->get('csrf_token') - ]; - } - - /** - * @return User - */ - public function getActor() - { - return $this->actor; - } - - /** - * @return Request - */ - public function getRequest() - { - return $this->request; - } -} diff --git a/framework/core/src/Http/GenerateRouteHandlerTrait.php b/framework/core/src/Http/GenerateRouteHandlerTrait.php deleted file mode 100644 index 184805738..000000000 --- a/framework/core/src/Http/GenerateRouteHandlerTrait.php +++ /dev/null @@ -1,41 +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\Http; - -use Flarum\Http\Controller\ControllerInterface; -use Illuminate\Contracts\Container\Container; -use InvalidArgumentException; -use Psr\Http\Message\ServerRequestInterface; - -trait GenerateRouteHandlerTrait -{ - /** - * @return \Closure - */ - protected function getHandlerGenerator(Container $container) - { - return function ($class) use ($container) { - return function (ServerRequestInterface $request, $routeParams) use ($class, $container) { - $controller = $container->make($class); - - if (! ($controller instanceof ControllerInterface)) { - throw new InvalidArgumentException( - 'Route handler must be an instance of '.ControllerInterface::class - ); - } - - $request = $request->withQueryParams(array_merge($request->getQueryParams(), $routeParams)); - - return $controller->handle($request); - }; - }; - } -} diff --git a/framework/core/src/Http/Handler/ControllerRouteHandler.php b/framework/core/src/Http/Handler/ControllerRouteHandler.php new file mode 100644 index 000000000..79e1b1e30 --- /dev/null +++ b/framework/core/src/Http/Handler/ControllerRouteHandler.php @@ -0,0 +1,71 @@ +<?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\Http\Handler; + +use Flarum\Http\Controller\ControllerInterface; +use Illuminate\Contracts\Container\Container; +use InvalidArgumentException; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +class ControllerRouteHandler +{ + /** + * @var Container + */ + protected $container; + + /** + * @var string + */ + protected $controller; + + /** + * @param Container $container + * @param string $controller + */ + public function __construct(Container $container, $controller) + { + $this->container = $container; + $this->controller = $controller; + } + + /** + * @param ServerRequestInterface $request + * @param array $routeParams + * @return ResponseInterface + */ + public function __invoke(ServerRequestInterface $request, array $routeParams) + { + $controller = $this->resolveController($this->controller); + + $request = $request->withQueryParams(array_merge($request->getQueryParams(), $routeParams)); + + return $controller->handle($request); + } + + /** + * @param string $class + * @return ControllerInterface + */ + protected function resolveController($class) + { + $controller = $this->container->make($class); + + if (! ($controller instanceof ControllerInterface)) { + throw new InvalidArgumentException( + 'Controller must be an instance of '.ControllerInterface::class + ); + } + + return $controller; + } +} diff --git a/framework/core/src/Http/Handler/RouteHandlerFactory.php b/framework/core/src/Http/Handler/RouteHandlerFactory.php new file mode 100644 index 000000000..bcd3f58df --- /dev/null +++ b/framework/core/src/Http/Handler/RouteHandlerFactory.php @@ -0,0 +1,38 @@ +<?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\Http\Handler; + +use Illuminate\Contracts\Container\Container; + +class RouteHandlerFactory +{ + /** + * @var Container + */ + protected $container; + + /** + * @param Container $container + */ + public function __construct(Container $container) + { + $this->container = $container; + } + + /** + * @param string $controller + * @return ControllerRouteHandler + */ + public function toController($controller) + { + return new ControllerRouteHandler($this->container, $controller); + } +} diff --git a/framework/core/src/Http/WebApp/AbstractWebApp.php b/framework/core/src/Http/WebApp/AbstractWebApp.php new file mode 100644 index 000000000..03f3fbfea --- /dev/null +++ b/framework/core/src/Http/WebApp/AbstractWebApp.php @@ -0,0 +1,188 @@ +<?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\Http\WebApp; + +use Flarum\Http\Controller; +use Flarum\Locale\LocaleManager; +use Flarum\Settings\SettingsRepositoryInterface; + +abstract class AbstractWebApp +{ + /** + * @var WebAppAssetsFactory + */ + protected $assets; + + /** + * @var WebAppViewFactory + */ + protected $view; + + /** + * @var SettingsRepositoryInterface + */ + protected $settings; + + /** + * @var LocaleManager + */ + protected $locales; + + /** + * @param WebAppAssetsFactory $assets + * @param WebAppViewFactory $view + * @param SettingsRepositoryInterface $settings + * @param LocaleManager $locales + */ + public function __construct(WebAppAssetsFactory $assets, WebAppViewFactory $view, SettingsRepositoryInterface $settings, LocaleManager $locales) + { + $this->assets = $assets; + $this->view = $view; + $this->settings = $settings; + $this->locales = $locales; + } + + /** + * @return WebAppView + */ + public function getView() + { + $view = $this->view->make($this->getLayout(), $this->getAssets()); + + $this->addDefaultAssets($view); + $this->addCustomLess($view); + $this->addTranslations($view); + + return $view; + } + + /** + * @return WebAppAssets + */ + 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 __DIR__.'/../../../views/'.$this->getName().'.blade.php'; + } + + /** + * Get a regular expression to match against translation keys. + * + * @return string + */ + protected function getTranslationFilter() + { + return '/^[^\.]+\.(?:'.$this->getName().'|lib)\./'; + } + + /** + * @param WebAppView $view + */ + private function addDefaultAssets(WebAppView $view) + { + $root = __DIR__.'/../../..'; + $name = $this->getName(); + + $view->getJs()->addFile("$root/js/$name/dist/app.js"); + $view->getCss()->addFile("$root/less/$name/app.less"); + } + + /** + * @param WebAppView $view + */ + private function addCustomLess(WebAppView $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); + + $css->addString(function () { + return $this->settings->get('custom_less'); + }); + } + + /** + * 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 WebAppView $view + */ + private function addTranslations(WebAppView $view) + { + $translations = array_get($this->locales->getTranslator()->getMessages(), '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); + } +} diff --git a/framework/core/src/Http/WebApp/WebAppAssets.php b/framework/core/src/Http/WebApp/WebAppAssets.php new file mode 100644 index 000000000..dc0bfb4d7 --- /dev/null +++ b/framework/core/src/Http/WebApp/WebAppAssets.php @@ -0,0 +1,164 @@ +<?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\Http\WebApp; + +use Flarum\Asset\JsCompiler; +use Flarum\Asset\LessCompiler; +use Flarum\Foundation\Application; +use Flarum\Locale\JsCompiler as LocaleJsCompiler; +use Flarum\Locale\LocaleManager; +use Illuminate\Contracts\Cache\Repository; + +class WebAppAssets +{ + /** + * @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; + } +} diff --git a/framework/core/src/Http/WebApp/WebAppAssetsFactory.php b/framework/core/src/Http/WebApp/WebAppAssetsFactory.php new file mode 100644 index 000000000..3f3627fe2 --- /dev/null +++ b/framework/core/src/Http/WebApp/WebAppAssetsFactory.php @@ -0,0 +1,54 @@ +<?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\Http\WebApp; + +use Flarum\Foundation\Application; +use Flarum\Locale\LocaleManager; +use Illuminate\Contracts\Cache\Repository; + +class WebAppAssetsFactory +{ + /** + * @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 WebAppAssets + */ + public function make($name) + { + return new WebAppAssets($name, $this->app, $this->cache, $this->locales); + } +} diff --git a/framework/core/src/Http/WebApp/WebAppView.php b/framework/core/src/Http/WebApp/WebAppView.php new file mode 100644 index 000000000..5020789d9 --- /dev/null +++ b/framework/core/src/Http/WebApp/WebAppView.php @@ -0,0 +1,446 @@ +<?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\Http\WebApp; + +use Flarum\Api\Client; +use Flarum\Api\Serializer\AbstractSerializer; +use Flarum\Api\Serializer\CurrentUserSerializer; +use Flarum\Asset\CompilerInterface; +use Flarum\Core\Exception\ValidationException; +use Flarum\Core\User; +use Flarum\Locale\JsCompiler; +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 WebAppView +{ + /** + * The title of the document, displayed in the <title> tag. + * + * @var null|string + */ + protected $title; + + /** + * The description of the document, displayed in a <meta> tag. + * + * @var null|string + */ + protected $description; + + /** + * The path to the client layout view to display. + * + * @var string + */ + protected $layout; + + /** + * The SEO content of the page, displayed within the layout in <noscript> + * tags. + * + * @var string + */ + protected $content; + + /** + * An API response to be preloaded into the page. + * + * @var null|array|object + */ + protected $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 = []; + + /** + * @var CompilerInterface + */ + protected $js; + + /** + * @var CompilerInterface + */ + protected $css; + + /** + * @var CompilerInterface + */ + protected $localeJs; + + /** + * @var CompilerInterface + */ + protected $localeCss; + + /** + * @var ClientAssets + */ + protected $assets; + + /** + * @var Client + */ + protected $api; + + /** + * @var Factory + */ + protected $view; + + /** + * @var LocaleManager + */ + protected $locales; + + /** + * @var AbstractSerializer + */ + protected $userSerializer; + + /** + * @param string $layout + * @param ClientAssets $assets + * @param Client $api + * @param Factory $view + * @param LocaleManager $locales + * @param AbstractSerializer $userSerializer + */ + public function __construct($layout, WebAppAssets $assets, Client $api, Factory $view, LocaleManager $locales, AbstractSerializer $userSerializer) + { + $this->layout = $layout; + $this->api = $api; + $this->assets = $assets; + $this->view = $view; + $this->locales = $locales; + $this->userSerializer = $userSerializer; + + $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); + } + + /** + * The title of the document, to be displayed in the <title> tag. + * + * @param null|string $title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * Set the SEO content of the page, to be displayed in <noscript> tags. + * + * @param null|string $content + */ + public function setContent($content) + { + $this->content = $content; + } + + /** + * Set the name of the client layout view to display. + * + * @param string $layout + */ + public function setLayout($layout) + { + $this->layout = $layout; + } + + /** + * 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; + } + + /** + * Set an API response to be preloaded into the page. This should be a + * JSON-API document. + * + * @param null|array|object $document + */ + public function setDocument($document) + { + $this->document = $document; + } + + /** + * 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', array_get($forum, 'data.attributes.debug')); + + $view = $this->view->file(__DIR__.'/../../../views/app.blade.php'); + + $view->title = $this->buildTitle(array_get($forum, 'data.attributes.title')); + $view->description = $this->description; + + $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 = implode("\n", $this->head); + $view->foot = implode("\n", $this->foot); + + 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->file($this->layout); + + $view->content = $this->buildContent(); + + return $view; + } + + protected function buildContent() + { + $view = $this->view->file(__DIR__.'/../../../views/content.blade.php'); + + $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); + }, $files); + } + + /** + * @return CompilerInterface + */ + public function getJs() + { + return $this->js; + } + + /** + * @return CompilerInterface + */ + public function getCss() + { + return $this->css; + } + + /** + * @return JsCompiler + */ + 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. + * + * @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. + * + * @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. + * + * @return array + */ + protected function buildSession(Request $request) + { + $actor = $request->getAttribute('actor'); + $session = $request->getAttribute('session'); + + return [ + 'userId' => $actor->id, + 'csrfToken' => $session->get('csrf_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; + } +} diff --git a/framework/core/src/Http/WebApp/WebAppViewFactory.php b/framework/core/src/Http/WebApp/WebAppViewFactory.php new file mode 100644 index 000000000..75def011d --- /dev/null +++ b/framework/core/src/Http/WebApp/WebAppViewFactory.php @@ -0,0 +1,63 @@ +<?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\Http\WebApp; + +use Flarum\Api\Client; +use Flarum\Api\Serializer\CurrentUserSerializer; +use Flarum\Locale\LocaleManager; +use Illuminate\Contracts\View\Factory; + +class WebAppViewFactory +{ + /** + * @var Client + */ + protected $api; + + /** + * @var Factory + */ + protected $view; + + /** + * @var LocaleManager + */ + protected $locales; + + /** + * @var CurrentUserSerializer + */ + protected $userSerializer; + + /** + * @param Client $api + * @param Factory $view + * @param LocaleManager $locales + * @param CurrentUserSerializer $userSerializer + */ + public function __construct(Client $api, Factory $view, LocaleManager $locales, CurrentUserSerializer $userSerializer) + { + $this->api = $api; + $this->view = $view; + $this->locales = $locales; + $this->userSerializer = $userSerializer; + } + + /** + * @param string $layout + * @param WebAppAssets $assets + * @return WebAppView + */ + public function make($layout, WebAppAssets $assets) + { + return new WebAppView($layout, $assets, $this->api, $this->view, $this->locales, $this->userSerializer); + } +} diff --git a/framework/core/src/Locale/JsCompiler.php b/framework/core/src/Locale/JsCompiler.php index 8800c0546..66c0392cd 100644 --- a/framework/core/src/Locale/JsCompiler.php +++ b/framework/core/src/Locale/JsCompiler.php @@ -23,16 +23,20 @@ class JsCompiler extends BaseJsCompiler public function compile() { - $output = "System.register('locale', [], function() { + $output = " + System.register('locale', [], function(_export) { return { execute: function() { - app.translator.translations = ".json_encode($this->translations).";\n"; + _export('default', function(app) { + app.translator.translations = ".json_encode($this->translations).";\n"; foreach ($this->files as $filename) { $output .= file_get_contents($filename); } - $output .= '} + $output .= ' + }); + } }; });'; diff --git a/framework/core/stubs/extension/src/Listener/AddClientAssets.php b/framework/core/stubs/extension/src/Listener/AddClientAssets.php index ef50f559f..babc8c68f 100755 --- a/framework/core/stubs/extension/src/Listener/AddClientAssets.php +++ b/framework/core/stubs/extension/src/Listener/AddClientAssets.php @@ -1,16 +1,16 @@ <?php namespace {{namespace}}\Listener; -use Flarum\Event\ConfigureClientView; +use Flarum\Event\ConfigureWebApp; use Illuminate\Contracts\Events\Dispatcher; class AddClientAssets { public function subscribe(Dispatcher $events) { - $events->listen(ConfigureClientView::class, [$this, 'addAssets']); + $events->listen(ConfigureWebApp::class, [$this, 'addAssets']); } - public function addAssets(ConfigureClientView $event) + public function addAssets(ConfigureWebApp $event) { if ($event->isForum()) { $event->addAssets([ diff --git a/framework/core/views/admin.blade.php b/framework/core/views/admin.blade.php index 2389cd206..35846d166 100644 --- a/framework/core/views/admin.blade.php +++ b/framework/core/views/admin.blade.php @@ -8,8 +8,8 @@ <div id="header-navigation" class="Header-navigation"></div> <div class="container"> <h1 class="Header-title"> - <a href="{{ $forum->attributes->baseUrl }}"> - {{ $forum->attributes->title }} + <a href="{{ array_get($forum, 'attributes.baseUrl') }}"> + {{ array_get($forum, 'attributes.title') }} </a> </h1> <div id="header-primary" class="Header-primary"></div> diff --git a/framework/core/views/app.blade.php b/framework/core/views/app.blade.php index 1a4ca8480..e8155c99a 100644 --- a/framework/core/views/app.blade.php +++ b/framework/core/views/app.blade.php @@ -2,14 +2,13 @@ <html> <head> <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>{{ $title }} - + - + - @foreach ($styles as $file) - + @foreach ($cssUrls as $url) + @endforeach {!! $head !!} @@ -21,36 +20,40 @@
    - @if (! $noJs) + @if ($allowJs) - @foreach ($scripts as $file) - + @foreach ($jsUrls as $url) + @endforeach + @else + @endif {!! $foot !!} diff --git a/framework/core/views/content.blade.php b/framework/core/views/content.blade.php index db90673f5..6dc2bca33 100644 --- a/framework/core/views/content.blade.php +++ b/framework/core/views/content.blade.php @@ -2,16 +2,22 @@ {{ $translator->trans('core.views.content.loading_text') }}
-@if (! $noJs) +@else +
+
+ {{ $translator->trans('core.views.content.load_error_message') }} +
+
+ + {!! $content !!} +@endif diff --git a/framework/core/views/forum.blade.php b/framework/core/views/forum.blade.php index 1c30c5bc9..fdd613e4c 100644 --- a/framework/core/views/forum.blade.php +++ b/framework/core/views/forum.blade.php @@ -1,6 +1,6 @@

- - {{ $forum->attributes->title }} + + {{ array_get($forum, 'attributes.title') }}