mirror of
https://github.com/flarum/framework.git
synced 2024-12-11 21:43:38 +08:00
Refactor translation and validation
We now use Symfony's Translation component. Yay! We get more powerful pluralisation and better a fallback mechanism. Will want to implement the caching mechanism at some point too. The API is replicated in JavaScript, which could definitely use some testing. Validators have been refactored so that they are decoupled from models completely (i.e. they simply validate arrays of user input). Language packs should include Laravel's validation messages. ref #267
This commit is contained in:
parent
154465069d
commit
bc3fa5d451
|
@ -46,6 +46,7 @@
|
|||
"dflydev/fig-cookies": "^1.0",
|
||||
"symfony/console": "^2.7",
|
||||
"symfony/yaml": "^2.7",
|
||||
"symfony/translation": "^2.7",
|
||||
"doctrine/dbal": "^2.5",
|
||||
"monolog/monolog": "^1.16.0",
|
||||
"franzl/whoops-middleware": "^0.1.0",
|
||||
|
|
3642
framework/core/composer.lock
generated
3642
framework/core/composer.lock
generated
File diff suppressed because it is too large
Load Diff
|
@ -123,7 +123,7 @@ export default class PermissionGrid extends Component {
|
|||
|
||||
return SettingDropdown.component({
|
||||
defaultLabel: minutes
|
||||
? app.trans('core.admin.permissions_allow_some_minutes_button', {count: minutes})
|
||||
? app.translator.transChoice('core.admin.permissions_allow_some_minutes_button', minutes, {count: minutes})
|
||||
: app.trans('core.admin.permissions_allow_indefinitely_button'),
|
||||
key: 'allow_renaming',
|
||||
options: [
|
||||
|
@ -155,7 +155,7 @@ export default class PermissionGrid extends Component {
|
|||
|
||||
return SettingDropdown.component({
|
||||
defaultLabel: minutes
|
||||
? app.trans('core.admin.permissions_allow_some_minutes_button', {count: minutes})
|
||||
? app.translator.transChoice('core.admin.permissions_allow_some_minutes_button', minutes, {count: minutes})
|
||||
: app.trans('core.admin.permissions_allow_indefinitely_button'),
|
||||
key: 'allow_post_editing',
|
||||
options: [
|
||||
|
|
|
@ -66,12 +66,13 @@ export default class PostStreamScrubber extends Component {
|
|||
|
||||
view() {
|
||||
const retain = this.subtree.retain();
|
||||
const count = this.count();
|
||||
const unreadCount = this.props.stream.discussion.unreadCount();
|
||||
const unreadPercent = Math.min(this.count() - this.index, unreadCount) / this.count();
|
||||
const unreadPercent = Math.min(count - this.index, unreadCount) / count;
|
||||
|
||||
const viewing = app.trans('core.forum.post_scrubber_viewing_text', {
|
||||
const viewing = app.translator.transChoice('core.forum.post_scrubber_viewing_text', count, {
|
||||
index: <span className="Scrubber-index">{retain || formatNumber(this.visibleIndex())}</span>,
|
||||
count: <span className="Scrubber-count">{formatNumber(this.count())}</span>
|
||||
count: <span className="Scrubber-count">{formatNumber(count)}</span>
|
||||
});
|
||||
|
||||
function styleUnread(element, isInitialized, context) {
|
||||
|
|
|
@ -125,6 +125,8 @@ export default class App {
|
|||
* @public
|
||||
*/
|
||||
boot() {
|
||||
this.translator.locale = this.locale;
|
||||
|
||||
this.initializers.toArray().forEach(initializer => initializer(this));
|
||||
}
|
||||
|
||||
|
@ -149,8 +151,6 @@ export default class App {
|
|||
* Set the <title> of the page.
|
||||
*
|
||||
* @param {String} title
|
||||
* @param {Boolean} [separator] Whether or not to separate the given title and
|
||||
* the forum's title.
|
||||
* @public
|
||||
*/
|
||||
setTitle(title) {
|
||||
|
|
|
@ -4,7 +4,11 @@ import extractText from 'flarum/utils/extractText';
|
|||
import extract from 'flarum/utils/extract';
|
||||
|
||||
/**
|
||||
* The `Translator` class translates strings using the loaded localization.
|
||||
* Translator with the same API as Symfony's.
|
||||
*
|
||||
* Derived from https://github.com/willdurand/BazingaJsTranslationBundle
|
||||
* which is available under the MIT License.
|
||||
* Copyright (c) William Durand <william.durand1@gmail.com>
|
||||
*/
|
||||
export default class Translator {
|
||||
constructor() {
|
||||
|
@ -15,46 +19,35 @@ export default class Translator {
|
|||
* @public
|
||||
*/
|
||||
this.translations = {};
|
||||
|
||||
this.locale = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the key of a translation that should be used for the given count.
|
||||
* The default implementation is for English plurals. It should be overridden
|
||||
* by a locale's JavaScript file if necessary.
|
||||
*
|
||||
* @param {Integer} count
|
||||
* @return {String}
|
||||
* @public
|
||||
*/
|
||||
plural(count) {
|
||||
return count === 1 ? 'one' : 'other';
|
||||
}
|
||||
trans(id, parameters) {
|
||||
const translation = this.translations[id];
|
||||
|
||||
/**
|
||||
* Translate a string.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Object} input
|
||||
* @param {VirtualElement} fallback
|
||||
* @return {VirtualElement}
|
||||
*/
|
||||
trans(key, input = {}, fallback = null) {
|
||||
const parts = key.split('.');
|
||||
let translation = this.translations;
|
||||
|
||||
// Drill down into the translation tree to find the translation for this
|
||||
// key.
|
||||
parts.forEach(part => {
|
||||
translation = translation && translation[part];
|
||||
});
|
||||
|
||||
// If this translation has multiple options and a 'count' has been provided
|
||||
// in the input, we'll work out which option to choose using the `plural`
|
||||
// method.
|
||||
if (translation && typeof translation === 'object' && typeof input.count !== 'undefined') {
|
||||
translation = translation[this.plural(extractText(input.count))];
|
||||
if (translation) {
|
||||
return this.apply(translation, parameters || {});
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
transChoice(id, number, parameters) {
|
||||
let translation = this.translations[id];
|
||||
|
||||
if (translation) {
|
||||
number = parseInt(number, 10);
|
||||
|
||||
translation = this.pluralize(translation, number);
|
||||
|
||||
return this.apply(translation, parameters || {});
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
apply(translation, input) {
|
||||
// If we've been given a user model as one of the input parameters, then
|
||||
// we'll extract the username and use that for the translation. In the
|
||||
// future there should be a hook here to inspect the user and change the
|
||||
|
@ -66,37 +59,226 @@ export default class Translator {
|
|||
if (!input.username) input.username = username(user);
|
||||
}
|
||||
|
||||
// If we've found the appropriate translation string, then we'll sub in the
|
||||
// input.
|
||||
if (typeof translation === 'string') {
|
||||
translation = translation.split(new RegExp('({[a-z0-9_]+}|</?[a-z0-9_]+>)', 'gi'));
|
||||
translation = translation.split(new RegExp('({[a-z0-9_]+}|</?[a-z0-9_]+>)', 'gi'));
|
||||
|
||||
const hydrated = [];
|
||||
const open = [hydrated];
|
||||
const hydrated = [];
|
||||
const open = [hydrated];
|
||||
|
||||
translation.forEach(part => {
|
||||
const match = part.match(new RegExp('{([a-z0-9_]+)}|<(/?)([a-z0-9_]+)>', 'i'));
|
||||
translation.forEach(part => {
|
||||
const match = part.match(new RegExp('{([a-z0-9_]+)}|<(/?)([a-z0-9_]+)>', 'i'));
|
||||
|
||||
if (match) {
|
||||
if (match[1]) {
|
||||
open[0].push(input[match[1]]);
|
||||
} else if (match[3]) {
|
||||
if (match[2]) {
|
||||
open.shift();
|
||||
} else {
|
||||
let tag = input[match[3]] || [];
|
||||
open[0].push(tag);
|
||||
open.unshift(tag.children || tag);
|
||||
if (match) {
|
||||
if (match[1]) {
|
||||
open[0].push(input[match[1]]);
|
||||
} else if (match[3]) {
|
||||
if (match[2]) {
|
||||
open.shift();
|
||||
} else {
|
||||
let tag = input[match[3]] || [];
|
||||
open[0].push(tag);
|
||||
open.unshift(tag.children || tag);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
open[0].push(part);
|
||||
}
|
||||
});
|
||||
|
||||
return hydrated.filter(part => part);
|
||||
}
|
||||
|
||||
pluralize(translation, number) {
|
||||
const sPluralRegex = new RegExp(/^\w+\: +(.+)$/),
|
||||
cPluralRegex = new RegExp(/^\s*((\{\s*(\-?\d+[\s*,\s*\-?\d+]*)\s*\})|([\[\]])\s*(-Inf|\-?\d+)\s*,\s*(\+?Inf|\-?\d+)\s*([\[\]]))\s?(.+?)$/),
|
||||
iPluralRegex = new RegExp(/^\s*(\{\s*(\-?\d+[\s*,\s*\-?\d+]*)\s*\})|([\[\]])\s*(-Inf|\-?\d+)\s*,\s*(\+?Inf|\-?\d+)\s*([\[\]])/),
|
||||
standardRules = [],
|
||||
explicitRules = [];
|
||||
|
||||
translation.split('|').forEach(part => {
|
||||
if (cPluralRegex.test(part)) {
|
||||
const matches = part.match(cPluralRegex);
|
||||
explicitRules[matches[0]] = matches[matches.length - 1];
|
||||
} else if (sPluralRegex.test(part)) {
|
||||
const matches = part.match(sPluralRegex);
|
||||
standardRules.push(matches[1]);
|
||||
} else {
|
||||
standardRules.push(part);
|
||||
}
|
||||
});
|
||||
|
||||
explicitRules.forEach((rule, e) => {
|
||||
if (iPluralRegex.test(e)) {
|
||||
const matches = e.match(iPluralRegex);
|
||||
|
||||
if (matches[1]) {
|
||||
const ns = matches[2].split(',');
|
||||
|
||||
for (let n in ns) {
|
||||
if (number == ns[n]) {
|
||||
return explicitRules[e];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
open[0].push(part);
|
||||
}
|
||||
});
|
||||
var leftNumber = this.convertNumber(matches[4]);
|
||||
var rightNumber = this.convertNumber(matches[5]);
|
||||
|
||||
return hydrated.filter(part => part);
|
||||
if (('[' === matches[3] ? number >= leftNumber : number > leftNumber) &&
|
||||
(']' === matches[6] ? number <= rightNumber : number < rightNumber)) {
|
||||
return explicitRules[e];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return standardRules[this.pluralPosition(number, this.locale)] || standardRules[0] || undefined;
|
||||
}
|
||||
|
||||
convertNumber(number) {
|
||||
if ('-Inf' === number) {
|
||||
return Number.NEGATIVE_INFINITY;
|
||||
} else if ('+Inf' === number || 'Inf' === number) {
|
||||
return Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
return fallback || [key];
|
||||
return parseInt(number, 10);
|
||||
}
|
||||
|
||||
pluralPosition(number, locale) {
|
||||
if ('pt_BR' === locale) {
|
||||
locale = 'xbr';
|
||||
}
|
||||
|
||||
if (locale.length > 3) {
|
||||
locale = locale.split('_')[0];
|
||||
}
|
||||
|
||||
switch (locale) {
|
||||
case 'bo':
|
||||
case 'dz':
|
||||
case 'id':
|
||||
case 'ja':
|
||||
case 'jv':
|
||||
case 'ka':
|
||||
case 'km':
|
||||
case 'kn':
|
||||
case 'ko':
|
||||
case 'ms':
|
||||
case 'th':
|
||||
case 'tr':
|
||||
case 'vi':
|
||||
case 'zh':
|
||||
return 0;
|
||||
case 'af':
|
||||
case 'az':
|
||||
case 'bn':
|
||||
case 'bg':
|
||||
case 'ca':
|
||||
case 'da':
|
||||
case 'de':
|
||||
case 'el':
|
||||
case 'en':
|
||||
case 'eo':
|
||||
case 'es':
|
||||
case 'et':
|
||||
case 'eu':
|
||||
case 'fa':
|
||||
case 'fi':
|
||||
case 'fo':
|
||||
case 'fur':
|
||||
case 'fy':
|
||||
case 'gl':
|
||||
case 'gu':
|
||||
case 'ha':
|
||||
case 'he':
|
||||
case 'hu':
|
||||
case 'is':
|
||||
case 'it':
|
||||
case 'ku':
|
||||
case 'lb':
|
||||
case 'ml':
|
||||
case 'mn':
|
||||
case 'mr':
|
||||
case 'nah':
|
||||
case 'nb':
|
||||
case 'ne':
|
||||
case 'nl':
|
||||
case 'nn':
|
||||
case 'no':
|
||||
case 'om':
|
||||
case 'or':
|
||||
case 'pa':
|
||||
case 'pap':
|
||||
case 'ps':
|
||||
case 'pt':
|
||||
case 'so':
|
||||
case 'sq':
|
||||
case 'sv':
|
||||
case 'sw':
|
||||
case 'ta':
|
||||
case 'te':
|
||||
case 'tk':
|
||||
case 'ur':
|
||||
case 'zu':
|
||||
return (number == 1) ? 0 : 1;
|
||||
|
||||
case 'am':
|
||||
case 'bh':
|
||||
case 'fil':
|
||||
case 'fr':
|
||||
case 'gun':
|
||||
case 'hi':
|
||||
case 'ln':
|
||||
case 'mg':
|
||||
case 'nso':
|
||||
case 'xbr':
|
||||
case 'ti':
|
||||
case 'wa':
|
||||
return ((number === 0) || (number == 1)) ? 0 : 1;
|
||||
|
||||
case 'be':
|
||||
case 'bs':
|
||||
case 'hr':
|
||||
case 'ru':
|
||||
case 'sr':
|
||||
case 'uk':
|
||||
return ((number % 10 == 1) && (number % 100 != 11)) ? 0 : (((number % 10 >= 2) && (number % 10 <= 4) && ((number % 100 < 10) || (number % 100 >= 20))) ? 1 : 2);
|
||||
|
||||
case 'cs':
|
||||
case 'sk':
|
||||
return (number == 1) ? 0 : (((number >= 2) && (number <= 4)) ? 1 : 2);
|
||||
|
||||
case 'ga':
|
||||
return (number == 1) ? 0 : ((number == 2) ? 1 : 2);
|
||||
|
||||
case 'lt':
|
||||
return ((number % 10 == 1) && (number % 100 != 11)) ? 0 : (((number % 10 >= 2) && ((number % 100 < 10) || (number % 100 >= 20))) ? 1 : 2);
|
||||
|
||||
case 'sl':
|
||||
return (number % 100 == 1) ? 0 : ((number % 100 == 2) ? 1 : (((number % 100 == 3) || (number % 100 == 4)) ? 2 : 3));
|
||||
|
||||
case 'mk':
|
||||
return (number % 10 == 1) ? 0 : 1;
|
||||
|
||||
case 'mt':
|
||||
return (number == 1) ? 0 : (((number === 0) || ((number % 100 > 1) && (number % 100 < 11))) ? 1 : (((number % 100 > 10) && (number % 100 < 20)) ? 2 : 3));
|
||||
|
||||
case 'lv':
|
||||
return (number === 0) ? 0 : (((number % 10 == 1) && (number % 100 != 11)) ? 1 : 2);
|
||||
|
||||
case 'pl':
|
||||
return (number == 1) ? 0 : (((number % 10 >= 2) && (number % 10 <= 4) && ((number % 100 < 12) || (number % 100 > 14))) ? 1 : 2);
|
||||
|
||||
case 'cy':
|
||||
return (number == 1) ? 0 : ((number == 2) ? 1 : (((number == 8) || (number == 11)) ? 2 : 3));
|
||||
|
||||
case 'ro':
|
||||
return (number == 1) ? 0 : (((number === 0) || ((number % 100 > 0) && (number % 100 < 20))) ? 1 : 2);
|
||||
|
||||
case 'ar':
|
||||
return (number === 0) ? 0 : ((number == 1) ? 1 : ((number == 2) ? 2 : (((number >= 3) && (number <= 10)) ? 3 : (((number >= 11) && (number <= 99)) ? 4 : 5))));
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,13 +13,13 @@ namespace Flarum\Admin\Controller;
|
|||
use Flarum\Foundation\Application;
|
||||
use Flarum\Http\Controller\AbstractClientController as BaseClientController;
|
||||
use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Flarum\Core\Permission;
|
||||
use Flarum\Api\Client;
|
||||
use Flarum\Settings\SettingsRepository;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Flarum\Event\PrepareUnserializedSettings;
|
||||
|
||||
class ClientController extends BaseClientController
|
||||
|
|
|
@ -14,6 +14,7 @@ use Flarum\Api\AccessToken;
|
|||
use Flarum\Api\ApiKey;
|
||||
use Flarum\Core\Guest;
|
||||
use Flarum\Core\User;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
@ -21,22 +22,22 @@ use Zend\Stratigility\MiddlewareInterface;
|
|||
|
||||
class AuthenticateWithHeader implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var Container
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix = 'Token ';
|
||||
|
||||
/**
|
||||
* @param Container $app
|
||||
* @var LocaleManager
|
||||
*/
|
||||
public function __construct(Container $app)
|
||||
protected $locales;
|
||||
|
||||
/**
|
||||
* @param LocaleManager $locales
|
||||
*/
|
||||
public function __construct(LocaleManager $locales)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->locales = $locales;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,24 +60,34 @@ class AuthenticateWithHeader implements MiddlewareInterface
|
|||
|
||||
$parts = explode(';', $header);
|
||||
|
||||
$actor = new Guest;
|
||||
|
||||
if (isset($parts[0]) && starts_with($parts[0], $this->prefix)) {
|
||||
$token = substr($parts[0], strlen($this->prefix));
|
||||
|
||||
if (($accessToken = AccessToken::find($token)) && $accessToken->isValid()) {
|
||||
$user = $accessToken->user;
|
||||
$actor = $accessToken->user;
|
||||
|
||||
$user->updateLastSeen()->save();
|
||||
|
||||
return $request->withAttribute('actor', $user);
|
||||
$actor->updateLastSeen()->save();
|
||||
} elseif (isset($parts[1]) && ($apiKey = ApiKey::valid($token))) {
|
||||
$userParts = explode('=', trim($parts[1]));
|
||||
|
||||
if (isset($userParts[0]) && $userParts[0] === 'userId') {
|
||||
return $request->withAttribute('actor', $user = User::find($userParts[1]));
|
||||
$actor = User::find($userParts[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $request->withAttribute('actor', new Guest);
|
||||
if ($actor->exists) {
|
||||
$locale = $actor->getPreference('locale');
|
||||
} else {
|
||||
$locale = array_get($request->getCookieParams(), 'locale');
|
||||
}
|
||||
|
||||
if ($locale && $this->locales->hasLocale($locale)) {
|
||||
$this->locales->setLocale($locale);
|
||||
}
|
||||
|
||||
return $request->withAttribute('actor', $actor ?: new Guest);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Flarum\Core\Command;
|
|||
use Flarum\Core\Access\AssertPermissionTrait;
|
||||
use Flarum\Core\Exception\PermissionDeniedException;
|
||||
use Flarum\Core\Group;
|
||||
use Flarum\Core\Validator\GroupValidator;
|
||||
use Flarum\Event\GroupWillBeSaved;
|
||||
use Flarum\Core\Support\DispatchEventsTrait;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
@ -23,11 +24,18 @@ class CreateGroupHandler
|
|||
use AssertPermissionTrait;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
* @var GroupValidator
|
||||
*/
|
||||
public function __construct(Dispatcher $events)
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
* @param GroupValidator $validator
|
||||
*/
|
||||
public function __construct(Dispatcher $events, GroupValidator $validator)
|
||||
{
|
||||
$this->events = $events;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,6 +61,8 @@ class CreateGroupHandler
|
|||
new GroupWillBeSaved($group, $actor, $data)
|
||||
);
|
||||
|
||||
$this->validator->assertValid($group->getAttributes());
|
||||
|
||||
$group->save();
|
||||
|
||||
$this->dispatchEventsFor($group, $actor);
|
||||
|
|
|
@ -14,6 +14,7 @@ use Flarum\Core\Access\AssertPermissionTrait;
|
|||
use Flarum\Core\Exception\PermissionDeniedException;
|
||||
use Flarum\Core\Repository\DiscussionRepository;
|
||||
use Flarum\Core\Support\DispatchEventsTrait;
|
||||
use Flarum\Core\Validator\DiscussionValidator;
|
||||
use Flarum\Event\DiscussionWillBeSaved;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
||||
|
@ -27,14 +28,21 @@ class EditDiscussionHandler
|
|||
*/
|
||||
protected $discussions;
|
||||
|
||||
/**
|
||||
* @var DiscussionValidator
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
* @param DiscussionRepository $discussions
|
||||
* @param DiscussionValidator $validator
|
||||
*/
|
||||
public function __construct(Dispatcher $events, DiscussionRepository $discussions)
|
||||
public function __construct(Dispatcher $events, DiscussionRepository $discussions, DiscussionValidator $validator)
|
||||
{
|
||||
$this->events = $events;
|
||||
$this->discussions = $discussions;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,6 +78,8 @@ class EditDiscussionHandler
|
|||
new DiscussionWillBeSaved($discussion, $actor, $data)
|
||||
);
|
||||
|
||||
$this->validator->assertValid($discussion->getDirty());
|
||||
|
||||
$discussion->save();
|
||||
|
||||
$this->dispatchEventsFor($discussion, $actor);
|
||||
|
|
|
@ -14,6 +14,7 @@ use Flarum\Core\Access\AssertPermissionTrait;
|
|||
use Flarum\Core\Exception\PermissionDeniedException;
|
||||
use Flarum\Core\Group;
|
||||
use Flarum\Core\Repository\GroupRepository;
|
||||
use Flarum\Core\Validator\GroupValidator;
|
||||
use Flarum\Event\GroupWillBeSaved;
|
||||
use Flarum\Core\Support\DispatchEventsTrait;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
@ -28,14 +29,21 @@ class EditGroupHandler
|
|||
*/
|
||||
protected $groups;
|
||||
|
||||
/**
|
||||
* @var GroupValidator
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
* @param GroupRepository $groups
|
||||
* @param GroupValidator $validator
|
||||
*/
|
||||
public function __construct(Dispatcher $events, GroupRepository $groups)
|
||||
public function __construct(Dispatcher $events, GroupRepository $groups, GroupValidator $validator)
|
||||
{
|
||||
$this->events = $events;
|
||||
$this->groups = $groups;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,6 +78,8 @@ class EditGroupHandler
|
|||
new GroupWillBeSaved($group, $actor, $data)
|
||||
);
|
||||
|
||||
$this->validator->assertValid($group->getDirty());
|
||||
|
||||
$group->save();
|
||||
|
||||
$this->dispatchEventsFor($group, $actor);
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace Flarum\Core\Command;
|
|||
|
||||
use Flarum\Core\Access\AssertPermissionTrait;
|
||||
use Flarum\Core\Repository\PostRepository;
|
||||
use Flarum\Core\Validator\PostValidator;
|
||||
use Flarum\Event\PostWillBeSaved;
|
||||
use Flarum\Core\Support\DispatchEventsTrait;
|
||||
use Flarum\Core\Post\CommentPost;
|
||||
|
@ -27,14 +28,21 @@ class EditPostHandler
|
|||
*/
|
||||
protected $posts;
|
||||
|
||||
/**
|
||||
* @var PostValidator
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
* @param PostRepository $posts
|
||||
* @param PostValidator $validator
|
||||
*/
|
||||
public function __construct(Dispatcher $events, PostRepository $posts)
|
||||
public function __construct(Dispatcher $events, PostRepository $posts, PostValidator $validator)
|
||||
{
|
||||
$this->events = $events;
|
||||
$this->posts = $posts;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,6 +81,8 @@ class EditPostHandler
|
|||
new PostWillBeSaved($post, $actor, $data)
|
||||
);
|
||||
|
||||
$this->validator->assertValid($post->getDirty());
|
||||
|
||||
$post->save();
|
||||
|
||||
$this->dispatchEventsFor($post, $actor);
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Flarum\Core\Command;
|
|||
use Flarum\Core\Access\AssertPermissionTrait;
|
||||
use Flarum\Core\User;
|
||||
use Flarum\Core\Repository\UserRepository;
|
||||
use Flarum\Core\Validator\UserValidator;
|
||||
use Flarum\Event\UserWillBeSaved;
|
||||
use Flarum\Event\UserGroupsWereChanged;
|
||||
use Flarum\Core\Support\DispatchEventsTrait;
|
||||
|
@ -28,14 +29,21 @@ class EditUserHandler
|
|||
*/
|
||||
protected $users;
|
||||
|
||||
/**
|
||||
* @var UserValidator
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
* @param UserRepository $users
|
||||
* @param UserValidator $validator
|
||||
*/
|
||||
public function __construct(Dispatcher $events, UserRepository $users)
|
||||
public function __construct(Dispatcher $events, UserRepository $users, UserValidator $validator)
|
||||
{
|
||||
$this->events = $events;
|
||||
$this->users = $users;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,6 +127,8 @@ class EditUserHandler
|
|||
new UserWillBeSaved($user, $actor, $data)
|
||||
);
|
||||
|
||||
$this->validator->assertValid(array_merge($user->getDirty(), array_only($attributes, ['password', 'email'])));
|
||||
|
||||
$user->save();
|
||||
|
||||
$this->dispatchEventsFor($user, $actor);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
namespace Flarum\Core\Command;
|
||||
|
||||
use Flarum\Core\Access\AssertPermissionTrait;
|
||||
use Flarum\Core\Validator\PostValidator;
|
||||
use Flarum\Event\PostWillBeSaved;
|
||||
use Flarum\Core\Repository\DiscussionRepository;
|
||||
use Flarum\Core\Post\CommentPost;
|
||||
|
@ -33,19 +34,27 @@ class PostReplyHandler
|
|||
*/
|
||||
protected $notifications;
|
||||
|
||||
/**
|
||||
* @var PostValidator
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
* @param DiscussionRepository $discussions
|
||||
* @param NotificationSyncer $notifications
|
||||
* @param PostValidator $validator
|
||||
*/
|
||||
public function __construct(
|
||||
Dispatcher $events,
|
||||
DiscussionRepository $discussions,
|
||||
NotificationSyncer $notifications
|
||||
NotificationSyncer $notifications,
|
||||
PostValidator $validator
|
||||
) {
|
||||
$this->events = $events;
|
||||
$this->discussions = $discussions;
|
||||
$this->notifications = $notifications;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,6 +88,8 @@ class PostReplyHandler
|
|||
new PostWillBeSaved($post, $actor, $command->data)
|
||||
);
|
||||
|
||||
$this->validator->assertValid($post->getAttributes());
|
||||
|
||||
$post->save();
|
||||
|
||||
$this->notifications->onePerUser(function () use ($post, $actor) {
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Flarum\Core\Command;
|
|||
use Flarum\Core\Access\AssertPermissionTrait;
|
||||
use Flarum\Core\User;
|
||||
use Flarum\Core\AuthToken;
|
||||
use Flarum\Core\Validator\UserValidator;
|
||||
use Flarum\Event\UserWillBeSaved;
|
||||
use Flarum\Core\Support\DispatchEventsTrait;
|
||||
use Flarum\Settings\SettingsRepository;
|
||||
|
@ -29,14 +30,21 @@ class RegisterUserHandler
|
|||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @var UserValidator
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
* @param SettingsRepository $settings
|
||||
* @param UserValidator $validator
|
||||
*/
|
||||
public function __construct(Dispatcher $events, SettingsRepository $settings)
|
||||
public function __construct(Dispatcher $events, SettingsRepository $settings, UserValidator $validator)
|
||||
{
|
||||
$this->events = $events;
|
||||
$this->settings = $settings;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -88,6 +96,8 @@ class RegisterUserHandler
|
|||
new UserWillBeSaved($user, $actor, $data)
|
||||
);
|
||||
|
||||
$this->validator->assertValid(array_merge($user->getAttributes(), compact('password')));
|
||||
|
||||
$user->save();
|
||||
|
||||
if (isset($token)) {
|
||||
|
|
|
@ -14,6 +14,7 @@ use Exception;
|
|||
use Flarum\Core\Access\AssertPermissionTrait;
|
||||
use Flarum\Core\Discussion;
|
||||
use Flarum\Core\Support\DispatchEventsTrait;
|
||||
use Flarum\Core\Validator\DiscussionValidator;
|
||||
use Flarum\Event\DiscussionWillBeSaved;
|
||||
use Illuminate\Contracts\Bus\Dispatcher as BusDispatcher;
|
||||
use Illuminate\Contracts\Events\Dispatcher as EventDispatcher;
|
||||
|
@ -28,15 +29,22 @@ class StartDiscussionHandler
|
|||
*/
|
||||
protected $bus;
|
||||
|
||||
/**
|
||||
* @var DiscussionValidator
|
||||
*/
|
||||
protected $validator;
|
||||
|
||||
/**
|
||||
* @param EventDispatcher $events
|
||||
* @param BusDispatcher $bus
|
||||
* @param DiscussionValidator $validator
|
||||
* @internal param Forum $forum
|
||||
*/
|
||||
public function __construct(EventDispatcher $events, BusDispatcher $bus)
|
||||
public function __construct(EventDispatcher $events, BusDispatcher $bus, DiscussionValidator $validator)
|
||||
{
|
||||
$this->events = $events;
|
||||
$this->bus = $bus;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,6 +72,8 @@ class StartDiscussionHandler
|
|||
new DiscussionWillBeSaved($discussion, $actor, $data)
|
||||
);
|
||||
|
||||
$this->validator->assertValid($discussion->getAttributes());
|
||||
|
||||
$discussion->save();
|
||||
|
||||
// Now that the discussion has been created, we can add the first post.
|
||||
|
|
|
@ -85,10 +85,6 @@ class CoreServiceProvider extends AbstractServiceProvider
|
|||
User::setHasher($this->app->make('hash'));
|
||||
User::setGate($this->app->make('flarum.gate'));
|
||||
|
||||
$this->validateModelWith(User::class, 'Flarum\Core\Validator\UserValidator');
|
||||
$this->validateModelWith(Group::class, 'Flarum\Core\Validator\GroupValidator');
|
||||
$this->validateModelWith(Post::class, 'Flarum\Core\Validator\PostValidator');
|
||||
|
||||
$events = $this->app->make('events');
|
||||
|
||||
$events->subscribe('Flarum\Core\Listener\DiscussionMetadataUpdater');
|
||||
|
@ -129,15 +125,4 @@ class CoreServiceProvider extends AbstractServiceProvider
|
|||
$event->add('indexProfile', 'boolval', true);
|
||||
$event->add('locale');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $model
|
||||
* @param string $validator
|
||||
*/
|
||||
protected function validateModelWith($model, $validator)
|
||||
{
|
||||
$model::saving(function ($model) use ($validator) {
|
||||
$this->app->make($validator)->assertValid($model);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -156,8 +156,6 @@ class User extends AbstractModel
|
|||
{
|
||||
$user = new static;
|
||||
|
||||
$user->assertValidPassword($password);
|
||||
|
||||
$user->username = $username;
|
||||
$user->email = $email;
|
||||
$user->password = $password;
|
||||
|
@ -227,15 +225,6 @@ class User extends AbstractModel
|
|||
public function requestEmailChange($email)
|
||||
{
|
||||
if ($email !== $this->email) {
|
||||
$validator = $this->makeValidator();
|
||||
|
||||
$validator->setRules(array_only($validator->getRules(), 'email'));
|
||||
$validator->setData(compact('email'));
|
||||
|
||||
if ($validator->fails()) {
|
||||
$this->throwValidationException($validator);
|
||||
}
|
||||
|
||||
$this->raise(new UserEmailChangeWasRequested($this, $email));
|
||||
}
|
||||
|
||||
|
@ -250,8 +239,6 @@ class User extends AbstractModel
|
|||
*/
|
||||
public function changePassword($password)
|
||||
{
|
||||
$this->assertValidPassword($password);
|
||||
|
||||
$this->password = $password;
|
||||
|
||||
$this->raise(new UserPasswordWasChanged($this));
|
||||
|
@ -259,20 +246,6 @@ class User extends AbstractModel
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate password input.
|
||||
*
|
||||
* @param string $password
|
||||
* @return void
|
||||
* @throws \Flarum\Core\Exception\ValidationException
|
||||
*/
|
||||
protected function assertValidPassword($password)
|
||||
{
|
||||
if (strlen($password) < 8) {
|
||||
throw new ValidationException(['password' => 'Password must be at least 8 characters']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the password attribute, storing it as a hash.
|
||||
*
|
||||
|
|
|
@ -10,14 +10,14 @@
|
|||
|
||||
namespace Flarum\Core\Validator;
|
||||
|
||||
use Flarum\Database\AbstractModel;
|
||||
use Flarum\Event\ConfigureModelValidator;
|
||||
use Flarum\Event\ConfigureValidator;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Contracts\Validation\ValidationException;
|
||||
use Illuminate\Validation\Factory;
|
||||
use Illuminate\Validation\Validator;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
|
||||
class AbstractValidator
|
||||
abstract class AbstractValidator
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
|
@ -35,39 +35,63 @@ class AbstractValidator
|
|||
protected $events;
|
||||
|
||||
/**
|
||||
* @param Factory $validator
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
public function __construct(Factory $validator, Dispatcher $events)
|
||||
protected $translator;
|
||||
|
||||
/**
|
||||
* @param Factory $validator
|
||||
* @param Dispatcher $events
|
||||
* @param TranslatorInterface $translator
|
||||
*/
|
||||
public function __construct(Factory $validator, Dispatcher $events, TranslatorInterface $translator)
|
||||
{
|
||||
$this->validator = $validator;
|
||||
$this->events = $events;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a model is valid.
|
||||
*
|
||||
* @param AbstractModel $model
|
||||
* @param array $attributes
|
||||
* @return bool
|
||||
*/
|
||||
public function valid(AbstractModel $model)
|
||||
public function valid(array $attributes)
|
||||
{
|
||||
return $this->makeValidator($model)->passes();
|
||||
return $this->makeValidator($attributes)->passes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an exception if a model is not valid.
|
||||
*
|
||||
* @throws ValidationException
|
||||
* @param array $attributes
|
||||
*/
|
||||
public function assertValid(AbstractModel $model)
|
||||
public function assertValid(array $attributes)
|
||||
{
|
||||
$validator = $this->makeValidator($model);
|
||||
$validator = $this->makeValidator($attributes);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$this->throwValidationException($validator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getRules()
|
||||
{
|
||||
return $this->rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getMessages()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Validator $validator
|
||||
* @throws ValidationException
|
||||
|
@ -80,72 +104,19 @@ class AbstractValidator
|
|||
/**
|
||||
* Make a new validator instance for this model.
|
||||
*
|
||||
* @param AbstractModel $model
|
||||
* @param array $attributes
|
||||
* @return \Illuminate\Validation\Validator
|
||||
*/
|
||||
protected function makeValidator(AbstractModel $model)
|
||||
protected function makeValidator(array $attributes)
|
||||
{
|
||||
$rules = $this->expandUniqueRules($this->rules, $model);
|
||||
$rules = array_only($this->getRules(), array_keys($attributes));
|
||||
|
||||
$validator = $this->validator->make($model->getAttributes(), $rules);
|
||||
$validator = $this->validator->make($attributes, $rules, $this->getMessages());
|
||||
|
||||
$this->events->fire(
|
||||
new ConfigureModelValidator($model, $validator)
|
||||
new ConfigureValidator($this, $validator)
|
||||
);
|
||||
|
||||
return $validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand 'unique' rules in a set of validation rules into a fuller form
|
||||
* that Laravel's validator can understand.
|
||||
*
|
||||
* @param array $rules
|
||||
* @param AbstractModel $model
|
||||
* @return array
|
||||
*/
|
||||
protected function expandUniqueRules($rules, AbstractModel $model)
|
||||
{
|
||||
foreach ($rules as $attribute => &$ruleset) {
|
||||
if (is_string($ruleset)) {
|
||||
$ruleset = explode('|', $ruleset);
|
||||
}
|
||||
|
||||
foreach ($ruleset as &$rule) {
|
||||
if (strpos($rule, 'unique') === 0) {
|
||||
$rule = $this->expandUniqueRule($attribute, $rule, $model);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand a 'unique' rule into a fuller form that Laravel's validator can
|
||||
* understand, based on this model's properties.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param string $rule
|
||||
* @param AbstractModel $model
|
||||
* @return string
|
||||
*/
|
||||
protected function expandUniqueRule($attribute, $rule, AbstractModel $model)
|
||||
{
|
||||
$parts = explode(':', $rule);
|
||||
$key = $model->getKey() ?: 'NULL';
|
||||
$rule = 'unique:'.$model->getTable().','.$attribute.','.$key.','.$model->getKeyName();
|
||||
|
||||
if (! empty($parts[1])) {
|
||||
$wheres = explode(',', $parts[1]);
|
||||
|
||||
foreach ($wheres as &$where) {
|
||||
$where .= ','.$this->$where;
|
||||
}
|
||||
|
||||
$rule .= ','.implode(',', $wheres);
|
||||
}
|
||||
|
||||
return $rule;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,15 +13,10 @@ namespace Flarum\Core\Validator;
|
|||
class DiscussionValidator extends AbstractValidator
|
||||
{
|
||||
protected $rules = [
|
||||
'title' => ['required', 'max:80'],
|
||||
'start_time' => ['required', 'date'],
|
||||
'comments_count' => ['integer'],
|
||||
'participants_count' => ['integer'],
|
||||
'start_user_id' => ['integer'],
|
||||
'start_post_id' => ['integer'],
|
||||
'last_time' => ['date'],
|
||||
'last_user_id' => ['integer'],
|
||||
'last_post_id' => ['integer'],
|
||||
'last_post_number' => ['integer'],
|
||||
'title' => [
|
||||
'required',
|
||||
'min:3',
|
||||
'max:80'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
|
@ -13,14 +13,9 @@ namespace Flarum\Core\Validator;
|
|||
class PostValidator extends AbstractValidator
|
||||
{
|
||||
protected $rules = [
|
||||
'discussion_id' => ['required', 'integer'],
|
||||
'time' => ['required', 'date'],
|
||||
'content' => ['required', 'max:65535'],
|
||||
'number' => ['integer'],
|
||||
'user_id' => ['integer'],
|
||||
'edit_time' => ['date'],
|
||||
'edit_user_id' => ['integer'],
|
||||
'hide_time' => ['date'],
|
||||
'hide_user_id' => ['integer']
|
||||
'content' => [
|
||||
'required',
|
||||
'max:65535'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
|
@ -13,12 +13,21 @@ namespace Flarum\Core\Validator;
|
|||
class UserValidator extends AbstractValidator
|
||||
{
|
||||
protected $rules = [
|
||||
'username' => ['required', 'alpha_dash', 'unique', 'min:3', 'max:30'],
|
||||
'email' => ['required', 'email', 'unique'],
|
||||
'password' => ['required'],
|
||||
'join_time' => ['date'],
|
||||
'last_seen_time' => ['date'],
|
||||
'discussions_count' => ['integer'],
|
||||
'posts_count' => ['integer']
|
||||
'username' => [
|
||||
'required',
|
||||
'alpha_dash',
|
||||
'unique:users',
|
||||
'min:3',
|
||||
'max:8'
|
||||
],
|
||||
'email' => [
|
||||
'required',
|
||||
'email',
|
||||
'unique:users'
|
||||
],
|
||||
'password' => [
|
||||
'required',
|
||||
'min:8'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
namespace Flarum\Event;
|
||||
|
||||
use Flarum\Database\AbstractModel;
|
||||
use Flarum\Core\Validator\AbstractValidator;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
/**
|
||||
|
@ -18,12 +18,12 @@ use Illuminate\Validation\Validator;
|
|||
* model is being built. This event can be used to add custom rules/extensions
|
||||
* to the validator for when validation takes place.
|
||||
*/
|
||||
class ConfigureModelValidator
|
||||
class ConfigureValidator
|
||||
{
|
||||
/**
|
||||
* @var AbstractModel
|
||||
* @var AbstractValidator
|
||||
*/
|
||||
public $model;
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var Validator
|
||||
|
@ -31,12 +31,12 @@ class ConfigureModelValidator
|
|||
public $validator;
|
||||
|
||||
/**
|
||||
* @param AbstractModel $model
|
||||
* @param AbstractValidator $type
|
||||
* @param Validator $validator
|
||||
*/
|
||||
public function __construct(AbstractModel $model, Validator $validator)
|
||||
public function __construct(AbstractValidator $type, Validator $validator)
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->type = $type;
|
||||
$this->validator = $validator;
|
||||
}
|
||||
}
|
|
@ -13,8 +13,8 @@ namespace Flarum\Forum\Controller;
|
|||
use Flarum\Api\Client;
|
||||
use Flarum\Formatter\Formatter;
|
||||
use Flarum\Foundation\Application;
|
||||
use Flarum\Settings\SettingsRepository;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Flarum\Settings\SettingsRepository;
|
||||
use Flarum\Http\Controller\AbstractClientController;
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
|
|
|
@ -129,7 +129,7 @@ abstract class AbstractClientController extends AbstractHtmlController
|
|||
{
|
||||
$actor = $request->getAttribute('actor');
|
||||
$assets = $this->getAssets();
|
||||
$locale = $this->getLocale($actor, $request);
|
||||
$locale = $this->locales->getLocale();
|
||||
$localeCompiler = $locale ? $this->getLocaleCompiler($locale) : null;
|
||||
|
||||
$view = new ClientView(
|
||||
|
@ -157,7 +157,7 @@ abstract class AbstractClientController extends AbstractHtmlController
|
|||
);
|
||||
|
||||
if ($localeCompiler) {
|
||||
$translations = $this->locales->getTranslations($locale);
|
||||
$translations = $this->locales->getTranslator()->getMessages()['messages'];
|
||||
|
||||
$translations = $this->filterTranslations($translations, $keys);
|
||||
|
||||
|
@ -293,30 +293,6 @@ abstract class AbstractClientController extends AbstractHtmlController
|
|||
return $compiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the locale to use.
|
||||
*
|
||||
* @param User $actor
|
||||
* @param Request $request
|
||||
* @return string
|
||||
*/
|
||||
protected function getLocale(User $actor, Request $request)
|
||||
{
|
||||
if ($actor->exists) {
|
||||
$locale = $actor->getPreference('locale');
|
||||
} else {
|
||||
$locale = array_get($request->getCookieParams(), 'locale');
|
||||
}
|
||||
|
||||
if (! $locale || ! $this->locales->hasLocale($locale)) {
|
||||
$locale = $this->settings->get('default_locale', 'en');
|
||||
}
|
||||
|
||||
if ($this->locales->hasLocale($locale)) {
|
||||
return $locale;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the directory where assets should be written.
|
||||
*
|
||||
|
@ -336,19 +312,12 @@ abstract class AbstractClientController extends AbstractHtmlController
|
|||
*/
|
||||
protected function filterTranslations(array $translations, array $keys)
|
||||
{
|
||||
$filtered = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$parts = explode('.', $key);
|
||||
$level = &$filtered;
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$level = &$level[$part];
|
||||
return array_filter($translations, function ($id) use ($keys) {
|
||||
foreach ($keys as $key) {
|
||||
if (substr($id, 0, strlen($key)) === $key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$level = array_get($translations, $key);
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,22 +96,22 @@ class ClientView implements Renderable
|
|||
protected $request;
|
||||
|
||||
/**
|
||||
* @var \Flarum\Asset\AssetManager
|
||||
* @var AssetManager
|
||||
*/
|
||||
protected $assets;
|
||||
|
||||
/**
|
||||
* @var JsCompiler
|
||||
*/
|
||||
protected $locale;
|
||||
protected $localeJs;
|
||||
|
||||
/**
|
||||
* @param Client $api
|
||||
* @param Request $request
|
||||
* @param User $actor
|
||||
* @param \Flarum\Asset\AssetManager $assets
|
||||
* @param AssetManager $assets
|
||||
* @param string $layout
|
||||
* @param JsCompiler $locale
|
||||
* @param JsCompiler $localeJs
|
||||
*/
|
||||
public function __construct(
|
||||
Client $api,
|
||||
|
@ -119,14 +119,14 @@ class ClientView implements Renderable
|
|||
User $actor,
|
||||
AssetManager $assets,
|
||||
$layout,
|
||||
JsCompiler $locale = null
|
||||
JsCompiler $localeJs = null
|
||||
) {
|
||||
$this->api = $api;
|
||||
$this->request = $request;
|
||||
$this->actor = $actor;
|
||||
$this->assets = $assets;
|
||||
$this->layout = $layout;
|
||||
$this->locale = $locale;
|
||||
$this->localeJs = $localeJs;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -214,7 +214,7 @@ class ClientView implements Renderable
|
|||
/**
|
||||
* Get the view's asset manager.
|
||||
*
|
||||
* @return \Flarum\Asset\AssetManager
|
||||
* @return AssetManager
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
|
@ -264,8 +264,8 @@ class ClientView implements Renderable
|
|||
$view->styles = [$this->assets->getCssFile()];
|
||||
$view->scripts = [$this->assets->getJsFile()];
|
||||
|
||||
if ($this->locale) {
|
||||
$view->scripts[] = $this->locale->getFile();
|
||||
if ($this->localeJs) {
|
||||
$view->scripts[] = $this->localeJs->getFile();
|
||||
}
|
||||
|
||||
$view->head = implode("\n", $this->headStrings);
|
||||
|
|
|
@ -12,12 +12,26 @@ namespace Flarum\Http\Middleware;
|
|||
|
||||
use Flarum\Api\AccessToken;
|
||||
use Flarum\Core\Guest;
|
||||
use Flarum\Locale\LocaleManager;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Zend\Stratigility\MiddlewareInterface;
|
||||
|
||||
class AuthenticateWithCookie implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @var LocaleManager
|
||||
*/
|
||||
protected $locales;
|
||||
|
||||
/**
|
||||
* @param LocaleManager $locales
|
||||
*/
|
||||
public function __construct(LocaleManager $locales)
|
||||
{
|
||||
$this->locales = $locales;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -36,17 +50,27 @@ class AuthenticateWithCookie implements MiddlewareInterface
|
|||
*/
|
||||
protected function logIn(Request $request)
|
||||
{
|
||||
$actor = new Guest;
|
||||
|
||||
if ($token = $this->getToken($request)) {
|
||||
if (! $token->isValid()) {
|
||||
// TODO: https://github.com/flarum/core/issues/253
|
||||
} elseif ($user = $token->user) {
|
||||
$user->updateLastSeen()->save();
|
||||
|
||||
return $request->withAttribute('actor', $user);
|
||||
} elseif ($actor = $token->user) {
|
||||
$actor->updateLastSeen()->save();
|
||||
}
|
||||
}
|
||||
|
||||
return $request->withAttribute('actor', new Guest);
|
||||
if ($actor->exists) {
|
||||
$locale = $actor->getPreference('locale');
|
||||
} else {
|
||||
$locale = array_get($request->getCookieParams(), 'locale');
|
||||
}
|
||||
|
||||
if ($locale && $this->locales->hasLocale($locale)) {
|
||||
$this->locales->setLocale($locale);
|
||||
}
|
||||
|
||||
return $request->withAttribute('actor', $actor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,15 +10,36 @@
|
|||
|
||||
namespace Flarum\Locale;
|
||||
|
||||
use Symfony\Component\Translation\Translator;
|
||||
|
||||
class LocaleManager
|
||||
{
|
||||
protected $locales = [];
|
||||
/**
|
||||
* @var Translator
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
protected $translations = [];
|
||||
protected $locales = [];
|
||||
|
||||
protected $js = [];
|
||||
|
||||
protected $config = [];
|
||||
/**
|
||||
* @param Translator $translator
|
||||
*/
|
||||
public function __construct(Translator $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function getLocale()
|
||||
{
|
||||
return $this->translator->getLocale();
|
||||
}
|
||||
|
||||
public function setLocale($locale)
|
||||
{
|
||||
$this->translator->setLocale($locale);
|
||||
}
|
||||
|
||||
public function addLocale($locale, $name)
|
||||
{
|
||||
|
@ -35,9 +56,9 @@ class LocaleManager
|
|||
return isset($this->locales[$locale]);
|
||||
}
|
||||
|
||||
public function addTranslations($locale, $translations)
|
||||
public function addTranslations($locale, $file)
|
||||
{
|
||||
$this->translations[$locale][] = $translations;
|
||||
$this->translator->addResource('yaml', $file, $locale);
|
||||
}
|
||||
|
||||
public function addJsFile($locale, $js)
|
||||
|
@ -45,26 +66,6 @@ class LocaleManager
|
|||
$this->js[$locale][] = $js;
|
||||
}
|
||||
|
||||
public function addConfig($locale, $config)
|
||||
{
|
||||
$this->config[$locale][] = $config;
|
||||
}
|
||||
|
||||
public function getTranslations($locale)
|
||||
{
|
||||
$files = array_get($this->translations, $locale, []);
|
||||
|
||||
$parts = explode('-', $locale);
|
||||
|
||||
if (count($parts) > 1) {
|
||||
$files = array_merge(array_get($this->translations, $parts[0], []), $files);
|
||||
}
|
||||
|
||||
$compiler = new TranslationCompiler($locale, $files);
|
||||
|
||||
return $compiler->getTranslations();
|
||||
}
|
||||
|
||||
public function getJsFiles($locale)
|
||||
{
|
||||
$files = array_get($this->js, $locale, []);
|
||||
|
@ -78,18 +79,19 @@ class LocaleManager
|
|||
return $files;
|
||||
}
|
||||
|
||||
public function getConfig($locale)
|
||||
/**
|
||||
* @return Translator
|
||||
*/
|
||||
public function getTranslator()
|
||||
{
|
||||
if (empty($this->config[$locale])) {
|
||||
return [];
|
||||
}
|
||||
return $this->translator;
|
||||
}
|
||||
|
||||
$config = [];
|
||||
|
||||
foreach ($this->config[$locale] as $file) {
|
||||
$config = array_merge($config, include $file);
|
||||
}
|
||||
|
||||
return $config;
|
||||
/**
|
||||
* @param Translator $translator
|
||||
*/
|
||||
public function setTranslator($translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ namespace Flarum\Locale;
|
|||
use Flarum\Event\ConfigureLocales;
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Symfony\Component\Translation\MessageSelector;
|
||||
use Symfony\Component\Translation\Translator;
|
||||
|
||||
class LocaleServiceProvider extends AbstractServiceProvider
|
||||
{
|
||||
|
@ -21,9 +23,9 @@ class LocaleServiceProvider extends AbstractServiceProvider
|
|||
*/
|
||||
public function boot(Dispatcher $events)
|
||||
{
|
||||
$manager = $this->app->make('flarum.localeManager');
|
||||
$locales = $this->app->make('flarum.localeManager');
|
||||
|
||||
$events->fire(new ConfigureLocales($manager));
|
||||
$events->fire(new ConfigureLocales($locales));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,11 +34,18 @@ class LocaleServiceProvider extends AbstractServiceProvider
|
|||
public function register()
|
||||
{
|
||||
$this->app->singleton('Flarum\Locale\LocaleManager');
|
||||
|
||||
$this->app->alias('Flarum\Locale\LocaleManager', 'flarum.localeManager');
|
||||
|
||||
$this->app->instance('translator', new Translator);
|
||||
$this->app->singleton('translator', function () {
|
||||
$defaultLocale = $this->app->make('flarum.settings')->get('default_locale');
|
||||
|
||||
$translator = new Translator($defaultLocale, new MessageSelector());
|
||||
$translator->setFallbackLocales([$defaultLocale]);
|
||||
$translator->addLoader('yaml', new YamlFileLoader());
|
||||
|
||||
return $translator;
|
||||
});
|
||||
$this->app->alias('translator', 'Symfony\Component\Translation\Translator');
|
||||
$this->app->alias('translator', 'Symfony\Component\Translation\TranslatorInterface');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Locale;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class TranslationCompiler
|
||||
{
|
||||
protected $locale;
|
||||
|
||||
protected $filenames;
|
||||
|
||||
public function __construct($locale, array $filenames)
|
||||
{
|
||||
$this->locale = $locale;
|
||||
$this->filenames = $filenames;
|
||||
}
|
||||
|
||||
public function getTranslations()
|
||||
{
|
||||
// @todo caching
|
||||
|
||||
$translations = [];
|
||||
|
||||
foreach ($this->filenames as $filename) {
|
||||
$translations = array_replace_recursive($translations, Yaml::parse(file_get_contents($filename)));
|
||||
}
|
||||
|
||||
// Temporary solution to resolve references.
|
||||
// TODO: Make it do more than one level deep, unit test.
|
||||
array_walk_recursive($translations, function (&$value, $key) use ($translations) {
|
||||
if (preg_match('/^=>\s*([a-z0-9_\.]+)$/i', $value, $matches)) {
|
||||
$value = array_get($translations, $matches[1]);
|
||||
}
|
||||
});
|
||||
|
||||
return $translations;
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Locale;
|
||||
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
|
||||
class Translator implements TranslatorInterface
|
||||
{
|
||||
protected $translations = [];
|
||||
|
||||
protected $plural;
|
||||
|
||||
public function setTranslations(array $translations)
|
||||
{
|
||||
$this->translations = $translations;
|
||||
}
|
||||
|
||||
public function setPlural(callable $plural)
|
||||
{
|
||||
$this->plural = $plural;
|
||||
}
|
||||
|
||||
protected function plural($count)
|
||||
{
|
||||
if ($this->plural) {
|
||||
$plural = $this->plural;
|
||||
|
||||
return $plural($count);
|
||||
}
|
||||
}
|
||||
|
||||
public function getLocale()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function setLocale($locale)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function trans($id, array $parameters = [], $domain = null, $locale = null)
|
||||
{
|
||||
$translation = array_get($this->translations, $id);
|
||||
|
||||
if (is_array($translation) && isset($parameters['count'])) {
|
||||
$plural = $this->plural($parameters['count']);
|
||||
|
||||
if ($plural) {
|
||||
$translation = $translation[$plural];
|
||||
}
|
||||
}
|
||||
|
||||
if (is_string($translation)) {
|
||||
foreach ($parameters as $k => $v) {
|
||||
$translation = str_replace('{'.$k.'}', $v, $translation);
|
||||
}
|
||||
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function transChoice($id, $number, array $parameters = [], $domain = null, $locale = null)
|
||||
{
|
||||
$parameters['count'] = $number;
|
||||
|
||||
return $this->trans($id, $parameters, $domain, $locale);
|
||||
}
|
||||
}
|
42
framework/core/src/Locale/YamlFileLoader.php
Normal file
42
framework/core/src/Locale/YamlFileLoader.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Locale;
|
||||
|
||||
use Symfony\Component\Translation\Loader\YamlFileLoader as BaseYamlFileLoader;
|
||||
use Symfony\Component\Translation\MessageCatalogueInterface;
|
||||
|
||||
class YamlFileLoader extends BaseYamlFileLoader
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($resource, $locale, $domain = 'messages')
|
||||
{
|
||||
$messages = parent::load($resource, $locale, $domain);
|
||||
|
||||
foreach ($messages->all($domain) as $id => $translation) {
|
||||
$messages->set($id, $this->getTranslation($messages, $id, $domain));
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
private function getTranslation(MessageCatalogueInterface $messages, $id, $domain)
|
||||
{
|
||||
$translation = $messages->get($id, $domain);
|
||||
|
||||
if (preg_match('/^=>\s*([a-z0-9_\.]+)$/i', $translation, $matches)) {
|
||||
return $this->getTranslation($messages, $matches[1], $domain);
|
||||
}
|
||||
|
||||
return $translation;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user