diff --git a/js/forum/Gulpfile.js b/js/forum/Gulpfile.js index 8a1ab4698..2eca8d9f6 100644 --- a/js/forum/Gulpfile.js +++ b/js/forum/Gulpfile.js @@ -30,7 +30,6 @@ gulp({ 'src/**/*.js', '../lib/**/*.js' ], - bootstrapFiles: [], modulePrefix: 'flarum', externalHelpers: true, outputFile: 'dist/app.js' diff --git a/js/forum/src/components/DiscussionHero.js b/js/forum/src/components/DiscussionHero.js index 98d52894a..633501ac0 100644 --- a/js/forum/src/components/DiscussionHero.js +++ b/js/forum/src/components/DiscussionHero.js @@ -31,7 +31,7 @@ export default class DiscussionHero extends Component { const badges = discussion.badges().toArray(); if (badges.length) { - items.add('badges', ); + items.add('badges', ); } items.add('title',

{discussion.title()}

); diff --git a/js/forum/src/components/DiscussionList.js b/js/forum/src/components/DiscussionList.js index faf94a923..b25e02c8e 100644 --- a/js/forum/src/components/DiscussionList.js +++ b/js/forum/src/components/DiscussionList.js @@ -82,8 +82,9 @@ export default class DiscussionList extends Component { * discussion results. * * @return {Object} + * @api */ - params() { + requestParams() { const params = Object.assign({include: ['startUser', 'lastUser']}, this.props.params); params.sort = this.sortMap()[params.sort]; @@ -111,8 +112,8 @@ export default class DiscussionList extends Component { if (this.props.params.q) { map.relevance = ''; } - map.recent = '-lastTime'; - map.replies = '-commentsCount'; + map.latest = '-lastTime'; + map.top = '-commentsCount'; map.newest = '-startTime'; map.oldest = '+startTime'; @@ -150,7 +151,7 @@ export default class DiscussionList extends Component { return m.deferred().resolve(preloadedDiscussions).promise; } - const params = this.params(); + const params = this.requestParams(); params.page = {offset}; params.include = params.include.join(','); diff --git a/js/forum/src/components/DiscussionRenamedNotification.js b/js/forum/src/components/DiscussionRenamedNotification.js index 69affe8ac..e29352f58 100644 --- a/js/forum/src/components/DiscussionRenamedNotification.js +++ b/js/forum/src/components/DiscussionRenamedNotification.js @@ -1,5 +1,4 @@ import Notification from 'flarum/components/Notification'; -import username from 'flarum/helpers/username'; /** * The `DiscussionRenamedNotification` component displays a notification which diff --git a/js/forum/src/components/DiscussionRenamedPost.js b/js/forum/src/components/DiscussionRenamedPost.js index 0827e43fe..5acc343a8 100644 --- a/js/forum/src/components/DiscussionRenamedPost.js +++ b/js/forum/src/components/DiscussionRenamedPost.js @@ -13,15 +13,18 @@ export default class DiscussionRenamedPost extends EventPost { return 'pencil'; } - description() { + descriptionKey() { + return 'core.discussion_renamed_post'; + } + + descriptionData() { const post = this.props.post; const oldTitle = post.content()[0]; const newTitle = post.content()[1]; - return app.trans('core.discussion_renamed', { - user: this.props.post.user(), + return { old: {oldTitle}, new: {newTitle} - }); + }; } } diff --git a/js/forum/src/components/EventPost.js b/js/forum/src/components/EventPost.js index 8aefd32ee..ca4023375 100644 --- a/js/forum/src/components/EventPost.js +++ b/js/forum/src/components/EventPost.js @@ -1,4 +1,5 @@ import Post from 'flarum/components/Post'; +import { ucfirst } from 'flarum/utils/string'; import usernameHelper from 'flarum/helpers/username'; import icon from 'flarum/helpers/icon'; @@ -16,19 +17,24 @@ import icon from 'flarum/helpers/icon'; export default class EventPost extends Post { attrs() { return { - className: 'EventPost EventPost--' + this.props.post.contentType() + className: 'EventPost ' + ucfirst(this.props.post.contentType()) + 'Post' }; } content() { const user = this.props.post.user(); const username = usernameHelper(user); + const data = Object.assign(this.descriptionData(), { + user, + username: user + ? {username} + : username + }); return [ icon(this.icon(), {className: 'EventPost-icon'}),
- {user ? {username} : username}{' '} - {this.description()} + {app.trans(this.descriptionKey(), data)}
]; } @@ -39,13 +45,24 @@ export default class EventPost extends Post { * @return {String} */ icon() { + return ''; } /** - * Get the description of the event. + * Get the translation key for the description of the event. * - * @return {VirtualElement} + * @return {String} */ - description() { + descriptionKey() { + return ''; + } + + /** + * Get the translation data for the description of the event. + * + * @return {Object} + */ + descriptionData() { + return {}; } } diff --git a/js/forum/src/components/NotificationGrid.js b/js/forum/src/components/NotificationGrid.js index d1492e921..754674d3a 100644 --- a/js/forum/src/components/NotificationGrid.js +++ b/js/forum/src/components/NotificationGrid.js @@ -74,7 +74,7 @@ export default class NotificationGrid extends Component { {this.types.map(type => ( - {type.label} + {icon(type.icon)} {type.label} {this.methods.map(method => ( @@ -181,7 +181,8 @@ export default class NotificationGrid extends Component { items.add('discussionRenamed', { name: 'discussionRenamed', - label: [icon('pencil'), ' ', app.trans('core.notify_discussion_renamed')] + icon: 'pencil', + label: app.trans('core.notify_discussion_renamed') }); return items; diff --git a/js/forum/src/components/PostStream.js b/js/forum/src/components/PostStream.js index aa81bbb79..26d05377f 100644 --- a/js/forum/src/components/PostStream.js +++ b/js/forum/src/components/PostStream.js @@ -167,7 +167,11 @@ class PostStream extends mixin(Component, evented) { posts() { return this.discussion.postIds() .slice(this.visibleStart, this.visibleEnd) - .map(id => app.store.getById('posts', id)); + .map(id => { + const post = app.store.getById('posts', id); + + return post && post.discussion() ? post : null; + }); } view() { @@ -365,10 +369,10 @@ class PostStream extends mixin(Component, evented) { this.discussion.postIds().slice(start, end).forEach(id => { const post = app.store.getById('posts', id); - if (!post) { - loadIds.push(id); - } else { + if (post && post.discussion()) { loaded.push(post); + } else { + loadIds.push(id); } }); diff --git a/js/lib/Translator.js b/js/lib/Translator.js index 404595819..777cc8a67 100644 --- a/js/lib/Translator.js +++ b/js/lib/Translator.js @@ -61,7 +61,9 @@ export default class Translator { // translation key. This will allow a gender property to determine which // translation key is used. if (input.user instanceof User) { - input.username = username(extract(input, 'user')); + const user = extract(input, 'user'); + + if (!input.username) input.username = username(user); } // If we've found the appropriate translation string, then we'll sub in the diff --git a/js/lib/extend.js b/js/lib/extend.js index 2aadcdea6..fe55c7012 100644 --- a/js/lib/extend.js +++ b/js/lib/extend.js @@ -59,17 +59,3 @@ export function override(object, method, newMethod) { return newMethod.apply(this, [original.bind(this)].concat(args)); }; } - -/** - * Register a notification type. - * - * @param {String} name The name of the notification type (equivalent to the - * serialized `contentType`) - * @param {Object} Component The constructor of the component that this - * notification type should be rendered with - * @param {String|Array} label vDOM to render a label in the notification - * preferences grid for this notification type - */ -export function notificationType(name, Component, label) { - // TODO -} diff --git a/js/lib/utils/string.js b/js/lib/utils/string.js index 69d89bd69..6d5a14aa8 100644 --- a/js/lib/utils/string.js +++ b/js/lib/utils/string.js @@ -36,3 +36,13 @@ export function slug(string) { export function getPlainContent(string) { return $('
').html(string.replace(/(<\/p>|
)/g, '$1 ')).text(); } + +/** + * Make a string's first character uppercase. + * + * @param {String} string + * @return {String} + */ +export function ucfirst(string) { + return string.substr(0, 1).toUpperCase() + string.substr(1); +} diff --git a/less/forum/DiscussionHero.less b/less/forum/DiscussionHero.less index 4eff60d1a..d8a60f918 100644 --- a/less/forum/DiscussionHero.less +++ b/less/forum/DiscussionHero.less @@ -1,6 +1,6 @@ .DiscussionHero { .badges { - margin-right: 5px; + margin-right: 10px; margin-bottom: -2px; } } diff --git a/less/forum/DiscussionListItem.less b/less/forum/DiscussionListItem.less index cb2306e9f..beb749e1e 100644 --- a/less/forum/DiscussionListItem.less +++ b/less/forum/DiscussionListItem.less @@ -25,7 +25,7 @@ white-space: nowrap; pointer-events: none; - .badge { + .Badge { margin-left: -15px; position: relative; pointer-events: auto; diff --git a/less/lib/App.less b/less/lib/App.less index 48bce816b..8b4ebe4b9 100755 --- a/less/lib/App.less +++ b/less/lib/App.less @@ -1,6 +1,8 @@ .App { position: relative !important; padding-top: @header-height; + overflow-x: hidden; + min-height: 100vh; @media @phone { padding-top: @header-height-phone; diff --git a/less/lib/Checkbox.less b/less/lib/Checkbox.less index 8cc9da46c..42b8576d7 100755 --- a/less/lib/Checkbox.less +++ b/less/lib/Checkbox.less @@ -32,7 +32,7 @@ .transition(background-color 0.2s); .on& { - background: @primary-color; + background: #58A400; } &:before { diff --git a/less/lib/scaffolding.less b/less/lib/scaffolding.less index c1a7dcbbf..a48c0db1b 100755 --- a/less/lib/scaffolding.less +++ b/less/lib/scaffolding.less @@ -72,11 +72,6 @@ p { margin: 0 auto; } -.global-page { - overflow-x: hidden; - min-height: 100vh; -} - mark { background: #FFE300; padding: 1px; diff --git a/locale/en/translations.yml b/locale/en/translations.yml index bfd409ba7..43939a938 100644 --- a/locale/en/translations.yml +++ b/locale/en/translations.yml @@ -25,7 +25,7 @@ core: delete_forever: Delete Forever deleted: "[deleted]" disclose_online: Allow others to see when I am online - discussion_renamed: "changed the title from {old} to {new}" + discussion_renamed_post: "{username} changed the title from {old} to {new}." discussion_renamed_notification: "{username} changed the title" discussion_replied: "{username} replied {ago}" discussion_started: "{username} started {ago}" @@ -93,10 +93,11 @@ core: send_password_reset_email: Send Password Reset Email settings: Settings sign_up: Sign Up + sort_latest: Latest sort_newest: Newest sort_oldest: Oldest - sort_recent: Recent - sort_replies: Replies + sort_relevance: Relevance + sort_top: Top start_a_discussion: Start a Discussion started_a_discussion: Started a discussion unread_posts: "{count} unread" diff --git a/src/Api/Actions/Activity/IndexAction.php b/src/Api/Actions/Activity/IndexAction.php index b78552da0..bae677d43 100644 --- a/src/Api/Actions/Activity/IndexAction.php +++ b/src/Api/Actions/Activity/IndexAction.php @@ -21,12 +21,12 @@ class IndexAction extends SerializeCollectionAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\ActivitySerializer'; + public $serializer = 'Flarum\Api\Serializers\ActivitySerializer'; /** * @inheritdoc */ - public static $include = [ + public $include = [ 'subject' => true, 'subject.user' => true, 'subject.discussion' => true @@ -35,27 +35,27 @@ class IndexAction extends SerializeCollectionAction /** * @inheritdoc */ - public static $link = ['user']; + public $link = ['user']; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * @param UserRepository $users diff --git a/src/Api/Actions/Discussions/CreateAction.php b/src/Api/Actions/Discussions/CreateAction.php index 0e19e2b74..10178b51e 100644 --- a/src/Api/Actions/Discussions/CreateAction.php +++ b/src/Api/Actions/Discussions/CreateAction.php @@ -18,12 +18,12 @@ class CreateAction extends BaseCreateAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\DiscussionSerializer'; + public $serializer = 'Flarum\Api\Serializers\DiscussionSerializer'; /** * @inheritdoc */ - public static $include = [ + public $include = [ 'posts' => true, 'startUser' => true, 'lastUser' => true, @@ -34,27 +34,27 @@ class CreateAction extends BaseCreateAction /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * Instantiate the action. diff --git a/src/Api/Actions/Discussions/IndexAction.php b/src/Api/Actions/Discussions/IndexAction.php index 9dd676957..870c7051f 100644 --- a/src/Api/Actions/Discussions/IndexAction.php +++ b/src/Api/Actions/Discussions/IndexAction.php @@ -22,12 +22,12 @@ class IndexAction extends SerializeCollectionAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\DiscussionSerializer'; + public $serializer = 'Flarum\Api\Serializers\DiscussionSerializer'; /** * @inheritdoc */ - public static $include = [ + public $include = [ 'startUser' => true, 'lastUser' => true, 'startPost' => false, @@ -40,27 +40,27 @@ class IndexAction extends SerializeCollectionAction /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = ['lastTime', 'commentsCount', 'startTime']; + public $sortFields = ['lastTime', 'commentsCount', 'startTime']; /** * @inheritdoc */ - public static $sort; + public $sort; /** * @param DiscussionSearcher $searcher @@ -93,7 +93,7 @@ class IndexAction extends SerializeCollectionAction $results = $this->searcher->search($criteria, $request->limit, $request->offset, $load); // TODO: add query params (filter, sort, include) to the pagination URLs - static::addPaginationLinks( + $this->addPaginationLinks( $document, $request, $request->http ? $this->url->toRoute('flarum.api.discussions.index') : '', diff --git a/src/Api/Actions/Discussions/ShowAction.php b/src/Api/Actions/Discussions/ShowAction.php index 04a7f4e6f..ddbda9305 100644 --- a/src/Api/Actions/Discussions/ShowAction.php +++ b/src/Api/Actions/Discussions/ShowAction.php @@ -19,12 +19,12 @@ class ShowAction extends SerializeResourceAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\DiscussionSerializer'; + public $serializer = 'Flarum\Api\Serializers\DiscussionSerializer'; /** * @inheritdoc */ - public static $include = [ + public $include = [ 'startUser' => false, 'lastUser' => false, 'startPost' => false, @@ -39,27 +39,27 @@ class ShowAction extends SerializeResourceAction /** * @inheritdoc */ - public static $link = ['posts', 'posts.discussion']; + public $link = ['posts', 'posts.discussion']; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = ['time']; + public $sortFields = ['time']; /** * @inheritdoc */ - public static $sort = ['time' => 'asc']; + public $sort = ['time' => 'asc']; /** * Instantiate the action. diff --git a/src/Api/Actions/Discussions/UpdateAction.php b/src/Api/Actions/Discussions/UpdateAction.php index 16048c55b..cbfb60420 100644 --- a/src/Api/Actions/Discussions/UpdateAction.php +++ b/src/Api/Actions/Discussions/UpdateAction.php @@ -17,37 +17,37 @@ class UpdateAction extends SerializeResourceAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\DiscussionSerializer'; + public $serializer = 'Flarum\Api\Serializers\DiscussionSerializer'; /** * @inheritdoc */ - public static $include = []; + public $include = []; /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * @param Dispatcher $bus diff --git a/src/Api/Actions/Forum/ShowAction.php b/src/Api/Actions/Forum/ShowAction.php index cb64ee4ec..2125cae5d 100644 --- a/src/Api/Actions/Forum/ShowAction.php +++ b/src/Api/Actions/Forum/ShowAction.php @@ -9,37 +9,37 @@ class ShowAction extends SerializeResourceAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\ForumSerializer'; + public $serializer = 'Flarum\Api\Serializers\ForumSerializer'; /** * @inheritdoc */ - public static $include = []; + public $include = []; /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * Get the forum, ready to be serialized and assigned to the JsonApi diff --git a/src/Api/Actions/Groups/IndexAction.php b/src/Api/Actions/Groups/IndexAction.php index 3326be049..8b57c07cc 100644 --- a/src/Api/Actions/Groups/IndexAction.php +++ b/src/Api/Actions/Groups/IndexAction.php @@ -10,37 +10,37 @@ class IndexAction extends SerializeCollectionAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\GroupSerializer'; + public $serializer = 'Flarum\Api\Serializers\GroupSerializer'; /** * @inheritdoc */ - public static $include = []; + public $include = []; /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * Get the groups, ready to be serialized and assigned to the document diff --git a/src/Api/Actions/Notifications/IndexAction.php b/src/Api/Actions/Notifications/IndexAction.php index 6b9000204..c7a1371b1 100644 --- a/src/Api/Actions/Notifications/IndexAction.php +++ b/src/Api/Actions/Notifications/IndexAction.php @@ -16,12 +16,12 @@ class IndexAction extends SerializeCollectionAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\NotificationSerializer'; + public $serializer = 'Flarum\Api\Serializers\NotificationSerializer'; /** * @inheritdoc */ - public static $include = [ + public $include = [ 'sender' => true, 'subject' => true, 'subject.discussion' => true @@ -30,27 +30,27 @@ class IndexAction extends SerializeCollectionAction /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 10; + public $limit = 10; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * Instantiate the action. diff --git a/src/Api/Actions/Notifications/UpdateAction.php b/src/Api/Actions/Notifications/UpdateAction.php index 9ea9d7cba..54316a02c 100644 --- a/src/Api/Actions/Notifications/UpdateAction.php +++ b/src/Api/Actions/Notifications/UpdateAction.php @@ -16,37 +16,37 @@ class UpdateAction extends SerializeResourceAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\NotificationSerializer'; + public $serializer = 'Flarum\Api\Serializers\NotificationSerializer'; /** * @inheritdoc */ - public static $include = []; + public $include = []; /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * @param Dispatcher $bus diff --git a/src/Api/Actions/Posts/CreateAction.php b/src/Api/Actions/Posts/CreateAction.php index 8a3eeac1f..23fa07a84 100644 --- a/src/Api/Actions/Posts/CreateAction.php +++ b/src/Api/Actions/Posts/CreateAction.php @@ -16,12 +16,12 @@ class CreateAction extends BaseCreateAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\PostSerializer'; + public $serializer = 'Flarum\Api\Serializers\PostSerializer'; /** * @inheritdoc */ - public static $include = [ + public $include = [ 'user' => true, 'discussion' => true ]; @@ -29,27 +29,27 @@ class CreateAction extends BaseCreateAction /** * @inheritdoc */ - public static $link = ['discussion.posts']; + public $link = ['discussion.posts']; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * Instantiate the action. diff --git a/src/Api/Actions/Posts/IndexAction.php b/src/Api/Actions/Posts/IndexAction.php index c6627b9f1..71881898e 100644 --- a/src/Api/Actions/Posts/IndexAction.php +++ b/src/Api/Actions/Posts/IndexAction.php @@ -12,12 +12,12 @@ class IndexAction extends SerializeCollectionAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\PostSerializer'; + public $serializer = 'Flarum\Api\Serializers\PostSerializer'; /** * @inheritdoc */ - public static $include = [ + public $include = [ 'user' => true, 'user.groups' => true, 'editUser' => true, @@ -28,27 +28,27 @@ class IndexAction extends SerializeCollectionAction /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * @param PostRepository $posts diff --git a/src/Api/Actions/Posts/ShowAction.php b/src/Api/Actions/Posts/ShowAction.php index 8c67757b1..e2fd92eff 100644 --- a/src/Api/Actions/Posts/ShowAction.php +++ b/src/Api/Actions/Posts/ShowAction.php @@ -16,12 +16,12 @@ class ShowAction extends SerializeResourceAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\PostSerializer'; + public $serializer = 'Flarum\Api\Serializers\PostSerializer'; /** * @inheritdoc */ - public static $include = [ + public $include = [ 'user' => true, 'user.groups' => true, 'editUser' => true, @@ -32,27 +32,27 @@ class ShowAction extends SerializeResourceAction /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * @param PostRepository $posts diff --git a/src/Api/Actions/Posts/UpdateAction.php b/src/Api/Actions/Posts/UpdateAction.php index 462c11e1d..204df58cf 100644 --- a/src/Api/Actions/Posts/UpdateAction.php +++ b/src/Api/Actions/Posts/UpdateAction.php @@ -16,37 +16,37 @@ class UpdateAction extends SerializeResourceAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\PostSerializer'; + public $serializer = 'Flarum\Api\Serializers\PostSerializer'; /** * @inheritdoc */ - public static $include = []; + public $include = []; /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * @param Dispatcher $bus diff --git a/src/Api/Actions/SerializeAction.php b/src/Api/Actions/SerializeAction.php index 8b04050a7..6cef0e892 100644 --- a/src/Api/Actions/SerializeAction.php +++ b/src/Api/Actions/SerializeAction.php @@ -1,6 +1,7 @@ buildJsonApiRequest($request); $document = new Document(); $data = $this->data($request, $document); event(new WillSerializeData($this, $data, $request)); - $serializer = new static::$serializer($request->actor, $request->include, $request->link); + $serializer = new $this->serializer($request->actor, $request->include, $request->link); $document->setData($this->serialize($serializer, $data)); @@ -107,17 +108,19 @@ abstract class SerializeAction extends JsonApiAction * @param Request $request * @return JsonApiRequest */ - protected static function buildJsonApiRequest(Request $request) + protected function buildJsonApiRequest(Request $request) { $request = new JsonApiRequest($request->input, $request->actor, $request->http); $criteria = new Criteria($request->input); - $request->include = static::sanitizeInclude($criteria->getInclude()); - $request->sort = static::sanitizeSort($criteria->getSort()); + event(new BuildApiAction($this)); + + $request->include = $this->sanitizeInclude($criteria->getInclude()); + $request->sort = $this->sanitizeSort($criteria->getSort()); $request->offset = $criteria->getOffset(); - $request->limit = static::sanitizeLimit($criteria->getLimit()); - $request->link = static::$link; + $request->limit = $this->sanitizeLimit($criteria->getLimit()); + $request->link = $this->link; return $request; } @@ -129,9 +132,9 @@ abstract class SerializeAction extends JsonApiAction * @param array $include * @return array */ - protected static function sanitizeInclude(array $include) + protected function sanitizeInclude(array $include) { - return array_intersect($include, array_keys(static::$include)) ?: array_keys(array_filter(static::$include)); + return array_intersect($include, array_keys($this->include)) ?: array_keys(array_filter($this->include)); } /** @@ -141,9 +144,9 @@ abstract class SerializeAction extends JsonApiAction * @param array $sort * @return array */ - protected static function sanitizeSort(array $sort) + protected function sanitizeSort(array $sort) { - return array_intersect_key($sort, array_flip(static::$sortFields)) ?: static::$sort; + return array_intersect_key($sort, array_flip($this->sortFields)) ?: $this->sort; } /** @@ -152,9 +155,9 @@ abstract class SerializeAction extends JsonApiAction * @param int $limit * @return int */ - protected static function sanitizeLimit($limit) + protected function sanitizeLimit($limit) { - return min($limit, static::$limitMax) ?: static::$limit; + return min($limit, $this->limitMax) ?: $this->limit; } /** @@ -169,10 +172,10 @@ abstract class SerializeAction extends JsonApiAction * is unknown ('last' link is ommitted). * @return void */ - protected static function addPaginationLinks(Document $document, JsonApiRequest $request, $url, $total = true) + protected function addPaginationLinks(Document $document, JsonApiRequest $request, $url, $total = true) { $input = []; - if ($request->limit != static::$limit) { + if ($request->limit != $this->limit) { array_set($input, 'page.limit', $request->limit); } diff --git a/src/Api/Actions/TokenAction.php b/src/Api/Actions/TokenAction.php index c4786f519..1ccff5dba 100644 --- a/src/Api/Actions/TokenAction.php +++ b/src/Api/Actions/TokenAction.php @@ -4,7 +4,7 @@ use Flarum\Api\Commands\GenerateAccessToken; use Flarum\Api\Request; use Flarum\Core\Users\UserRepository; use Flarum\Core\Exceptions\PermissionDeniedException; -use Flarum\Core\Users\Events\UserEmailChangeWasRequested; +use Flarum\Events\UserEmailChangeWasRequested; use Illuminate\Contracts\Bus\Dispatcher; use Zend\Diactoros\Response\JsonResponse; diff --git a/src/Api/Actions/Users/CreateAction.php b/src/Api/Actions/Users/CreateAction.php index ce46aa5fd..8f26cb860 100644 --- a/src/Api/Actions/Users/CreateAction.php +++ b/src/Api/Actions/Users/CreateAction.php @@ -15,37 +15,37 @@ class CreateAction extends BaseCreateAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\UserSerializer'; + public $serializer = 'Flarum\Api\Serializers\UserSerializer'; /** * @inheritdoc */ - public static $include = []; + public $include = []; /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * @param Dispatcher $bus diff --git a/src/Api/Actions/Users/DeleteAvatarAction.php b/src/Api/Actions/Users/DeleteAvatarAction.php index 2919c2623..8d843845d 100644 --- a/src/Api/Actions/Users/DeleteAvatarAction.php +++ b/src/Api/Actions/Users/DeleteAvatarAction.php @@ -16,7 +16,7 @@ class DeleteAvatarAction extends SerializeResourceAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\UserSerializer'; + public $serializer = 'Flarum\Api\Serializers\UserSerializer'; /** * @param Dispatcher $bus diff --git a/src/Api/Actions/Users/IndexAction.php b/src/Api/Actions/Users/IndexAction.php index 0229c5336..bb7543efb 100644 --- a/src/Api/Actions/Users/IndexAction.php +++ b/src/Api/Actions/Users/IndexAction.php @@ -22,39 +22,39 @@ class IndexAction extends SerializeCollectionAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\UserSerializer'; + public $serializer = 'Flarum\Api\Serializers\UserSerializer'; /** * @inheritdoc */ - public static $include = [ + public $include = [ 'groups' => true ]; /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = ['username', 'postsCount', 'discussionsCount', 'lastSeenTime', 'joinTime']; + public $sortFields = ['username', 'postsCount', 'discussionsCount', 'lastSeenTime', 'joinTime']; /** * @inheritdoc */ - public static $sort; + public $sort; /** * @param UserSearcher $searcher @@ -84,7 +84,7 @@ class IndexAction extends SerializeCollectionAction $results = $this->searcher->search($criteria, $request->limit, $request->offset, $request->include); - static::addPaginationLinks( + $this->addPaginationLinks( $document, $request, $this->url->toRoute('flarum.api.users.index'), diff --git a/src/Api/Actions/Users/ShowAction.php b/src/Api/Actions/Users/ShowAction.php index 263636b8f..371fff19e 100644 --- a/src/Api/Actions/Users/ShowAction.php +++ b/src/Api/Actions/Users/ShowAction.php @@ -15,39 +15,39 @@ class ShowAction extends SerializeResourceAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\CurrentUserSerializer'; + public $serializer = 'Flarum\Api\Serializers\CurrentUserSerializer'; /** * @inheritdoc */ - public static $include = [ + public $include = [ 'groups' => true ]; /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * @param UserRepository $users diff --git a/src/Api/Actions/Users/UpdateAction.php b/src/Api/Actions/Users/UpdateAction.php index 6f30713f7..616b61f14 100644 --- a/src/Api/Actions/Users/UpdateAction.php +++ b/src/Api/Actions/Users/UpdateAction.php @@ -16,37 +16,37 @@ class UpdateAction extends SerializeResourceAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\CurrentUserSerializer'; + public $serializer = 'Flarum\Api\Serializers\CurrentUserSerializer'; /** * @inheritdoc */ - public static $include = []; + public $include = []; /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * @param Dispatcher $bus diff --git a/src/Api/Actions/Users/UploadAvatarAction.php b/src/Api/Actions/Users/UploadAvatarAction.php index 2b699efcb..545bfd855 100644 --- a/src/Api/Actions/Users/UploadAvatarAction.php +++ b/src/Api/Actions/Users/UploadAvatarAction.php @@ -16,37 +16,37 @@ class UploadAvatarAction extends SerializeResourceAction /** * @inheritdoc */ - public static $serializer = 'Flarum\Api\Serializers\UserSerializer'; + public $serializer = 'Flarum\Api\Serializers\UserSerializer'; /** * @inheritdoc */ - public static $include = []; + public $include = []; /** * @inheritdoc */ - public static $link = []; + public $link = []; /** * @inheritdoc */ - public static $limitMax = 50; + public $limitMax = 50; /** * @inheritdoc */ - public static $limit = 20; + public $limit = 20; /** * @inheritdoc */ - public static $sortFields = []; + public $sortFields = []; /** * @inheritdoc */ - public static $sort; + public $sort; /** * @param Dispatcher $bus diff --git a/src/Api/ApiServiceProvider.php b/src/Api/ApiServiceProvider.php index 737892f3d..c3c9860d8 100644 --- a/src/Api/ApiServiceProvider.php +++ b/src/Api/ApiServiceProvider.php @@ -1,6 +1,11 @@ routes(); + + $this->registerActivitySerializers(); + $this->registerNotificationSerializers(); + } + + /** + * Register activity serializers. + */ + protected function registerActivitySerializers() + { + $blueprints = []; + $serializers = [ + 'posted' => 'Flarum\Api\Serializers\PostBasicSerializer', + 'startedDiscussion' => 'Flarum\Api\Serializers\PostBasicSerializer', + 'joined' => 'Flarum\Api\Serializers\UserSerializer' + ]; + + event(new RegisterActivityTypes($blueprints, $serializers)); + + foreach ($serializers as $type => $serializer) { + ActivitySerializer::setSubjectSerializer($type, $serializer); + } + } + + /** + * Register notification serializers. + */ + protected function registerNotificationSerializers() + { + $blueprints = []; + $serializers = [ + 'discussionRenamed' => 'Flarum\Api\Serializers\DiscussionBasicSerializer' + ]; + + event(new RegisterNotificationTypes($blueprints, $serializers)); + + foreach ($serializers as $type => $serializer) { + NotificationSerializer::setSubjectSerializer($type, $serializer); + } } protected function routes() @@ -268,6 +312,8 @@ class ApiServiceProvider extends ServiceProvider 'flarum.api.groups.delete', $this->action('Flarum\Api\Actions\Groups\DeleteAction') ); + + event(new RegisterApiRoutes($routes)); } protected function action($class) diff --git a/src/Api/Serializers/Serializer.php b/src/Api/Serializers/Serializer.php index 75818e120..5da1f43ed 100644 --- a/src/Api/Serializers/Serializer.php +++ b/src/Api/Serializers/Serializer.php @@ -1,10 +1,10 @@ getDefaultAttributes($model); - event(new SerializeAttributes($this, $model, $attributes)); + event(new ApiAttributes($this, $model, $attributes)); return $attributes; } @@ -57,13 +50,26 @@ abstract class Serializer extends SerializerAbstract */ protected function getRelationshipFromMethod($name) { - if (isset(static::$relationMethods[$name])) { - return call_user_func(static::$relationMethods[$name], $this); + if ($relationship = $this->getCustomRelationship($name)) { + return $relationship; } return parent::getRelationshipFromMethod($name); } + /** + * Get a custom relationship. + * + * @param string $name + * @return mixed + */ + protected function getCustomRelationship($name) + { + return app('events')->until( + new ApiRelationship($this, $name) + ); + } + /** * Get a closure that returns a Collection/Resource representing a relation. * @@ -154,16 +160,4 @@ abstract class Serializer extends SerializerAbstract { return $this->getRelationship($serializer, $relation, true); } - - /** - * Add a custom relation to the model. - * - * @param string $name The name of the relation. - * @param callable $callback The callback to execute. This should return a - * relation closure {@see Serializer::getRelationship()} - */ - public static function setRelationMethod($name, callable $callback) - { - static::$relationMethods[get_called_class()][$name] = $callback; - } } diff --git a/src/Console/GenerateExtensionCommand.php b/src/Console/GenerateExtensionCommand.php index db4680644..caa0ab873 100644 --- a/src/Console/GenerateExtensionCommand.php +++ b/src/Console/GenerateExtensionCommand.php @@ -42,10 +42,12 @@ class GenerateExtensionCommand extends Command public function fire() { do { - $name = $this->ask('Extension name (-):'); - } while (! preg_match('/^([a-z0-9]+)-([a-z0-9-]+)$/i', $name, $match)); + $vendor = $this->ask('Vendor name:'); + } while (! preg_match('/^([a-z0-9-]+)$/i', $vendor)); - list(, $vendor, $package) = $match; + do { + $name = $this->ask('Extension name:'); + } while (! preg_match('/^([a-z0-9-]+)$/i', $name)); do { $title = $this->ask('Title:'); @@ -64,9 +66,8 @@ class GenerateExtensionCommand extends Command $dir = public_path().'/extensions/'.$name; $replacements = [ - '{{namespace}}' => ucfirst($vendor).'\\'.ucfirst($package), - '{{escapedNamespace}}' => ucfirst($vendor).'\\\\'.ucfirst($package), - '{{classPrefix}}' => ucfirst($package), + '{{namespace}}' => ucfirst($vendor).'\\'.ucfirst($name), + '{{escapedNamespace}}' => ucfirst($vendor).'\\\\'.ucfirst($name), '{{name}}' => $name ]; @@ -78,7 +79,7 @@ class GenerateExtensionCommand extends Command 'name' => $name, 'title' => $title, 'description' => $description, - 'tags' => [], + 'keywords' => [], 'version' => '0.1.0', 'author' => [ 'name' => $authorName, diff --git a/src/Console/ImportCommand.php b/src/Console/ImportCommand.php index ae0f3a414..4329b380f 100644 --- a/src/Console/ImportCommand.php +++ b/src/Console/ImportCommand.php @@ -9,7 +9,7 @@ use Flarum\Core\Discussions\Discussion; use Flarum\Core\Discussions\DiscussionState; use Flarum\Core\Posts\CommentPost; use Flarum\Tags\Tag; -use Flarum\Core\Posts\Events\PostWasPosted; +use Flarum\Events\PostWasPosted; use Symfony\Component\Console\Helper\ProgressBar; class ImportCommand extends Command diff --git a/src/Core/Activity/Activity.php b/src/Core/Activity/Activity.php index 4048b2f0b..16b5de481 100644 --- a/src/Core/Activity/Activity.php +++ b/src/Core/Activity/Activity.php @@ -27,7 +27,7 @@ class Activity extends Model /** * {@inheritdoc} */ - protected static $dateAttributes = ['time']; + protected $dates = ['time']; /** * A map of activity types and the model classes to use for their subjects. diff --git a/src/Core/Activity/ActivityServiceProvider.php b/src/Core/Activity/ActivityServiceProvider.php index b36bc2d19..c689ebbff 100644 --- a/src/Core/Activity/ActivityServiceProvider.php +++ b/src/Core/Activity/ActivityServiceProvider.php @@ -1,5 +1,7 @@ extend([ - (new Extend\EventSubscriber('Flarum\Core\Activity\Listeners\UserActivitySyncer')), + $this->registerActivityTypes(); - (new Extend\ActivityType('Flarum\Core\Activity\PostedBlueprint')) - ->subjectSerializer('Flarum\Api\Serializers\PostBasicSerializer'), + $events = $this->app->make('events'); + $events->subscribe('Flarum\Core\Activity\Listeners\UserActivitySyncer'); + } - (new Extend\ActivityType('Flarum\Core\Activity\StartedDiscussionBlueprint')) - ->subjectSerializer('Flarum\Api\Serializers\PostBasicSerializer'), + /** + * Register activity types. + * + * @return void + */ + public function registerActivityTypes() + { + $blueprints = [ + 'Flarum\Core\Activity\PostedBlueprint', + 'Flarum\Core\Activity\StartedDiscussionBlueprint', + 'Flarum\Core\Activity\JoinedBlueprint' + ]; - (new Extend\ActivityType('Flarum\Core\Activity\JoinedBlueprint')) - ->subjectSerializer('Flarum\Api\Serializers\UserBasicSerializer') - ]); + event(new RegisterActivityTypes($blueprints)); + + foreach ($blueprints as $blueprint) { + Activity::setSubjectModel( + $blueprint::getType(), + $blueprint::getSubjectModel() + ); + } } /** diff --git a/src/Core/Activity/Listeners/UserActivitySyncer.php b/src/Core/Activity/Listeners/UserActivitySyncer.php index cc8e8688d..2aebbcd3e 100755 --- a/src/Core/Activity/Listeners/UserActivitySyncer.php +++ b/src/Core/Activity/Listeners/UserActivitySyncer.php @@ -5,11 +5,11 @@ use Flarum\Core\Activity\PostedBlueprint; use Flarum\Core\Activity\StartedDiscussionBlueprint; use Flarum\Core\Activity\JoinedBlueprint; use Flarum\Core\Posts\Post; -use Flarum\Core\Posts\Events\PostWasPosted; -use Flarum\Core\Posts\Events\PostWasDeleted; -use Flarum\Core\Posts\Events\PostWasHidden; -use Flarum\Core\Posts\Events\PostWasRestored; -use Flarum\Core\Users\Events\UserWasRegistered; +use Flarum\Events\PostWasPosted; +use Flarum\Events\PostWasDeleted; +use Flarum\Events\PostWasHidden; +use Flarum\Events\PostWasRestored; +use Flarum\Events\UserWasRegistered; use Illuminate\Contracts\Events\Dispatcher; class UserActivitySyncer @@ -33,15 +33,15 @@ class UserActivitySyncer */ public function subscribe(Dispatcher $events) { - $events->listen('Flarum\Core\Posts\Events\PostWasPosted', __CLASS__.'@whenPostWasPosted'); - $events->listen('Flarum\Core\Posts\Events\PostWasHidden', __CLASS__.'@whenPostWasHidden'); - $events->listen('Flarum\Core\Posts\Events\PostWasRestored', __CLASS__.'@whenPostWasRestored'); - $events->listen('Flarum\Core\Posts\Events\PostWasDeleted', __CLASS__.'@whenPostWasDeleted'); - $events->listen('Flarum\Core\Users\Events\UserWasRegistered', __CLASS__.'@whenUserWasRegistered'); + $events->listen('Flarum\Events\PostWasPosted', __CLASS__.'@whenPostWasPosted'); + $events->listen('Flarum\Events\PostWasHidden', __CLASS__.'@whenPostWasHidden'); + $events->listen('Flarum\Events\PostWasRestored', __CLASS__.'@whenPostWasRestored'); + $events->listen('Flarum\Events\PostWasDeleted', __CLASS__.'@whenPostWasDeleted'); + $events->listen('Flarum\Events\UserWasRegistered', __CLASS__.'@whenUserWasRegistered'); } /** - * @param \Flarum\Core\Posts\Events\PostWasPosted $event + * @param \Flarum\Events\PostWasPosted $event * @return void */ public function whenPostWasPosted(PostWasPosted $event) @@ -50,7 +50,7 @@ class UserActivitySyncer } /** - * @param \Flarum\Core\Posts\Events\PostWasHidden $event + * @param \Flarum\Events\PostWasHidden $event * @return void */ public function whenPostWasHidden(PostWasHidden $event) @@ -59,7 +59,7 @@ class UserActivitySyncer } /** - * @param \Flarum\Core\Posts\Events\PostWasRestored $event + * @param \Flarum\Events\PostWasRestored $event * @return void */ public function whenPostWasRestored(PostWasRestored $event) @@ -68,7 +68,7 @@ class UserActivitySyncer } /** - * @param \Flarum\Core\Posts\Events\PostWasDeleted $event + * @param \Flarum\Events\PostWasDeleted $event * @return void */ public function whenPostWasDeleted(PostWasDeleted $event) @@ -77,7 +77,7 @@ class UserActivitySyncer } /** - * @param \Flarum\Core\Users\Events\UserWasRegistered $event + * @param \Flarum\Events\UserWasRegistered $event * @return void */ public function whenUserWasRegistered(UserWasRegistered $event) diff --git a/src/Core/CoreServiceProvider.php b/src/Core/CoreServiceProvider.php index ed3618bab..063701907 100644 --- a/src/Core/CoreServiceProvider.php +++ b/src/Core/CoreServiceProvider.php @@ -1,8 +1,7 @@ hasPermission('forum.'.$action) ?: null; + $events = $this->app->make('events'); + + $events->listen(ModelAllow::class, function (ModelAllow $event) { + if ($event->model instanceof Forum && + $event->actor->hasPermission('forum.'.$event->action)) { + return true; + } }); } @@ -33,14 +37,6 @@ class CoreServiceProvider extends ServiceProvider */ public function register() { - $this->app->singleton('Flarum\Core\Settings\SettingsRepository', function() { - return new MemoryCacheSettingsRepository( - new DatabaseSettingsRepository( - $this->app->make('Illuminate\Database\ConnectionInterface') - ) - ); - }); - $this->app->singleton('flarum.forum', 'Flarum\Core\Forum'); // TODO: probably use Illuminate's AggregateServiceProvider diff --git a/src/Core/Discussions/Commands/DeleteDiscussionHandler.php b/src/Core/Discussions/Commands/DeleteDiscussionHandler.php index b09c0d7f5..745d96184 100644 --- a/src/Core/Discussions/Commands/DeleteDiscussionHandler.php +++ b/src/Core/Discussions/Commands/DeleteDiscussionHandler.php @@ -1,7 +1,7 @@ extend([ - new Extend\EventSubscriber('Flarum\Core\Discussions\Listeners\DiscussionMetadataUpdater') - ]); - Discussion::setValidator($this->app->make('validator')); - Discussion::allow('*', function (Discussion $discussion, User $user, $action) { - return $user->hasPermission('discussion.'.$action) ?: null; - }); + $events = $this->app->make('events'); - // Allow a user to rename their own discussion. - Discussion::allow('rename', function (Discussion $discussion, User $user) { - return $discussion->start_user_id == $user->id ?: null; - // TODO: add limitations to time etc. according to a config setting - }); + $events->subscribe('Flarum\Core\Discussions\Listeners\DiscussionMetadataUpdater'); - Discussion::allow('delete', function (Discussion $discussion, User $user) { - return $discussion->start_user_id == $user->id && $discussion->participants_count == 1 ?: null; - // TODO: add limitations to time etc. according to a config setting + $events->listen(ModelAllow::class, function (ModelAllow $event) { + if ($event->model instanceof Discussion) { + if ($event->action === 'rename' && + $event->model->start_user_id == $event->actor->id) { + return true; + } + + if ($event->action === 'delete' && + $event->model->start_user_id == $event->actor->id && + $event->model->participants_count == 1) { + return true; + } + + if ($event->actor->hasPermission('discussion.'.$event->action)) { + return true; + } + } }); } @@ -49,21 +55,15 @@ class DiscussionsServiceProvider extends ServiceProvider 'Flarum\Core\Discussions\Search\Fulltext\MySqlFulltextDriver' ); - $this->app->instance('flarum.discussionGambits', [ - 'Flarum\Core\Discussions\Search\Gambits\AuthorGambit', - 'Flarum\Core\Discussions\Search\Gambits\UnreadGambit' - ]); - $this->app->when('Flarum\Core\Discussions\Search\DiscussionSearcher') ->needs('Flarum\Core\Search\GambitManager') ->give(function (Container $app) { $gambits = new GambitManager($app); - - foreach ($app->make('flarum.discussionGambits') as $gambit) { - $gambits->add($gambit); - } - $gambits->setFulltextGambit('Flarum\Core\Discussions\Search\Gambits\FulltextGambit'); + $gambits->add('Flarum\Core\Discussions\Search\Gambits\AuthorGambit'); + $gambits->add('Flarum\Core\Discussions\Search\Gambits\UnreadGambit'); + + event(new RegisterDiscussionGambits($gambits)); return $gambits; }); diff --git a/src/Core/Discussions/Listeners/DiscussionMetadataUpdater.php b/src/Core/Discussions/Listeners/DiscussionMetadataUpdater.php index 416b467c3..30414333f 100755 --- a/src/Core/Discussions/Listeners/DiscussionMetadataUpdater.php +++ b/src/Core/Discussions/Listeners/DiscussionMetadataUpdater.php @@ -1,10 +1,10 @@ extend([ - new Extend\PostFormatter('Flarum\Core\Formatter\LinkifyFormatter') - ]); } /** diff --git a/src/Core/Model.php b/src/Core/Model.php index ee461350a..922cbe4eb 100755 --- a/src/Core/Model.php +++ b/src/Core/Model.php @@ -1,6 +1,13 @@ dates); + $dates = $this->dates; + + event(new ModelDates($this, $dates)); + + return $dates; } /** - * Add an attribute to be converted to a date. + * Check whether or not a user has permission to perform an action, + * according to the collected conditions. * - * @param string $attribute + * @param User $actor + * @param string $action + * @return bool */ - public static function addDateAttribute($attribute) + public function can(User $actor, $action) { - static::$dateAttributes[] = $attribute; + $can = static::$dispatcher->until(new ModelAllow($this, $actor, $action)); + + if ($can !== null) { + return $can; + } + + return false; + } + + /** + * Assert that the user has a certain permission for this model, throwing + * an exception if they don't. + * + * @param User $actor + * @param string $action + * @throws PermissionDeniedException + */ + public function assertCan(User $actor, $action) + { + if (! $this->can($actor, $action)) { + throw new PermissionDeniedException; + } + } + + /** + * Scope a query to only include records that are visible to a user. + * + * @param Builder $query + * @param User $actor + */ + public function scopeWhereVisibleTo(Builder $query, User $actor) + { + event(new ScopeModelVisibility($this, $query, $actor)); } /** @@ -71,40 +106,38 @@ abstract class Model extends Eloquent // If a custom relation with this key has been set up, then we will load // and return results from the query and hydrate the relationship's // value on the "relationships" array. - if (isset(static::$relationMethods[get_called_class()][$key])) { - return $this->getCustomRelationship($key); + if ($relation = $this->getCustomRelation($key)) { + if (! $relation instanceof Relation) { + throw new LogicException('Relationship method must return an object of type ' + . 'Illuminate\Database\Eloquent\Relations\Relation'); + } + + return $this->relations[$key] = $relation->getResults(); } } /** - * Get a relationship value from a custom relationship method. + * Get a custom relation object. * * @param string $name * @return mixed - * - * @throws \LogicException */ - protected function getCustomRelationship($name) + protected function getCustomRelation($name) { - $relation = static::$relationMethods[get_called_class()][$name]($this); - - if (! $relation instanceof Relation) { - throw new LogicException('Relationship method must return an object of type ' - . 'Illuminate\Database\Eloquent\Relations\Relation'); - } - - return $this->relations[$name] = $relation->getResults(); + return static::$dispatcher->until( + new ModelRelationship($this, $name) + ); } /** - * Add a custom relation to the model. - * - * @param string $name The name of the relation. - * @param callable $callback The callback to execute. This should return an - * object of type Illuminate\Database\Eloquent\Relations\Relation. + * @inheritdoc */ - public static function setRelationMethod($name, callable $callback) + public function __call($method, $arguments) { - static::$relationMethods[get_called_class()][$name] = $callback; + if ($relation = $this->getCustomRelation($method)) { + return $relation; + } + + return parent::__call($method, $arguments); } } diff --git a/src/Core/Notifications/Listeners/DiscussionRenamedNotifier.php b/src/Core/Notifications/Listeners/DiscussionRenamedNotifier.php index 1d6c1d545..c8276c7a8 100755 --- a/src/Core/Notifications/Listeners/DiscussionRenamedNotifier.php +++ b/src/Core/Notifications/Listeners/DiscussionRenamedNotifier.php @@ -1,6 +1,6 @@ whereIn('type', $user->getAlertableNotificationTypes()) ->where('is_deleted', false) ->groupBy('type', 'subject_id') - ->latest('time') + ->orderByRaw('MAX(time) DESC') ->skip($offset) ->take($limit); diff --git a/src/Core/Notifications/NotificationSyncer.php b/src/Core/Notifications/NotificationSyncer.php index 7e32614b1..71943320c 100644 --- a/src/Core/Notifications/NotificationSyncer.php +++ b/src/Core/Notifications/NotificationSyncer.php @@ -1,6 +1,6 @@ extend([ - (new Extend\EventSubscriber('Flarum\Core\Notifications\Listeners\DiscussionRenamedNotifier')), + $events = $this->app->make('events'); - (new Extend\NotificationType('Flarum\Core\Notifications\DiscussionRenamedBlueprint')) - ->subjectSerializer('Flarum\Api\Serializers\DiscussionBasicSerializer') - ->enableByDefault('alert') - ]); + $events->subscribe('Flarum\Core\Notifications\Listeners\DiscussionRenamedNotifier'); + + $this->registerNotificationTypes(); + } + + /** + * Register notification types. + * + * @return void + */ + public function registerNotificationTypes() + { + $blueprints = [ + 'Flarum\Core\Notifications\DiscussionRenamedBlueprint' => ['alert'] + ]; + + event(new RegisterNotificationTypes($blueprints)); + + foreach ($blueprints as $blueprint => $enabled) { + Notification::setSubjectModel( + $type = $blueprint::getType(), + $blueprint::getSubjectModel() + ); + + User::addPreference( + User::getNotificationPreferenceKey($type, 'alert'), + 'boolval', + in_array('alert', $enabled) + ); + + if ((new ReflectionClass($blueprint))->implementsInterface('Flarum\Core\Notifications\MailableBlueprint')) { + User::addPreference( + User::getNotificationPreferenceKey($type, 'email'), + 'boolval', + in_array('email', $enabled) + ); + } + } } /** diff --git a/src/Core/Posts/Commands/DeletePostHandler.php b/src/Core/Posts/Commands/DeletePostHandler.php index cc5d7b43b..ca854727f 100644 --- a/src/Core/Posts/Commands/DeletePostHandler.php +++ b/src/Core/Posts/Commands/DeletePostHandler.php @@ -1,7 +1,7 @@ exists = true; $instance->setRawAttributes($attributes, true); diff --git a/src/Core/Posts/PostsServiceProvider.php b/src/Core/Posts/PostsServiceProvider.php index 765fbfa38..cb93b7fd6 100644 --- a/src/Core/Posts/PostsServiceProvider.php +++ b/src/Core/Posts/PostsServiceProvider.php @@ -2,6 +2,9 @@ use Flarum\Core\Discussions\Discussion; use Flarum\Core\Users\User; +use Flarum\Events\ModelAllow; +use Flarum\Events\RegisterPostTypes; +use Flarum\Events\ScopePostVisibility; use Flarum\Support\ServiceProvider; use Flarum\Extend; @@ -14,45 +17,73 @@ class PostsServiceProvider extends ServiceProvider */ public function boot() { - $this->extend([ - new Extend\PostType('Flarum\Core\Posts\CommentPost'), - new Extend\PostType('Flarum\Core\Posts\DiscussionRenamedPost') - ]); - Post::setValidator($this->app->make('validator')); CommentPost::setFormatter($this->app->make('flarum.formatter')); - Post::allow('*', function ($post, $user, $action) { - return $post->discussion->can($user, $action.'Posts') ?: null; + $this->registerPostTypes(); + + $events = $this->app->make('events'); + + $events->listen(ModelAllow::class, function (ModelAllow $event) { + if ($event->model instanceof Post) { + $post = $event->model; + $action = $event->action; + $actor = $event->actor; + + if ($action === 'view' && + (! $post->hide_user_id || $post->can($actor, 'edit'))) { + return true; + } + + // A post is allowed to be edited if the user has permission to moderate + // the discussion which it's in, or if they are the author and the post + // hasn't been deleted by someone else. + if ($action === 'edit' && + ($post->discussion->can($actor, 'editPosts') || + ($post->user_id == $actor->id && + (! $post->hide_user_id || $post->hide_user_id == $actor->id)))) { + return true; + } + + if ($post->discussion->can($actor, $action.'Posts')) { + return true; + } + } }); // When fetching a discussion's posts: if the user doesn't have permission // to moderate the discussion, then they can't see posts that have been // hidden by someone other than themself. - Discussion::addPostVisibilityScope(function ($query, User $user, Discussion $discussion) { - if (! $discussion->can($user, 'editPosts')) { - $query->where(function ($query) use ($user) { + $events->listen(ScopePostVisibility::class, function (ScopePostVisibility $event) { + $user = $event->actor; + + if (! $event->discussion->can($user, 'editPosts')) { + $event->query->where(function ($query) use ($user) { $query->whereNull('hide_user_id') - ->orWhere('hide_user_id', $user->id); + ->orWhere('hide_user_id', $user->id); }); } }); + } - Post::allow('view', function ($post, $user) { - return ! $post->hide_user_id || $post->can($user, 'edit') ?: null; - }); + /** + * Register post types. + * + * @return void + */ + public function registerPostTypes() + { + $models = [ + 'Flarum\Core\Posts\CommentPost', + 'Flarum\Core\Posts\DiscussionRenamedPost' + ]; - // A post is allowed to be edited if the user has permission to moderate - // the discussion which it's in, or if they are the author and the post - // hasn't been deleted by someone else. - Post::allow('edit', function ($post, $user) { - if ($post->discussion->can($user, 'editPosts') || - ($post->user_id == $user->id && (! $post->hide_user_id || $post->hide_user_id == $user->id)) - ) { - return true; - } - }); + event(new RegisterPostTypes($models)); + + foreach ($models as $model) { + Post::setModel($model::$type, $model); + } } /** diff --git a/src/Core/Search/RegexGambit.php b/src/Core/Search/RegexGambit.php index 594ced2a9..cf8557fd1 100644 --- a/src/Core/Search/RegexGambit.php +++ b/src/Core/Search/RegexGambit.php @@ -17,7 +17,7 @@ abstract class RegexGambit implements Gambit if ($matches = $this->match($bit)) { list($negate) = array_splice($matches, 1, 1); - $this->conditions($searcher, $matches, !! $negate); + $this->conditions($search, $matches, !! $negate); } return !! $matches; diff --git a/src/Core/Settings/SettingsServiceProvider.php b/src/Core/Settings/SettingsServiceProvider.php new file mode 100644 index 000000000..1bc6064ac --- /dev/null +++ b/src/Core/Settings/SettingsServiceProvider.php @@ -0,0 +1,22 @@ +app->singleton('Flarum\Core\Settings\SettingsRepository', function () { + return new MemoryCacheSettingsRepository( + new DatabaseSettingsRepository( + $this->app->make('Illuminate\Database\ConnectionInterface') + ) + ); + }); + } +} diff --git a/src/Core/Support/Locked.php b/src/Core/Support/Locked.php index 56dc9df78..bce8cd2c3 100644 --- a/src/Core/Support/Locked.php +++ b/src/Core/Support/Locked.php @@ -2,6 +2,8 @@ use Flarum\Core\Exceptions\PermissionDeniedException; use Flarum\Core\Users\User; +use Flarum\Events\ModelAllow; +use Illuminate\Contracts\Events\Dispatcher; /** * 'Lock' an object, allowing the permission of a user to perform an action to @@ -9,75 +11,5 @@ use Flarum\Core\Users\User; */ trait Locked { - /** - * @var callable[] - */ - protected static $conditions = []; - /** - * Get the condition callbacks for the specified action. - * - * @param string $action - * @return callable[] - */ - protected static function getConditions($action) - { - $conditions = array_get(static::$conditions, $action, []); - $all = array_get(static::$conditions, '*', []); - - return array_merge($conditions, $all); - } - - /** - * Allow the specified action if the given condition is satisfied. - * - * @param string $action - * @param callable $condition The condition callback. Parameters are the - * object that is locked, the user performing the action, - * and the name of the action. This condition will be ignored if it - * returns null; otherwise, the return value will determine whether or - * not the action is allowed. - */ - public static function allow($action, callable $condition) - { - foreach ((array)$action as $action) { - static::$conditions[$action][] = $condition; - } - } - - /** - * Check whether or not a user has permission to perform an action, - * according to the collected conditions. - * - * @param User $actor - * @param string $action - * @return bool - */ - public function can(User $actor, $action) - { - foreach ($this->getConditions($action) as $condition) { - $can = $condition($this, $actor, $action); - - if ($can !== null) { - return $can; - } - } - - return false; - } - - /** - * Assert that the user has a certain permission for this model, throwing - * an exception if they don't. - * - * @param User $actor - * @param string $action - * @throws PermissionDeniedException - */ - public function assertCan(User $actor, $action) - { - if (! $this->can($actor, $action)) { - throw new PermissionDeniedException; - } - } } diff --git a/src/Core/Support/ValidatesBeforeSave.php b/src/Core/Support/ValidatesBeforeSave.php index 1beebc5c9..85cc1626b 100644 --- a/src/Core/Support/ValidatesBeforeSave.php +++ b/src/Core/Support/ValidatesBeforeSave.php @@ -1,6 +1,7 @@ getDirty(); + $rules = $this->expandUniqueRules($this->rules); - $rules = $this->expandUniqueRules(array_only($this->rules, array_keys($dirty))); + $validator = static::$validator->make($this->getAttributes(), $rules); - $validator = static::$validator->make($dirty, $rules); - - // TODO: event + event(new ModelValidator($this, $validator)); return $validator; } diff --git a/src/Core/Support/VisibleScope.php b/src/Core/Support/VisibleScope.php deleted file mode 100644 index d09cf29f8..000000000 --- a/src/Core/Support/VisibleScope.php +++ /dev/null @@ -1,40 +0,0 @@ -raise(new UserWasDeleted($user)); }); + + event(new RegisterUserPreferences); } /** diff --git a/src/Core/Users/UsersServiceProvider.php b/src/Core/Users/UsersServiceProvider.php index d7be4d49b..14e446f77 100644 --- a/src/Core/Users/UsersServiceProvider.php +++ b/src/Core/Users/UsersServiceProvider.php @@ -1,6 +1,9 @@ extend([ - new Extend\EventSubscriber('Flarum\Core\Users\Listeners\UserMetadataUpdater'), - new Extend\EventSubscriber('Flarum\Core\Users\Listeners\EmailConfirmationMailer') - ]); - User::setHasher($this->app->make('hash')); User::setFormatter($this->app->make('flarum.formatter')); User::setValidator($this->app->make('validator')); - User::addPreference('discloseOnline', 'boolval', true); - User::addPreference('indexProfile', 'boolval', true); + $events = $this->app->make('events'); - User::allow('*', function (User $user, User $actor, $action) { - return $actor->hasPermission('user.'.$action) ?: null; + $events->listen(RegisterUserPreferences::class, function (RegisterUserPreferences $event) { + $event->register('discloseOnline', 'boolval', true); + $event->register('indexProfile', 'boolval', true); }); - User::allow(['edit', 'delete'], function (User $user, User $actor) { - return $user->id == $actor->id ?: null; + $events->listen(ModelAllow::class, function (ModelAllow $event) { + if ($event->model instanceof User) { + if (in_array($event->action, ['edit', 'delete']) && + $event->model->id == $event->actor->id) { + return true; + } + + if ($event->actor->hasPermission('user.'.$event->action)) { + return true; + } + } }); + + $events->subscribe('Flarum\Core\Users\Listeners\UserMetadataUpdater'); + $events->subscribe('Flarum\Core\Users\Listeners\EmailConfirmationMailer'); } /** @@ -63,19 +73,14 @@ class UsersServiceProvider extends ServiceProvider public function registerGambits() { - $this->app->instance('flarum.userGambits', []); - $this->app->when('Flarum\Core\Users\Search\UserSearcher') ->needs('Flarum\Core\Search\GambitManager') ->give(function (Container $app) { $gambits = new GambitManager($app); - - foreach ($app->make('flarum.userGambits') as $gambit) { - $gambits->add($gambit); - } - $gambits->setFulltextGambit('Flarum\Core\Users\Search\Gambits\FulltextGambit'); + event(new RegisterUserGambits($gambits)); + return $gambits; }); } diff --git a/src/Api/Events/SerializeAttributes.php b/src/Events/ApiAttributes.php similarity index 82% rename from src/Api/Events/SerializeAttributes.php rename to src/Events/ApiAttributes.php index a92cdf7c6..98a73e2bd 100644 --- a/src/Api/Events/SerializeAttributes.php +++ b/src/Events/ApiAttributes.php @@ -1,8 +1,8 @@ -serializer = $serializer; $this->model = $model; $this->attributes = &$attributes; + $this->actor = $serializer->actor; } } diff --git a/src/Events/ApiRelationship.php b/src/Events/ApiRelationship.php new file mode 100644 index 000000000..59df26de8 --- /dev/null +++ b/src/Events/ApiRelationship.php @@ -0,0 +1,26 @@ +serializer = $serializer; + $this->relationship = $relationship; + } +} diff --git a/src/Core/Users/Events/AvatarWillBeDeleted.php b/src/Events/AvatarWillBeDeleted.php similarity index 92% rename from src/Core/Users/Events/AvatarWillBeDeleted.php rename to src/Events/AvatarWillBeDeleted.php index e1fc60bae..04b41ed80 100644 --- a/src/Core/Users/Events/AvatarWillBeDeleted.php +++ b/src/Events/AvatarWillBeDeleted.php @@ -1,4 +1,4 @@ -action = $action; + } + + public function serializer($serializer) + { + $this->action->serializer = $serializer; + } + + public function addInclude($relation, $default = true) + { + $this->action->include[$relation] = $default; + } + + public function removeInclude($relation) + { + unset($this->action->include[$relation]); + } + + public function addLink($relation) + { + $this->action->link[] = $relation; + } + + public function removeLink($relation) + { + if (($k = array_search($relation, $this->action->link)) !== false) { + unset($this->action->link[$k]); + } + } + + public function limitMax($limitMax) + { + $this->action->limitMax = $limitMax; + } + + public function limit($limit) + { + $this->action->limit = $limit; + } + + public function addSortField($field) + { + $this->action->sortFields[] = $field; + } + + public function removeSortField($field) + { + if (($k = array_search($field, $this->action->sortFields)) !== false) { + unset($this->action->sortFields[$k]); + } + } + + public function sort($sort) + { + $this->action->sort = $sort; + } +} diff --git a/src/Events/BuildClientView.php b/src/Events/BuildClientView.php new file mode 100644 index 000000000..2528f91a5 --- /dev/null +++ b/src/Events/BuildClientView.php @@ -0,0 +1,57 @@ +action = $action; + $this->view = $view; + $this->keys = &$keys; + } + + public function forumAssets($files) + { + if ($this->action instanceof ForumClientAction) { + $this->view->getAssets()->addFiles((array) $files); + } + } + + public function forumBootstrapper($bootstrapper) + { + if ($this->action instanceof ForumClientAction) { + $this->view->addBootstrapper($bootstrapper); + } + } + + public function forumTranslations(array $keys) + { + if ($this->action instanceof ForumClientAction) { + foreach ($keys as $key) { + $this->keys[] = $key; + } + } + }} diff --git a/src/Core/Discussions/Events/DiscussionSearchWillBePerformed.php b/src/Events/DiscussionSearchWillBePerformed.php similarity index 91% rename from src/Core/Discussions/Events/DiscussionSearchWillBePerformed.php rename to src/Events/DiscussionSearchWillBePerformed.php index 74ad66495..3fb74983c 100644 --- a/src/Core/Discussions/Events/DiscussionSearchWillBePerformed.php +++ b/src/Events/DiscussionSearchWillBePerformed.php @@ -1,4 +1,4 @@ -model = $model; + $this->actor = $actor; + $this->action = $action; + } +} diff --git a/src/Events/ModelDates.php b/src/Events/ModelDates.php new file mode 100644 index 000000000..371b47cdf --- /dev/null +++ b/src/Events/ModelDates.php @@ -0,0 +1,30 @@ +model = $model; + $this->dates = &$dates; + } +} diff --git a/src/Events/ModelRelationship.php b/src/Events/ModelRelationship.php new file mode 100644 index 000000000..ef8b92754 --- /dev/null +++ b/src/Events/ModelRelationship.php @@ -0,0 +1,30 @@ +model = $model; + $this->relationship = $relationship; + } +} diff --git a/src/Events/ModelValidator.php b/src/Events/ModelValidator.php new file mode 100644 index 000000000..171b3ad17 --- /dev/null +++ b/src/Events/ModelValidator.php @@ -0,0 +1,32 @@ +model = $model; + $this->validator = $validator; + } +} diff --git a/src/Core/Notifications/Events/NotificationWillBeSent.php b/src/Events/NotificationWillBeSent.php similarity index 91% rename from src/Core/Notifications/Events/NotificationWillBeSent.php rename to src/Events/NotificationWillBeSent.php index fc30a253e..030332514 100644 --- a/src/Core/Notifications/Events/NotificationWillBeSent.php +++ b/src/Events/NotificationWillBeSent.php @@ -1,4 +1,4 @@ -blueprints = &$blueprints; + $this->serializers = &$serializers; + } + + public function register($blueprint, $serializer) + { + if (! (new ReflectionClass($blueprint))->implementsInterface('Flarum\Core\Activity\Blueprint')) { + throw new InvalidArgumentException('Activity blueprint ' . $blueprint + . ' must implement Flarum\Core\Activity\Blueprint'); + } + + $this->blueprints[] = $blueprint; + + $this->serializers[$blueprint::getType()] = $serializer; + } +} diff --git a/src/Events/RegisterApiRoutes.php b/src/Events/RegisterApiRoutes.php new file mode 100644 index 000000000..aca6a9846 --- /dev/null +++ b/src/Events/RegisterApiRoutes.php @@ -0,0 +1,19 @@ +routes = $routes; + } +} diff --git a/src/Events/RegisterDiscussionGambits.php b/src/Events/RegisterDiscussionGambits.php new file mode 100644 index 000000000..c7ca0ec40 --- /dev/null +++ b/src/Events/RegisterDiscussionGambits.php @@ -0,0 +1,19 @@ +gambits = $gambits; + } +} diff --git a/src/Events/RegisterForumRoutes.php b/src/Events/RegisterForumRoutes.php new file mode 100644 index 000000000..d859930b5 --- /dev/null +++ b/src/Events/RegisterForumRoutes.php @@ -0,0 +1,19 @@ +routes = $routes; + } +} diff --git a/src/Events/RegisterLocales.php b/src/Events/RegisterLocales.php new file mode 100644 index 000000000..737698433 --- /dev/null +++ b/src/Events/RegisterLocales.php @@ -0,0 +1,24 @@ +manager = $manager; + } + + public function addTranslations($locale, $file) + { + $this->manager->addTranslations($locale, $file); + } +} diff --git a/src/Events/RegisterNotificationTypes.php b/src/Events/RegisterNotificationTypes.php new file mode 100644 index 000000000..0c44fc9b1 --- /dev/null +++ b/src/Events/RegisterNotificationTypes.php @@ -0,0 +1,35 @@ +blueprints = &$blueprints; + $this->serializers = &$serializers; + } + + public function register($blueprint, $serializer, $enabledByDefault = []) + { + if (! (new ReflectionClass($blueprint))->implementsInterface('Flarum\Core\Notifications\Blueprint')) { + throw new InvalidArgumentException('Notification blueprint ' . $blueprint + . ' must implement Flarum\Core\Notifications\Blueprint'); + } + + $this->blueprints[$blueprint] = $enabledByDefault; + + $this->serializers[$blueprint::getType()] = $serializer; + } +} diff --git a/src/Events/RegisterPostTypes.php b/src/Events/RegisterPostTypes.php new file mode 100644 index 000000000..5798fd137 --- /dev/null +++ b/src/Events/RegisterPostTypes.php @@ -0,0 +1,16 @@ +models = &$models; + } + + public function register($class) + { + $this->models[] = $class; + } +} diff --git a/src/Events/RegisterUserGambits.php b/src/Events/RegisterUserGambits.php new file mode 100644 index 000000000..c906b5d72 --- /dev/null +++ b/src/Events/RegisterUserGambits.php @@ -0,0 +1,27 @@ +gambits = $gambits; + } + + /** + * @param string $gambit + */ + public function register($gambit) + { + $this->gambits->add($gambit); + } +} diff --git a/src/Events/RegisterUserPreferences.php b/src/Events/RegisterUserPreferences.php new file mode 100644 index 000000000..359017fc0 --- /dev/null +++ b/src/Events/RegisterUserPreferences.php @@ -0,0 +1,11 @@ +model = $model; + $this->query = $query; + $this->actor = $actor; + } +} diff --git a/src/Events/ScopePostVisibility.php b/src/Events/ScopePostVisibility.php new file mode 100644 index 000000000..8fa63f822 --- /dev/null +++ b/src/Events/ScopePostVisibility.php @@ -0,0 +1,38 @@ +discussion = $discussion; + $this->query = $query; + $this->actor = $actor; + } +} diff --git a/src/Core/Users/Events/UserAvatarWasChanged.php b/src/Events/UserAvatarWasChanged.php similarity index 88% rename from src/Core/Users/Events/UserAvatarWasChanged.php rename to src/Events/UserAvatarWasChanged.php index c90a8e237..0ac341464 100644 --- a/src/Core/Users/Events/UserAvatarWasChanged.php +++ b/src/Events/UserAvatarWasChanged.php @@ -1,4 +1,4 @@ -class = $class; - } - - public function subjectSerializer($serializer) - { - $this->serializer = $serializer; - - return $this; - } - - public function extend(Container $container) - { - $class = $this->class; - $type = $class::getType(); - - Activity::setSubjectModel($type, $class::getSubjectModel()); - - if ($this->serializer) { - ActivitySerializer::setSubjectSerializer($type, $this->serializer); - } - } -} diff --git a/src/Extend/AdminClient.php b/src/Extend/AdminClient.php deleted file mode 100644 index b12e82ae5..000000000 --- a/src/Extend/AdminClient.php +++ /dev/null @@ -1,34 +0,0 @@ -assets = array_merge($this->assets, $assets); - - return $this; - } - - public function translations($keys) - { - $this->translations = array_merge($this->translations, $keys); - - return $this; - } - - public function extend(Container $container) - { - $container->make('events')->listen('Flarum\Admin\Events\RenderView', function ($event) { - $event->assets->addFiles($this->assets); - }); - - IndexAction::$translations = array_merge(IndexAction::$translations, $this->translations); - } -} diff --git a/src/Extend/Api.php b/src/Extend/Api.php deleted file mode 100644 index 616f2d07f..000000000 --- a/src/Extend/Api.php +++ /dev/null @@ -1,35 +0,0 @@ -routes[] = compact('method', 'url', 'name', 'action'); - - return $this; - } - - public function extend(Container $container) - { - if ($container->make('type') !== 'api') { - return; - } - - if (count($this->routes)) { - $routes = $container->make('flarum.api.routes'); - - foreach ($this->routes as $route) { - $method = $route['method']; - $routes->$method($route['url'], $route['name'], function (ServerRequestInterface $httpRequest, $routeParams) use ($container, $route) { - $action = $container->make($route['action']); - - return $action->handle($httpRequest, $routeParams); - }); - } - } - } -} diff --git a/src/Extend/ApiAction.php b/src/Extend/ApiAction.php deleted file mode 100644 index cd2f25951..000000000 --- a/src/Extend/ApiAction.php +++ /dev/null @@ -1,143 +0,0 @@ -action = $action; - } - - public function serializer($serializer) - { - $this->serializer = $serializer; - - return $this; - } - - public function addInclude($relation, $default = true) - { - $this->addInclude[] = compact('relation', 'default'); - - return $this; - } - - public function removeInclude($relation) - { - $this->removeInclude[] = $relation; - - return $this; - } - - public function addLink($relation) - { - $this->addLink[] = $relation; - - return $this; - } - - public function removeLink($relation) - { - $this->removeLink[] = $relation; - - return $this; - } - - public function limitMax($limitMax) - { - $this->limitMax = $limitMax; - - return $this; - } - - public function limit($limit) - { - $this->limit = $limit; - - return $this; - } - - public function addSortField($field) - { - $this->addSortFields[] = $field; - - return $this; - } - - public function removeSortField($field) - { - $this->removeSortFields[] = $field; - - return $this; - } - - public function sort($sort) - { - $this->sort = $sort; - - return $this; - } - - public function extend(Container $container) - { - foreach ((array) $this->action as $action) { - if ($this->serializer) { - $action::$serializer = $this->serializer; - } - foreach ($this->addInclude as $include) { - $action::$include[$include['relation']] = $include['default']; - } - foreach ($this->removeInclude as $relation) { - unset($action::$include[$relation]); - } - foreach ($this->addLink as $relation) { - $action::$link[] = $relation; - } - foreach ($this->removeLink as $relation) { - if (($k = array_search($relation, $action::$link)) !== false) { - unset($action::$link[$k]); - } - } - if ($this->limitMax) { - $action::$limitMax = $this->limitMax; - } - if ($this->limit) { - $action::$limit = $this->limit; - } - foreach ($this->addSortFields as $field) { - $action::$sortFields[] = $field; - } - foreach ($this->removeSortFields as $field) { - if (($k = array_search($field, $action::$sortFields)) !== false) { - unset($action::$sortFields[$k]); - } - } - if ($this->sort) { - $action::$sort = $this->sort; - } - } - } -} diff --git a/src/Extend/ApiSerializer.php b/src/Extend/ApiSerializer.php deleted file mode 100644 index 14010c745..000000000 --- a/src/Extend/ApiSerializer.php +++ /dev/null @@ -1,61 +0,0 @@ -serializer = $serializer; - } - - public function attributes($callback) - { - $this->attributes[] = $callback; - - return $this; - } - - public function hasOne($relation, $related) - { - $this->relations[$relation] = function ($serializer) use ($relation, $related) { - return $serializer->hasOne($related, $relation); - }; - - return $this; - } - - public function hasMany($relation, $related) - { - $this->relations[$relation] = function ($serializer) use ($relation, $related) { - return $serializer->hasMany($related, $relation); - }; - - return $this; - } - - public function extend(Container $container) - { - $serializer = $this->serializer; - - if (count($this->attributes)) { - $container->make('events')->listen('Flarum\Api\Events\SerializeAttributes', function ($event) use ($serializer) { - if ($event->serializer instanceof $serializer) { - foreach ($this->attributes as $callback) { - $callback($event->attributes, $event->model, $event->serializer->actor->getUser()); - } - } - }); - } - - foreach ($this->relations as $relation => $callback) { - $serializer::addRelationship($relation, $callback); - } - } -} diff --git a/src/Extend/DiscussionGambit.php b/src/Extend/DiscussionGambit.php deleted file mode 100644 index 40ee5dbbb..000000000 --- a/src/Extend/DiscussionGambit.php +++ /dev/null @@ -1,18 +0,0 @@ -class = $class; - } - - public function extend(Container $container) - { - $container->make('flarum.discussionGambits')[] = $this->class; - } -} diff --git a/src/Extend/EventSubscriber.php b/src/Extend/EventSubscriber.php deleted file mode 100644 index 27b129afc..000000000 --- a/src/Extend/EventSubscriber.php +++ /dev/null @@ -1,20 +0,0 @@ -subscriber = $subscriber; - } - - public function extend(Container $container) - { - foreach ((array) $this->subscriber as $subscriber) { - $container->make('events')->subscribe($subscriber); - } - } -} diff --git a/src/Extend/ExtenderInterface.php b/src/Extend/ExtenderInterface.php deleted file mode 100644 index adc96c30a..000000000 --- a/src/Extend/ExtenderInterface.php +++ /dev/null @@ -1,8 +0,0 @@ -assets = array_merge($this->assets, $assets); - - return $this; - } - - public function translations($keys) - { - $this->translations = array_merge($this->translations, $keys); - - return $this; - } - - public function route($method, $url, $name, $action = 'Flarum\Forum\Actions\IndexAction') - { - $this->routes[] = compact('method', 'url', 'name', 'action'); - - return $this; - } - - public function extend(Container $container) - { - if ($container->make('type') !== 'forum') { - return; - } - - $container->make('events')->listen('Flarum\Forum\Events\RenderView', function ($event) { - $event->assets->addFiles($this->assets); - }); - - IndexAction::$translations = array_merge(IndexAction::$translations, $this->translations); - - if (count($this->routes)) { - $routes = $container->make('flarum.forum.routes'); - - foreach ($this->routes as $route) { - $method = $route['method']; - $routes->$method($route['url'], $route['name'], function (ServerRequestInterface $httpRequest, $routeParams) use ($container, $route) { - $action = $container->make($route['action']); - - return $action->handle($httpRequest, $routeParams); - }); - } - } - } -} diff --git a/src/Extend/Locale.php b/src/Extend/Locale.php deleted file mode 100644 index 6178db770..000000000 --- a/src/Extend/Locale.php +++ /dev/null @@ -1,57 +0,0 @@ -locale = $locale; - } - - public function translations($translations) - { - $this->translations = $translations; - - return $this; - } - - public function config($config) - { - $this->config = $config; - - return $this; - } - - public function js($js) - { - $this->js = $js; - - return $this; - } - - public function extend(Container $container) - { - $manager = $container->make('flarum.localeManager'); - - if ($this->translations) { - $manager->addTranslations($this->locale, $this->translations); - } - - if ($this->config) { - $manager->addConfig($this->locale, $this->config); - } - - if ($this->js) { - $manager->addJsFile($this->locale, $this->js); - } - } -} diff --git a/src/Extend/Model.php b/src/Extend/Model.php deleted file mode 100644 index bc449e0ec..000000000 --- a/src/Extend/Model.php +++ /dev/null @@ -1,96 +0,0 @@ -model = $model; - } - - public function scopeVisible(Closure $callback) - { - $this->scopeVisible[] = $callback; - - return $this; - } - - public function allow($action, Closure $callback) - { - $this->allow[] = compact('action', 'callback'); - - return $this; - } - - public function date($attribute) - { - $this->dates[] = $attribute; - } - - public function hasOne($relation, $related, $foreignKey = null, $localKey = null) - { - $this->relations[$relation] = function ($model) use ($relation, $related, $foreignKey, $localKey) { - return $model->hasOne($related, $foreignKey, $localKey, $relation); - }; - - return $this; - } - - public function belongsTo($relation, $related, $foreignKey = null, $otherKey = null) - { - $this->relations[$relation] = function ($model) use ($relation, $related, $foreignKey, $otherKey) { - return $model->belongsTo($related, $foreignKey, $otherKey, $relation); - }; - - return $this; - } - - public function hasMany($relation, $related, $foreignKey = null, $localKey = null) - { - $this->relations[$relation] = function ($model) use ($relation, $related, $foreignKey, $localKey) { - return $model->hasMany($related, $foreignKey, $localKey, $relation); - }; - - return $this; - } - - public function belongsToMany($relation, $related, $table = null, $foreignKey = null, $otherKey = null) - { - $this->relations[$relation] = function ($model) use ($relation, $related, $table, $foreignKey, $otherKey) { - return $model->belongsToMany($related, $table, $foreignKey, $otherKey, $relation); - }; - - return $this; - } - - public function extend(Container $container) - { - $model = $this->model; - - foreach ($this->relations as $relation => $callback) { - $model::setRelationMethod($relation, $callback); - } - - foreach ($this->scopeVisible as $callback) { - $model::addVisibleScope($callback); - } - - foreach ($this->allow as $info) { - $model::allow($info['action'], $info['callback']); - } - - foreach ($this->dates as $attribute) { - $model::addDateAttribute($attribute); - } - } -} diff --git a/src/Extend/NotificationType.php b/src/Extend/NotificationType.php deleted file mode 100644 index 6517b86ce..000000000 --- a/src/Extend/NotificationType.php +++ /dev/null @@ -1,51 +0,0 @@ -class = $class; - } - - public function subjectSerializer($serializer) - { - $this->serializer = $serializer; - - return $this; - } - - public function enableByDefault($method) - { - $this->enabled[] = $method; - - return $this; - } - - public function extend(Container $container) - { - $class = $this->class; - $type = $class::getType(); - - Notification::setSubjectModel($type, $class::getSubjectModel()); - - User::addPreference(User::getNotificationPreferenceKey($type, 'alert'), 'boolval', in_array('alert', $this->enabled)); - - if ((new ReflectionClass($class))->implementsInterface('Flarum\Core\Notifications\MailableBlueprint')) { - User::addPreference(User::getNotificationPreferenceKey($type, 'email'), 'boolval', in_array('email', $this->enabled)); - } - - NotificationSerializer::setSubjectSerializer($type, $this->serializer); - } -} diff --git a/src/Extend/PostFormatter.php b/src/Extend/PostFormatter.php deleted file mode 100644 index 04018272c..000000000 --- a/src/Extend/PostFormatter.php +++ /dev/null @@ -1,20 +0,0 @@ -class = $class; - } - - public function extend(Container $container) - { - $container->make('flarum.formatter')->add($this->class); - } -} diff --git a/src/Extend/PostType.php b/src/Extend/PostType.php deleted file mode 100644 index 57e762ef0..000000000 --- a/src/Extend/PostType.php +++ /dev/null @@ -1,21 +0,0 @@ -class = $class; - } - - public function extend(Container $container) - { - $class = $this->class; - - Post::setModel($class::$type, $class); - } -} diff --git a/src/Forum/Actions/ClientAction.php b/src/Forum/Actions/ClientAction.php index 57e511a89..b63afaf02 100644 --- a/src/Forum/Actions/ClientAction.php +++ b/src/Forum/Actions/ClientAction.php @@ -44,7 +44,7 @@ abstract class ClientAction extends BaseClientAction 'core.delete_forever', 'core.deleted', 'core.disclose_online', - 'core.discussion_renamed', + 'core.discussion_renamed_post', 'core.discussion_renamed_notification', 'core.discussion_replied', 'core.discussion_started', @@ -112,10 +112,11 @@ abstract class ClientAction extends BaseClientAction 'core.send_password_reset_email', 'core.settings', 'core.sign_up', + 'core.sort_latest', 'core.sort_newest', 'core.sort_oldest', - 'core.sort_recent', - 'core.sort_replies', + 'core.sort_relevance', + 'core.sort_top', 'core.start_a_discussion', 'core.started_a_discussion', 'core.unread_posts', diff --git a/src/Forum/Actions/LoginAction.php b/src/Forum/Actions/LoginAction.php index 833f90ea6..dfcf1a863 100644 --- a/src/Forum/Actions/LoginAction.php +++ b/src/Forum/Actions/LoginAction.php @@ -1,7 +1,7 @@ command = $command; - $this->params = $params; - } -} diff --git a/src/Forum/Events/RenderView.php b/src/Forum/Events/RenderView.php deleted file mode 100644 index 1607288cf..000000000 --- a/src/Forum/Events/RenderView.php +++ /dev/null @@ -1,17 +0,0 @@ -view = &$view; - $this->assets = $assets; - $this->action = $action; - } -} diff --git a/src/Forum/ForumServiceProvider.php b/src/Forum/ForumServiceProvider.php index 354cac101..9f8cc6a47 100644 --- a/src/Forum/ForumServiceProvider.php +++ b/src/Forum/ForumServiceProvider.php @@ -1,6 +1,7 @@ action('Flarum\Forum\Actions\SavePasswordAction') ); + + event(new RegisterForumRoutes($routes)); } protected function action($class) diff --git a/src/Locale/JsCompiler.php b/src/Locale/JsCompiler.php index 1d574956a..96b18e10f 100644 --- a/src/Locale/JsCompiler.php +++ b/src/Locale/JsCompiler.php @@ -13,14 +13,18 @@ class JsCompiler extends RevisionCompiler public function compile() { - $output = "var initLocale = function(app) { - app.translator.translations = ".json_encode($this->translations).";"; + $output = "System.register('locale', [], function() { + return { + execute: function() { + app.translator.translations = ".json_encode($this->translations).";\n"; foreach ($this->files as $filename) { $output .= file_get_contents($filename); } - $output .= "};"; + $output .= "} + }; +});"; return $output; } diff --git a/src/Locale/LocaleServiceProvider.php b/src/Locale/LocaleServiceProvider.php index ac339dca8..0ce07488e 100644 --- a/src/Locale/LocaleServiceProvider.php +++ b/src/Locale/LocaleServiceProvider.php @@ -1,7 +1,7 @@ setupLocale('en'); + $manager = $this->app->make('flarum.localeManager'); + + $this->registerLocale($manager, 'en'); + + event(new RegisterLocales($manager)); } - public function setupLocale($locale) + public function registerLocale(LocaleManager $manager, $locale) { $dir = __DIR__.'/../../locale/'.$locale; - $this->extend( - (new Extend\Locale($locale)) - ->translations($dir.'/translations.yml') - ->config($dir.'/config.php') - ->js($dir.'/config.js') - ); + $manager->addTranslations($locale, $dir.'/translations.yml'); + $manager->addConfig($locale, $dir.'/config.php'); + $manager->addJsFile($locale, $dir.'/config.js'); } public function register() diff --git a/src/Support/ClientAction.php b/src/Support/ClientAction.php index 3ad5c5a96..64025e29f 100644 --- a/src/Support/ClientAction.php +++ b/src/Support/ClientAction.php @@ -7,6 +7,7 @@ use Flarum\Assets\LessCompiler; use Flarum\Core; use Flarum\Core\Settings\SettingsRepository; use Flarum\Core\Users\User; +use Flarum\Events\BuildClientView; use Flarum\Locale\JsCompiler as LocaleJsCompiler; use Flarum\Locale\LocaleManager; use Psr\Http\Message\ServerRequestInterface as Request; @@ -107,7 +108,7 @@ abstract class ClientAction extends HtmlAction $translations = $this->locales->getTranslations($actor->locale); $keys = $this->translationKeys; - // TODO: event($this, $view, $keys) + event(new BuildClientView($this, $view, $keys)); $translations = $this->filterTranslations($translations, $keys); diff --git a/src/Support/ClientView.php b/src/Support/ClientView.php index c36388c64..c2679a6f2 100644 --- a/src/Support/ClientView.php +++ b/src/Support/ClientView.php @@ -47,6 +47,13 @@ class ClientView implements Renderable */ protected $layout; + /** + * 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 . * @@ -156,6 +163,16 @@ class ClientView implements Renderable $this->footStrings[] = $string; } + /** + * 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. * @@ -196,6 +213,7 @@ class ClientView implements Renderable $view->head = implode("\n", $this->headStrings); $view->foot = implode("\n", $this->footStrings); + $view->bootstrappers = $this->bootstrappers; return $view->render(); } diff --git a/src/Support/Extension.php b/src/Support/Extension.php new file mode 100644 index 000000000..65b947b28 --- /dev/null +++ b/src/Support/Extension.php @@ -0,0 +1,7 @@ +app->make('Flarum\Core\Settings\SettingsRepository')->get('extensions_enabled'); - $extensions = json_decode($config, true); - $providers = []; - - foreach ($extensions as $extension) { - if (file_exists($file = public_path().'/extensions/'.$extension.'/bootstrap.php') || - file_exists($file = base_path().'/extensions/'.$extension.'/bootstrap.php')) { - $providers[$extension] = require $file; - } - } - - // @todo store $providers somewhere (in Core?) so that extensions can talk to each other - } -} diff --git a/src/Support/ExtensionsServiceProvider.php b/src/Support/ExtensionsServiceProvider.php new file mode 100644 index 000000000..8399c4897 --- /dev/null +++ b/src/Support/ExtensionsServiceProvider.php @@ -0,0 +1,23 @@ +app->make('Flarum\Core\Settings\SettingsRepository')->get('extensions_enabled'); + $extensions = json_decode($config, true); + $providers = []; + + foreach ($extensions as $extension) { + if (file_exists($file = public_path().'/extensions/'.$extension.'/bootstrap.php')) { + $providerName = require $file; + $providers[$extension] = $this->app->register($providerName); + } + } + } +} diff --git a/src/Support/ServiceProvider.php b/src/Support/ServiceProvider.php index f67858b49..b50ac38f3 100644 --- a/src/Support/ServiceProvider.php +++ b/src/Support/ServiceProvider.php @@ -1,22 +1,9 @@ extend($this->extenders()); - } - /** * Register the service provider. * @@ -25,32 +12,4 @@ class ServiceProvider extends IlluminateServiceProvider public function register() { } - - /** - * @return ExtenderInterface[] - */ - public function extenders() - { - return []; - } - - /** - * @param ExtenderInterface|ExtenderInterface[] $extenders - * @return void - */ - protected function extend($extenders) - { - if (! is_array($extenders)) { - $extenders = [$extenders]; - } - - foreach ($extenders as $extender) { - if (! $extender instanceof ExtenderInterface) { - throw new InvalidArgumentException('Argument must be an object of type ' - . ExtenderInterface::class); - } - - $extender->extend($this->app); - } - } } diff --git a/stubs/extension/.editorconfig b/stubs/extension/.editorconfig new file mode 100644 index 000000000..5612a5e74 --- /dev/null +++ b/stubs/extension/.editorconfig @@ -0,0 +1,32 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.js] +indent_style = space +indent_size = 2 + +[*.{css,less}] +indent_style = space +indent_size = 2 + +[*.html] +indent_style = space +indent_size = 2 + +[*.{diff,md}] +trim_trailing_whitespace = false + +[*.php] +indent_style = space +indent_size = 4 diff --git a/stubs/extension/.eslintignore b/stubs/extension/.eslintignore new file mode 100644 index 000000000..86b7c8854 --- /dev/null +++ b/stubs/extension/.eslintignore @@ -0,0 +1,5 @@ +**/bower_components/**/* +**/node_modules/**/* +vendor/**/* +**/Gulpfile.js +**/dist/**/* diff --git a/stubs/extension/.eslintrc b/stubs/extension/.eslintrc new file mode 100644 index 000000000..9cebc759d --- /dev/null +++ b/stubs/extension/.eslintrc @@ -0,0 +1,171 @@ +{ + "parser": "babel-eslint", // https://github.com/babel/babel-eslint + "env": { // http://eslint.org/docs/user-guide/configuring.html#specifying-environments + "browser": true // browser global variables + }, + "ecmaFeatures": { + "arrowFunctions": true, + "blockBindings": true, + "classes": true, + "defaultParams": true, + "destructuring": true, + "forOf": true, + "generators": false, + "modules": true, + "objectLiteralComputedProperties": true, + "objectLiteralDuplicateProperties": false, + "objectLiteralShorthandMethods": true, + "objectLiteralShorthandProperties": true, + "spread": true, + "superInFunctions": true, + "templateStrings": true, + "jsx": true + }, + "globals": { + "m": true, + "app": true, + "$": true, + "moment": true + }, + "rules": { +/** + * Strict mode + */ + // babel inserts "use strict"; for us + "strict": [2, "never"], // http://eslint.org/docs/rules/strict + +/** + * ES6 + */ + "no-var": 2, // http://eslint.org/docs/rules/no-var + "prefer-const": 2, // http://eslint.org/docs/rules/prefer-const + +/** + * Variables + */ + "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow + "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names + "no-unused-vars": [2, { // http://eslint.org/docs/rules/no-unused-vars + "vars": "local", + "args": "after-used" + }], + "no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define + +/** + * Possible errors + */ + "comma-dangle": [2, "never"], // http://eslint.org/docs/rules/comma-dangle + "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign + "no-console": 1, // http://eslint.org/docs/rules/no-console + "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger + "no-alert": 1, // http://eslint.org/docs/rules/no-alert + "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition + "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys + "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case + "no-empty": 2, // http://eslint.org/docs/rules/no-empty + "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign + "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast + "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi + "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign + "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations + "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp + "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace + "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls + "no-reserved-keys": 2, // http://eslint.org/docs/rules/no-reserved-keys + "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays + "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable + "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan + "block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var + +/** + * Best practices + */ + "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return + "curly": [2, "multi-line"], // http://eslint.org/docs/rules/curly + "default-case": 2, // http://eslint.org/docs/rules/default-case + "dot-notation": [2, { // http://eslint.org/docs/rules/dot-notation + "allowKeywords": true + }], + "eqeqeq": 2, // http://eslint.org/docs/rules/eqeqeq + "no-caller": 2, // http://eslint.org/docs/rules/no-caller + "no-else-return": 2, // http://eslint.org/docs/rules/no-else-return + "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null + "no-eval": 2, // http://eslint.org/docs/rules/no-eval + "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native + "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind + "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough + "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal + "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval + "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks + "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func + "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str + "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign + "no-new": 2, // http://eslint.org/docs/rules/no-new + "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func + "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers + "no-octal": 2, // http://eslint.org/docs/rules/no-octal + "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape + "no-param-reassign": 2, // http://eslint.org/docs/rules/no-param-reassign + "no-proto": 2, // http://eslint.org/docs/rules/no-proto + "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare + "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign + "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare + "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences + "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal + "no-with": 2, // http://eslint.org/docs/rules/no-with + "radix": 2, // http://eslint.org/docs/rules/radix + "vars-on-top": 2, // http://eslint.org/docs/rules/vars-on-top + "wrap-iife": [2, "any"], // http://eslint.org/docs/rules/wrap-iife + "yoda": 2, // http://eslint.org/docs/rules/yoda + +/** + * Style + */ + "indent": [2, 2], // http://eslint.org/docs/rules/indent + "brace-style": [2, // http://eslint.org/docs/rules/brace-style + "1tbs", { + "allowSingleLine": true + }], + "quotes": [ + 2, "single", "avoid-escape" // http://eslint.org/docs/rules/quotes + ], + "camelcase": [2, { // http://eslint.org/docs/rules/camelcase + "properties": "never" + }], + "comma-spacing": [2, { // http://eslint.org/docs/rules/comma-spacing + "before": false, + "after": true + }], + "comma-style": [2, "last"], // http://eslint.org/docs/rules/comma-style + "eol-last": 2, // http://eslint.org/docs/rules/eol-last + "func-names": 1, // http://eslint.org/docs/rules/func-names + "key-spacing": [2, { // http://eslint.org/docs/rules/key-spacing + "beforeColon": false, + "afterColon": true + }], + "new-cap": [2, { // http://eslint.org/docs/rules/new-cap + "newIsCap": true + }], + "no-multiple-empty-lines": [2, { // http://eslint.org/docs/rules/no-multiple-empty-lines + "max": 2 + }], + "no-new-object": 2, // http://eslint.org/docs/rules/no-new-object + "no-spaced-func": 2, // http://eslint.org/docs/rules/no-spaced-func + "no-trailing-spaces": 2, // http://eslint.org/docs/rules/no-trailing-spaces + "no-wrap-func": 2, // http://eslint.org/docs/rules/no-wrap-func + "no-underscore-dangle": 0, // http://eslint.org/docs/rules/no-underscore-dangle + "one-var": [2, "never"], // http://eslint.org/docs/rules/one-var + "padded-blocks": [2, "never"], // http://eslint.org/docs/rules/padded-blocks + "semi": [2, "always"], // http://eslint.org/docs/rules/semi + "semi-spacing": [2, { // http://eslint.org/docs/rules/semi-spacing + "before": false, + "after": true + }], + "space-after-keywords": 2, // http://eslint.org/docs/rules/space-after-keywords + "space-before-blocks": 2, // http://eslint.org/docs/rules/space-before-blocks + "space-before-function-paren": [2, "never"], // http://eslint.org/docs/rules/space-before-function-paren + "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops + "space-return-throw-case": 2, // http://eslint.org/docs/rules/space-return-throw-case + "spaced-line-comment": 2, // http://eslint.org/docs/rules/spaced-line-comment + } +} diff --git a/stubs/extension/bootstrap.php b/stubs/extension/bootstrap.php index 1d29892f9..891396609 100644 --- a/stubs/extension/bootstrap.php +++ b/stubs/extension/bootstrap.php @@ -4,6 +4,7 @@ // classes in the src directory to be autoloaded. require __DIR__.'/vendor/autoload.php'; -// Register our service provider with the Flarum application. In here we can -// register bindings and execute code when the application boots. -return $this->app->register('{{namespace}}\{{classPrefix}}ServiceProvider'); +// Return the name of our Extension class. Flarum will register it as a service +// provider, allowing it to register bindings and execute code when the +// application boots. +return '{{namespace}}\Extension'; diff --git a/stubs/extension/js/bootstrap.js b/stubs/extension/js/bootstrap.js deleted file mode 100644 index 421294ea7..000000000 --- a/stubs/extension/js/bootstrap.js +++ /dev/null @@ -1,8 +0,0 @@ -import { extend, override } from 'flarum/extension-utils'; -import app from 'flarum/app'; - -app.initializers.add('{{name}}', function() { - - // @todo - -}); diff --git a/stubs/extension/js/src/main.js b/stubs/extension/js/src/main.js new file mode 100644 index 000000000..ac168d85b --- /dev/null +++ b/stubs/extension/js/src/main.js @@ -0,0 +1,4 @@ +import { extend } from 'flarum/extend'; +import app from 'flarum/app'; + +// TODO diff --git a/stubs/extension/locale/en.yml b/stubs/extension/locale/en.yml index 7d28ca184..15828fbe1 100644 --- a/stubs/extension/locale/en.yml +++ b/stubs/extension/locale/en.yml @@ -1,2 +1,2 @@ {{name}}: - # hello_world: Hello, world! + # hello_world: "Hello, world!" diff --git a/stubs/extension/src/Extension.php b/stubs/extension/src/Extension.php new file mode 100644 index 000000000..a64d047d1 --- /dev/null +++ b/stubs/extension/src/Extension.php @@ -0,0 +1,27 @@ +subscribe('{{namespace}}\Listeners\AddClientAssets'); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + // + } +} diff --git a/stubs/extension/src/Listeners/AddClientAssets.php b/stubs/extension/src/Listeners/AddClientAssets.php new file mode 100755 index 000000000..4a3634b50 --- /dev/null +++ b/stubs/extension/src/Listeners/AddClientAssets.php @@ -0,0 +1,33 @@ +listen(RegisterLocales::class, __CLASS__.'@addLocale'); + $events->listen(BuildClientView::class, __CLASS__.'@addAssets'); + } + + public function addLocale(RegisterLocales $event) + { + $event->addTranslations('en', __DIR__.'/../../locale/en.yml'); + } + + public function addAssets(BuildClientView $event) + { + $event->forumAssets([ + __DIR__.'/../../js/dist/extension.js', + __DIR__.'/../../less/extension.less' + ]); + + $event->forumBootstrapper('{{name}}/main'); + + $event->forumTranslations([ + // '{{name}}.hello_world' + ]); + } +} diff --git a/stubs/extension/src/ServiceProvider.php b/stubs/extension/src/ServiceProvider.php deleted file mode 100644 index dc71b1235..000000000 --- a/stubs/extension/src/ServiceProvider.php +++ /dev/null @@ -1,39 +0,0 @@ -extend([ - (new Extend\Locale('en'))->translations(__DIR__.'/../locale/en.yml'), - - (new Extend\ForumClient()) - ->assets([ - __DIR__.'/../js/dist/extension.js', - __DIR__.'/../less/extension.less' - ]) - ->translations([ - // Add the keys of translations you would like to be available - // for use by the JS client application. - ]) - ]); - } - - /** - * Register the service provider. - * - * @return void - */ - public function register() - { - // - } -} diff --git a/views/app.blade.php b/views/app.blade.php index 57ad2af8d..5a3627f03 100644 --- a/views/app.blade.php +++ b/views/app.blade.php @@ -35,7 +35,11 @@ document: {!! json_encode($document) !!}, session: {!! json_encode($session) !!} }; - initLocale(app); + + @foreach ($bootstrappers as $bootstrapper) + System.import('{{ $bootstrapper }}'); + @endforeach + app.boot(); } catch (e) { document.write('
Something went wrong.
');