1
0
mirror of https://github.com/flarum/core.git synced 2025-08-13 20:04:24 +02:00

Compare commits

..

24 Commits

Author SHA1 Message Date
David Sevilla Martin
4242a82b80 Use composer.json instead of composer.lock 2019-11-06 19:25:30 -05:00
David Sevilla Martin
cee4cb6840 Cache composer dependencies, skip installing dependencies if cache was used 2019-11-06 19:24:03 -05:00
David Sevilla Martin
9c0d921f49 Fix Modal width on <768px screens not occupying the whole page 2019-11-06 17:48:00 -05:00
flarum-bot
d7bdc173a4 Bundled output for commit 937354512b [skip ci] 2019-10-28 15:37:34 +00:00
Daniël Klabbers
937354512b Update User.js
Use recommended `anonymous`, see https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/crossOrigin
2019-10-28 16:35:45 +01:00
J.C.Ködel
2dedfe4b92 Fix Color Thief cross origin bug
When users have external avatar urls (for instance: in a SSO environment where the avatar is provided by another domain), color thief fails to get the avatar dominant color because the canvas would be tainted. 

Following the instructions here (https://lokeshdhakar.com/projects/color-thief/ on the "Does it work if the image is hosted on another domain?"), adding an `image.crossOrigin = 'Anonymous';` solves the issue.

Tested on my forum which before suffered from a JS error and works fine (without this fix, the canvas remain in the `body` while an script error is thrown by color thief)
2019-10-28 16:35:45 +01:00
Franz Liedke
9f6ec80432 Revert search performance regression
We decided it is better to have a less intelligent search (that does not
match search terms in titles) for some people than a bad-performing
search for everyone.

We will revisit the search performance topic in the next release cycle,
possibly with larger changes around indexing.

Refs #1738, #1741, #1764.
2019-10-26 15:41:39 +02:00
Daniël Klabbers
aa31b8307d improve queue error handling 2019-10-18 13:13:30 +02:00
Daniël Klabbers
dc06d5b5c9 added return type hint to memory cache 2019-10-08 15:51:19 +02:00
Daniël Klabbers
2c867d2292 add type hinting to settings repository 2019-10-08 15:39:01 +02:00
luceos
b09ac3f3f8 Apply fixes from StyleCI
[ci skip] [skip ci]
2019-10-07 09:56:10 +02:00
Daniël Klabbers
21f54c5562 added ability to re-use existing error handling stack 2019-10-07 09:56:10 +02:00
David Sevilla Martin
a0ace316e8 Alias 'flarum.queue.connection' to Queue contract 2019-10-05 16:14:27 -04:00
Tariq Hussein
6c96c932e0 Fixes #1877 Replace getIdsForUsername() with subquery instead. (#1878) 2019-10-02 01:04:01 +02:00
flarum-bot
522e41aa71 Bundled output for commit bbd891965f [skip ci] 2019-10-01 21:50:25 +00:00
Madalin Tache
bbd891965f Update window size (#1894)
This small change attempts to fix #1727, as i just got my eye on it and figured i could simply fix it while seeing it.
2019-10-01 23:48:54 +02:00
flarum-bot
0f43445a90 Bundled output for commit 7a684660e9 [skip ci] 2019-10-01 21:39:07 +00:00
David Sevilla Martín
7a684660e9 Enable scrollbars in login button popups (#1900)
Fixes #1716
2019-10-01 23:37:42 +02:00
Daniël Klabbers
12dc4fff57 works towards #1789 by allowing event subscribing (#1810) 2019-10-01 11:12:46 +02:00
Franz Liedke
1b5a200781 Amend the existing rel attribute of links
...instead of overwriting. This will play more nicely with extensions.

Refs #859.
2019-09-26 23:02:39 +02:00
Franz Liedke
1bdf7764a9 Stop opening external links in new tabs
We accept that this may be desired by forum owners and will offer an
extension to enable this feature. By default, we will not make any
assumptions and simply adopt the web's and browsers' default behavior.

Fixes #859.
2019-09-26 23:01:24 +02:00
Franz Liedke
3417c0cbee Cleanup code from #1876
- Extract a method for email address generation
- Consistent types
- No docblocks for types where superfluous
- Tweak console output
- Don't inherit from integration test's base class in unit test
2019-09-24 01:00:22 +02:00
Stefan Totev
738ca405fe Normalize Base URL during installation
- Fix base url when is appended with a script filename
- Add default base url http://flarum.local when CLI wizard used
- Remove some code duplication
- Add minor improvement to the UX when CLI wizard used
- Add tests
- Extract base url normalisation into its own value object
2019-09-24 00:26:51 +02:00
Matteo Contrini
09609a9f20 Change rel for external links to nofollow ugc (#1884) 2019-09-23 23:37:49 +02:00
27 changed files with 290 additions and 42 deletions

View File

@@ -43,7 +43,21 @@ jobs:
name: 'PHP ${{ matrix.php }} / ${{ matrix.db }} ${{ matrix.prefixStr }}'
steps:
- uses: actions/checkout@master
- name: Checkout repository
uses: actions/checkout@v1
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- uses: actions/cache@v1
id: cache
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Select PHP version
run: sudo update-alternatives --set php $(which php${{ matrix.php }})
@@ -52,6 +66,7 @@ jobs:
run: mysql -uroot -proot -e 'CREATE DATABASE flarum_test;' --port 13306
- name: Install Composer dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: composer install
- name: Setup Composer tests

View File

@@ -60,6 +60,7 @@
"s9e/text-formatter": "^1.2.0",
"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",

2
js/dist/admin.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
js/dist/forum.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -91,6 +91,7 @@ Object.assign(User.prototype, {
user.freshness = new Date();
m.redraw();
};
image.crossOrigin = 'anonymous';
image.src = this.avatarUrl();
},

View File

@@ -13,7 +13,7 @@ export default class LogInButton extends Button {
props.className = (props.className || '') + ' LogInButton';
props.onclick = function() {
const width = 600;
const width = 580;
const height = 400;
const $window = $(window);
@@ -22,7 +22,7 @@ export default class LogInButton extends Button {
`height=${height},` +
`top=${$window.height() / 2 - height / 2},` +
`left=${$window.width() / 2 - width / 2},` +
'status=no,scrollbars=no,resizable=no');
'status=no,scrollbars=yes,resizable=no');
};
super.initProps(props);

View File

@@ -156,6 +156,7 @@
}
}
.Modal {
max-width: 100%;
margin: 0;
-webkit-transform: none !important;
transform: none !important;

View File

@@ -33,7 +33,7 @@ class Configuring
public $console;
/**
* @param Application $app
* @param Application $app
* @param ConsoleApplication $console
*/
public function __construct(Application $app, ConsoleApplication $console)

View File

@@ -13,9 +13,14 @@ namespace Flarum\Console;
use Flarum\Console\Event\Configuring;
use Flarum\Foundation\Application;
use Flarum\Foundation\ErrorHandling\Registry;
use Flarum\Foundation\ErrorHandling\Reporter;
use Flarum\Foundation\SiteInterface;
use Illuminate\Contracts\Events\Dispatcher;
use Symfony\Component\Console\Application as ConsoleApplication;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
class Server
{
@@ -45,7 +50,33 @@ class Server
{
$app = Application::getInstance();
$this->handleErrors($app, $console);
$events = $app->make(Dispatcher::class);
$events->fire(new Configuring($app, $console));
}
private function handleErrors(Application $app, ConsoleApplication $console)
{
$dispatcher = new EventDispatcher();
$dispatcher->addListener(ConsoleEvents::ERROR, function (ConsoleErrorEvent $event) use ($app) {
/** @var Registry $registry */
$registry = $app->make(Registry::class);
$error = $registry->handle($event->getError());
/** @var Reporter[] $reporters */
$reporters = $app->tagged(Reporter::class);
if ($error->shouldBeReported()) {
foreach ($reporters as $reporter) {
$reporter->report($error->getException());
}
}
});
$console->setDispatcher($dispatcher);
}
}

View File

@@ -53,7 +53,7 @@ class FulltextGambit implements GambitInterface
// discussions that have a relevant title or that contain relevant posts.
$query
->addSelect('posts_ft.most_relevant_post_id')
->leftJoin(
->join(
new Expression('('.$subquery->toSql().') '.$grammar->wrapTable('posts_ft')),
'posts_ft.discussion_id', '=', 'discussions.id'
)

View File

@@ -139,8 +139,8 @@ class Formatter
$dom = $configurator->tags['URL']->template->asDOM();
foreach ($dom->getElementsByTagName('a') as $a) {
$a->setAttribute('target', '_blank');
$a->setAttribute('rel', 'nofollow');
$rel = $a->getAttribute('rel');
$a->setAttribute('rel', "$rel nofollow ugc");
}
$dom->saveChanges();

72
src/Install/BaseUrl.php Normal file
View File

@@ -0,0 +1,72 @@
<?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\Install;
use Psr\Http\Message\UriInterface;
final class BaseUrl
{
/** @var string */
private $normalized;
private function __construct(string $baseUrl)
{
$this->normalized = $this->normalize($baseUrl);
}
public static function fromString(string $baseUrl): self
{
return new self($baseUrl);
}
public static function fromUri(UriInterface $baseUrl): self
{
return new self((string) $baseUrl);
}
public function __toString(): string
{
return $this->normalized;
}
public function toEmail(string $mailbox): string
{
$host = preg_replace('/^www\./i', '', parse_url($this->normalized, PHP_URL_HOST));
return "$mailbox@$host";
}
private function normalize(string $baseUrl): string
{
// Empty base url is still valid
if (empty($baseUrl)) {
return '';
}
$normalizedBaseUrl = trim($baseUrl, '/');
if (! preg_match('#^https?://#i', $normalizedBaseUrl)) {
$normalizedBaseUrl = sprintf('http://%s', $normalizedBaseUrl);
}
$parseUrl = parse_url($normalizedBaseUrl);
$path = $parseUrl['path'] ?? null;
if (isset($parseUrl['path']) && strrpos($parseUrl['path'], '.php') !== false) {
$path = substr($parseUrl['path'], 0, strrpos($parseUrl['path'], '/'));
}
return rtrim(
sprintf('%s://%s%s', $parseUrl['scheme'], $parseUrl['host'], $path),
'/'
);
}
}

View File

@@ -13,6 +13,7 @@ namespace Flarum\Install\Console;
use Exception;
use Flarum\Install\AdminUser;
use Flarum\Install\BaseUrl;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\Installation;
use Symfony\Component\Console\Input\InputInterface;
@@ -45,7 +46,7 @@ class FileDataProvider implements DataProviderInterface
// Define configuration variables
$this->debug = $configuration['debug'] ?? false;
$this->baseUrl = isset($configuration['baseUrl']) ? rtrim($configuration['baseUrl'], '/') : null;
$this->baseUrl = $configuration['baseUrl'] ?? 'http://flarum.local';
$this->databaseConfiguration = $configuration['databaseConfiguration'] ?? [];
$this->adminUser = $configuration['adminUser'] ?? [];
$this->settings = $configuration['settings'] ?? [];
@@ -58,7 +59,7 @@ class FileDataProvider implements DataProviderInterface
{
return $installation
->debugMode($this->debug)
->baseUrl($this->baseUrl ?? 'http://flarum.local')
->baseUrl(BaseUrl::fromString($this->baseUrl))
->databaseConfig($this->getDatabaseConfiguration())
->adminUser($this->getAdminUser())
->settings($this->settings);

View File

@@ -12,6 +12,7 @@
namespace Flarum\Install\Console;
use Flarum\Install\AdminUser;
use Flarum\Install\BaseUrl;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\Installation;
use Illuminate\Support\Str;
@@ -28,6 +29,7 @@ class UserDataProvider implements DataProviderInterface
protected $questionHelper;
/** @var BaseUrl */
protected $baseUrl;
public function __construct(InputInterface $input, OutputInterface $output, QuestionHelper $questionHelper)
@@ -49,7 +51,7 @@ class UserDataProvider implements DataProviderInterface
private function getDatabaseConfiguration(): DatabaseConfig
{
$host = $this->ask('Database host:');
$host = $this->ask('Database host (required):');
$port = 3306;
if (Str::contains($host, ':')) {
@@ -60,31 +62,33 @@ class UserDataProvider implements DataProviderInterface
'mysql',
$host,
intval($port),
$this->ask('Database name:'),
$this->ask('Database user:'),
$this->ask('Database name (required):'),
$this->ask('Database user (required):'),
$this->secret('Database password:'),
$this->ask('Prefix:')
);
}
private function getBaseUrl()
private function getBaseUrl(): BaseUrl
{
return $this->baseUrl = rtrim($this->ask('Base URL:'), '/');
$baseUrl = $this->ask('Base URL (Default: http://flarum.local):', 'http://flarum.local');
return $this->baseUrl = BaseUrl::fromString($baseUrl);
}
private function getAdminUser(): AdminUser
{
return new AdminUser(
$this->ask('Admin username:'),
$this->ask('Admin username (Default: admin):', 'admin'),
$this->askForAdminPassword(),
$this->ask('Admin email address:')
$this->ask('Admin email address (required):')
);
}
private function askForAdminPassword()
{
while (true) {
$password = $this->secret('Admin password:');
$password = $this->secret('Admin password (required >= 8 characters):');
if (strlen($password) < 8) {
$this->validationError('Password must be at least 8 characters.');
@@ -105,11 +109,10 @@ class UserDataProvider implements DataProviderInterface
private function getSettings()
{
$title = $this->ask('Forum title:');
$baseUrl = $this->baseUrl ?: 'http://localhost';
return [
'forum_title' => $title,
'mail_from' => 'noreply@'.preg_replace('/^www\./i', '', parse_url($baseUrl, PHP_URL_HOST)),
'mail_from' => $this->baseUrl->toEmail('noreply'),
'welcome_title' => 'Welcome to '.$title,
];
}

View File

@@ -13,6 +13,7 @@ namespace Flarum\Install\Controller;
use Flarum\Http\SessionAuthenticator;
use Flarum\Install\AdminUser;
use Flarum\Install\BaseUrl;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\Installation;
use Flarum\Install\StepFailed;
@@ -54,7 +55,7 @@ class InstallController implements RequestHandlerInterface
public function handle(Request $request): ResponseInterface
{
$input = $request->getParsedBody();
$baseUrl = rtrim((string) $request->getUri(), '/');
$baseUrl = BaseUrl::fromUri($request->getUri());
try {
$pipeline = $this->installation
@@ -63,7 +64,7 @@ class InstallController implements RequestHandlerInterface
->adminUser($this->makeAdminUser($input))
->settings([
'forum_title' => Arr::get($input, 'forumTitle'),
'mail_from' => 'noreply@'.preg_replace('/^www\./i', '', parse_url($baseUrl, PHP_URL_HOST)),
'mail_from' => $baseUrl->toEmail('noreply'),
'welcome_title' => 'Welcome to '.Arr::get($input, 'forumTitle'),
])
->build();

View File

@@ -65,7 +65,7 @@ class Installation
return $this;
}
public function baseUrl($baseUrl)
public function baseUrl(BaseUrl $baseUrl)
{
$this->baseUrl = $baseUrl;

View File

@@ -11,6 +11,7 @@
namespace Flarum\Install\Steps;
use Flarum\Install\BaseUrl;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\ReversibleStep;
use Flarum\Install\Step;
@@ -25,7 +26,7 @@ class StoreConfig implements Step, ReversibleStep
private $configFile;
public function __construct($debugMode, DatabaseConfig $dbConfig, $baseUrl, $configFile)
public function __construct($debugMode, DatabaseConfig $dbConfig, BaseUrl $baseUrl, $configFile)
{
$this->debugMode = $debugMode;
$this->dbConfig = $dbConfig;
@@ -57,7 +58,7 @@ class StoreConfig implements Step, ReversibleStep
return [
'debug' => $this->debugMode,
'database' => $this->dbConfig->toArray(),
'url' => $this->baseUrl,
'url' => (string) $this->baseUrl,
'paths' => $this->getPathsConfig(),
];
}

View File

@@ -13,10 +13,14 @@ namespace Flarum\Queue;
use Flarum\Console\Event\Configuring;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\ErrorHandling\Registry;
use Flarum\Foundation\ErrorHandling\Reporter;
use Illuminate\Contracts\Debug\ExceptionHandler as ExceptionHandling;
use Illuminate\Contracts\Queue\Factory;
use Illuminate\Contracts\Queue\Queue;
use Illuminate\Queue\Connectors\ConnectorInterface;
use Illuminate\Queue\Console as Commands;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Queue\Failed\NullFailedJobProvider;
use Illuminate\Queue\Listener as QueueListener;
use Illuminate\Queue\SyncQueue;
@@ -90,6 +94,8 @@ class QueueServiceProvider extends AbstractServiceProvider
return new NullFailedJobProvider();
});
$this->app->alias('flarum.queue.connection', Queue::class);
$this->app->alias(ConnectorInterface::class, 'queue.connection');
$this->app->alias(Factory::class, 'queue');
$this->app->alias(Worker::class, 'queue.worker');
@@ -106,4 +112,23 @@ class QueueServiceProvider extends AbstractServiceProvider
}
});
}
public function boot()
{
$this->app['events']->listen(JobFailed::class, function (JobFailed $event) {
/** @var Registry $registry */
$registry = $this->app->make(Registry::class);
$error = $registry->handle($event->exception);
/** @var Reporter[] $reporters */
$reporters = $this->app->tagged(Reporter::class);
if ($error->shouldBeReported()) {
foreach ($reporters as $reporter) {
$reporter->report($error->getException());
}
}
});
}
}

View File

@@ -22,7 +22,7 @@ class DatabaseSettingsRepository implements SettingsRepositoryInterface
$this->database = $connection;
}
public function all()
public function all(): array
{
return $this->database->table('settings')->pluck('value', 'key')->all();
}

View File

@@ -26,7 +26,7 @@ class MemoryCacheSettingsRepository implements SettingsRepositoryInterface
$this->inner = $inner;
}
public function all()
public function all(): array
{
if (! $this->isCached) {
$this->cache = $this->inner->all();

View File

@@ -13,7 +13,7 @@ namespace Flarum\Settings;
interface SettingsRepositoryInterface
{
public function all();
public function all(): array;
public function get($key, $default = null);

View File

@@ -30,15 +30,25 @@ class FulltextGambit implements GambitInterface
$this->users = $users;
}
/**
* @param $searchValue
* @return \Illuminate\Database\Eloquent\Builder
*/
private function getUserSearchSubQuery($searchValue)
{
return $this->users
->query()
->select('id')
->where('username', 'like', "{$searchValue}%");
}
/**
* {@inheritdoc}
*/
public function apply(AbstractSearch $search, $bit)
public function apply(AbstractSearch $search, $searchValue)
{
$users = $this->users->getIdsForUsername($bit, $search->getActor());
$search->getQuery()->whereIn('id', $users);
$search->setDefaultSort(['id' => $users]);
$search->getQuery()
->whereIn('id',
$this->getUserSearchSubQuery($searchValue));
}
}

View File

@@ -76,7 +76,7 @@ class ListDiscussionsControllerTest extends ApiControllerTestCase
/**
* @test
*/
public function can_search_for_word_in_title_and_post()
public function can_search_for_word_in_post()
{
$this->database()->table('posts')->insert([
['id' => 2, 'discussion_id' => 2, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '<t><p>not in text</p></t>'],
@@ -98,7 +98,7 @@ class ListDiscussionsControllerTest extends ApiControllerTestCase
}, $data['data']);
// Order-independent comparison
$this->assertEquals(['2', '3'], $ids, 'IDs do not match', 0.0, 10, true);
$this->assertEquals(['3'], $ids, 'IDs do not match', 0.0, 10, true);
}
/**
@@ -126,7 +126,7 @@ class ListDiscussionsControllerTest extends ApiControllerTestCase
}, $data['data']);
// Order-independent comparison
$this->assertEquals(['2', '3'], $ids, 'IDs do not match', 0.0, 10, true);
$this->assertEquals(['3'], $ids, 'IDs do not match', 0.0, 10, true);
}
/**

View File

@@ -10,6 +10,7 @@
*/
use Flarum\Install\AdminUser;
use Flarum\Install\BaseUrl;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\Installation;
@@ -48,7 +49,7 @@ $installation = new Installation(
$pipeline = $installation
->configPath('config.php')
->debugMode(true)
->baseUrl('http://localhost')
->baseUrl(BaseUrl::fromString('http://localhost'))
->databaseConfig(new DatabaseConfig(
'mysql',
env('DB_HOST', 'localhost'),

View File

@@ -0,0 +1,85 @@
<?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\Tests\unit;
use Flarum\Install\BaseUrl;
use PHPUnit\Framework\TestCase;
use Zend\Diactoros\Uri;
class BaseUrlTest extends TestCase
{
/**
* @dataProvider urlProvider
*/
public function test_base_url_simulating_cli_installer($uri, $expected)
{
$this->assertEquals($expected, BaseUrl::fromString($uri));
}
/**
* @dataProvider urlProvider
*/
public function test_base_url_simulating_web_installer($uri, $expected)
{
$uri = new Uri($uri);
$this->assertEquals($expected, BaseUrl::fromUri($uri));
}
/**
* @dataProvider emailProvider
*/
public function test_default_email_generation($uri, $expected)
{
$this->assertEquals(
$expected,
BaseUrl::fromString($uri)->toEmail('noreply')
);
}
public function urlProvider()
{
return [
['', ''],
['flarum.org', 'http://flarum.org'],
['flarum.org/', 'http://flarum.org'],
['http://flarum.org', 'http://flarum.org'],
['http://flarum.org/', 'http://flarum.org'],
['https://flarum.org', 'https://flarum.org'],
['http://flarum.org/index.php', 'http://flarum.org'],
['http://flarum.org/index.php/', 'http://flarum.org'],
['http://flarum.org/flarum', 'http://flarum.org/flarum'],
['http://flarum.org/flarum/index.php', 'http://flarum.org/flarum'],
['http://flarum.org/flarum/index.php/', 'http://flarum.org/flarum'],
['sub.flarum.org', 'http://sub.flarum.org'],
['http://sub.flarum.org', 'http://sub.flarum.org'],
];
}
public function emailProvider()
{
return [
['flarum.org', 'noreply@flarum.org'],
['flarum.org/', 'noreply@flarum.org'],
['http://flarum.org', 'noreply@flarum.org'],
['http://flarum.org/', 'noreply@flarum.org'],
['https://flarum.org', 'noreply@flarum.org'],
['http://flarum.org/index.php', 'noreply@flarum.org'],
['http://flarum.org/index.php/', 'noreply@flarum.org'],
['http://flarum.org/flarum', 'noreply@flarum.org'],
['http://flarum.org/flarum/index.php', 'noreply@flarum.org'],
['http://flarum.org/flarum/index.php/', 'noreply@flarum.org'],
['sub.flarum.org', 'noreply@sub.flarum.org'],
['http://sub.flarum.org', 'noreply@sub.flarum.org'],
];
}
}