mirror of
https://github.com/flarum/core.git
synced 2025-08-13 11:54:32 +02:00
Compare commits
1 Commits
as/depreca
...
cw/drop-mo
Author | SHA1 | Date | |
---|---|---|---|
|
df77ccf7ac |
@@ -76,11 +76,11 @@
|
||||
"psr/http-server-handler": "^1.0",
|
||||
"psr/http-server-middleware": "^1.0",
|
||||
"s9e/text-formatter": "^2.3.6",
|
||||
"symfony/config": "^4.3.4",
|
||||
"symfony/console": "^4.3.4",
|
||||
"symfony/event-dispatcher": "^4.3.4",
|
||||
"symfony/translation": "^4.3.4",
|
||||
"symfony/yaml": "^4.3.4",
|
||||
"symfony/config": "^3.3",
|
||||
"symfony/console": "^4.2",
|
||||
"symfony/event-dispatcher": "^4.3.2",
|
||||
"symfony/translation": "^3.3",
|
||||
"symfony/yaml": "^3.3",
|
||||
"tobscure/json-api": "^0.3.0",
|
||||
"wikimedia/less.php": "^3.0"
|
||||
},
|
||||
|
4
js/dist/admin.js
vendored
4
js/dist/admin.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/admin.js.map
vendored
2
js/dist/admin.js.map
vendored
File diff suppressed because one or more lines are too long
6
js/dist/forum.js
vendored
6
js/dist/forum.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/forum.js.map
vendored
2
js/dist/forum.js.map
vendored
File diff suppressed because one or more lines are too long
2
js/shims.d.ts
vendored
2
js/shims.d.ts
vendored
@@ -4,6 +4,7 @@ import Mithril from 'mithril';
|
||||
// Other third-party libs
|
||||
import * as _dayjs from 'dayjs';
|
||||
import * as _$ from 'jquery';
|
||||
import * as _ColorThief from 'color-thief-browser';
|
||||
|
||||
// Globals from flarum/core
|
||||
import Application from './src/common/Application';
|
||||
@@ -22,6 +23,7 @@ declare global {
|
||||
const $: typeof _$;
|
||||
const m: Mithril.Static;
|
||||
const dayjs: typeof _dayjs;
|
||||
const ColorThief: _ColorThief;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,3 +1,17 @@
|
||||
import Store from './Store';
|
||||
import Mithril from 'mithril';
|
||||
|
||||
interface ModelData {
|
||||
type?: string;
|
||||
id?: string;
|
||||
attributes?: any;
|
||||
relationships?: any;
|
||||
}
|
||||
|
||||
interface SaveOptions extends Mithril.RequestOptions<any> {
|
||||
meta?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `Model` class represents a local data resource. It provides methods to
|
||||
* persist changes via the API.
|
||||
@@ -5,55 +19,58 @@
|
||||
* @abstract
|
||||
*/
|
||||
export default class Model {
|
||||
/**
|
||||
* The resource object from the API.
|
||||
*
|
||||
* @type {Object}
|
||||
* @public
|
||||
*/
|
||||
data: ModelData = {};
|
||||
|
||||
/**
|
||||
* The time at which the model's data was last updated. Watching the value
|
||||
* of this property is a fast way to retain/cache a subtree if data hasn't
|
||||
* changed.
|
||||
*
|
||||
* @type {Date}
|
||||
* @public
|
||||
*/
|
||||
freshness: Date = new Date();
|
||||
|
||||
/**
|
||||
* Whether or not the resource exists on the server.
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @public
|
||||
*/
|
||||
exists: boolean = false;
|
||||
|
||||
/**
|
||||
* The data store that this resource should be persisted to.
|
||||
*
|
||||
* @type {Store}
|
||||
* @protected
|
||||
*/
|
||||
store?: Store = null;
|
||||
|
||||
/**
|
||||
* @param {Object} data A resource object from the API.
|
||||
* @param {Store} store The data store that this model should be persisted to.
|
||||
* @public
|
||||
*/
|
||||
constructor(data = {}, store = null) {
|
||||
/**
|
||||
* The resource object from the API.
|
||||
*
|
||||
* @type {Object}
|
||||
* @public
|
||||
*/
|
||||
constructor(data: ModelData = {}, store = null) {
|
||||
this.data = data;
|
||||
|
||||
/**
|
||||
* The time at which the model's data was last updated. Watching the value
|
||||
* of this property is a fast way to retain/cache a subtree if data hasn't
|
||||
* changed.
|
||||
*
|
||||
* @type {Date}
|
||||
* @public
|
||||
*/
|
||||
this.freshness = new Date();
|
||||
|
||||
/**
|
||||
* Whether or not the resource exists on the server.
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @public
|
||||
*/
|
||||
this.exists = false;
|
||||
|
||||
/**
|
||||
* The data store that this resource should be persisted to.
|
||||
*
|
||||
* @type {Store}
|
||||
* @protected
|
||||
*/
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model's ID.
|
||||
*
|
||||
* @return {Integer}
|
||||
* @return {String}
|
||||
* @public
|
||||
* @final
|
||||
*/
|
||||
id() {
|
||||
id(): string | undefined {
|
||||
return this.data.id;
|
||||
}
|
||||
|
||||
@@ -121,8 +138,8 @@ export default class Model {
|
||||
* @return {Promise}
|
||||
* @public
|
||||
*/
|
||||
save(attributes, options = {}) {
|
||||
const data = {
|
||||
save(attributes, options: SaveOptions = {}) {
|
||||
const data: ModelData = {
|
||||
type: this.data.type,
|
||||
id: this.data.id,
|
||||
attributes,
|
||||
@@ -152,7 +169,7 @@ export default class Model {
|
||||
|
||||
this.pushData(data);
|
||||
|
||||
const request = { data };
|
||||
const request: any = { data };
|
||||
if (options.meta) request.meta = options.meta;
|
||||
|
||||
return app
|
||||
@@ -220,11 +237,11 @@ export default class Model {
|
||||
* @return {String}
|
||||
* @protected
|
||||
*/
|
||||
apiEndpoint() {
|
||||
apiEndpoint(): string {
|
||||
return '/' + this.data.type + (this.exists ? '/' + this.data.id : '');
|
||||
}
|
||||
|
||||
copyData() {
|
||||
copyData(): ModelData {
|
||||
return JSON.parse(JSON.stringify(this.data));
|
||||
}
|
||||
|
||||
@@ -236,8 +253,8 @@ export default class Model {
|
||||
* @return {*}
|
||||
* @public
|
||||
*/
|
||||
static attribute(name, transform) {
|
||||
return function () {
|
||||
static attribute<T>(name: string, transform?: Function) {
|
||||
return function (this: Model): T | null | undefined {
|
||||
const value = this.data.attributes && this.data.attributes[name];
|
||||
|
||||
return transform ? transform(value) : value;
|
||||
@@ -254,8 +271,8 @@ export default class Model {
|
||||
* has not been loaded; or the model if it has been loaded.
|
||||
* @public
|
||||
*/
|
||||
static hasOne(name) {
|
||||
return function () {
|
||||
static hasOne<T>(name: string) {
|
||||
return function (this: Model): T | null | false {
|
||||
if (this.data.relationships) {
|
||||
const relationship = this.data.relationships[name];
|
||||
|
||||
@@ -278,8 +295,8 @@ export default class Model {
|
||||
* loaded, and undefined for those that have not.
|
||||
* @public
|
||||
*/
|
||||
static hasMany(name) {
|
||||
return function () {
|
||||
static hasMany<T>(name: string) {
|
||||
return function (this: Model): T[] | false {
|
||||
if (this.data.relationships) {
|
||||
const relationship = this.data.relationships[name];
|
||||
|
||||
@@ -299,7 +316,7 @@ export default class Model {
|
||||
* @return {Date|null}
|
||||
* @public
|
||||
*/
|
||||
static transformDate(value) {
|
||||
static transformDate(value: string): Date | null {
|
||||
return value ? new Date(value) : null;
|
||||
}
|
||||
|
||||
@@ -310,7 +327,7 @@ export default class Model {
|
||||
* @return {Object}
|
||||
* @protected
|
||||
*/
|
||||
static getIdentifier(model) {
|
||||
static getIdentifier(model: Model) {
|
||||
return {
|
||||
type: model.data.type,
|
||||
id: model.data.id,
|
@@ -19,7 +19,6 @@ import extract from './utils/extract';
|
||||
import ScrollListener from './utils/ScrollListener';
|
||||
import stringToColor from './utils/stringToColor';
|
||||
import subclassOf from './utils/subclassOf';
|
||||
import SuperTextarea from './utils/SuperTextarea';
|
||||
import patchMithril from './utils/patchMithril';
|
||||
import classList from './utils/classList';
|
||||
import extractText from './utils/extractText';
|
||||
@@ -91,7 +90,6 @@ export default {
|
||||
'utils/stringToColor': stringToColor,
|
||||
'utils/Stream': Stream,
|
||||
'utils/subclassOf': subclassOf,
|
||||
'utils/SuperTextarea': SuperTextarea,
|
||||
'utils/setRouteWithForcedRefresh': setRouteWithForcedRefresh,
|
||||
'utils/patchMithril': patchMithril,
|
||||
'utils/classList': classList,
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import dayjs from 'dayjs';
|
||||
import * as Mithril from 'mithril';
|
||||
|
||||
/**
|
||||
* The `fullTime` helper displays a formatted time string wrapped in a <time>
|
||||
* tag.
|
||||
*
|
||||
* @param {Date} time
|
||||
* @return {Object}
|
||||
*/
|
||||
export default function fullTime(time: Date): Mithril.Vnode {
|
||||
export default function fullTime(time) {
|
||||
const d = dayjs(time);
|
||||
|
||||
const datetime = d.format();
|
@@ -1,13 +1,14 @@
|
||||
import dayjs from 'dayjs';
|
||||
import * as Mithril from 'mithril';
|
||||
import humanTimeUtil from '../utils/humanTime';
|
||||
|
||||
/**
|
||||
* The `humanTime` helper displays a time in a human-friendly time-ago format
|
||||
* (e.g. '12 days ago'), wrapped in a <time> tag with other information about
|
||||
* the time.
|
||||
*
|
||||
* @param {Date} time
|
||||
* @return {Object}
|
||||
*/
|
||||
export default function humanTime(time: Date): Mithril.Vnode {
|
||||
export default function humanTime(time) {
|
||||
const d = dayjs(time);
|
||||
|
||||
const datetime = d.format();
|
@@ -2,40 +2,40 @@ import Model from '../Model';
|
||||
import computed from '../utils/computed';
|
||||
import ItemList from '../utils/ItemList';
|
||||
import Badge from '../components/Badge';
|
||||
import User from './User';
|
||||
import Post from './Post';
|
||||
|
||||
export default class Discussion extends Model {}
|
||||
export default class Discussion extends Model {
|
||||
title = Model.attribute<string>('title');
|
||||
slug = Model.attribute<string>('slug');
|
||||
|
||||
Object.assign(Discussion.prototype, {
|
||||
title: Model.attribute('title'),
|
||||
slug: Model.attribute('slug'),
|
||||
createdAt = Model.attribute<Date>('createdAt', Model.transformDate);
|
||||
user = Model.hasOne<User>('user');
|
||||
firstPost = Model.hasOne<Post>('firstPost');
|
||||
|
||||
createdAt: Model.attribute('createdAt', Model.transformDate),
|
||||
user: Model.hasOne('user'),
|
||||
firstPost: Model.hasOne('firstPost'),
|
||||
lastPostedAt = Model.attribute<Date>('lastPostedAt', Model.transformDate);
|
||||
lastPostedUser = Model.hasOne<User>('lastPostedUser');
|
||||
lastPost = Model.hasOne<Post>('lastPost');
|
||||
lastPostNumber = Model.attribute<number>('lastPostNumber');
|
||||
|
||||
lastPostedAt: Model.attribute('lastPostedAt', Model.transformDate),
|
||||
lastPostedUser: Model.hasOne('lastPostedUser'),
|
||||
lastPost: Model.hasOne('lastPost'),
|
||||
lastPostNumber: Model.attribute('lastPostNumber'),
|
||||
commentCount = Model.attribute<number>('commentCount');
|
||||
replyCount = computed<number>('commentCount', (commentCount) => Math.max(0, commentCount - 1));
|
||||
posts = Model.hasMany<Post>('posts');
|
||||
mostRelevantPost = Model.hasOne<Post>('mostRelevantPost');
|
||||
|
||||
commentCount: Model.attribute('commentCount'),
|
||||
replyCount: computed('commentCount', (commentCount) => Math.max(0, commentCount - 1)),
|
||||
posts: Model.hasMany('posts'),
|
||||
mostRelevantPost: Model.hasOne('mostRelevantPost'),
|
||||
lastReadAt = Model.attribute<Date>('lastReadAt', Model.transformDate);
|
||||
lastReadPostNumber = Model.attribute<number>('lastReadPostNumber');
|
||||
isUnread = computed<boolean>('unreadCount', (unreadCount) => !!unreadCount);
|
||||
isRead = computed<boolean>('unreadCount', (unreadCount) => app.session.user && !unreadCount);
|
||||
|
||||
lastReadAt: Model.attribute('lastReadAt', Model.transformDate),
|
||||
lastReadPostNumber: Model.attribute('lastReadPostNumber'),
|
||||
isUnread: computed('unreadCount', (unreadCount) => !!unreadCount),
|
||||
isRead: computed('unreadCount', (unreadCount) => app.session.user && !unreadCount),
|
||||
hiddenAt = Model.attribute<Date>('hiddenAt', Model.transformDate);
|
||||
hiddenUser = Model.hasOne<User>('hiddenUser');
|
||||
isHidden = computed<boolean>('hiddenAt', (hiddenAt) => !!hiddenAt);
|
||||
|
||||
hiddenAt: Model.attribute('hiddenAt', Model.transformDate),
|
||||
hiddenUser: Model.hasOne('hiddenUser'),
|
||||
isHidden: computed('hiddenAt', (hiddenAt) => !!hiddenAt),
|
||||
|
||||
canReply: Model.attribute('canReply'),
|
||||
canRename: Model.attribute('canRename'),
|
||||
canHide: Model.attribute('canHide'),
|
||||
canDelete: Model.attribute('canDelete'),
|
||||
canReply = Model.attribute<boolean>('canReply');
|
||||
canRename = Model.attribute<boolean>('canRename');
|
||||
canHide = Model.attribute<boolean>('canHide');
|
||||
canDelete = Model.attribute<boolean>('canDelete');
|
||||
|
||||
/**
|
||||
* Remove a post from the discussion's posts relationship.
|
||||
@@ -55,7 +55,7 @@ Object.assign(Discussion.prototype, {
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the estimated number of unread posts in this discussion for the current
|
||||
@@ -64,7 +64,7 @@ Object.assign(Discussion.prototype, {
|
||||
* @return {Integer}
|
||||
* @public
|
||||
*/
|
||||
unreadCount() {
|
||||
unreadCount(): number {
|
||||
const user = app.session.user;
|
||||
|
||||
if (user && user.markedAllAsReadAt() < this.lastPostedAt()) {
|
||||
@@ -75,7 +75,7 @@ Object.assign(Discussion.prototype, {
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Badge components that apply to this discussion.
|
||||
@@ -83,7 +83,7 @@ Object.assign(Discussion.prototype, {
|
||||
* @return {ItemList}
|
||||
* @public
|
||||
*/
|
||||
badges() {
|
||||
badges(): ItemList {
|
||||
const items = new ItemList();
|
||||
|
||||
if (this.isHidden()) {
|
||||
@@ -91,7 +91,7 @@ Object.assign(Discussion.prototype, {
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all of the post IDs in this discussion.
|
||||
@@ -99,9 +99,9 @@ Object.assign(Discussion.prototype, {
|
||||
* @return {Array}
|
||||
* @public
|
||||
*/
|
||||
postIds() {
|
||||
postIds(): string[] {
|
||||
const posts = this.data.relationships.posts;
|
||||
|
||||
return posts ? posts.data.map((link) => link.id) : [];
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
import Model from '../Model';
|
||||
|
||||
class Group extends Model {}
|
||||
|
||||
Object.assign(Group.prototype, {
|
||||
nameSingular: Model.attribute('nameSingular'),
|
||||
namePlural: Model.attribute('namePlural'),
|
||||
color: Model.attribute('color'),
|
||||
icon: Model.attribute('icon'),
|
||||
isHidden: Model.attribute('isHidden'),
|
||||
});
|
||||
|
||||
Group.ADMINISTRATOR_ID = '1';
|
||||
Group.GUEST_ID = '2';
|
||||
Group.MEMBER_ID = '3';
|
||||
|
||||
export default Group;
|
13
js/src/common/models/Group.ts
Normal file
13
js/src/common/models/Group.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import Model from '../Model';
|
||||
|
||||
export default class Group extends Model {
|
||||
static ADMINISTRATOR_ID = '1';
|
||||
static GUEST_ID = '2';
|
||||
static MEMBER_ID = '3';
|
||||
|
||||
nameSingular = Model.attribute<string>('nameSingular');
|
||||
namePlural = Model.attribute<string>('namePlural');
|
||||
color = Model.attribute<string>('color');
|
||||
icon = Model.attribute<string>('icon');
|
||||
isHidden = Model.attribute<boolean>('isHidden');
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
import Model from '../Model';
|
||||
|
||||
export default class Notification extends Model {}
|
||||
|
||||
Object.assign(Notification.prototype, {
|
||||
contentType: Model.attribute('contentType'),
|
||||
content: Model.attribute('content'),
|
||||
createdAt: Model.attribute('createdAt', Model.transformDate),
|
||||
|
||||
isRead: Model.attribute('isRead'),
|
||||
|
||||
user: Model.hasOne('user'),
|
||||
fromUser: Model.hasOne('fromUser'),
|
||||
subject: Model.hasOne('subject'),
|
||||
});
|
14
js/src/common/models/Notification.ts
Normal file
14
js/src/common/models/Notification.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import Model from '../Model';
|
||||
import User from './User';
|
||||
|
||||
export default class Notification extends Model {
|
||||
contentType = Model.attribute<string>('contentType');
|
||||
content = Model.attribute<any>('content');
|
||||
createdAt = Model.attribute<Date>('createdAt', Model.transformDate);
|
||||
|
||||
isRead = Model.attribute<boolean>('isRead');
|
||||
|
||||
user = Model.hasOne<User>('user');
|
||||
fromUser = Model.hasOne<User>('fromUser');
|
||||
subject = Model.hasOne<any>('subject');
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
import Model from '../Model';
|
||||
import computed from '../utils/computed';
|
||||
import { getPlainContent } from '../utils/string';
|
||||
|
||||
export default class Post extends Model {}
|
||||
|
||||
Object.assign(Post.prototype, {
|
||||
number: Model.attribute('number'),
|
||||
discussion: Model.hasOne('discussion'),
|
||||
|
||||
createdAt: Model.attribute('createdAt', Model.transformDate),
|
||||
user: Model.hasOne('user'),
|
||||
contentType: Model.attribute('contentType'),
|
||||
content: Model.attribute('content'),
|
||||
contentHtml: Model.attribute('contentHtml'),
|
||||
contentPlain: computed('contentHtml', getPlainContent),
|
||||
|
||||
editedAt: Model.attribute('editedAt', Model.transformDate),
|
||||
editedUser: Model.hasOne('editedUser'),
|
||||
isEdited: computed('editedAt', (editedAt) => !!editedAt),
|
||||
|
||||
hiddenAt: Model.attribute('hiddenAt', Model.transformDate),
|
||||
hiddenUser: Model.hasOne('hiddenUser'),
|
||||
isHidden: computed('hiddenAt', (hiddenAt) => !!hiddenAt),
|
||||
|
||||
canEdit: Model.attribute('canEdit'),
|
||||
canHide: Model.attribute('canHide'),
|
||||
canDelete: Model.attribute('canDelete'),
|
||||
});
|
29
js/src/common/models/Post.ts
Normal file
29
js/src/common/models/Post.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import Model from '../Model';
|
||||
import computed from '../utils/computed';
|
||||
import { getPlainContent } from '../utils/string';
|
||||
import Discussion from './Discussion';
|
||||
import User from './User';
|
||||
|
||||
export default class Post extends Model {
|
||||
number = Model.attribute<number>('number');
|
||||
discussion = Model.hasOne<Discussion>('discussion');
|
||||
|
||||
createdAt = Model.attribute<Date>('createdAt', Model.transformDate);
|
||||
user = Model.hasOne<User>('user');
|
||||
contentType = Model.attribute<string>('contentType');
|
||||
content = Model.attribute<string>('content');
|
||||
contentHtml = Model.attribute<string>('contentHtml');
|
||||
contentPlain = computed<string>('contentHtml', getPlainContent);
|
||||
|
||||
editedAt = Model.attribute<Date>('editedAt', Model.transformDate);
|
||||
editedUser = Model.hasOne<User>('editedUser');
|
||||
isEdited = computed<boolean>('editedAt', (editedAt) => !!editedAt);
|
||||
|
||||
hiddenAt = Model.attribute<Date>('hiddenAt', Model.transformDate);
|
||||
hiddenUser = Model.hasOne<User>('hiddenUser');
|
||||
isHidden = computed<boolean>('hiddenAt', (hiddenAt) => !!hiddenAt);
|
||||
|
||||
canEdit = Model.attribute<boolean>('canEdit');
|
||||
canHide = Model.attribute<boolean>('canHide');
|
||||
canDelete = Model.attribute<boolean>('canDelete');
|
||||
}
|
@@ -5,34 +5,33 @@ import stringToColor from '../utils/stringToColor';
|
||||
import ItemList from '../utils/ItemList';
|
||||
import computed from '../utils/computed';
|
||||
import GroupBadge from '../components/GroupBadge';
|
||||
import Group from './Group';
|
||||
|
||||
export default class User extends Model {}
|
||||
export default class User extends Model {
|
||||
username = Model.attribute<string>('username');
|
||||
displayName = Model.attribute<string>('displayName');
|
||||
email = Model.attribute<string>('email');
|
||||
isEmailConfirmed = Model.attribute<boolean>('isEmailConfirmed');
|
||||
password = Model.attribute<string>('password');
|
||||
|
||||
Object.assign(User.prototype, {
|
||||
username: Model.attribute('username'),
|
||||
displayName: Model.attribute('displayName'),
|
||||
email: Model.attribute('email'),
|
||||
isEmailConfirmed: Model.attribute('isEmailConfirmed'),
|
||||
password: Model.attribute('password'),
|
||||
avatarUrl = Model.attribute<string>('avatarUrl');
|
||||
preferences = Model.attribute<any>('preferences');
|
||||
groups = Model.hasMany<Group>('groups');
|
||||
|
||||
avatarUrl: Model.attribute('avatarUrl'),
|
||||
preferences: Model.attribute('preferences'),
|
||||
groups: Model.hasMany('groups'),
|
||||
joinTime = Model.attribute<Date>('joinTime', Model.transformDate);
|
||||
lastSeenAt = Model.attribute<Date>('lastSeenAt', Model.transformDate);
|
||||
markedAllAsReadAt = Model.attribute<Date>('markedAllAsReadAt', Model.transformDate);
|
||||
unreadNotificationCount = Model.attribute<number>('unreadNotificationCount');
|
||||
newNotificationCount = Model.attribute<number>('newNotificationCount');
|
||||
|
||||
joinTime: Model.attribute('joinTime', Model.transformDate),
|
||||
lastSeenAt: Model.attribute('lastSeenAt', Model.transformDate),
|
||||
markedAllAsReadAt: Model.attribute('markedAllAsReadAt', Model.transformDate),
|
||||
unreadNotificationCount: Model.attribute('unreadNotificationCount'),
|
||||
newNotificationCount: Model.attribute('newNotificationCount'),
|
||||
discussionCount = Model.attribute<number>('discussionCount');
|
||||
commentCount = Model.attribute<number>('commentCount');
|
||||
|
||||
discussionCount: Model.attribute('discussionCount'),
|
||||
commentCount: Model.attribute('commentCount'),
|
||||
canEdit = Model.attribute<boolean>('canEdit');
|
||||
canDelete = Model.attribute<boolean>('canDelete');
|
||||
|
||||
canEdit: Model.attribute('canEdit'),
|
||||
canDelete: Model.attribute('canDelete'),
|
||||
|
||||
avatarColor: null,
|
||||
color: computed('username', 'avatarUrl', 'avatarColor', function (username, avatarUrl, avatarColor) {
|
||||
avatarColor = null;
|
||||
color = computed<string>('username', 'avatarUrl', 'avatarColor', (username, avatarUrl, avatarColor) => {
|
||||
// If we've already calculated and cached the dominant color of the user's
|
||||
// avatar, then we can return that in RGB format. If we haven't, we'll want
|
||||
// to calculate it. Unless the user doesn't have an avatar, in which case
|
||||
@@ -45,7 +44,7 @@ Object.assign(User.prototype, {
|
||||
}
|
||||
|
||||
return '#' + stringToColor(username);
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Check whether or not the user has been seen in the last 5 minutes.
|
||||
@@ -53,16 +52,16 @@ Object.assign(User.prototype, {
|
||||
* @return {Boolean}
|
||||
* @public
|
||||
*/
|
||||
isOnline() {
|
||||
isOnline(): boolean {
|
||||
return dayjs().subtract(5, 'minutes').isBefore(this.lastSeenAt());
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Badge components that apply to this user.
|
||||
*
|
||||
* @return {ItemList}
|
||||
*/
|
||||
badges() {
|
||||
badges(): ItemList {
|
||||
const items = new ItemList();
|
||||
const groups = this.groups();
|
||||
|
||||
@@ -73,7 +72,7 @@ Object.assign(User.prototype, {
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the dominant color of the user's avatar. The dominant color will
|
||||
@@ -93,7 +92,7 @@ Object.assign(User.prototype, {
|
||||
};
|
||||
image.crossOrigin = 'anonymous';
|
||||
image.src = this.avatarUrl();
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's preferences.
|
||||
@@ -107,5 +106,5 @@ Object.assign(User.prototype, {
|
||||
Object.assign(preferences, newPreferences);
|
||||
|
||||
return this.save({ preferences });
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,3 +1,5 @@
|
||||
import Model from '../Model';
|
||||
|
||||
/**
|
||||
* The `computed` utility creates a function that will cache its output until
|
||||
* any of the dependent values are dirty.
|
||||
@@ -7,14 +9,14 @@
|
||||
* dependent values.
|
||||
* @return {Function}
|
||||
*/
|
||||
export default function computed(...dependentKeys) {
|
||||
export default function computed<T, M = Model>(...dependentKeys: any[]) {
|
||||
const keys = dependentKeys.slice(0, -1);
|
||||
const compute = dependentKeys.slice(-1)[0];
|
||||
|
||||
const dependentValues = {};
|
||||
let computedValue;
|
||||
|
||||
return function () {
|
||||
return function (this: M): T {
|
||||
let recompute = false;
|
||||
|
||||
// Read all of the dependent values. If any of them have changed since last
|
@@ -1,6 +1,3 @@
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/plugin/relativeTime';
|
||||
|
||||
/**
|
||||
* The `humanTime` utility converts a date to a localized, human-readable time-
|
||||
* ago string.
|
||||
|
@@ -56,7 +56,9 @@ export default class CommentPost extends Post {
|
||||
]);
|
||||
}
|
||||
|
||||
refreshContent() {
|
||||
onupdate(vnode) {
|
||||
super.onupdate();
|
||||
|
||||
const contentHtml = this.isEditing() ? '' : this.attrs.post.contentHtml();
|
||||
|
||||
// If the post content has changed since the last render, we'll run through
|
||||
@@ -64,28 +66,13 @@ export default class CommentPost extends Post {
|
||||
// necessary because TextFormatter outputs them for e.g. syntax highlighting.
|
||||
if (this.contentHtml !== contentHtml) {
|
||||
this.$('.Post-body script').each(function () {
|
||||
const script = document.createElement('script');
|
||||
script.textContent = this.textContent;
|
||||
Array.from(this.attributes).forEach((attr) => script.setAttribute(attr.name, attr.value));
|
||||
this.parentNode.replaceChild(script, this);
|
||||
eval.call(window, $(this).text());
|
||||
});
|
||||
}
|
||||
|
||||
this.contentHtml = contentHtml;
|
||||
}
|
||||
|
||||
oncreate(vnode) {
|
||||
super.oncreate(vnode);
|
||||
|
||||
this.refreshContent();
|
||||
}
|
||||
|
||||
onupdate(vnode) {
|
||||
super.onupdate(vnode);
|
||||
|
||||
this.refreshContent();
|
||||
}
|
||||
|
||||
isEditing() {
|
||||
return app.composer.bodyMatches(EditPostComposer, { post: this.attrs.post });
|
||||
}
|
||||
|
@@ -287,9 +287,7 @@ export default class PostStream extends Component {
|
||||
* @return {Integer}
|
||||
*/
|
||||
getMarginTop() {
|
||||
const headerId = app.screen() === 'phone' ? '#app-navigation' : '#header';
|
||||
|
||||
return this.$() && $(headerId).outerHeight() + parseInt(this.$().css('margin-top'), 10);
|
||||
return this.$() && $('#header').outerHeight() + parseInt(this.$().css('margin-top'), 10);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -10,23 +10,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Fix a solid white box to the top of the viewport. This toolbar's contents
|
||||
// will differ depending on the device: on phones it will be content
|
||||
// controls, whereas on desktops it will be the header. We will overlay
|
||||
// these things on top of it later.
|
||||
.App:before {
|
||||
content: " ";
|
||||
.header-background();
|
||||
border-bottom: 0;
|
||||
position: absolute;
|
||||
|
||||
.affix& {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.scrolled& {
|
||||
.box-shadow(0 2px 6px @shadow-color);
|
||||
}
|
||||
}
|
||||
|
||||
// PHONES: Somewhere on the page there will be a .App-backControl, a
|
||||
// .App-primaryControl, and a .App-titleControl. We will position these on the
|
||||
// left, right, and center of the header respectively.
|
||||
@media @phone {
|
||||
.App-navigation {
|
||||
.header-background();
|
||||
border-bottom: 0;
|
||||
position: absolute;
|
||||
|
||||
.affix & {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.scrolled & {
|
||||
.box-shadow(0 2px 6px @shadow-color);
|
||||
}
|
||||
}
|
||||
.App-primaryControl, .App-titleControl, .App-backControl {
|
||||
position: absolute !important;
|
||||
z-index: @zindex-header + 1;
|
||||
@@ -228,18 +234,18 @@
|
||||
display: none;
|
||||
}
|
||||
.App-header {
|
||||
.header-background();
|
||||
padding: 8px;
|
||||
height: @header-height;
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: @zindex-header;
|
||||
|
||||
.affix & {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.scrolled & {
|
||||
.box-shadow(0 2px 6px @shadow-color);
|
||||
}
|
||||
|
||||
& when (@config-colored-header = true) {
|
||||
.light-contents(@header-color, @header-control-bg, @header-control-color);
|
||||
}
|
||||
|
@@ -105,10 +105,6 @@
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.off.Checkbox--switch .Checkbox-display {
|
||||
background: @muted-more-color;
|
||||
}
|
||||
}
|
||||
.Modal-footer {
|
||||
border: 0;
|
||||
|
@@ -54,10 +54,9 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
HttpMiddleware\StartSession::class,
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
'flarum.admin.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
Middleware\RequireAdministrateAbility::class
|
||||
HttpMiddleware\SetLocale::class,
|
||||
Middleware\RequireAdministrateAbility::class,
|
||||
];
|
||||
});
|
||||
|
||||
@@ -69,10 +68,6 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.admin.route_resolver', function () {
|
||||
return new HttpMiddleware\ResolveRoute($this->app->make('flarum.admin.routes'));
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.admin.handler', function () {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
@@ -80,7 +75,7 @@ class AdminServiceProvider extends AbstractServiceProvider
|
||||
$pipe->pipe($this->app->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
$pipe->pipe(new HttpMiddleware\DispatchRoute($this->app->make('flarum.admin.routes')));
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
|
@@ -51,9 +51,8 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
HttpMiddleware\AuthenticateWithHeader::class,
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
'flarum.api.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class
|
||||
];
|
||||
});
|
||||
|
||||
@@ -65,10 +64,6 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.api.route_resolver', function () {
|
||||
return new HttpMiddleware\ResolveRoute($this->app->make('flarum.api.routes'));
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.api.handler', function () {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
@@ -76,16 +71,10 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
$pipe->pipe($this->app->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
$pipe->pipe(new HttpMiddleware\DispatchRoute($this->app->make('flarum.api.routes')));
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.api.notification_serializers', function () {
|
||||
return [
|
||||
'discussionRenamed' => BasicDiscussionSerializer::class
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +82,7 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->setNotificationSerializers();
|
||||
$this->registerNotificationSerializers();
|
||||
|
||||
AbstractSerializeController::setContainer($this->app);
|
||||
AbstractSerializeController::setEventDispatcher($events = $this->app->make('events'));
|
||||
@@ -105,12 +94,13 @@ class ApiServiceProvider extends AbstractServiceProvider
|
||||
/**
|
||||
* Register notification serializers.
|
||||
*/
|
||||
protected function setNotificationSerializers()
|
||||
protected function registerNotificationSerializers()
|
||||
{
|
||||
$blueprints = [];
|
||||
$serializers = $this->app->make('flarum.api.notification_serializers');
|
||||
$serializers = [
|
||||
'discussionRenamed' => BasicDiscussionSerializer::class
|
||||
];
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->app->make('events')->dispatch(
|
||||
new ConfigureNotificationTypes($blueprints, $serializers)
|
||||
);
|
||||
|
@@ -110,10 +110,6 @@ class Discussion extends AbstractModel
|
||||
Notification::whereSubject($discussion)->delete();
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated beta 15, remove beta 16
|
||||
* When needed, the `Flarum\Discussion\Event\Saving` event should be listened to directly.
|
||||
*/
|
||||
static::saving(function (self $discussion) {
|
||||
$event = new GetModelIsPrivate($discussion);
|
||||
|
||||
|
@@ -13,9 +13,6 @@ use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* @deprecated in beta 15, removed in beta 16
|
||||
*/
|
||||
class ConfigureNotificationTypes
|
||||
{
|
||||
/**
|
||||
|
@@ -9,9 +9,6 @@
|
||||
|
||||
namespace Flarum\Event;
|
||||
|
||||
/**
|
||||
* @deprecated in beta 15, remove in beta 16. Use the Post extender instead.
|
||||
*/
|
||||
class ConfigurePostTypes
|
||||
{
|
||||
private $models;
|
||||
|
@@ -13,9 +13,6 @@ use Flarum\Database\AbstractModel;
|
||||
|
||||
/**
|
||||
* Determine whether or not a model should be marked as `is_private`.
|
||||
*
|
||||
* @deprecated beta 15, remove beta 16
|
||||
* When needed, the `Flarum\Discussion\Event\Saving` event should be listened to directly.
|
||||
*/
|
||||
class GetModelIsPrivate
|
||||
{
|
||||
|
@@ -14,28 +14,11 @@ use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Csrf implements ExtenderInterface
|
||||
{
|
||||
protected $csrfExemptRoutes = [];
|
||||
protected $csrfExemptPaths = [];
|
||||
|
||||
/**
|
||||
* Exempt a named route from CSRF checks.
|
||||
*
|
||||
* @param string $routeName
|
||||
*/
|
||||
public function exemptRoute(string $routeName)
|
||||
{
|
||||
$this->csrfExemptRoutes[] = $routeName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exempt a path from csrf checks. Wildcards are supported.
|
||||
*
|
||||
* @deprecated beta 15, remove beta 16. Exempt routes should be used instead.
|
||||
*/
|
||||
public function exemptPath(string $path)
|
||||
{
|
||||
$this->csrfExemptRoutes[] = $path;
|
||||
$this->csrfExemptPaths[] = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -43,7 +26,7 @@ class Csrf implements ExtenderInterface
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->extend('flarum.http.csrfExemptPaths', function ($existingExemptPaths) {
|
||||
return array_merge($existingExemptPaths, $this->csrfExemptRoutes);
|
||||
return array_merge($existingExemptPaths, $this->csrfExemptPaths);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -25,7 +25,7 @@ class Event implements ExtenderInterface
|
||||
* - the class attribute of a class with a public `handle` method, which accepts an instance of the event as a parameter
|
||||
*
|
||||
* @param string $event
|
||||
* @param callable|string $listener
|
||||
* @param callable $listener
|
||||
*/
|
||||
public function listen(string $event, $listener)
|
||||
{
|
||||
|
@@ -10,93 +10,38 @@
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Formatter\Event\Configuring;
|
||||
use Flarum\Formatter\Formatter as ActualFormatter;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
|
||||
class Formatter implements ExtenderInterface, LifecycleInterface
|
||||
{
|
||||
private $configurationCallbacks = [];
|
||||
private $parsingCallbacks = [];
|
||||
private $renderingCallbacks = [];
|
||||
private $callback;
|
||||
|
||||
/**
|
||||
* Configure the formatter. This can be used to add support for custom markdown/bbcode/etc tags,
|
||||
* or otherwise change the formatter. Please see documentation for the s9e text formatter library for more
|
||||
* information on how to use this.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callback can be a closure or invokable class, and should accept:
|
||||
* - \s9e\TextFormatter\Configurator $configurator
|
||||
*/
|
||||
public function configure($callback)
|
||||
{
|
||||
$this->configurationCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the system for parsing. This can be used to modify the text that will be parsed, or to modify the parser.
|
||||
* Please note that the text to be parsed must be returned, regardless of whether it's changed.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callback can be a closure or invokable class, and should accept:
|
||||
* - \s9e\TextFormatter\Parser $parser
|
||||
* - mixed $context
|
||||
* - string $text: The text to be parsed.
|
||||
*
|
||||
* The callback should return:
|
||||
* - string $text: The text to be parsed.
|
||||
*/
|
||||
public function parse($callback)
|
||||
{
|
||||
$this->parsingCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the system for rendering. This can be used to modify the xml that will be rendered, or to modify the renderer.
|
||||
* Please note that the xml to be rendered must be returned, regardless of whether it's changed.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
*
|
||||
* The callback can be a closure or invokable class, and should accept:
|
||||
* - \s9e\TextFormatter\Rendered $renderer
|
||||
* - mixed $context
|
||||
* - string $xml: The xml to be rendered.
|
||||
* - ServerRequestInterface $request
|
||||
*
|
||||
* The callback should return:
|
||||
* - string $xml: The xml to be rendered.
|
||||
*/
|
||||
public function render($callback)
|
||||
{
|
||||
$this->renderingCallbacks[] = $callback;
|
||||
$this->callback = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->extend('flarum.formatter', function ($formatter, $container) {
|
||||
foreach ($this->configurationCallbacks as $callback) {
|
||||
$formatter->addConfigurationCallback(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
$events = $container->make(Dispatcher::class);
|
||||
|
||||
foreach ($this->parsingCallbacks as $callback) {
|
||||
$formatter->addParsingCallback(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
$events->listen(
|
||||
Configuring::class,
|
||||
function (Configuring $event) use ($container) {
|
||||
if (is_string($this->callback)) {
|
||||
$callback = $container->make($this->callback);
|
||||
} else {
|
||||
$callback = $this->callback;
|
||||
}
|
||||
|
||||
foreach ($this->renderingCallbacks as $callback) {
|
||||
$formatter->addRenderingCallback(ContainerUtil::wrapCallback($callback, $container));
|
||||
$callback($event->configurator);
|
||||
}
|
||||
|
||||
return $formatter;
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
public function onEnable(Container $container, Extension $extension)
|
||||
|
@@ -12,7 +12,6 @@ namespace Flarum\Extend;
|
||||
use Flarum\Extension\Event\Disabled;
|
||||
use Flarum\Extension\Event\Enabled;
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Flarum\Foundation\Event\ClearingCache;
|
||||
use Flarum\Frontend\Assets;
|
||||
use Flarum\Frontend\Compiler\Source\SourceCollector;
|
||||
@@ -172,7 +171,11 @@ class Frontend implements ExtenderInterface
|
||||
"flarum.frontend.$this->frontend",
|
||||
function (ActualFrontend $frontend, Container $container) {
|
||||
foreach ($this->content as $content) {
|
||||
$frontend->content(ContainerUtil::wrapCallback($content, $container));
|
||||
if (is_string($content)) {
|
||||
$content = $container->make($content);
|
||||
}
|
||||
|
||||
$frontend->content($content);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@@ -11,14 +11,12 @@ namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Database\AbstractModel;
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class Model implements ExtenderInterface
|
||||
{
|
||||
private $modelClass;
|
||||
private $customRelations = [];
|
||||
|
||||
/**
|
||||
* @param string $modelClass The ::class attribute of the model you are modifying.
|
||||
@@ -50,9 +48,7 @@ class Model implements ExtenderInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a default value for a given attribute, which can be an explicit value, a closure,
|
||||
* or an instance of an invokable class. Unlike with some other extenders,
|
||||
* it CANNOT be the `::class` attribute of an invokable class.
|
||||
* Add a default value for a given attribute, which can be an explicit value, or a closure.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
@@ -161,7 +157,7 @@ class Model implements ExtenderInterface
|
||||
* @param string $name: The name of the relation. This doesn't have to be anything in particular,
|
||||
* but has to be unique from other relation names for this model, and should
|
||||
* work as the name of a method.
|
||||
* @param callable|string $callback
|
||||
* @param callable $callable
|
||||
*
|
||||
* The callable can be a closure or invokable class, and should accept:
|
||||
* - $instance: An instance of this model.
|
||||
@@ -172,17 +168,15 @@ class Model implements ExtenderInterface
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function relationship(string $name, $callback)
|
||||
public function relationship(string $name, callable $callable)
|
||||
{
|
||||
$this->customRelations[$name] = $callback;
|
||||
Arr::set(AbstractModel::$customRelations, "$this->modelClass.$name", $callable);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
foreach ($this->customRelations as $name => $callback) {
|
||||
Arr::set(AbstractModel::$customRelations, "$this->modelClass.$name", ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
// Nothing needed here.
|
||||
}
|
||||
}
|
||||
|
@@ -1,78 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Notification implements ExtenderInterface
|
||||
{
|
||||
private $blueprints = [];
|
||||
private $serializers = [];
|
||||
private $drivers = [];
|
||||
private $typesEnabledByDefault = [];
|
||||
|
||||
/**
|
||||
* @param string $blueprint The ::class attribute of the blueprint class.
|
||||
* This blueprint should implement \Flarum\Notification\Blueprint\BlueprintInterface.
|
||||
* @param string $serializer The ::class attribute of the serializer class.
|
||||
* This serializer should extend from \Flarum\Api\Serializer\AbstractSerializer.
|
||||
* @param string[] $driversEnabledByDefault The names of the drivers enabled by default for this notification type.
|
||||
* (example: alert, email).
|
||||
* @return self
|
||||
*/
|
||||
public function type(string $blueprint, string $serializer, array $driversEnabledByDefault = [])
|
||||
{
|
||||
$this->blueprints[$blueprint] = $driversEnabledByDefault;
|
||||
$this->serializers[$blueprint::getType()] = $serializer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $driverName The name of the notification driver.
|
||||
* @param string $driver The ::class attribute of the driver class.
|
||||
* This driver should implement \Flarum\Notification\Driver\NotificationDriverInterface.
|
||||
* @param string[] $typesEnabledByDefault The names of blueprint classes of types enabled by default for this driver.
|
||||
* @return self
|
||||
*/
|
||||
public function driver(string $driverName, string $driver, array $typesEnabledByDefault = [])
|
||||
{
|
||||
$this->drivers[$driverName] = $driver;
|
||||
$this->typesEnabledByDefault[$driverName] = $typesEnabledByDefault;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->extend('flarum.notification.blueprints', function ($existingBlueprints) {
|
||||
$existingBlueprints = array_merge($existingBlueprints, $this->blueprints);
|
||||
|
||||
foreach ($this->typesEnabledByDefault as $driverName => $typesEnabledByDefault) {
|
||||
foreach ($typesEnabledByDefault as $blueprintClass) {
|
||||
if (isset($existingBlueprints[$blueprintClass]) && (! in_array($driverName, $existingBlueprints[$blueprintClass]))) {
|
||||
$existingBlueprints[$blueprintClass][] = $driverName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $existingBlueprints;
|
||||
});
|
||||
|
||||
$container->extend('flarum.api.notification_serializers', function ($existingSerializers) {
|
||||
return array_merge($existingSerializers, $this->serializers);
|
||||
});
|
||||
|
||||
$container->extend('flarum.notification.drivers', function ($existingDrivers) {
|
||||
return array_merge($existingDrivers, $this->drivers);
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Post\Post as PostModel;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Post implements ExtenderInterface
|
||||
{
|
||||
private $postTypes = [];
|
||||
|
||||
/**
|
||||
* Register a new post type. This is generally done for custom 'event posts',
|
||||
* such as those that appear when a discussion is renamed.
|
||||
*
|
||||
* @param string $postType: The ::class attribute of the custom Post type that is being added.
|
||||
*/
|
||||
public function type(string $postType)
|
||||
{
|
||||
$this->postTypes[] = $postType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
foreach ($this->postTypes as $postType) {
|
||||
PostModel::setModel($postType::$type, $postType);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class ServiceProvider implements ExtenderInterface
|
||||
{
|
||||
private $providers = [];
|
||||
|
||||
/**
|
||||
* Register a service provider.
|
||||
*
|
||||
* @param string $serviceProviderClass The ::class attribute of the service provider class.
|
||||
* @return self
|
||||
*/
|
||||
public function register(string $serviceProviderClass)
|
||||
{
|
||||
$this->providers[] = $serviceProviderClass;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$app = $container->make('flarum');
|
||||
|
||||
foreach ($this->providers as $provider) {
|
||||
$app->register($provider);
|
||||
}
|
||||
}
|
||||
}
|
@@ -35,7 +35,7 @@ class User implements ExtenderInterface
|
||||
* This can be used to give a user permissions for groups they aren't actually in, based on context.
|
||||
* It will not change the group badges displayed for the user.
|
||||
*
|
||||
* @param callable|string $callback
|
||||
* @param callable $callable
|
||||
*
|
||||
* The callable can be a closure or invokable class, and should accept:
|
||||
* - \Flarum\User\User $user: the user in question.
|
||||
@@ -44,9 +44,9 @@ class User implements ExtenderInterface
|
||||
* The callable should return:
|
||||
* - array $groupIds: an array of ids for the groups the user belongs to.
|
||||
*/
|
||||
public function permissionGroups($callback)
|
||||
public function permissionGroups(callable $callable)
|
||||
{
|
||||
$this->groupProcessors[] = $callback;
|
||||
$this->groupProcessors[] = $callable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Validator implements ExtenderInterface
|
||||
{
|
||||
private $configurationCallbacks = [];
|
||||
private $validator;
|
||||
|
||||
/**
|
||||
* @param string $validatorClass: The ::class attribute of the validator you are modifying.
|
||||
* The validator should inherit from \Flarum\Foundation\AbstractValidator.
|
||||
*/
|
||||
public function __construct($validatorClass)
|
||||
{
|
||||
$this->validator = $validatorClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator. This is often used to adjust validation rules, but can be
|
||||
* used to make other changes to the validator as well.
|
||||
*
|
||||
* @param callable $callable
|
||||
*
|
||||
* The callable can be a closure or invokable class, and should accept:
|
||||
* - \Flarum\Foundation\AbstractValidator $flarumValidator: The Flarum validator wrapper
|
||||
* - \Illuminate\Validation\Validator $validator: The Laravel validator instance
|
||||
*/
|
||||
public function configure($callback)
|
||||
{
|
||||
$this->configurationCallbacks[] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->resolving($this->validator, function ($validator, $container) {
|
||||
foreach ($this->configurationCallbacks as $callback) {
|
||||
$validator->addConfiguration(ContainerUtil::wrapCallback($callback, $container));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -11,9 +11,6 @@ namespace Flarum\Formatter\Event;
|
||||
|
||||
use s9e\TextFormatter\Configurator;
|
||||
|
||||
/**
|
||||
* @deprecated beta 15, removed beta 16. Use the Formatter extender instead.
|
||||
*/
|
||||
class Configuring
|
||||
{
|
||||
/**
|
||||
|
@@ -11,9 +11,6 @@ namespace Flarum\Formatter\Event;
|
||||
|
||||
use s9e\TextFormatter\Parser;
|
||||
|
||||
/**
|
||||
* @deprecated beta 15, removed beta 16. Use the Formatter extender instead.
|
||||
*/
|
||||
class Parsing
|
||||
{
|
||||
/**
|
||||
|
@@ -12,9 +12,6 @@ namespace Flarum\Formatter\Event;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use s9e\TextFormatter\Renderer;
|
||||
|
||||
/**
|
||||
* @deprecated beta 15, removed beta 16. Use the Formatter extender instead.
|
||||
*/
|
||||
class Rendering
|
||||
{
|
||||
/**
|
||||
|
@@ -20,12 +20,6 @@ use s9e\TextFormatter\Unparser;
|
||||
|
||||
class Formatter
|
||||
{
|
||||
protected $configurationCallbacks = [];
|
||||
|
||||
protected $parsingCallbacks = [];
|
||||
|
||||
protected $renderingCallbacks = [];
|
||||
|
||||
/**
|
||||
* @var Repository
|
||||
*/
|
||||
@@ -53,21 +47,6 @@ class Formatter
|
||||
$this->cacheDir = $cacheDir;
|
||||
}
|
||||
|
||||
public function addConfigurationCallback($callback)
|
||||
{
|
||||
$this->configurationCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
public function addParsingCallback($callback)
|
||||
{
|
||||
$this->parsingCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
public function addRenderingCallback($callback)
|
||||
{
|
||||
$this->renderingCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse text.
|
||||
*
|
||||
@@ -79,13 +58,8 @@ class Formatter
|
||||
{
|
||||
$parser = $this->getParser($context);
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->events->dispatch(new Parsing($parser, $context, $text));
|
||||
|
||||
foreach ($this->parsingCallbacks as $callback) {
|
||||
$text = $callback($parser, $context, $text);
|
||||
}
|
||||
|
||||
return $parser->parse($text);
|
||||
}
|
||||
|
||||
@@ -101,13 +75,8 @@ class Formatter
|
||||
{
|
||||
$renderer = $this->getRenderer();
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->events->dispatch(new Rendering($renderer, $context, $xml, $request));
|
||||
|
||||
foreach ($this->renderingCallbacks as $callback) {
|
||||
$xml = $callback($renderer, $context, $xml, $request);
|
||||
}
|
||||
|
||||
return $renderer->render($xml);
|
||||
}
|
||||
|
||||
@@ -153,13 +122,8 @@ class Formatter
|
||||
$configurator->Autolink;
|
||||
$configurator->tags->onDuplicate('replace');
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->events->dispatch(new Configuring($configurator));
|
||||
|
||||
foreach ($this->configurationCallbacks as $callback) {
|
||||
$callback($configurator);
|
||||
}
|
||||
|
||||
$this->configureExternalLinks($configurator);
|
||||
|
||||
return $configurator;
|
||||
|
@@ -64,9 +64,8 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
HttpMiddleware\StartSession::class,
|
||||
HttpMiddleware\RememberFromCookie::class,
|
||||
HttpMiddleware\AuthenticateWithSession::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
'flarum.forum.route_resolver',
|
||||
HttpMiddleware\CheckCsrfToken::class,
|
||||
HttpMiddleware\SetLocale::class,
|
||||
HttpMiddleware\ShareErrorsFromSession::class
|
||||
];
|
||||
});
|
||||
@@ -79,10 +78,6 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
$this->app->bind('flarum.forum.route_resolver', function () {
|
||||
return new HttpMiddleware\ResolveRoute($this->app->make('flarum.forum.routes'));
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.forum.handler', function () {
|
||||
$pipe = new MiddlewarePipe;
|
||||
|
||||
@@ -90,7 +85,7 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
$pipe->pipe($this->app->make($middleware));
|
||||
}
|
||||
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
$pipe->pipe(new HttpMiddleware\DispatchRoute($this->app->make('flarum.forum.routes')));
|
||||
|
||||
return $pipe;
|
||||
});
|
||||
@@ -203,8 +198,8 @@ class ForumServiceProvider extends AbstractServiceProvider
|
||||
$factory = $this->app->make(RouteHandlerFactory::class);
|
||||
$defaultRoute = $this->app->make('flarum.settings')->get('default_route');
|
||||
|
||||
if (isset($routes->getRouteData()[0]['GET'][$defaultRoute]['handler'])) {
|
||||
$toDefaultController = $routes->getRouteData()[0]['GET'][$defaultRoute]['handler'];
|
||||
if (isset($routes->getRouteData()[0]['GET'][$defaultRoute])) {
|
||||
$toDefaultController = $routes->getRouteData()[0]['GET'][$defaultRoute];
|
||||
} else {
|
||||
$toDefaultController = $factory->toForum(Content\Index::class);
|
||||
}
|
||||
|
@@ -18,16 +18,6 @@ use Symfony\Component\Translation\TranslatorInterface;
|
||||
|
||||
abstract class AbstractValidator
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $configuration = [];
|
||||
|
||||
public function addConfiguration($callable)
|
||||
{
|
||||
$this->configuration[] = $callable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
@@ -102,17 +92,10 @@ abstract class AbstractValidator
|
||||
|
||||
$validator = $this->validator->make($attributes, $rules, $this->getMessages());
|
||||
|
||||
/**
|
||||
* @deprecated in beta 15, removed in beta 16.
|
||||
*/
|
||||
$this->events->dispatch(
|
||||
new Validating($this, $validator)
|
||||
);
|
||||
|
||||
foreach ($this->configuration as $callable) {
|
||||
$callable($this, $validator);
|
||||
}
|
||||
|
||||
return $validator;
|
||||
}
|
||||
}
|
||||
|
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Foundation;
|
||||
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class ContainerUtil
|
||||
{
|
||||
/**
|
||||
* Wraps a callback so that string-based invokable classes get resolved only when actually used.
|
||||
*
|
||||
* @internal Backwards compatability not guaranteed.
|
||||
*
|
||||
* @param callable|string $callback: A callable, or a ::class attribute of an invokable class
|
||||
* @param Container $container
|
||||
*/
|
||||
public static function wrapCallback($callback, Container $container)
|
||||
{
|
||||
if (is_string($callback)) {
|
||||
$callback = function () use ($container, $callback) {
|
||||
$callback = $container->make($callback);
|
||||
|
||||
return call_user_func_array($callback, func_get_args());
|
||||
};
|
||||
}
|
||||
|
||||
return $callback;
|
||||
}
|
||||
}
|
@@ -13,7 +13,6 @@ use Flarum\Foundation\AbstractValidator;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
/**
|
||||
* @deprecated in Beta 15, remove in beta 16. Use the Validator extender instead.
|
||||
* The `Validating` event is called when a validator instance for a
|
||||
* model is being built. This event can be used to add custom rules/extensions
|
||||
* to the validator for when validation takes place.
|
||||
|
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace Flarum\Foundation;
|
||||
|
||||
use Flarum\Http\Middleware as HttpMiddleware;
|
||||
use Flarum\Http\Middleware\DispatchRoute;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
@@ -85,9 +85,8 @@ class InstalledApp implements AppInterface
|
||||
$pipe = new MiddlewarePipe;
|
||||
$pipe->pipe(new BasePath($this->basePath()));
|
||||
$pipe->pipe(
|
||||
new HttpMiddleware\ResolveRoute($this->container->make('flarum.update.routes'))
|
||||
new DispatchRoute($this->container->make('flarum.update.routes'))
|
||||
);
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return $pipe;
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ class HttpServiceProvider extends AbstractServiceProvider
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton('flarum.http.csrfExemptPaths', function () {
|
||||
return ['token'];
|
||||
return ['/api/token'];
|
||||
});
|
||||
|
||||
$this->app->bind(Middleware\CheckCsrfToken::class, function ($app) {
|
||||
|
@@ -28,10 +28,7 @@ class CheckCsrfToken implements Middleware
|
||||
{
|
||||
$path = $request->getAttribute('originalUri')->getPath();
|
||||
foreach ($this->exemptRoutes as $exemptRoute) {
|
||||
/**
|
||||
* @deprecated path match should be removed in beta 16, only route name match should be supported.
|
||||
*/
|
||||
if ($exemptRoute === $request->getAttribute('routeName') || fnmatch($exemptRoute, $path)) {
|
||||
if (fnmatch($exemptRoute, $path)) {
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface as Middleware;
|
||||
use Psr\Http\Server\RequestHandlerInterface as Handler;
|
||||
|
||||
class ResolveRoute implements Middleware
|
||||
class DispatchRoute implements Middleware
|
||||
{
|
||||
/**
|
||||
* @var RouteCollection
|
||||
@@ -41,7 +41,7 @@ class ResolveRoute implements Middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given request from our route collection.
|
||||
* Dispatch the given request to our route collection.
|
||||
*
|
||||
* @throws MethodNotAllowedException
|
||||
* @throws RouteNotFoundException
|
||||
@@ -59,12 +59,10 @@ class ResolveRoute implements Middleware
|
||||
case Dispatcher::METHOD_NOT_ALLOWED:
|
||||
throw new MethodNotAllowedException($method);
|
||||
case Dispatcher::FOUND:
|
||||
$request = $request
|
||||
->withAttribute('routeName', $routeInfo[1]['name'])
|
||||
->withAttribute('routeHandler', $routeInfo[1]['handler'])
|
||||
->withAttribute('routeParameters', $routeInfo[2]);
|
||||
$handler = $routeInfo[1];
|
||||
$parameters = $routeInfo[2];
|
||||
|
||||
return $handler->handle($request);
|
||||
return $handler($request, $parameters);
|
||||
}
|
||||
}
|
||||
|
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Http\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface as Middleware;
|
||||
use Psr\Http\Server\RequestHandlerInterface as Handler;
|
||||
|
||||
class ExecuteRoute implements Middleware
|
||||
{
|
||||
/**
|
||||
* Executes the route handler resolved in ResolveRoute.
|
||||
*/
|
||||
public function process(Request $request, Handler $handler): Response
|
||||
{
|
||||
$handler = $request->getAttribute('routeHandler');
|
||||
$parameters = $request->getAttribute('routeParameters');
|
||||
|
||||
return $handler($request, $parameters);
|
||||
}
|
||||
}
|
@@ -66,7 +66,7 @@ class RouteCollection
|
||||
$routeDatas = $this->routeParser->parse($path);
|
||||
|
||||
foreach ($routeDatas as $routeData) {
|
||||
$this->dataGenerator->addRoute($method, $routeData, ['name' => $name, 'handler' => $handler]);
|
||||
$this->dataGenerator->addRoute($method, $routeData, $handler);
|
||||
}
|
||||
|
||||
$this->reverse[$name] = $routeDatas;
|
||||
|
@@ -13,7 +13,9 @@ use Flarum\Foundation\AppInterface;
|
||||
use Flarum\Foundation\ErrorHandling\Registry;
|
||||
use Flarum\Foundation\ErrorHandling\Reporter;
|
||||
use Flarum\Foundation\ErrorHandling\WhoopsFormatter;
|
||||
use Flarum\Http\Middleware as HttpMiddleware;
|
||||
use Flarum\Http\Middleware\DispatchRoute;
|
||||
use Flarum\Http\Middleware\HandleErrors;
|
||||
use Flarum\Http\Middleware\StartSession;
|
||||
use Flarum\Install\Console\InstallCommand;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Laminas\Stratigility\MiddlewarePipe;
|
||||
@@ -36,16 +38,15 @@ class Installer implements AppInterface
|
||||
public function getRequestHandler()
|
||||
{
|
||||
$pipe = new MiddlewarePipe;
|
||||
$pipe->pipe(new HttpMiddleware\HandleErrors(
|
||||
$pipe->pipe(new HandleErrors(
|
||||
$this->container->make(Registry::class),
|
||||
$this->container->make(WhoopsFormatter::class),
|
||||
$this->container->tagged(Reporter::class)
|
||||
));
|
||||
$pipe->pipe($this->container->make(HttpMiddleware\StartSession::class));
|
||||
$pipe->pipe($this->container->make(StartSession::class));
|
||||
$pipe->pipe(
|
||||
new HttpMiddleware\ResolveRoute($this->container->make('flarum.install.routes'))
|
||||
new DispatchRoute($this->container->make('flarum.install.routes'))
|
||||
);
|
||||
$pipe->pipe(new HttpMiddleware\ExecuteRoute());
|
||||
|
||||
return $pipe;
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ class WritablePaths implements PrerequisiteInterface
|
||||
})->map(function ($path, $index) {
|
||||
return [
|
||||
'message' => 'The '.$this->getAbsolutePath($path).' directory is not writable.',
|
||||
'detail' => 'Please make sure your web server/PHP user has write access to this directory'.(in_array($index, $this->wildcards) ? ' and its contents' : '').'. Read the <a href="https://docs.flarum.org/install.html#folder-ownership">installation documentation</a> for a detailed explanation and steps to resolve this error.'
|
||||
'detail' => 'Please chmod this directory'.(in_array($index, $this->wildcards) ? ' and its contents' : '').' to 0775.'
|
||||
];
|
||||
});
|
||||
}
|
||||
|
@@ -1,50 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Notification\Driver;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Job\SendNotificationsJob;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
|
||||
class AlertNotificationDriver implements NotificationDriverInterface
|
||||
{
|
||||
/**
|
||||
* @var Queue
|
||||
*/
|
||||
private $queue;
|
||||
|
||||
public function __construct(Queue $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function send(BlueprintInterface $blueprint, array $users): void
|
||||
{
|
||||
if (count($users)) {
|
||||
$this->queue->push(new SendNotificationsJob($blueprint, $users));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function registerType(string $blueprintClass, array $driversEnabledByDefault): void
|
||||
{
|
||||
User::addPreference(
|
||||
User::getNotificationPreferenceKey($blueprintClass::getType(), 'alert'),
|
||||
'boolval',
|
||||
in_array('alert', $driversEnabledByDefault)
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Notification\Driver;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Job\SendEmailNotificationJob;
|
||||
use Flarum\Notification\MailableInterface;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
use ReflectionClass;
|
||||
|
||||
class EmailNotificationDriver implements NotificationDriverInterface
|
||||
{
|
||||
/**
|
||||
* @var Queue
|
||||
*/
|
||||
private $queue;
|
||||
|
||||
public function __construct(Queue $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function send(BlueprintInterface $blueprint, array $users): void
|
||||
{
|
||||
if ($blueprint instanceof MailableInterface) {
|
||||
$this->mailNotifications($blueprint, $users);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mail a notification to a list of users.
|
||||
*
|
||||
* @param MailableInterface $blueprint
|
||||
* @param User[] $recipients
|
||||
*/
|
||||
protected function mailNotifications(MailableInterface $blueprint, array $recipients)
|
||||
{
|
||||
foreach ($recipients as $user) {
|
||||
if ($user->shouldEmail($blueprint::getType())) {
|
||||
$this->queue->push(new SendEmailNotificationJob($blueprint, $user));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function registerType(string $blueprintClass, array $driversEnabledByDefault): void
|
||||
{
|
||||
if ((new ReflectionClass($blueprintClass))->implementsInterface(MailableInterface::class)) {
|
||||
User::addPreference(
|
||||
User::getNotificationPreferenceKey($blueprintClass::getType(), 'email'),
|
||||
'boolval',
|
||||
in_array('email', $driversEnabledByDefault)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Notification\Driver;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\User\User;
|
||||
|
||||
interface NotificationDriverInterface
|
||||
{
|
||||
/**
|
||||
* Conditionally sends a notification to users, generally using a queue.
|
||||
*
|
||||
* @param BlueprintInterface $blueprint
|
||||
* @param User[] $users
|
||||
* @return void
|
||||
*/
|
||||
public function send(BlueprintInterface $blueprint, array $users): void;
|
||||
|
||||
/**
|
||||
* Logic for registering a notification type, generally used for adding a user preference.
|
||||
*
|
||||
* @param string $blueprintClass
|
||||
* @param array $driversEnabledByDefault
|
||||
* @return void
|
||||
*/
|
||||
public function registerType(string $blueprintClass, array $driversEnabledByDefault): void;
|
||||
}
|
@@ -11,9 +11,6 @@ namespace Flarum\Notification\Event;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
|
||||
/**
|
||||
* @deprecated in beta 15, removed in beta 16
|
||||
*/
|
||||
class Sending
|
||||
{
|
||||
/**
|
||||
|
@@ -10,6 +10,7 @@
|
||||
namespace Flarum\Notification\Job;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Event\Sending;
|
||||
use Flarum\Notification\Notification;
|
||||
use Flarum\Queue\AbstractJob;
|
||||
use Flarum\User\User;
|
||||
@@ -34,6 +35,8 @@ class SendNotificationsJob extends AbstractJob
|
||||
|
||||
public function handle()
|
||||
{
|
||||
event(new Sending($this->blueprint, $this->recipients));
|
||||
|
||||
Notification::notify($this->recipients, $this->blueprint);
|
||||
}
|
||||
}
|
||||
|
@@ -12,76 +12,51 @@ namespace Flarum\Notification;
|
||||
use Flarum\Event\ConfigureNotificationTypes;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Notification\Blueprint\DiscussionRenamedBlueprint;
|
||||
use Flarum\User\User;
|
||||
use ReflectionClass;
|
||||
|
||||
class NotificationServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton('flarum.notification.drivers', function () {
|
||||
return [
|
||||
'alert' => Driver\AlertNotificationDriver::class,
|
||||
'email' => Driver\EmailNotificationDriver::class,
|
||||
];
|
||||
});
|
||||
|
||||
$this->app->singleton('flarum.notification.blueprints', function () {
|
||||
return [
|
||||
DiscussionRenamedBlueprint::class => ['alert']
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->setNotificationDrivers();
|
||||
$this->setNotificationTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register notification drivers.
|
||||
*/
|
||||
protected function setNotificationDrivers()
|
||||
{
|
||||
foreach ($this->app->make('flarum.notification.drivers') as $driverName => $driver) {
|
||||
NotificationSyncer::addNotificationDriver($driverName, $this->app->make($driver));
|
||||
}
|
||||
$this->registerNotificationTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register notification types.
|
||||
*/
|
||||
protected function setNotificationTypes()
|
||||
public function registerNotificationTypes()
|
||||
{
|
||||
$blueprints = $this->app->make('flarum.notification.blueprints');
|
||||
$blueprints = [
|
||||
DiscussionRenamedBlueprint::class => ['alert']
|
||||
];
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16
|
||||
$this->app->make('events')->dispatch(
|
||||
new ConfigureNotificationTypes($blueprints)
|
||||
);
|
||||
|
||||
foreach ($blueprints as $blueprint => $driversEnabledByDefault) {
|
||||
$this->addType($blueprint, $driversEnabledByDefault);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addType(string $blueprint, array $driversEnabledByDefault)
|
||||
{
|
||||
Notification::setSubjectModel(
|
||||
$type = $blueprint::getType(),
|
||||
$blueprint::getSubjectModel()
|
||||
);
|
||||
|
||||
foreach (NotificationSyncer::getNotificationDrivers() as $driverName => $driver) {
|
||||
$driver->registerType(
|
||||
$blueprint,
|
||||
$driversEnabledByDefault
|
||||
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(MailableInterface::class)) {
|
||||
User::addPreference(
|
||||
User::getNotificationPreferenceKey($type, 'email'),
|
||||
'boolval',
|
||||
in_array('email', $enabled)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,9 +10,10 @@
|
||||
namespace Flarum\Notification;
|
||||
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Driver\NotificationDriverInterface;
|
||||
use Flarum\Notification\Event\Sending;
|
||||
use Flarum\Notification\Job\SendEmailNotificationJob;
|
||||
use Flarum\Notification\Job\SendNotificationsJob;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
|
||||
/**
|
||||
* The Notification Syncer commits notification blueprints to the database, and
|
||||
@@ -37,11 +38,14 @@ class NotificationSyncer
|
||||
protected static $sentTo = [];
|
||||
|
||||
/**
|
||||
* A map of notification drivers.
|
||||
*
|
||||
* @var NotificationDriverInterface[]
|
||||
* @var Queue
|
||||
*/
|
||||
protected static $notificationDrivers = [];
|
||||
protected $queue;
|
||||
|
||||
public function __construct(Queue $queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a notification so that it is visible to the specified users, and not
|
||||
@@ -98,13 +102,12 @@ class NotificationSyncer
|
||||
// receiving this notification for the first time (we know because they
|
||||
// didn't have a record in the database). As both operations can be
|
||||
// intensive on resources (database and mail server), we queue them.
|
||||
foreach (static::getNotificationDrivers() as $driverName => $driver) {
|
||||
$driver->send($blueprint, $newRecipients);
|
||||
if (count($newRecipients)) {
|
||||
$this->queue->push(new SendNotificationsJob($blueprint, $newRecipients));
|
||||
}
|
||||
|
||||
if (count($newRecipients)) {
|
||||
// Deprecated in beta 15, removed in beta 16
|
||||
event(new Sending($blueprint, $newRecipients));
|
||||
if ($blueprint instanceof MailableInterface) {
|
||||
$this->mailNotifications($blueprint, $newRecipients);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +150,21 @@ class NotificationSyncer
|
||||
static::$onePerUser = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mail a notification to a list of users.
|
||||
*
|
||||
* @param MailableInterface $blueprint
|
||||
* @param User[] $recipients
|
||||
*/
|
||||
protected function mailNotifications(MailableInterface $blueprint, array $recipients)
|
||||
{
|
||||
foreach ($recipients as $user) {
|
||||
if ($user->shouldEmail($blueprint::getType())) {
|
||||
$this->queue->push(new SendEmailNotificationJob($blueprint, $user));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the deleted status of a list of notification records.
|
||||
*
|
||||
@@ -157,23 +175,4 @@ class NotificationSyncer
|
||||
{
|
||||
Notification::whereIn('id', $ids)->update(['is_deleted' => $isDeleted]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a notification driver to the list.
|
||||
*
|
||||
* @param string $driverName
|
||||
* @param NotificationDriverInterface $driver
|
||||
*/
|
||||
public static function addNotificationDriver(string $driverName, NotificationDriverInterface $driver)
|
||||
{
|
||||
static::$notificationDrivers[$driverName] = $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return NotificationDriverInterface[]
|
||||
*/
|
||||
public static function getNotificationDrivers(): array
|
||||
{
|
||||
return static::$notificationDrivers;
|
||||
}
|
||||
}
|
||||
|
@@ -96,10 +96,6 @@ class Post extends AbstractModel
|
||||
$post->discussion->save();
|
||||
});
|
||||
|
||||
/**
|
||||
* @deprecated beta 15, remove beta 16
|
||||
* When needed, the `Flarum\Discussion\Event\Saving` event should be listened to directly.
|
||||
*/
|
||||
static::saving(function (self $post) {
|
||||
$event = new GetModelIsPrivate($post);
|
||||
|
||||
@@ -222,7 +218,7 @@ class Post extends AbstractModel
|
||||
* @param string $model The class name of the model for that type.
|
||||
* @return void
|
||||
*/
|
||||
public static function setModel(string $type, string $model)
|
||||
public static function setModel($type, $model)
|
||||
{
|
||||
static::$models[$type] = $model;
|
||||
}
|
||||
|
@@ -21,20 +21,19 @@ class PostServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
CommentPost::setFormatter($this->app->make('flarum.formatter'));
|
||||
|
||||
$this->setPostTypes();
|
||||
$this->registerPostTypes();
|
||||
|
||||
$events = $this->app->make('events');
|
||||
$events->subscribe(PostPolicy::class);
|
||||
}
|
||||
|
||||
protected function setPostTypes()
|
||||
public function registerPostTypes()
|
||||
{
|
||||
$models = [
|
||||
CommentPost::class,
|
||||
DiscussionRenamedPost::class
|
||||
];
|
||||
|
||||
// Deprecated in beta 15, remove in beta 16.
|
||||
$this->app->make('events')->dispatch(
|
||||
new ConfigurePostTypes($models)
|
||||
);
|
||||
|
@@ -44,13 +44,13 @@ class UpdateServiceProvider extends AbstractServiceProvider
|
||||
$route = $this->app->make(RouteHandlerFactory::class);
|
||||
|
||||
$routes->get(
|
||||
'/{path:.*}',
|
||||
'/',
|
||||
'index',
|
||||
$route->toController(Controller\IndexController::class)
|
||||
);
|
||||
|
||||
$routes->post(
|
||||
'/{path:.*}',
|
||||
'/',
|
||||
'update',
|
||||
$route->toController(Controller\UpdateController::class)
|
||||
);
|
||||
|
@@ -11,7 +11,6 @@ namespace Flarum\User;
|
||||
|
||||
use Flarum\Event\ConfigureUserPreferences;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Foundation\ContainerUtil;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\DisplayName\DriverInterface;
|
||||
use Flarum\User\DisplayName\UsernameDriver;
|
||||
@@ -78,7 +77,11 @@ class UserServiceProvider extends AbstractServiceProvider
|
||||
public function boot()
|
||||
{
|
||||
foreach ($this->app->make('flarum.user.group_processors') as $callback) {
|
||||
User::addGroupProcessor(ContainerUtil::wrapCallback($callback, $this->app));
|
||||
if (is_string($callback)) {
|
||||
$callback = $this->app->make($callback);
|
||||
}
|
||||
|
||||
User::addGroupProcessor($callback);
|
||||
}
|
||||
|
||||
User::setHasher($this->app->make('hash'));
|
||||
|
@@ -50,7 +50,6 @@ class CsrfTest extends TestCase
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @deprecated
|
||||
*/
|
||||
public function create_user_post_doesnt_need_csrf_token_if_whitelisted()
|
||||
{
|
||||
@@ -83,37 +82,19 @@ class CsrfTest extends TestCase
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function create_user_post_doesnt_need_csrf_token_if_whitelisted_via_routename()
|
||||
public function post_to_unknown_route_will_cause_400_error_without_csrf_override()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\Csrf)
|
||||
->exemptRoute('users.create')
|
||||
);
|
||||
|
||||
$this->prepDb();
|
||||
|
||||
$response = $this->send(
|
||||
$this->request('POST', '/api/users', [
|
||||
'json' => [
|
||||
'data' => [
|
||||
'attributes' => $this->testUser
|
||||
]
|
||||
],
|
||||
])
|
||||
$this->request('POST', '/api/fake/route/i/made/up')
|
||||
);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
|
||||
$user = User::where('username', $this->testUser['username'])->firstOrFail();
|
||||
|
||||
$this->assertEquals(0, $user->is_email_confirmed);
|
||||
$this->assertEquals($this->testUser['username'], $user->username);
|
||||
$this->assertEquals($this->testUser['email'], $user->email);
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @deprecated
|
||||
*/
|
||||
public function csrf_matches_wildcards_properly()
|
||||
{
|
||||
|
@@ -1,145 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Formatter\Formatter;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
|
||||
class FormatterTest extends TestCase
|
||||
{
|
||||
protected function getFormatter()
|
||||
{
|
||||
$formatter = $this->app()->getContainer()->make(Formatter::class);
|
||||
$formatter->flush();
|
||||
|
||||
return $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_config_doesnt_work_by_default()
|
||||
{
|
||||
$formatter = $this->getFormatter();
|
||||
|
||||
$this->assertEquals('<t>[B]something[/B]</t>', $formatter->parse('[B]something[/B]'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_config_works_if_added_with_closure()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->configure(function ($config) {
|
||||
$config->BBCodes->addFromRepository('B');
|
||||
}));
|
||||
|
||||
$formatter = $this->getFormatter();
|
||||
|
||||
$this->assertEquals('<b>something</b>', $formatter->render($formatter->parse('[B]something[/B]')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_config_works_if_added_with_invokable_class()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->configure(InvokableConfig::class));
|
||||
|
||||
$formatter = $this->getFormatter();
|
||||
|
||||
$this->assertEquals('<b>something</b>', $formatter->render($formatter->parse('[B]something[/B]')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_parsing_doesnt_work_by_default()
|
||||
{
|
||||
$this->assertEquals('<t>Text<a></t>', $this->getFormatter()->parse('Text<a>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_parsing_works_if_added_with_closure()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->parse(function ($parser, $context, $text) {
|
||||
return 'ReplacedText<a>';
|
||||
}));
|
||||
|
||||
$this->assertEquals('<t>ReplacedText<a></t>', $this->getFormatter()->parse('Text<a>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_parsing_works_if_added_with_invokable_class()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->parse(InvokableParsing::class));
|
||||
|
||||
$this->assertEquals('<t>ReplacedText<a></t>', $this->getFormatter()->parse('Text<a>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_rendering_doesnt_work_by_default()
|
||||
{
|
||||
$this->assertEquals('Text', $this->getFormatter()->render('<p>Text</p>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_rendering_works_if_added_with_closure()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->render(function ($renderer, $context, $xml, $request) {
|
||||
return '<html>ReplacedText</html>';
|
||||
}));
|
||||
|
||||
$this->assertEquals('ReplacedText', $this->getFormatter()->render('<html>Text</html>'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_formatter_rendering_works_if_added_with_invokable_class()
|
||||
{
|
||||
$this->extend((new Extend\Formatter)->render(InvokableRendering::class));
|
||||
|
||||
$this->assertEquals('ReplacedText', $this->getFormatter()->render('<html>Text</html>'));
|
||||
}
|
||||
}
|
||||
|
||||
class InvokableConfig
|
||||
{
|
||||
public function __invoke($config)
|
||||
{
|
||||
$config->BBCodes->addFromRepository('B');
|
||||
}
|
||||
}
|
||||
|
||||
class InvokableParsing
|
||||
{
|
||||
public function __invoke($parser, $context, $text)
|
||||
{
|
||||
return 'ReplacedText<a>';
|
||||
}
|
||||
}
|
||||
|
||||
class InvokableRendering
|
||||
{
|
||||
public function __invoke($renderer, $context, $xml, $request)
|
||||
{
|
||||
return '<html>ReplacedText</html>';
|
||||
}
|
||||
}
|
@@ -134,23 +134,6 @@ class ModelTest extends TestCase
|
||||
$this->assertEquals([], $user->customRelation()->get()->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_relationship_can_be_invokable_class()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\Model(User::class))
|
||||
->relationship('customRelation', CustomRelationClass::class)
|
||||
);
|
||||
|
||||
$this->prepDB();
|
||||
|
||||
$user = User::find(1);
|
||||
|
||||
$this->assertEquals([], $user->customRelation()->get()->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
@@ -343,7 +326,7 @@ class ModelTest extends TestCase
|
||||
|
||||
$this->app();
|
||||
|
||||
$post = new ModelTestCustomPost;
|
||||
$post = new CustomPost;
|
||||
|
||||
$this->assertEquals(42, $post->answer);
|
||||
|
||||
@@ -433,18 +416,10 @@ class ModelTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
class ModelTestCustomPost extends AbstractEventPost
|
||||
class CustomPost extends AbstractEventPost
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $type = 'customPost';
|
||||
}
|
||||
|
||||
class CustomRelationClass
|
||||
{
|
||||
public function __invoke(User $user)
|
||||
{
|
||||
return $user->hasMany(Discussion::class, 'user_id');
|
||||
}
|
||||
}
|
||||
|
@@ -1,184 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Notification\Blueprint\BlueprintInterface;
|
||||
use Flarum\Notification\Driver\NotificationDriverInterface;
|
||||
use Flarum\Notification\Notification;
|
||||
use Flarum\Notification\NotificationSyncer;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
|
||||
class NotificationTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_type_doesnt_exist_by_default()
|
||||
{
|
||||
$this->assertArrayNotHasKey('customNotificationType', Notification::getSubjectModels());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_serializer_doesnt_exist_by_default()
|
||||
{
|
||||
$this->app();
|
||||
|
||||
$this->assertNotContains(
|
||||
'customNotificationTypeSerializer',
|
||||
$this->app->getContainer()->make('flarum.api.notification_serializers')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_driver_doesnt_exist_by_default()
|
||||
{
|
||||
$this->assertArrayNotHasKey('customNotificationDriver', NotificationSyncer::getNotificationDrivers());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_type_exists_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Notification)->type(
|
||||
CustomNotificationType::class,
|
||||
'customNotificationTypeSerializer'
|
||||
));
|
||||
|
||||
$this->app();
|
||||
|
||||
$this->assertArrayHasKey('customNotificationType', Notification::getSubjectModels());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_serializer_exists_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Notification)->type(
|
||||
CustomNotificationType::class,
|
||||
'customNotificationTypeSerializer'
|
||||
));
|
||||
|
||||
$this->app();
|
||||
|
||||
$this->assertContains(
|
||||
'customNotificationTypeSerializer',
|
||||
$this->app->getContainer()->make('flarum.api.notification_serializers')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_driver_exists_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Notification())->driver(
|
||||
'customNotificationDriver',
|
||||
CustomNotificationDriver::class
|
||||
));
|
||||
|
||||
$this->app();
|
||||
|
||||
$this->assertArrayHasKey('customNotificationDriver', NotificationSyncer::getNotificationDrivers());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function notification_driver_enabled_types_exist_if_added()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\Notification())
|
||||
->type(CustomNotificationType::class, 'customSerializer')
|
||||
->type(SecondCustomNotificationType::class, 'secondCustomSerializer', ['customDriver'])
|
||||
->type(ThirdCustomNotificationType::class, 'thirdCustomSerializer')
|
||||
->driver('customDriver', CustomNotificationDriver::class, [CustomNotificationType::class])
|
||||
->driver('secondCustomDriver', SecondCustomNotificationDriver::class, [SecondCustomNotificationType::class])
|
||||
);
|
||||
|
||||
$this->app();
|
||||
|
||||
$blueprints = $this->app->getContainer()->make('flarum.notification.blueprints');
|
||||
|
||||
$this->assertContains('customDriver', $blueprints[CustomNotificationType::class]);
|
||||
$this->assertCount(1, $blueprints[CustomNotificationType::class]);
|
||||
$this->assertContains('customDriver', $blueprints[SecondCustomNotificationType::class]);
|
||||
$this->assertContains('secondCustomDriver', $blueprints[SecondCustomNotificationType::class]);
|
||||
$this->assertEmpty($blueprints[ThirdCustomNotificationType::class]);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomNotificationType implements BlueprintInterface
|
||||
{
|
||||
public function getFromUser()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
public function getSubject()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
public function getData()
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
public static function getType()
|
||||
{
|
||||
return 'customNotificationType';
|
||||
}
|
||||
|
||||
public static function getSubjectModel()
|
||||
{
|
||||
return 'customNotificationTypeSubjectModel';
|
||||
}
|
||||
}
|
||||
|
||||
class SecondCustomNotificationType extends CustomNotificationType
|
||||
{
|
||||
public static function getType()
|
||||
{
|
||||
return 'secondCustomNotificationType';
|
||||
}
|
||||
}
|
||||
|
||||
class ThirdCustomNotificationType extends CustomNotificationType
|
||||
{
|
||||
public static function getType()
|
||||
{
|
||||
return 'thirdCustomNotificationType';
|
||||
}
|
||||
}
|
||||
|
||||
class CustomNotificationDriver implements NotificationDriverInterface
|
||||
{
|
||||
public function send(BlueprintInterface $blueprint, array $users): void
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
public function registerType(string $blueprintClass, array $driversEnabledByDefault): void
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
class SecondCustomNotificationDriver extends CustomNotificationDriver
|
||||
{
|
||||
// ...
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Post\AbstractEventPost;
|
||||
use Flarum\Post\MergeableInterface;
|
||||
use Flarum\Post\Post;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
|
||||
class PostTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_post_type_doesnt_exist_by_default()
|
||||
{
|
||||
$this->assertArrayNotHasKey('customPost', Post::getModels());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_post_type_exists_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Post)->type(PostTestCustomPost::class));
|
||||
|
||||
// Needed for extenders to be booted
|
||||
$this->app();
|
||||
|
||||
$this->assertArrayHasKey('customPost', Post::getModels());
|
||||
}
|
||||
}
|
||||
|
||||
class PostTestCustomPost extends AbstractEventPost implements MergeableInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $type = 'customPost';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveAfter(Post $previous = null)
|
||||
{
|
||||
$this->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -1,119 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
|
||||
class ServiceProviderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function providers_dont_work_by_default()
|
||||
{
|
||||
$this->app();
|
||||
|
||||
$this->assertIsArray(
|
||||
$this->app->getContainer()->make('flarum.forum.middleware')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function providers_first_register_order_is_correct()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\ServiceProvider())
|
||||
->register(CustomServiceProvider::class)
|
||||
);
|
||||
|
||||
$this->app();
|
||||
|
||||
$this->assertEquals(
|
||||
'overriden_by_custom_provider_register',
|
||||
$this->app->getContainer()->make('flarum.forum.middleware')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function providers_second_register_order_is_correct()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\ServiceProvider())
|
||||
->register(CustomServiceProvider::class)
|
||||
->register(SecondCustomServiceProvider::class)
|
||||
);
|
||||
|
||||
$this->app();
|
||||
|
||||
$this->assertEquals(
|
||||
'overriden_by_second_custom_provider_register',
|
||||
$this->app->getContainer()->make('flarum.forum.middleware')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function providers_boot_order_is_correct()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\ServiceProvider())
|
||||
->register(ThirdCustomProvider::class)
|
||||
->register(CustomServiceProvider::class)
|
||||
->register(SecondCustomServiceProvider::class)
|
||||
);
|
||||
|
||||
$this->app();
|
||||
|
||||
$this->assertEquals(
|
||||
'overriden_by_third_custom_provider_boot',
|
||||
$this->app->getContainer()->make('flarum.forum.middleware')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
// First we override the singleton here.
|
||||
$this->app->extend('flarum.forum.middleware', function () {
|
||||
return 'overriden_by_custom_provider_register';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class SecondCustomServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
// Second we check that the singleton was overriden here.
|
||||
$this->app->extend('flarum.forum.middleware', function ($forumRoutes) {
|
||||
return 'overriden_by_second_custom_provider_register';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ThirdCustomProvider extends AbstractServiceProvider
|
||||
{
|
||||
public function boot()
|
||||
{
|
||||
// Third we override one last time here, to make sure this is the final result.
|
||||
$this->app->extend('flarum.forum.middleware', function ($forumRoutes) {
|
||||
return 'overriden_by_third_custom_provider_boot';
|
||||
});
|
||||
}
|
||||
}
|
@@ -88,19 +88,6 @@ class UserTest extends TestCase
|
||||
|
||||
$this->assertNotContains('viewUserList', $user->getPermissions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function processor_can_be_invokable_class()
|
||||
{
|
||||
$this->extend((new Extend\User)->permissionGroups(CustomGroupProcessorClass::class));
|
||||
|
||||
$this->prepDb();
|
||||
$user = User::find(2);
|
||||
|
||||
$this->assertNotContains('viewUserList', $user->getPermissions());
|
||||
}
|
||||
}
|
||||
|
||||
class CustomDisplayNameDriver implements DriverInterface
|
||||
@@ -110,13 +97,3 @@ class CustomDisplayNameDriver implements DriverInterface
|
||||
return $user->email.'$$$suffix';
|
||||
}
|
||||
}
|
||||
|
||||
class CustomGroupProcessorClass
|
||||
{
|
||||
public function __invoke(User $user, array $groupIds)
|
||||
{
|
||||
return array_filter($groupIds, function ($id) {
|
||||
return $id != 3;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,97 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Group\GroupValidator;
|
||||
use Flarum\Tests\integration\TestCase;
|
||||
use Flarum\User\UserValidator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class ValidatorTest extends TestCase
|
||||
{
|
||||
private function extendToRequireLongPassword()
|
||||
{
|
||||
$this->extend((new Extend\Validator(UserValidator::class))->configure(function ($flarumValidator, $validator) {
|
||||
$validator->setRules([
|
||||
'password' => [
|
||||
'required',
|
||||
'min:20'
|
||||
]
|
||||
] + $validator->getRules());
|
||||
}));
|
||||
}
|
||||
|
||||
private function extendToRequireLongPasswordViaInvokableClass()
|
||||
{
|
||||
$this->extend((new Extend\Validator(UserValidator::class))->configure(CustomValidatorClass::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_validation_rule_does_not_exist_by_default()
|
||||
{
|
||||
$this->app()->getContainer()->make(UserValidator::class)->assertValid(['password' => 'simplePassword']);
|
||||
|
||||
// If we have gotten this far, no validation exception has been thrown, so the test is succesful.
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_validation_rule_exists_if_added()
|
||||
{
|
||||
$this->extendToRequireLongPassword();
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
|
||||
$this->app()->getContainer()->make(UserValidator::class)->assertValid(['password' => 'simplePassword']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_validation_rule_exists_if_added_via_invokable_class()
|
||||
{
|
||||
$this->extendToRequireLongPasswordViaInvokableClass();
|
||||
|
||||
$this->expectException(ValidationException::class);
|
||||
|
||||
$this->app()->getContainer()->make(UserValidator::class)->assertValid(['password' => 'simplePassword']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_validation_rule_doesnt_affect_other_validators()
|
||||
{
|
||||
$this->extendToRequireLongPassword();
|
||||
|
||||
$this->app()->getContainer()->make(GroupValidator::class)->assertValid(['password' => 'simplePassword']);
|
||||
|
||||
// If we have gotten this far, no validation exception has been thrown, so the test is succesful.
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
class CustomValidatorClass
|
||||
{
|
||||
public function __invoke($flarumValidator, $validator)
|
||||
{
|
||||
$validator->setRules([
|
||||
'password' => [
|
||||
'required',
|
||||
'min:20'
|
||||
]
|
||||
] + $validator->getRules());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user