Merge branch 'master' into 1236-user-preferences

This commit is contained in:
Daniël Klabbers 2019-03-08 22:07:54 +01:00 committed by GitHub
commit c19b2e99bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
146 changed files with 3242 additions and 2079 deletions

2
.gitignore vendored
View File

@ -4,6 +4,6 @@ composer.phar
node_modules
.DS_Store
Thumbs.db
/tests/tmp
/tests/integration/tmp
.vagrant
.idea/*

View File

@ -7,13 +7,14 @@ cache:
install:
- composer install
- mysql -e 'CREATE DATABASE flarum;'
- mysql -e 'CREATE DATABASE flarum_test;'
before_script:
- echo 'error_reporting = E_ALL' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
script:
- vendor/bin/phpunit --coverage-clover=coverage.xml
after_success:
- bash <(curl -s https://codecov.io/bash)
- composer test:setup
- composer test
jobs:
include:
@ -23,8 +24,8 @@ jobs:
- php: 7.2
env: DB=mysql
- php: 7.2
env: DB=mysql PREFIX=forum_
- php: 7.3
env: DB=mysql
- php: 7.1
addons:
@ -36,6 +37,14 @@ jobs:
mariadb: '10.2'
env: DB=mariadb
- php: 7.3
addons:
mariadb: '10.2'
env: DB=mariadb
- php: 7.2
env: DB=mysql PREFIX=forum_
- stage: build
language: generic
if: branch = master AND type = push

View File

@ -4,12 +4,37 @@
### Added
- New `hasPermission()` helper method for `Group` objects ([9684fbc](https://github.com/flarum/core/commit/9684fbc4da07d32aa322d9228302a23418412cb9))
- Expose supported mail drivers in IoC container ([208bad3](https://github.com/flarum/core/commit/208bad393f37bfdb76007afcddfa4b7451563e9d))
- More test for some API endpoints ([1670590](https://github.com/flarum/core/commit/167059027e5a066d618599c90164ef1b5a509148))
- The `Formatter\Rendering` event now receives the HTTP request instance as well ([0ab9fac](https://github.com/flarum/core/commit/0ab9facc4bd59a260575e6fc650793c663e5866a))
- More and better validation in installer UIs
- Check and enforce minimum MariaDB ([7ff9a90](https://github.com/flarum/core/commit/7ff9a90204923293adc520d3c02dc984845d4f9f))
- Revert publication of assets when installation fails ([ed9591c](https://github.com/flarum/core/commit/ed9591c16fb2ea7a4be3387b805d855a53e0a7d5))
- Benefit from Laravel's database reconnection logic in long-running tasks ([e0becd0](https://github.com/flarum/core/commit/e0becd0c7bda939048923c1f86648793feee78d5))
### Changed
- Performance: Actually cache translations on disk ([0d16fac](https://github.com/flarum/core/commit/0d16fac001bb735ee66e82871183516aeac269b7))
- Allow per-site extenders to override extension extenders ([ba594de](https://github.com/flarum/core/commit/ba594de13a033480834d53d73f747b05fe9796f8))
- Do not resolve objects from the IoC container (in service providers and extenders) until they are actually used
- Replace event subscribers (that resolve objects from the IoC container) with listeners (that resolve lazily)
- Use custom service provider for Mail component ([ac5e26a](https://github.com/flarum/core/commit/ac5e26a254d89e21bd4c115b6cbd40338e2e4b4b))
- Update to Laravel 5.7, revert custom logic for building database index names
- Refactored installer, extracted Installation class and pipeline for reuse in CLI and web installers ([790d5be](https://github.com/flarum/core/commit/790d5beee5e283178716bc8f9901c758d9e5b6a0))
- Use whitelist for enabling pre-installed extensions during installation ([4585f03](https://github.com/flarum/core/commit/4585f03ee356c92942fbc2ae8c683c651b473954))
- Update minimum MySQL version ([7ff9a90](https://github.com/flarum/core/commit/7ff9a90204923293adc520d3c02dc984845d4f9f))
### Fixed
- Fix signing up via OAuth providers ([67f9375](https://github.com/flarum/core/commit/67f9375d4745add194ae3249d526197c32fd5461))
- Signing up via OAuth providers was broken ([67f9375](https://github.com/flarum/core/commit/67f9375d4745add194ae3249d526197c32fd5461))
- Group badges were overlapping ([16eb1fa](https://github.com/flarum/core/commit/16eb1fa63b6d7b80ec30c24c0e406a2b7ab09934))
- API: Endpoint for uninstalling extensions returned an error ([c761802](https://github.com/flarum/core/commit/c76180290056ddbab67baf5ede814fcedf1dcf14))
- Documentation links in installer were outdated ([b58380e](https://github.com/flarum/core/commit/b58380e224ee54abdade3d0a4cc107ef5c91c9a9))
- Event posts where counted when aggregating user posts ([671fdec](https://github.com/flarum/core/commit/671fdec8d0a092ccceb5d4d5f657d0f4287fc4c7))
- Admins could not reset user passwords ([c67fb2d](https://github.com/flarum/core/commit/c67fb2d4b6a128c71d65dc6703310c0b62f91be2))
- Several down migrations were invalid
- Validation errors on reset password page resulted in HTTP 404 ([4611abe](https://github.com/flarum/core/commit/4611abe5db8b94ca3dc7bf9c447fca7c67358ee3))
### Removed
- `php flarum install --defaults` - this was meant to be used in our old development VM ([44c9109](https://github.com/flarum/core/commit/44c91099cd77138bb5fc29f14fb1e81a9781272d))
## [0.1.0-beta.8.1](https://github.com/flarum/core/compare/v0.1.0-beta.8...v0.1.0-beta.8.1)

View File

@ -26,20 +26,20 @@
"dflydev/fig-cookies": "^1.0.2",
"doctrine/dbal": "^2.7",
"franzl/whoops-middleware": "^0.4.0",
"illuminate/bus": "5.5.*",
"illuminate/cache": "5.5.*",
"illuminate/config": "5.5.*",
"illuminate/container": "5.5.*",
"illuminate/contracts": "5.5.*",
"illuminate/database": "5.5.*",
"illuminate/events": "5.5.*",
"illuminate/filesystem": "5.5.*",
"illuminate/hashing": "5.5.*",
"illuminate/mail": "5.5.*",
"illuminate/session": "5.5.*",
"illuminate/support": "5.5.*",
"illuminate/validation": "5.5.*",
"illuminate/view": "5.5.*",
"illuminate/bus": "5.7.*",
"illuminate/cache": "5.7.*",
"illuminate/config": "5.7.*",
"illuminate/container": "5.7.*",
"illuminate/contracts": "5.7.*",
"illuminate/database": "5.7.*",
"illuminate/events": "5.7.*",
"illuminate/filesystem": "5.7.*",
"illuminate/hashing": "5.7.*",
"illuminate/mail": "5.7.*",
"illuminate/session": "5.7.*",
"illuminate/support": "5.7.*",
"illuminate/validation": "5.7.*",
"illuminate/view": "5.7.*",
"intervention/image": "^2.3.0",
"league/flysystem": "^1.0.11",
"matthiasmullie/minify": "^1.3",
@ -54,8 +54,7 @@
"psr/http-server-middleware": "^1.0",
"s9e/text-formatter": "^1.2.0",
"symfony/config": "^3.3",
"symfony/console": "^3.3",
"symfony/http-foundation": "^3.3",
"symfony/console": "^4.2",
"symfony/translation": "^3.3",
"symfony/yaml": "^3.3",
"tobscure/json-api": "^0.3.0",
@ -87,5 +86,14 @@
"branch-alias": {
"dev-master": "0.1.x-dev"
}
},
"scripts": {
"test": [
"@test:unit",
"@test:integration"
],
"test:unit": "phpunit -c tests/phpunit.unit.xml",
"test:integration": "phpunit -c tests/phpunit.integration.xml",
"test:setup": "@php tests/integration/setup.php"
}
}

4
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

8
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

701
js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"private": true,
"name": "@flarum/core",
"dependencies": {
"bootstrap": "^3.3.7",
"bootstrap": "^3.4.1",
"classnames": "^2.2.5",
"color-thief-browser": "^2.0.2",
"expose-loader": "^0.7.5",

View File

@ -84,17 +84,21 @@ export default class EditGroupModal extends Modal {
return items;
}
submitData() {
return {
nameSingular: this.nameSingular(),
namePlural: this.namePlural(),
color: this.color(),
icon: this.icon()
};
}
onsubmit(e) {
e.preventDefault();
this.loading = true;
this.group.save({
nameSingular: this.nameSingular(),
namePlural: this.namePlural(),
color: this.color(),
icon: this.icon()
}, {errorHandler: this.onerror.bind(this)})
this.group.save(this.submitData(), {errorHandler: this.onerror.bind(this)})
.then(this.hide.bind(this))
.catch(() => {
this.loading = false;

View File

@ -77,7 +77,7 @@ export default class EditUserModal extends Modal {
<label>{app.translator.trans('core.forum.edit_user.password_heading')}</label>
<div>
<label className="checkbox">
<input type="checkbox" checked={this.setPassword()} onChange={e => {
<input type="checkbox" onchange={e => {
this.setPassword(e.target.checked);
m.redraw(true);
if (e.target.checked) this.$('[name=password]').select();

View File

@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@ -24,18 +23,14 @@ return [
})
->delete();
$schema->table('access_tokens', function (Blueprint $table) use ($schema) {
$schema->table('access_tokens', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('access_tokens', function (Blueprint $table) use ($schema) {
$schema->table('access_tokens', function (Blueprint $table) {
$table->dropForeign(['user_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,21 +9,18 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('api_keys', function (Blueprint $table) use ($schema) {
$schema->table('api_keys', function (Blueprint $table) {
$table->dropPrimary(['id']);
$table->renameColumn('id', 'key');
$table->unique('key');
Migration::fixIndexNames($schema, $table);
});
$schema->table('api_keys', function (Blueprint $table) use ($schema) {
$schema->table('api_keys', function (Blueprint $table) {
$table->increments('id');
$table->string('allowed_ips')->nullable();
$table->string('scopes')->nullable();
@ -32,25 +29,19 @@ return [
$table->dateTime('last_activity_at')->nullable();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('api_keys', function (Blueprint $table) use ($schema) {
$schema->table('api_keys', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropColumn('id', 'allowed_ips', 'user_id', 'scopes', 'created_at');
Migration::fixIndexNames($schema, $table);
});
$schema->table('api_keys', function (Blueprint $table) use ($schema) {
$schema->table('api_keys', function (Blueprint $table) {
$table->dropUnique(['key']);
$table->renameColumn('key', 'id');
$table->primary('id');
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@ -34,26 +33,22 @@ return [
'last_post_id' => $selectId('posts', 'last_post_id'),
]);
$schema->table('discussions', function (Blueprint $table) use ($schema) {
$schema->table('discussions', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
$table->foreign('last_posted_user_id')->references('id')->on('users')->onDelete('set null');
$table->foreign('hidden_user_id')->references('id')->on('users')->onDelete('set null');
$table->foreign('first_post_id')->references('id')->on('posts')->onDelete('set null');
$table->foreign('last_post_id')->references('id')->on('posts')->onDelete('set null');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('discussions', function (Blueprint $table) use ($schema) {
$schema->table('discussions', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropForeign(['last_posted_user_id']);
$table->dropForeign(['hidden_user_id']);
$table->dropForeign(['first_post_id']);
$table->dropForeign(['last_post_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@ -27,20 +26,16 @@ return [
})
->delete();
$schema->table('discussion_user', function (Blueprint $table) use ($schema) {
$schema->table('discussion_user', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('discussion_id')->references('id')->on('discussions')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('discussion_user', function (Blueprint $table) use ($schema) {
$schema->table('discussion_user', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropForeign(['discussion_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@ -24,18 +23,14 @@ return [
})
->delete();
$schema->table('email_tokens', function (Blueprint $table) use ($schema) {
$schema->table('email_tokens', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('email_tokens', function (Blueprint $table) use ($schema) {
$schema->table('email_tokens', function (Blueprint $table) {
$table->dropForeign(['user_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@ -24,18 +23,14 @@ return [
})
->delete();
$schema->table('group_permission', function (Blueprint $table) use ($schema) {
$schema->table('group_permission', function (Blueprint $table) {
$table->foreign('group_id')->references('id')->on('groups')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('group_permission', function (Blueprint $table) use ($schema) {
$schema->table('group_permission', function (Blueprint $table) {
$table->dropForeign(['group_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@ -27,20 +26,16 @@ return [
})
->delete();
$schema->table('group_user', function (Blueprint $table) use ($schema) {
$schema->table('group_user', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('group_id')->references('id')->on('groups')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('group_user', function (Blueprint $table) use ($schema) {
$schema->table('group_user', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropForeign(['group_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@ -31,20 +30,16 @@ return [
})
->update(['from_user_id' => null]);
$schema->table('notifications', function (Blueprint $table) use ($schema) {
$schema->table('notifications', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('from_user_id')->references('id')->on('users')->onDelete('set null');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('notifications', function (Blueprint $table) use ($schema) {
$schema->table('notifications', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropForeign(['from_user_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@ -24,18 +23,14 @@ return [
})
->delete();
$schema->table('password_tokens', function (Blueprint $table) use ($schema) {
$schema->table('password_tokens', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('password_tokens', function (Blueprint $table) use ($schema) {
$schema->table('password_tokens', function (Blueprint $table) {
$table->dropForeign(['user_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,7 +9,6 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
@ -32,23 +31,18 @@ return [
'hidden_user_id' => $selectId('users', 'hidden_user_id'),
]);
$schema->table('posts', function (Blueprint $table) use ($schema) {
$schema->table('posts', function (Blueprint $table) {
$table->foreign('user_id')->references('id')->on('users')->onDelete('set null');
$table->foreign('edited_user_id')->references('id')->on('users')->onDelete('set null');
$table->foreign('hidden_user_id')->references('id')->on('users')->onDelete('set null');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('posts', function (Blueprint $table) use ($schema) {
$schema->table('posts', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropForeign(['discussion_id']);
$table->dropForeign(['edited_user_id']);
$table->dropForeign(['hidden_user_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,30 +9,25 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('users', function (Blueprint $table) use ($schema) {
$schema->table('users', function (Blueprint $table) {
$table->index('joined_at');
$table->index('last_seen_at');
$table->index('discussion_count');
$table->index('comment_count');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('users', function (Blueprint $table) use ($schema) {
$schema->table('users', function (Blueprint $table) {
$table->dropIndex(['joined_at']);
$table->dropIndex(['last_seen_at']);
$table->dropIndex(['discussion_count']);
$table->dropIndex(['comment_count']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,13 +9,12 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('discussions', function (Blueprint $table) use ($schema) {
$schema->table('discussions', function (Blueprint $table) {
$table->index('last_posted_at');
$table->index('last_posted_user_id');
$table->index('created_at');
@ -23,13 +22,11 @@ return [
$table->index('comment_count');
$table->index('participant_count');
$table->index('hidden_at');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('discussions', function (Blueprint $table) use ($schema) {
$schema->table('discussions', function (Blueprint $table) {
$table->dropIndex(['last_posted_at']);
$table->dropIndex(['last_posted_user_id']);
$table->dropIndex(['created_at']);
@ -37,8 +34,6 @@ return [
$table->dropIndex(['comment_count']);
$table->dropIndex(['participant_count']);
$table->dropIndex(['hidden_at']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,24 +9,19 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('notifications', function (Blueprint $table) use ($schema) {
$schema->table('notifications', function (Blueprint $table) {
$table->index('user_id');
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('notifications', function (Blueprint $table) use ($schema) {
$schema->table('notifications', function (Blueprint $table) {
$table->dropIndex(['user_id']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -9,28 +9,23 @@
* file that was distributed with this source code.
*/
use Flarum\Database\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return [
'up' => function (Builder $schema) {
$schema->table('posts', function (Blueprint $table) use ($schema) {
$schema->table('posts', function (Blueprint $table) {
$table->index(['discussion_id', 'number']);
$table->index(['discussion_id', 'created_at']);
$table->index(['user_id', 'created_at']);
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) {
$schema->table('posts', function (Blueprint $table) use ($schema) {
$schema->table('posts', function (Blueprint $table) {
$table->dropIndex(['discussion_id', 'number']);
$table->dropIndex(['discussion_id', 'created_at']);
$table->dropIndex(['user_id', 'created_at']);
Migration::fixIndexNames($schema, $table);
});
}
];

View File

@ -24,7 +24,7 @@ return [
},
'down' => function (Builder $schema) {
$schema->table('auth_tokens', function (Blueprint $table) {
$schema->table('registration_tokens', function (Blueprint $table) {
$table->dropColumn('provider', 'identifier', 'user_attributes');
$table->string('payload', 150)->change();

View File

@ -41,7 +41,10 @@ class AdminServiceProvider extends AbstractServiceProvider
});
$this->app->singleton('flarum.admin.routes', function () {
return new RouteCollection;
$routes = new RouteCollection;
$this->populateRoutes($routes);
return $routes;
});
$this->app->singleton('flarum.admin.middleware', function (Application $app) {
@ -103,8 +106,6 @@ class AdminServiceProvider extends AbstractServiceProvider
*/
public function boot()
{
$this->populateRoutes($this->app->make('flarum.admin.routes'));
$this->loadViewsFrom(__DIR__.'/../../views', 'flarum.admin');
$events = $this->app->make('events');

View File

@ -40,7 +40,10 @@ class ApiServiceProvider extends AbstractServiceProvider
});
$this->app->singleton('flarum.api.routes', function () {
return new RouteCollection;
$routes = new RouteCollection;
$this->populateRoutes($routes);
return $routes;
});
$this->app->singleton('flarum.api.middleware', function (Application $app) {
@ -90,8 +93,6 @@ class ApiServiceProvider extends AbstractServiceProvider
*/
public function boot()
{
$this->populateRoutes($this->app->make('flarum.api.routes'));
$this->registerNotificationSerializers();
AbstractSerializeController::setContainer($this->app);

View File

@ -102,7 +102,7 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
);
$serializer = static::$container->make($this->serializer);
$serializer->setActor($request->getAttribute('actor'));
$serializer->setRequest($request);
$element = $this->createElement($data, $serializer)
->with($this->extractInclude($request))

View File

@ -20,6 +20,7 @@ use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use InvalidArgumentException;
use LogicException;
use Psr\Http\Message\ServerRequestInterface as Request;
use Tobscure\JsonApi\AbstractSerializer as BaseAbstractSerializer;
use Tobscure\JsonApi\Collection;
use Tobscure\JsonApi\Relationship;
@ -28,6 +29,11 @@ use Tobscure\JsonApi\SerializerInterface;
abstract class AbstractSerializer extends BaseAbstractSerializer
{
/**
* @var Request
*/
protected $request;
/**
* @var User
*/
@ -43,6 +49,23 @@ abstract class AbstractSerializer extends BaseAbstractSerializer
*/
protected static $container;
/**
* @return Request
*/
public function getRequest()
{
return $this->request;
}
/**
* @param Request $request
*/
public function setRequest(Request $request)
{
$this->request = $request;
$this->actor = $request->getAttribute('actor');
}
/**
* @return User
*/
@ -51,14 +74,6 @@ abstract class AbstractSerializer extends BaseAbstractSerializer
return $this->actor;
}
/**
* @param User $actor
*/
public function setActor(User $actor)
{
$this->actor = $actor;
}
/**
* {@inheritdoc}
*/
@ -231,7 +246,7 @@ abstract class AbstractSerializer extends BaseAbstractSerializer
{
$serializer = static::$container->make($class);
$serializer->setActor($this->actor);
$serializer->setRequest($this->request);
return $serializer;
}

View File

@ -44,7 +44,7 @@ class BasicPostSerializer extends AbstractSerializer
];
if ($post instanceof CommentPost) {
$attributes['contentHtml'] = $post->content_html;
$attributes['contentHtml'] = $post->formatContent($this->request);
} else {
$attributes['content'] = $post->content;
}

View File

@ -43,8 +43,6 @@ class PostSerializer extends BasicPostSerializer
$canEdit = $gate->allows('edit', $post);
if ($post instanceof CommentPost) {
$attributes['contentHtml'] = $post->content_html;
if ($canEdit) {
$attributes['content'] = $post->content;
}

View File

@ -12,8 +12,9 @@
namespace Flarum\Database;
use Flarum\Foundation\AbstractServiceProvider;
use Illuminate\Database\ConnectionResolver;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Database\Capsule\Manager;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\ConnectionResolverInterface;
class DatabaseServiceProvider extends AbstractServiceProvider
{
@ -22,29 +23,39 @@ class DatabaseServiceProvider extends AbstractServiceProvider
*/
public function register()
{
$this->app->singleton('flarum.db', function () {
$factory = new ConnectionFactory($this->app);
$this->app->singleton(Manager::class, function ($app) {
$manager = new Manager($app);
$dbConfig = $this->app->config('database');
$dbConfig['engine'] = 'InnoDB';
$connection = $factory->make($dbConfig);
$connection->setEventDispatcher($this->app->make('Illuminate\Contracts\Events\Dispatcher'));
$config = $app->config('database');
$config['engine'] = 'InnoDB';
$config['prefix_indexes'] = true;
return $connection;
$manager->addConnection($config, 'flarum');
return $manager;
});
$this->app->alias('flarum.db', 'Illuminate\Database\ConnectionInterface');
$this->app->singleton(ConnectionResolverInterface::class, function ($app) {
$manager = $app->make(Manager::class);
$manager->setAsGlobal();
$manager->bootEloquent();
$this->app->singleton('Illuminate\Database\ConnectionResolverInterface', function () {
$resolver = new ConnectionResolver([
'flarum' => $this->app->make('flarum.db'),
]);
$resolver->setDefaultConnection('flarum');
$dbManager = $manager->getDatabaseManager();
$dbManager->setDefaultConnection('flarum');
return $resolver;
return $dbManager;
});
$this->app->alias('Illuminate\Database\ConnectionResolverInterface', 'db');
$this->app->alias(ConnectionResolverInterface::class, 'db');
$this->app->singleton(ConnectionInterface::class, function ($app) {
$resolver = $app->make(ConnectionResolverInterface::class);
return $resolver->connection();
});
$this->app->alias(ConnectionInterface::class, 'db.connection');
$this->app->alias(ConnectionInterface::class, 'flarum.db');
}
/**
@ -52,7 +63,7 @@ class DatabaseServiceProvider extends AbstractServiceProvider
*/
public function boot()
{
AbstractModel::setConnectionResolver($this->app->make('Illuminate\Database\ConnectionResolverInterface'));
AbstractModel::setConnectionResolver($this->app->make(ConnectionResolverInterface::class));
AbstractModel::setEventDispatcher($this->app->make('events'));
}
}

View File

@ -31,8 +31,6 @@ abstract class Migration
'up' => function (Builder $schema) use ($name, $definition) {
$schema->create($name, function (Blueprint $table) use ($schema, $definition) {
$definition($table);
static::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) use ($name) {
@ -68,8 +66,6 @@ abstract class Migration
$type = array_shift($options);
$table->addColumn($type, $columnName, $options);
}
Migration::fixIndexNames($schema, $table);
});
},
'down' => function (Builder $schema) use ($tableName, $columnDefinitions) {
@ -193,27 +189,4 @@ abstract class Migration
}
];
}
/**
* Add a prefix to index names on the given table blueprint.
*
* Laravel 5.5 doesn't automatically add the table prefix to index
* names, but this has been fixed in 5.7. We will manually fix the
* names for now, and we can remove this when we upgrade to 5.7.
*/
public static function fixIndexNames(Builder $schema, Blueprint $table)
{
$indexCommands = [
'unique', 'index', 'spatialIndex', 'foreign',
'dropUnique', 'dropIndex', 'dropSpatialIndex', 'dropForeign'
];
$prefix = $schema->getConnection()->getTablePrefix();
foreach ($table->getCommands() as $command) {
if (in_array($command->name, $indexCommands)) {
$command->index = $prefix.$command->index;
}
}
}
}

View File

@ -13,6 +13,7 @@ namespace Flarum\Database;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\Application;
use Illuminate\Filesystem\Filesystem;
class MigrationServiceProvider extends AbstractServiceProvider
{
@ -21,12 +22,12 @@ class MigrationServiceProvider extends AbstractServiceProvider
*/
public function register()
{
$this->app->singleton('Flarum\Database\MigrationRepositoryInterface', function ($app) {
$this->app->singleton(MigrationRepositoryInterface::class, function ($app) {
return new DatabaseMigrationRepository($app['flarum.db'], 'migrations');
});
$this->app->bind(MigrationCreator::class, function (Application $app) {
return new MigrationCreator($app->make('Illuminate\Filesystem\Filesystem'), $app->basePath());
return new MigrationCreator($app->make(Filesystem::class), $app->basePath());
});
}
}

View File

@ -146,7 +146,9 @@ class Migrator
*/
public function reset($path, Extension $extension = null)
{
$migrations = array_reverse($this->repository->getRan($extension->getId()));
$migrations = array_reverse($this->repository->getRan(
$extension ? $extension->getId() : null
));
$count = count($migrations);

View File

@ -20,9 +20,6 @@ use Illuminate\Contracts\Events\Dispatcher;
class DiscussionMetadataUpdater
{
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(Posted::class, [$this, 'whenPostWasPosted']);
@ -31,9 +28,6 @@ class DiscussionMetadataUpdater
$events->listen(Restored::class, [$this, 'whenPostWasRestored']);
}
/**
* @param Posted $event
*/
public function whenPostWasPosted(Posted $event)
{
$discussion = $event->post->discussion;
@ -46,9 +40,6 @@ class DiscussionMetadataUpdater
}
}
/**
* @param \Flarum\Post\Event\Deleted $event
*/
public function whenPostWasDeleted(Deleted $event)
{
$this->removePost($event->post);
@ -60,17 +51,11 @@ class DiscussionMetadataUpdater
}
}
/**
* @param \Flarum\Post\Event\Hidden $event
*/
public function whenPostWasHidden(Hidden $event)
{
$this->removePost($event->post);
}
/**
* @param Restored $event
*/
public function whenPostWasRestored(Restored $event)
{
$discussion = $event->post->discussion;
@ -83,9 +68,6 @@ class DiscussionMetadataUpdater
}
}
/**
* @param Post $post
*/
protected function removePost(Post $post)
{
$discussion = $post->discussion;

View File

@ -15,7 +15,6 @@ use Flarum\Discussion\Event\Renamed;
use Flarum\Notification\Blueprint\DiscussionRenamedBlueprint;
use Flarum\Notification\NotificationSyncer;
use Flarum\Post\DiscussionRenamedPost;
use Illuminate\Contracts\Events\Dispatcher;
class DiscussionRenamedLogger
{
@ -24,26 +23,12 @@ class DiscussionRenamedLogger
*/
protected $notifications;
/**
* @param NotificationSyncer $notifications
*/
public function __construct(NotificationSyncer $notifications)
{
$this->notifications = $notifications;
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(Renamed::class, [$this, 'whenDiscussionWasRenamed']);
}
/**
* @param \Flarum\Discussion\Event\Renamed $event
*/
public function whenDiscussionWasRenamed(Renamed $event)
public function handle(Renamed $event)
{
$post = DiscussionRenamedPost::reply(
$event->discussion->id,

View File

@ -11,6 +11,7 @@
namespace Flarum\Discussion;
use Flarum\Discussion\Event\Renamed;
use Flarum\Foundation\AbstractServiceProvider;
class DiscussionServiceProvider extends AbstractServiceProvider
@ -24,6 +25,9 @@ class DiscussionServiceProvider extends AbstractServiceProvider
$events->subscribe(DiscussionMetadataUpdater::class);
$events->subscribe(DiscussionPolicy::class);
$events->subscribe(DiscussionRenamedLogger::class);
$events->listen(
Renamed::class, DiscussionRenamedLogger::class
);
}
}

View File

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

View File

@ -21,7 +21,7 @@ class Formatter implements ExtenderInterface, LifecycleInterface
{
protected $callback;
public function configure(callable $callback)
public function configure($callback)
{
$this->callback = $callback;
@ -34,8 +34,14 @@ class Formatter implements ExtenderInterface, LifecycleInterface
$events->listen(
Configuring::class,
function (Configuring $event) {
call_user_func($this->callback, $event->configurator);
function (Configuring $event) use ($container) {
if (is_string($this->callback)) {
$callback = $container->make($this->callback);
} else {
$callback = $this->callback;
}
$callback($event->configurator);
}
);
}

View File

@ -14,7 +14,6 @@ namespace Flarum\Extension;
use Flarum\Extension\Event\Disabling;
use Flarum\Http\Exception\ForbiddenException;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Contracts\Events\Dispatcher;
class DefaultLanguagePackGuard
{
@ -28,26 +27,17 @@ class DefaultLanguagePackGuard
$this->settings = $settings;
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
public function handle(Disabling $event)
{
$events->listen(Disabling::class, [$this, 'whenExtensionWillBeDisabled']);
}
if (! in_array('flarum-locale', $event->extension->extra)) {
return;
}
/**
* @param Disabling $event
* @throws ForbiddenException
*/
public function whenExtensionWillBeDisabled(Disabling $event)
{
if (in_array('flarum-locale', $event->extension->extra)) {
$defaultLocale = $this->settings->get('default_locale');
$locale = array_get($event->extension->extra, 'flarum-locale.code');
if ($locale === $defaultLocale) {
throw new ForbiddenException('You cannot disable the default language pack!');
}
$defaultLocale = $this->settings->get('default_locale');
$locale = array_get($event->extension->extra, 'flarum-locale.code');
if ($locale === $defaultLocale) {
throw new ForbiddenException('You cannot disable the default language pack!');
}
}
}

View File

@ -11,12 +11,18 @@
namespace Flarum\Extension;
use Flarum\Database\Migrator;
use Flarum\Extend\Compat;
use Flarum\Extend\LifecycleInterface;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemInterface;
use League\Flysystem\MountManager;
use League\Flysystem\Plugin\ListFiles;
/**
* @property string $name
@ -307,6 +313,25 @@ class Extension implements Arrayable
return realpath($this->path.'/assets/') !== false;
}
public function copyAssetsTo(FilesystemInterface $target)
{
if (! $this->hasAssets()) {
return;
}
$mount = new MountManager([
'source' => $source = new Filesystem(new Local($this->getPath().'/assets')),
'target' => $target,
]);
$source->addPlugin(new ListFiles);
$assetFiles = $source->listFiles('/', true);
foreach ($assetFiles as $file) {
$mount->copy("source://$file[path]", "target://extensions/$this->id/$file[path]");
}
}
/**
* Tests whether the extension has migrations.
*
@ -317,6 +342,19 @@ class Extension implements Arrayable
return realpath($this->path.'/migrations/') !== false;
}
public function migrate(Migrator $migrator, $direction = 'up')
{
if (! $this->hasMigrations()) {
return;
}
if ($direction == 'up') {
return $migrator->run($this->getPath().'/migrations', $this);
} else {
return $migrator->reset($this->getPath().'/migrations', $this);
}
}
/**
* Generates an array result for the object.
*

View File

@ -222,26 +222,16 @@ class ExtensionManager
* Runs the database migrations for the extension.
*
* @param Extension $extension
* @param bool|true $up
* @param string $direction
* @return void
*/
public function migrate(Extension $extension, $up = true)
public function migrate(Extension $extension, $direction = 'up')
{
if (! $extension->hasMigrations()) {
return;
}
$migrationDir = $extension->getPath().'/migrations';
$this->app->bind('Illuminate\Database\Schema\Builder', function ($container) {
return $container->make('Illuminate\Database\ConnectionInterface')->getSchemaBuilder();
});
if ($up) {
$this->migrator->run($migrationDir, $extension);
} else {
$this->migrator->reset($migrationDir, $extension);
}
$extension->migrate($this->migrator, $direction);
}
/**
@ -252,7 +242,7 @@ class ExtensionManager
*/
public function migrateDown(Extension $extension)
{
return $this->migrate($extension, false);
return $this->migrate($extension, 'down');
}
/**

View File

@ -11,6 +11,7 @@
namespace Flarum\Extension;
use Flarum\Extension\Event\Disabling;
use Flarum\Foundation\AbstractServiceProvider;
use Illuminate\Contracts\Container\Container;
@ -27,7 +28,7 @@ class ExtensionServiceProvider extends AbstractServiceProvider
// Boot extensions when the app is booting. This must be done as a boot
// listener on the app rather than in the service provider's boot method
// below, so that extensions have a chance to register things on the
// container before the core boot code runs.
// container before the core boots up (and starts resolving services).
$this->app->booting(function (Container $app) {
$app->make('flarum.extensions')->extend($app);
});
@ -38,8 +39,9 @@ class ExtensionServiceProvider extends AbstractServiceProvider
*/
public function boot()
{
$events = $this->app->make('events');
$events->subscribe(DefaultLanguagePackGuard::class);
$this->app->make('events')->listen(
Disabling::class,
DefaultLanguagePackGuard::class
);
}
}

View File

@ -11,6 +11,7 @@
namespace Flarum\Formatter\Event;
use Psr\Http\Message\ServerRequestInterface;
use s9e\TextFormatter\Renderer;
class Rendering
@ -30,15 +31,22 @@ class Rendering
*/
public $xml;
/**
* @var ServerRequestInterface
*/
public $request;
/**
* @param Renderer $renderer
* @param mixed $context
* @param string $xml
* @param ServerRequestInterface|null $request
*/
public function __construct(Renderer $renderer, $context, &$xml)
public function __construct(Renderer $renderer, $context, &$xml, ServerRequestInterface $request = null)
{
$this->renderer = $renderer;
$this->context = $context;
$this->xml = &$xml;
$this->request = $request;
}
}

View File

@ -16,6 +16,7 @@ use Flarum\Formatter\Event\Parsing;
use Flarum\Formatter\Event\Rendering;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Contracts\Events\Dispatcher;
use Psr\Http\Message\ServerRequestInterface;
use s9e\TextFormatter\Configurator;
use s9e\TextFormatter\Unparser;
@ -69,13 +70,14 @@ class Formatter
*
* @param string $xml
* @param mixed $context
* @param ServerRequestInterface|null $request
* @return string
*/
public function render($xml, $context = null)
public function render($xml, $context = null, ServerRequestInterface $request = null)
{
$renderer = $this->getRenderer();
$this->events->dispatch(new Rendering($renderer, $context, $xml));
$this->events->dispatch(new Rendering($renderer, $context, $xml, $request));
return $renderer->render($xml);
}

View File

@ -58,7 +58,7 @@ class Index
$apiDocument = $this->getApiDocument($request->getAttribute('actor'), $params);
$document->content = $this->view->make('flarum.forum::frontend.content.index', compact('apiDocument', 'page', 'forum'));
$document->content = $this->view->make('flarum.forum::frontend.content.index', compact('apiDocument', 'page'));
$document->payload['apiDocument'] = $apiDocument;
return $document;

View File

@ -18,6 +18,7 @@ use Flarum\User\PasswordToken;
use Flarum\User\UserValidator;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Validation\Factory;
use Illuminate\Support\MessageBag;
use Illuminate\Validation\ValidationException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
@ -86,9 +87,9 @@ class SavePasswordController implements RequestHandlerInterface
throw new ValidationException($validator);
}
} catch (ValidationException $e) {
$request->getAttribute('session')->put('errors', $e->errors());
$request->getAttribute('session')->put('errors', new MessageBag($e->errors()));
return new RedirectResponse($this->url->to('forum')->route('resetPassword', ['token' => $token->id]));
return new RedirectResponse($this->url->to('forum')->route('resetPassword', ['token' => $token->token]));
}
$token->user->changePassword($password);

View File

@ -30,6 +30,7 @@ use Flarum\Http\RouteHandlerFactory;
use Flarum\Http\UrlGenerator;
use Flarum\Locale\LocaleManager;
use Flarum\Settings\Event\Saved;
use Flarum\Settings\Event\Saving;
use Flarum\Settings\SettingsRepositoryInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Zend\Stratigility\MiddlewarePipe;
@ -46,7 +47,10 @@ class ForumServiceProvider extends AbstractServiceProvider
});
$this->app->singleton('flarum.forum.routes', function () {
return new RouteCollection;
$routes = new RouteCollection;
$this->populateRoutes($routes);
return $routes;
});
$this->app->singleton('flarum.forum.middleware', function (Application $app) {
@ -110,8 +114,6 @@ class ForumServiceProvider extends AbstractServiceProvider
*/
public function boot()
{
$this->populateRoutes($this->app->make('flarum.forum.routes'));
$this->loadViewsFrom(__DIR__.'/../../views', 'flarum.forum');
$this->app->make('view')->share([
@ -140,15 +142,26 @@ class ForumServiceProvider extends AbstractServiceProvider
$this->app->make(LocaleManager::class)
);
$recompile->whenSettingsSaved($event);
$validator = new ValidateCustomLess(
$this->app->make('flarum.assets.forum'),
$this->app->make('flarum.locales'),
$this->app
);
$validator->whenSettingsSaved($event);
}
);
$events->subscribe(
new ValidateCustomLess(
$this->app->make('flarum.assets.forum'),
$this->app->make('flarum.locales'),
$this->app
)
$events->listen(
Saving::class,
function (Saving $event) {
$validator = new ValidateCustomLess(
$this->app->make('flarum.assets.forum'),
$this->app->make('flarum.locales'),
$this->app
);
$validator->whenSettingsSaving($event);
}
);
}

View File

@ -19,7 +19,6 @@ use Flarum\Settings\Event\Saving;
use Flarum\Settings\OverrideSettingsRepository;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Filesystem\FilesystemAdapter;
use League\Flysystem\Adapter\NullAdapter;
use League\Flysystem\Filesystem;
@ -54,67 +53,55 @@ class ValidateCustomLess
$this->container = $container;
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(Saving::class, [$this, 'whenSettingsSaving']);
$events->listen(Saved::class, [$this, 'whenSettingsSaved']);
}
/**
* @param Saving $event
* @throws ValidationException
*/
public function whenSettingsSaving(Saving $event)
{
if (isset($event->settings['custom_less'])) {
// We haven't saved the settings yet, but we want to trial a full
// recompile of the CSS to see if this custom LESS will break
// anything. In order to do that, we will temporarily override the
// settings repository with the new settings so that the recompile
// is effective. We will also use a dummy filesystem so that nothing
// is actually written yet.
$settings = $this->container->make(SettingsRepositoryInterface::class);
$this->container->extend(
SettingsRepositoryInterface::class,
function ($settings) use ($event) {
return new OverrideSettingsRepository($settings, $event->settings);
}
);
$assetsDir = $this->assets->getAssetsDir();
$this->assets->setAssetsDir(new FilesystemAdapter(new Filesystem(new NullAdapter)));
try {
$this->assets->makeCss()->commit();
foreach ($this->locales->getLocales() as $locale => $name) {
$this->assets->makeLocaleCss($locale)->commit();
}
} catch (Less_Exception_Parser $e) {
throw new ValidationException(['custom_less' => $e->getMessage()]);
}
$this->assets->setAssetsDir($assetsDir);
$this->container->instance(SettingsRepositoryInterface::class, $settings);
if (! isset($event->settings['custom_less'])) {
return;
}
}
/**
* @param Saved $event
*/
public function whenSettingsSaved(Saved $event)
{
if (isset($event->settings['custom_less'])) {
$this->assets->makeCss()->flush();
// We haven't saved the settings yet, but we want to trial a full
// recompile of the CSS to see if this custom LESS will break
// anything. In order to do that, we will temporarily override the
// settings repository with the new settings so that the recompile
// is effective. We will also use a dummy filesystem so that nothing
// is actually written yet.
$settings = $this->container->make(SettingsRepositoryInterface::class);
$this->container->extend(
SettingsRepositoryInterface::class,
function ($settings) use ($event) {
return new OverrideSettingsRepository($settings, $event->settings);
}
);
$assetsDir = $this->assets->getAssetsDir();
$this->assets->setAssetsDir(new FilesystemAdapter(new Filesystem(new NullAdapter)));
try {
$this->assets->makeCss()->commit();
foreach ($this->locales->getLocales() as $locale => $name) {
$this->assets->makeLocaleCss($locale)->flush();
$this->assets->makeLocaleCss($locale)->commit();
}
} catch (Less_Exception_Parser $e) {
throw new ValidationException(['custom_less' => $e->getMessage()]);
}
$this->assets->setAssetsDir($assetsDir);
$this->container->instance(SettingsRepositoryInterface::class, $settings);
}
public function whenSettingsSaved(Saved $event)
{
if (! isset($event->settings['custom_less'])) {
return;
}
$this->assets->makeCss()->flush();
foreach ($this->locales->getLocales() as $locale => $name) {
$this->assets->makeLocaleCss($locale)->flush();
}
}
}

View File

@ -42,6 +42,11 @@ class InstalledApp implements AppInterface
$this->config = $config;
}
public function getContainer()
{
return $this->container;
}
/**
* @return \Psr\Http\Server\RequestHandlerInterface
*/

View File

@ -26,7 +26,6 @@ use Flarum\Locale\LocaleServiceProvider;
use Flarum\Notification\NotificationServiceProvider;
use Flarum\Post\PostServiceProvider;
use Flarum\Search\SearchServiceProvider;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\Settings\SettingsServiceProvider;
use Flarum\Update\UpdateServiceProvider;
use Flarum\User\SessionServiceProvider;
@ -36,10 +35,10 @@ use Illuminate\Cache\Repository as CacheRepository;
use Illuminate\Config\Repository as ConfigRepository;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Contracts\Cache\Store;
use Illuminate\Contracts\Container\Container;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Filesystem\FilesystemServiceProvider;
use Illuminate\Hashing\HashServiceProvider;
use Illuminate\Mail\MailServiceProvider;
use Illuminate\Validation\ValidationServiceProvider;
use Illuminate\View\ViewServiceProvider;
use Monolog\Formatter\LineFormatter;
@ -73,7 +72,7 @@ class InstalledSite implements SiteInterface
/**
* Create and boot a Flarum application instance.
*
* @return AppInterface
* @return InstalledApp
*/
public function bootApp(): AppInterface
{
@ -107,51 +106,42 @@ class InstalledSite implements SiteInterface
$this->registerLogger($laravel);
$this->registerCache($laravel);
$laravel->register(DatabaseServiceProvider::class);
$laravel->register(MigrationServiceProvider::class);
$laravel->register(SettingsServiceProvider::class);
$laravel->register(LocaleServiceProvider::class);
$laravel->register(AdminServiceProvider::class);
$laravel->register(ApiServiceProvider::class);
$laravel->register(BusServiceProvider::class);
$laravel->register(FilesystemServiceProvider::class);
$laravel->register(HashServiceProvider::class);
$laravel->register(MailServiceProvider::class);
$laravel->register(ViewServiceProvider::class);
$laravel->register(ValidationServiceProvider::class);
$settings = $laravel->make(SettingsRepositoryInterface::class);
$config->set('mail.driver', $settings->get('mail_driver'));
$config->set('mail.host', $settings->get('mail_host'));
$config->set('mail.port', $settings->get('mail_port'));
$config->set('mail.from.address', $settings->get('mail_from'));
$config->set('mail.from.name', $settings->get('forum_title'));
$config->set('mail.encryption', $settings->get('mail_encryption'));
$config->set('mail.username', $settings->get('mail_username'));
$config->set('mail.password', $settings->get('mail_password'));
$laravel->register(DatabaseServiceProvider::class);
$laravel->register(DiscussionServiceProvider::class);
$laravel->register(ExtensionServiceProvider::class);
$laravel->register(FilesystemServiceProvider::class);
$laravel->register(FormatterServiceProvider::class);
$laravel->register(ForumServiceProvider::class);
$laravel->register(FrontendServiceProvider::class);
$laravel->register(GroupServiceProvider::class);
$laravel->register(HashServiceProvider::class);
$laravel->register(LocaleServiceProvider::class);
$laravel->register(MailServiceProvider::class);
$laravel->register(MigrationServiceProvider::class);
$laravel->register(NotificationServiceProvider::class);
$laravel->register(PostServiceProvider::class);
$laravel->register(SearchServiceProvider::class);
$laravel->register(SessionServiceProvider::class);
$laravel->register(UserServiceProvider::class);
$laravel->register(SettingsServiceProvider::class);
$laravel->register(UpdateServiceProvider::class);
$laravel->register(UserServiceProvider::class);
$laravel->register(ValidationServiceProvider::class);
$laravel->register(ViewServiceProvider::class);
$laravel->register(ApiServiceProvider::class);
$laravel->register(ForumServiceProvider::class);
$laravel->register(AdminServiceProvider::class);
$laravel->register(ExtensionServiceProvider::class);
$laravel->booting(function (Container $app) {
// Run all local-site extenders before booting service providers
// (but after those from "real" extensions, which have been set up
// in a service provider above).
foreach ($this->extenders as $extension) {
$extension->extend($app);
}
});
$laravel->boot();
foreach ($this->extenders as $extension) {
$extension->extend($laravel);
}
return $laravel;
}

View File

@ -0,0 +1,83 @@
<?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\Foundation;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Mail\Mailer;
use Illuminate\Mail\Transport\LogTransport;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Swift_Mailer;
use Swift_SendmailTransport;
use Swift_SmtpTransport;
use Swift_Transport;
class MailServiceProvider extends AbstractServiceProvider
{
public function register()
{
$this->app->singleton('mail.supported_drivers', function () {
return ['smtp', 'mail', 'log'];
});
$this->app->singleton('swift.mailer', function ($app) {
$settings = $app->make(SettingsRepositoryInterface::class);
return new Swift_Mailer(
$this->buildTransport($settings)
);
});
$this->app->singleton('mailer', function ($app) {
$mailer = new Mailer(
$app['view'], $app['swift.mailer'], $app['events']
);
if ($app->bound('queue')) {
$mailer->setQueue($app->make('queue'));
}
$settings = $app->make(SettingsRepositoryInterface::class);
$mailer->alwaysFrom($settings->get('mail_from'), $settings->get('forum_title'));
return $mailer;
});
}
private function buildTransport(SettingsRepositoryInterface $settings): Swift_Transport
{
switch ($settings->get('mail_driver')) {
case 'smtp':
return $this->buildSmtpTransport($settings);
case 'mail':
return new Swift_SendmailTransport;
case 'log':
return new LogTransport($this->app->make(LoggerInterface::class));
default:
throw new InvalidArgumentException('Invalid mail driver configuration');
}
}
private function buildSmtpTransport(SettingsRepositoryInterface $settings): Swift_Transport
{
$transport = new Swift_SmtpTransport(
$settings->get('mail_host'),
$settings->get('mail_port'),
$settings->get('mail_encryption')
);
$transport->setUsername($settings->get('mail_username'));
$transport->setPassword($settings->get('mail_password'));
return $transport;
}
}

58
src/Install/AdminUser.php Normal file
View File

@ -0,0 +1,58 @@
<?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 Carbon\Carbon;
use Illuminate\Hashing\BcryptHasher;
class AdminUser
{
private $username;
private $password;
private $email;
public function __construct($username, $password, $email)
{
$this->username = $username;
$this->password = $password;
$this->email = $email;
$this->validate();
}
public function getUsername()
{
return $this->username;
}
public function getAttributes(): array
{
return [
'username' => $this->username,
'email' => $this->email,
'password' => (new BcryptHasher)->make($this->password),
'joined_at' => Carbon::now(),
'is_email_confirmed' => 1,
];
}
private function validate()
{
if (! filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
throw new ValidationFailed('You must enter a valid email.');
}
if (! $this->username || preg_match('/[^a-z0-9_-]/i', $this->username)) {
throw new ValidationFailed('Username can only contain letters, numbers, underscores, and dashes.');
}
}
}

View File

@ -11,15 +11,9 @@
namespace Flarum\Install\Console;
use Flarum\Install\Installation;
interface DataProviderInterface
{
public function getDatabaseConfiguration();
public function getBaseUrl();
public function getAdminUser();
public function getSettings();
public function isDebugMode(): bool;
public function configure(Installation $installation): Installation;
}

View File

@ -1,111 +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\Install\Console;
class DefaultsDataProvider implements DataProviderInterface
{
protected $databaseConfiguration = [
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'flarum',
'username' => 'root',
'password' => '',
'prefix' => '',
'port' => '3306',
];
protected $debug = false;
protected $baseUrl = 'http://flarum.local';
protected $adminUser = [
'username' => 'admin',
'password' => 'password',
'password_confirmation' => 'password',
'email' => 'admin@example.com',
];
protected $settings = [
'allow_post_editing' => 'reply',
'allow_renaming' => '10',
'allow_sign_up' => '1',
'custom_less' => '',
'default_locale' => 'en',
'default_route' => '/all',
'extensions_enabled' => '[]',
'forum_title' => 'Development Forum',
'forum_description' => '',
'mail_driver' => 'mail',
'mail_from' => 'noreply@flarum.dev',
'theme_colored_header' => '0',
'theme_dark_mode' => '0',
'theme_primary_color' => '#4D698E',
'theme_secondary_color' => '#4D698E',
'welcome_message' => 'This is beta software and you should not use it in production.',
'welcome_title' => 'Welcome to Development Forum',
];
public function getDatabaseConfiguration()
{
return $this->databaseConfiguration;
}
public function setDatabaseConfiguration(array $databaseConfiguration)
{
$this->databaseConfiguration = $databaseConfiguration;
}
public function getBaseUrl()
{
return $this->baseUrl;
}
public function setBaseUrl($baseUrl)
{
$this->baseUrl = $baseUrl;
}
public function getAdminUser()
{
return $this->adminUser;
}
public function setAdminUser(array $adminUser)
{
$this->adminUser = $adminUser;
}
public function getSettings()
{
return $this->settings;
}
public function setSettings(array $settings)
{
$this->settings = $settings;
}
public function setSetting($key, $value)
{
$this->settings[$key] = $value;
}
public function isDebugMode(): bool
{
return $this->debug;
}
public function setDebugMode(bool $debug = true)
{
$this->debug = $debug;
}
}

View File

@ -12,12 +12,14 @@
namespace Flarum\Install\Console;
use Exception;
use Flarum\Install\AdminUser;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\Installation;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Yaml\Yaml;
class FileDataProvider implements DataProviderInterface
{
protected $default;
protected $debug = false;
protected $baseUrl = null;
protected $databaseConfiguration = [];
@ -26,9 +28,6 @@ class FileDataProvider implements DataProviderInterface
public function __construct(InputInterface $input)
{
// Get default configuration
$this->default = new DefaultsDataProvider();
// Get configuration file path
$configurationFile = $input->getOption('file');
@ -55,28 +54,35 @@ class FileDataProvider implements DataProviderInterface
}
}
public function getDatabaseConfiguration()
public function configure(Installation $installation): Installation
{
return $this->databaseConfiguration + $this->default->getDatabaseConfiguration();
return $installation
->debugMode($this->debug)
->baseUrl($this->baseUrl ?? 'http://flarum.local')
->databaseConfig($this->getDatabaseConfiguration())
->adminUser($this->getAdminUser())
->settings($this->settings);
}
public function getBaseUrl()
private function getDatabaseConfiguration(): DatabaseConfig
{
return (! is_null($this->baseUrl)) ? $this->baseUrl : $this->default->getBaseUrl();
return new DatabaseConfig(
$this->databaseConfiguration['driver'] ?? 'mysql',
$this->databaseConfiguration['host'] ?? 'localhost',
$this->databaseConfiguration['port'] ?? 3306,
$this->databaseConfiguration['database'] ?? 'flarum',
$this->databaseConfiguration['username'] ?? 'root',
$this->databaseConfiguration['password'] ?? '',
$this->databaseConfiguration['prefix'] ?? ''
);
}
public function getAdminUser()
private function getAdminUser(): AdminUser
{
return $this->adminUser + $this->default->getAdminUser();
}
public function getSettings()
{
return $this->settings + $this->default->getSettings();
}
public function isDebugMode(): bool
{
return $this->debug;
return new AdminUser(
$this->adminUser['username'] ?? 'admin',
$this->adminUser['password'] ?? 'password',
$this->adminUser['email'] ?? 'admin@example.com'
);
}
}

View File

@ -11,65 +11,32 @@
namespace Flarum\Install\Console;
use Carbon\Carbon;
use Exception;
use Flarum\Console\AbstractCommand;
use Flarum\Database\DatabaseMigrationRepository;
use Flarum\Database\Migrator;
use Flarum\Extension\ExtensionManager;
use Flarum\Foundation\Application as FlarumApplication;
use Flarum\Foundation\Site;
use Flarum\Group\Group;
use Flarum\Install\Prerequisite\PrerequisiteInterface;
use Flarum\Settings\DatabaseSettingsRepository;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Translation\Translator;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\Connectors\ConnectionFactory;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Hashing\BcryptHasher;
use Illuminate\Validation\Factory;
use PDO;
use Flarum\Install\Installation;
use Flarum\Install\Pipeline;
use Flarum\Install\Step;
use Symfony\Component\Console\Input\InputOption;
class InstallCommand extends AbstractCommand
{
/**
* @var Installation
*/
protected $installation;
/**
* @var DataProviderInterface
*/
protected $dataSource;
/**
* @var Application
* @param Installation $installation
*/
protected $application;
/**
* @var Filesystem
*/
protected $filesystem;
/**
* @var ConnectionInterface
*/
protected $db;
/**
* @var Migrator
*/
protected $migrator;
/**
* @param Application $application
* @param Filesystem $filesystem
*/
public function __construct(Application $application, Filesystem $filesystem)
public function __construct(Installation $installation)
{
$this->application = $application;
$this->installation = $installation;
parent::__construct();
$this->filesystem = $filesystem;
}
protected function configure()
@ -77,12 +44,6 @@ class InstallCommand extends AbstractCommand
$this
->setName('install')
->setDescription("Run Flarum's installation migration and seeds")
->addOption(
'defaults',
'd',
InputOption::VALUE_NONE,
'Create default settings and user'
)
->addOption(
'file',
'f',
@ -104,274 +65,64 @@ class InstallCommand extends AbstractCommand
{
$this->init();
$prerequisites = $this->getPrerequisites();
$prerequisites->check();
$errors = $prerequisites->getErrors();
$problems = $this->installation->prerequisites()->problems();
if (empty($errors)) {
if ($problems->isEmpty()) {
$this->info('Installing Flarum...');
$this->install();
$this->info('DONE.');
} else {
$this->output->writeln(
'<error>Please fix the following errors before we can continue with the installation.</error>'
);
$this->showErrors($errors);
$this->showProblems($problems);
}
}
protected function init()
{
if ($this->dataSource === null) {
if ($this->input->getOption('defaults')) {
$this->dataSource = new DefaultsDataProvider();
} elseif ($this->input->getOption('file')) {
$this->dataSource = new FileDataProvider($this->input);
} else {
$this->dataSource = new UserDataProvider($this->input, $this->output, $this->getHelperSet()->get('question'));
}
if ($this->input->getOption('file')) {
$this->dataSource = new FileDataProvider($this->input);
} else {
$this->dataSource = new UserDataProvider($this->input, $this->output, $this->getHelperSet()->get('question'));
}
}
public function setDataSource(DataProviderInterface $dataSource)
{
$this->dataSource = $dataSource;
}
protected function install()
{
try {
$this->dbConfig = $this->dataSource->getDatabaseConfiguration();
$pipeline = $this->dataSource->configure(
$this->installation->configPath($this->input->getOption('config'))
)->build();
$validation = $this->getValidator()->make(
$this->dbConfig,
[
'driver' => 'required|in:mysql',
'host' => 'required',
'database' => 'required|string',
'username' => 'required|string',
'prefix' => 'nullable|alpha_dash|max:10',
'port' => 'nullable|integer|min:1|max:65535',
]
);
if ($validation->fails()) {
throw new Exception(implode("\n", call_user_func_array('array_merge', $validation->getMessageBag()->toArray())));
}
$this->baseUrl = $this->dataSource->getBaseUrl();
$this->settings = $this->dataSource->getSettings();
$this->adminUser = $admin = $this->dataSource->getAdminUser();
if (strlen($admin['password']) < 8) {
throw new Exception('Password must be at least 8 characters.');
}
if ($admin['password'] !== $admin['password_confirmation']) {
throw new Exception('The password did not match its confirmation.');
}
if (! filter_var($admin['email'], FILTER_VALIDATE_EMAIL)) {
throw new Exception('You must enter a valid email.');
}
if (! $admin['username'] || preg_match('/[^a-z0-9_-]/i', $admin['username'])) {
throw new Exception('Username can only contain letters, numbers, underscores, and dashes.');
}
$this->storeConfiguration($this->dataSource->isDebugMode());
$this->runMigrations();
$this->writeSettings();
$this->createAdminUser();
$this->publishAssets();
// Now that the installation of core is complete, boot up a new
// application instance before enabling extensions so that all of
// the application services are available.
Site::fromPaths([
'base' => $this->application->basePath(),
'public' => $this->application->publicPath(),
'storage' => $this->application->storagePath(),
])->bootApp();
$this->application = FlarumApplication::getInstance();
$this->enableBundledExtensions();
} catch (Exception $e) {
@unlink($this->getConfigFile());
throw $e;
}
$this->runPipeline($pipeline);
}
protected function storeConfiguration(bool $debugMode)
private function runPipeline(Pipeline $pipeline)
{
$dbConfig = $this->dbConfig;
$config = [
'debug' => $debugMode,
'database' => $laravelDbConfig = [
'driver' => $dbConfig['driver'],
'host' => $dbConfig['host'],
'database' => $dbConfig['database'],
'username' => $dbConfig['username'],
'password' => $dbConfig['password'],
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => $dbConfig['prefix'],
'port' => $dbConfig['port'],
'strict' => false
],
'url' => $this->baseUrl,
'paths' => [
'api' => 'api',
'admin' => 'admin',
],
];
$this->info('Testing config');
$factory = new ConnectionFactory($this->application);
$laravelDbConfig['engine'] = 'InnoDB';
$this->db = $factory->make($laravelDbConfig);
$version = $this->db->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION);
if (version_compare($version, '5.5.0', '<')) {
throw new Exception('MySQL version too low. You need at least MySQL 5.5.');
}
$repository = new DatabaseMigrationRepository(
$this->db, 'migrations'
);
$files = $this->application->make('files');
$this->migrator = new Migrator($repository, $this->db, $files);
$this->info('Writing config');
file_put_contents(
$this->getConfigFile(),
'<?php return '.var_export($config, true).';'
);
$pipeline
->on('start', function (Step $step) {
$this->output->write($step->getMessage().'...');
})->on('end', function () {
$this->output->write("<info>done</info>\n");
})->on('fail', function () {
$this->output->write("<error>failed</error>\n");
$this->output->writeln('Rolling back...');
})->on('rollback', function (Step $step) {
$this->output->writeln($step->getMessage().' (rollback)');
})
->run();
}
protected function runMigrations()
protected function showProblems($problems)
{
$this->migrator->setOutput($this->output);
$this->migrator->getRepository()->createRepository();
$this->migrator->run(__DIR__.'/../../../migrations');
}
protected function writeSettings()
{
$settings = new DatabaseSettingsRepository($this->db);
$this->info('Writing default settings');
$settings->set('version', $this->application->version());
foreach ($this->settings as $k => $v) {
$settings->set($k, $v);
}
}
protected function createAdminUser()
{
$admin = $this->adminUser;
if ($admin['password'] !== $admin['password_confirmation']) {
throw new Exception('The password did not match its confirmation.');
}
$this->info('Creating admin user '.$admin['username']);
$uid = $this->db->table('users')->insertGetId([
'username' => $admin['username'],
'email' => $admin['email'],
'password' => (new BcryptHasher)->make($admin['password']),
'joined_at' => Carbon::now(),
'is_email_confirmed' => 1,
]);
$this->db->table('group_user')->insert([
'user_id' => $uid,
'group_id' => Group::ADMINISTRATOR_ID,
]);
}
protected function enableBundledExtensions()
{
$extensions = new ExtensionManager(
new DatabaseSettingsRepository($this->db),
$this->application,
$this->migrator,
$this->application->make(Dispatcher::class),
$this->application->make('files')
$this->output->writeln(
'<error>Please fix the following problems before we can continue with the installation.</error>'
);
$disabled = [
'flarum-akismet',
'flarum-auth-facebook',
'flarum-auth-github',
'flarum-auth-twitter',
'flarum-pusher',
];
foreach ($problems as $problem) {
$this->info($problem['message']);
foreach ($extensions->getExtensions() as $name => $extension) {
if (in_array($name, $disabled)) {
continue;
}
$this->info('Enabling extension: '.$name);
$extensions->enable($name);
}
}
protected function publishAssets()
{
$this->filesystem->copyDirectory(
$this->application->basePath().'/vendor/components/font-awesome/webfonts',
$this->application->publicPath().'/assets/fonts'
);
}
protected function getConfigFile()
{
return $this->input->getOption('config') ?: base_path('config.php');
}
/**
* @return \Flarum\Install\Prerequisite\PrerequisiteInterface
*/
protected function getPrerequisites()
{
return $this->application->make(PrerequisiteInterface::class);
}
/**
* @return \Illuminate\Contracts\Validation\Factory
*/
protected function getValidator()
{
return new Factory($this->application->make(Translator::class));
}
protected function showErrors($errors)
{
foreach ($errors as $error) {
$this->info($error['message']);
if (isset($error['detail'])) {
$this->output->writeln('<comment>'.$error['detail'].'</comment>');
if (isset($problem['detail'])) {
$this->output->writeln('<comment>'.$problem['detail'].'</comment>');
}
}
}

View File

@ -11,6 +11,9 @@
namespace Flarum\Install\Console;
use Flarum\Install\AdminUser;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\Installation;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@ -33,75 +36,91 @@ class UserDataProvider implements DataProviderInterface
$this->questionHelper = $questionHelper;
}
public function getDatabaseConfiguration()
public function configure(Installation $installation): Installation
{
return $installation
->debugMode(false)
->baseUrl($this->getBaseUrl())
->databaseConfig($this->getDatabaseConfiguration())
->adminUser($this->getAdminUser())
->settings($this->getSettings());
}
private function getDatabaseConfiguration(): DatabaseConfig
{
$host = $this->ask('Database host:');
$port = '3306';
$port = 3306;
if (str_contains($host, ':')) {
list($host, $port) = explode(':', $host, 2);
}
return [
'driver' => 'mysql',
'host' => $host,
'port' => $port,
'database' => $this->ask('Database name:'),
'username' => $this->ask('Database user:'),
'password' => $this->secret('Database password:'),
'prefix' => $this->ask('Prefix:'),
];
return new DatabaseConfig(
'mysql',
$host,
intval($port),
$this->ask('Database name:'),
$this->ask('Database user:'),
$this->secret('Database password:'),
$this->ask('Prefix:')
);
}
public function getBaseUrl()
private function getBaseUrl()
{
return $this->baseUrl = rtrim($this->ask('Base URL:'), '/');
}
public function getAdminUser()
private function getAdminUser(): AdminUser
{
return [
'username' => $this->ask('Admin username:'),
'password' => $this->secret('Admin password:'),
'password_confirmation' => $this->secret('Admin password (confirmation):'),
'email' => $this->ask('Admin email address:'),
];
return new AdminUser(
$this->ask('Admin username:'),
$this->askForAdminPassword(),
$this->ask('Admin email address:')
);
}
public function getSettings()
private function askForAdminPassword()
{
while (true) {
$password = $this->secret('Admin password:');
if (strlen($password) < 8) {
$this->validationError('Password must be at least 8 characters.');
continue;
}
$confirmation = $this->secret('Admin password (confirmation):');
if ($password !== $confirmation) {
$this->validationError('The password did not match its confirmation.');
continue;
}
return $password;
}
}
private function getSettings()
{
$title = $this->ask('Forum title:');
$baseUrl = $this->baseUrl ?: 'http://localhost';
return [
'allow_post_editing' => 'reply',
'allow_renaming' => '10',
'allow_sign_up' => '1',
'custom_less' => '',
'default_locale' => 'en',
'default_route' => '/all',
'extensions_enabled' => '[]',
'forum_title' => $title,
'forum_description' => '',
'mail_driver' => 'mail',
'mail_from' => 'noreply@'.preg_replace('/^www\./i', '', parse_url($baseUrl, PHP_URL_HOST)),
'theme_colored_header' => '0',
'theme_dark_mode' => '0',
'theme_primary_color' => '#4D698E',
'theme_secondary_color' => '#4D698E',
'welcome_message' => 'This is beta software and you should not use it in production.',
'welcome_title' => 'Welcome to '.$title,
];
}
protected function ask($question, $default = null)
private function ask($question, $default = null)
{
$question = new Question("<question>$question</question> ", $default);
return $this->questionHelper->ask($this->input, $this->output, $question);
}
protected function secret($question)
private function secret($question)
{
$question = new Question("<question>$question</question> ");
@ -110,8 +129,9 @@ class UserDataProvider implements DataProviderInterface
return $this->questionHelper->ask($this->input, $this->output, $question);
}
public function isDebugMode(): bool
private function validationError($message)
{
return false;
$this->output->writeln("<error>$message</error>");
$this->output->writeln('Please try again.');
}
}

View File

@ -12,7 +12,7 @@
namespace Flarum\Install\Controller;
use Flarum\Http\Controller\AbstractHtmlController;
use Flarum\Install\Prerequisite\PrerequisiteInterface;
use Flarum\Install\Installation;
use Illuminate\Contracts\View\Factory;
use Psr\Http\Message\ServerRequestInterface as Request;
@ -24,18 +24,18 @@ class IndexController extends AbstractHtmlController
protected $view;
/**
* @var \Flarum\Install\Prerequisite\PrerequisiteInterface
* @var Installation
*/
protected $prerequisite;
protected $installation;
/**
* @param Factory $view
* @param PrerequisiteInterface $prerequisite
* @param Installation $installation
*/
public function __construct(Factory $view, PrerequisiteInterface $prerequisite)
public function __construct(Factory $view, Installation $installation)
{
$this->view = $view;
$this->prerequisite = $prerequisite;
$this->installation = $installation;
}
/**
@ -46,13 +46,12 @@ class IndexController extends AbstractHtmlController
{
$view = $this->view->make('flarum.install::app')->with('title', 'Install Flarum');
$this->prerequisite->check();
$errors = $this->prerequisite->getErrors();
$problems = $this->installation->prerequisites()->problems();
if (count($errors)) {
$view->with('content', $this->view->make('flarum.install::errors')->with('errors', $errors));
} else {
if ($problems->isEmpty()) {
$view->with('content', $this->view->make('flarum.install::install'));
} else {
$view->with('content', $this->view->make('flarum.install::problems')->with('problems', $problems));
}
return $view;

View File

@ -11,21 +11,23 @@
namespace Flarum\Install\Controller;
use Exception;
use Flarum\Http\SessionAuthenticator;
use Flarum\Install\Console\DefaultsDataProvider;
use Flarum\Install\Console\InstallCommand;
use Flarum\Install\AdminUser;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\Installation;
use Flarum\Install\StepFailed;
use Flarum\Install\ValidationFailed;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\StreamOutput;
use Zend\Diactoros\Response;
use Zend\Diactoros\Response\HtmlResponse;
class InstallController implements RequestHandlerInterface
{
protected $command;
/**
* @var Installation
*/
protected $installation;
/**
* @var SessionAuthenticator
@ -34,12 +36,12 @@ class InstallController implements RequestHandlerInterface
/**
* InstallController constructor.
* @param InstallCommand $command
* @param Installation $installation
* @param SessionAuthenticator $authenticator
*/
public function __construct(InstallCommand $command, SessionAuthenticator $authenticator)
public function __construct(Installation $installation, SessionAuthenticator $authenticator)
{
$this->command = $command;
$this->installation = $installation;
$this->authenticator = $authenticator;
}
@ -50,55 +52,78 @@ class InstallController implements RequestHandlerInterface
public function handle(Request $request): ResponseInterface
{
$input = $request->getParsedBody();
$data = new DefaultsDataProvider;
$host = array_get($input, 'mysqlHost');
$port = '3306';
if (str_contains($host, ':')) {
list($host, $port) = explode(':', $host, 2);
}
$data->setDatabaseConfiguration([
'driver' => 'mysql',
'host' => $host,
'database' => array_get($input, 'mysqlDatabase'),
'username' => array_get($input, 'mysqlUsername'),
'password' => array_get($input, 'mysqlPassword'),
'prefix' => array_get($input, 'tablePrefix'),
'port' => $port,
]);
$data->setAdminUser([
'username' => array_get($input, 'adminUsername'),
'password' => array_get($input, 'adminPassword'),
'password_confirmation' => array_get($input, 'adminPasswordConfirmation'),
'email' => array_get($input, 'adminEmail'),
]);
$baseUrl = rtrim((string) $request->getUri(), '/');
$data->setBaseUrl($baseUrl);
$data->setSetting('forum_title', array_get($input, 'forumTitle'));
$data->setSetting('mail_from', 'noreply@'.preg_replace('/^www\./i', '', parse_url($baseUrl, PHP_URL_HOST)));
$data->setSetting('welcome_title', 'Welcome to '.array_get($input, 'forumTitle'));
$body = fopen('php://temp', 'wb+');
$input = new StringInput('');
$output = new StreamOutput($body);
$this->command->setDataSource($data);
try {
$this->command->run($input, $output);
} catch (Exception $e) {
return new HtmlResponse($e->getMessage(), 500);
$pipeline = $this->installation
->baseUrl($baseUrl)
->databaseConfig($this->makeDatabaseConfig($input))
->adminUser($this->makeAdminUser($input))
->settings([
'forum_title' => array_get($input, 'forumTitle'),
'mail_from' => 'noreply@'.preg_replace('/^www\./i', '', parse_url($baseUrl, PHP_URL_HOST)),
'welcome_title' => 'Welcome to '.array_get($input, 'forumTitle'),
])
->build();
} catch (ValidationFailed $e) {
return new Response\HtmlResponse($e->getMessage(), 500);
}
try {
$pipeline->run();
} catch (StepFailed $e) {
return new Response\HtmlResponse($e->getPrevious()->getMessage(), 500);
}
$session = $request->getAttribute('session');
$this->authenticator->logIn($session, 1);
return new Response($body);
return new Response\EmptyResponse;
}
private function makeDatabaseConfig(array $input): DatabaseConfig
{
$host = array_get($input, 'mysqlHost');
$port = 3306;
if (str_contains($host, ':')) {
list($host, $port) = explode(':', $host, 2);
}
return new DatabaseConfig(
'mysql',
$host,
intval($port),
array_get($input, 'mysqlDatabase'),
array_get($input, 'mysqlUsername'),
array_get($input, 'mysqlPassword'),
array_get($input, 'tablePrefix')
);
}
/**
* @param array $input
* @return AdminUser
* @throws ValidationFailed
*/
private function makeAdminUser(array $input): AdminUser
{
return new AdminUser(
array_get($input, 'adminUsername'),
$this->getConfirmedAdminPassword($input),
array_get($input, 'adminEmail')
);
}
private function getConfirmedAdminPassword(array $input): string
{
$password = array_get($input, 'adminPassword');
$confirmation = array_get($input, 'adminPasswordConfirmation');
if ($password !== $confirmation) {
throw new ValidationFailed('The admin password did not match its confirmation.');
}
return $password;
}
}

View File

@ -0,0 +1,101 @@
<?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 Illuminate\Contracts\Support\Arrayable;
class DatabaseConfig implements Arrayable
{
private $driver;
private $host;
private $port;
private $database;
private $username;
private $password;
private $prefix;
public function __construct($driver, $host, $port, $database, $username, $password, $prefix)
{
$this->driver = $driver;
$this->host = $host;
$this->port = $port;
$this->database = $database;
$this->username = $username;
$this->password = $password;
$this->prefix = $prefix;
$this->validate();
}
public function toArray()
{
return [
'driver' => $this->driver,
'host' => $this->host,
'port' => $this->port,
'database' => $this->database,
'username' => $this->username,
'password' => $this->password,
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => $this->prefix,
'strict' => false,
'engine' => 'InnoDB',
'prefix_indexes' => true
];
}
private function validate()
{
if (empty($this->driver)) {
throw new ValidationFailed('Please specify a database driver.');
}
if ($this->driver !== 'mysql') {
throw new ValidationFailed('Currently, only MySQL/MariaDB is supported.');
}
if (empty($this->host)) {
throw new ValidationFailed('Please specify the hostname of your database server.');
}
if (! is_int($this->port) || $this->port < 1 || $this->port > 65535) {
throw new ValidationFailed('Please provide a valid port number between 1 and 65535.');
}
if (empty($this->database)) {
throw new ValidationFailed('Please specify the database name.');
}
if (! is_string($this->database)) {
throw new ValidationFailed('The database name must be a non-empty string.');
}
if (empty($this->username)) {
throw new ValidationFailed('Please specify the username for accessing the database.');
}
if (! is_string($this->database)) {
throw new ValidationFailed('The username must be a non-empty string.');
}
if (! empty($this->prefix)) {
if (! preg_match('/^[\pL\pM\pN_-]+$/u', $this->prefix)) {
throw new ValidationFailed('The prefix may only contain characters, dashes and underscores.');
}
if (strlen($this->prefix) > 10) {
throw new ValidationFailed('The prefix should be no longer than 10 characters.');
}
}
}
}

View File

@ -14,11 +14,6 @@ namespace Flarum\Install;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Http\RouteCollection;
use Flarum\Http\RouteHandlerFactory;
use Flarum\Install\Prerequisite\Composite;
use Flarum\Install\Prerequisite\PhpExtensions;
use Flarum\Install\Prerequisite\PhpVersion;
use Flarum\Install\Prerequisite\PrerequisiteInterface;
use Flarum\Install\Prerequisite\WritablePaths;
class InstallServiceProvider extends AbstractServiceProvider
{
@ -27,32 +22,17 @@ class InstallServiceProvider extends AbstractServiceProvider
*/
public function register()
{
$this->app->bind(
PrerequisiteInterface::class,
function () {
return new Composite(
new PhpVersion('7.1.0'),
new PhpExtensions([
'dom',
'gd',
'json',
'mbstring',
'openssl',
'pdo_mysql',
'tokenizer',
]),
new WritablePaths([
base_path(),
public_path('assets'),
storage_path(),
])
);
}
);
$this->app->singleton('flarum.install.routes', function () {
return new RouteCollection;
});
$this->app->singleton(Installation::class, function () {
return new Installation(
$this->app->basePath(),
$this->app->publicPath(),
$this->app->storagePath()
);
});
}
/**

View File

@ -0,0 +1,164 @@
<?php
/*
* This file is part of Flarum.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Flarum\Install;
class Installation
{
private $basePath;
private $publicPath;
private $storagePath;
private $configPath;
private $debug = false;
private $baseUrl;
private $customSettings = [];
/** @var DatabaseConfig */
private $dbConfig;
/** @var AdminUser */
private $adminUser;
// A few instance variables to persist objects between steps.
// Could also be local variables in build(), but this way
// access in closures is easier. :)
/** @var \Illuminate\Database\ConnectionInterface */
private $db;
public function __construct($basePath, $publicPath, $storagePath)
{
$this->basePath = $basePath;
$this->publicPath = $publicPath;
$this->storagePath = $storagePath;
}
public function configPath($path)
{
$this->configPath = $path;
return $this;
}
public function debugMode($flag)
{
$this->debug = $flag;
return $this;
}
public function databaseConfig(DatabaseConfig $dbConfig)
{
$this->dbConfig = $dbConfig;
return $this;
}
public function baseUrl($baseUrl)
{
$this->baseUrl = $baseUrl;
return $this;
}
public function settings($settings)
{
$this->customSettings = $settings;
return $this;
}
public function adminUser(AdminUser $admin)
{
$this->adminUser = $admin;
return $this;
}
public function prerequisites(): Prerequisite\PrerequisiteInterface
{
return new Prerequisite\Composite(
new Prerequisite\PhpVersion('7.1.0'),
new Prerequisite\PhpExtensions([
'dom',
'gd',
'json',
'mbstring',
'openssl',
'pdo_mysql',
'tokenizer',
]),
new Prerequisite\WritablePaths([
$this->basePath,
$this->getAssetPath(),
$this->storagePath,
])
);
}
public function build(): Pipeline
{
$pipeline = new Pipeline;
$pipeline->pipe(function () {
return new Steps\ConnectToDatabase(
$this->dbConfig,
function ($connection) {
$this->db = $connection;
}
);
});
$pipeline->pipe(function () {
return new Steps\StoreConfig(
$this->debug, $this->dbConfig, $this->baseUrl, $this->getConfigPath()
);
});
$pipeline->pipe(function () {
return new Steps\RunMigrations($this->db, $this->getMigrationPath());
});
$pipeline->pipe(function () {
return new Steps\WriteSettings($this->db, $this->customSettings);
});
$pipeline->pipe(function () {
return new Steps\CreateAdminUser($this->db, $this->adminUser);
});
$pipeline->pipe(function () {
return new Steps\PublishAssets($this->basePath, $this->getAssetPath());
});
$pipeline->pipe(function () {
return new Steps\EnableBundledExtensions($this->db, $this->basePath, $this->getAssetPath());
});
return $pipeline;
}
private function getConfigPath()
{
return $this->basePath.'/'.($this->configPath ?? 'config.php');
}
private function getAssetPath()
{
return "$this->publicPath/assets";
}
private function getMigrationPath()
{
return __DIR__.'/../../migrations';
}
}

View File

@ -17,6 +17,8 @@ use Flarum\Http\Middleware\HandleErrorsWithWhoops;
use Flarum\Http\Middleware\StartSession;
use Flarum\Install\Console\InstallCommand;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Translation\Translator;
use Illuminate\Validation\Factory;
use Zend\Stratigility\MiddlewarePipe;
class Installer implements AppInterface
@ -52,7 +54,10 @@ class Installer implements AppInterface
public function getConsoleCommands()
{
return [
$this->container->make(InstallCommand::class),
new InstallCommand(
$this->container->make(Installation::class),
new Factory($this->container->make(Translator::class))
),
];
}
}

108
src/Install/Pipeline.php Normal file
View File

@ -0,0 +1,108 @@
<?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 Exception;
use SplStack;
class Pipeline
{
/**
* @var callable[]
*/
private $steps;
/**
* @var callable[]
*/
private $callbacks;
/**
* @var SplStack
*/
private $successfulSteps;
public function __construct(array $steps = [])
{
$this->steps = $steps;
}
public function pipe(callable $factory)
{
$this->steps[] = $factory;
return $this;
}
public function on($event, callable $callback)
{
$this->callbacks[$event] = $callback;
return $this;
}
public function run()
{
$this->successfulSteps = new SplStack;
try {
foreach ($this->steps as $factory) {
$this->runStep($factory);
}
} catch (StepFailed $failure) {
$this->revertReversibleSteps();
throw $failure;
}
}
/**
* @param callable $factory
* @throws StepFailed
*/
private function runStep(callable $factory)
{
/** @var Step $step */
$step = $factory();
$this->fireCallbacks('start', $step);
try {
$step->run();
$this->successfulSteps->push($step);
$this->fireCallbacks('end', $step);
} catch (Exception $e) {
$this->fireCallbacks('fail', $step);
throw new StepFailed('Step failed', 0, $e);
}
}
private function revertReversibleSteps()
{
foreach ($this->successfulSteps as $step) {
if ($step instanceof ReversibleStep) {
$this->fireCallbacks('rollback', $step);
$step->revert();
}
}
}
private function fireCallbacks($event, Step $step)
{
if (isset($this->callbacks[$event])) {
($this->callbacks[$event])($step);
}
}
}

View File

@ -1,24 +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\Install\Prerequisite;
abstract class AbstractPrerequisite implements PrerequisiteInterface
{
protected $errors = [];
abstract public function check();
public function getErrors()
{
return $this->errors;
}
}

View File

@ -11,6 +11,8 @@
namespace Flarum\Install\Prerequisite;
use Illuminate\Support\Collection;
class Composite implements PrerequisiteInterface
{
/**
@ -25,21 +27,14 @@ class Composite implements PrerequisiteInterface
}
}
public function check()
public function problems(): Collection
{
return array_reduce(
$this->prerequisites,
function ($previous, PrerequisiteInterface $prerequisite) {
return $prerequisite->check() && $previous;
function (Collection $errors, PrerequisiteInterface $condition) {
return $errors->concat($condition->problems());
},
true
new Collection
);
}
public function getErrors()
{
return collect($this->prerequisites)->map(function (PrerequisiteInterface $prerequisite) {
return $prerequisite->getErrors();
})->reduce('array_merge', []);
}
}

View File

@ -11,7 +11,9 @@
namespace Flarum\Install\Prerequisite;
class PhpExtensions extends AbstractPrerequisite
use Illuminate\Support\Collection;
class PhpExtensions implements PrerequisiteInterface
{
protected $extensions;
@ -20,14 +22,15 @@ class PhpExtensions extends AbstractPrerequisite
$this->extensions = $extensions;
}
public function check()
public function problems(): Collection
{
foreach ($this->extensions as $extension) {
if (! extension_loaded($extension)) {
$this->errors[] = [
return (new Collection($this->extensions))
->reject(function ($extension) {
return extension_loaded($extension);
})->map(function ($extension) {
return [
'message' => "The PHP extension '$extension' is required.",
];
}
}
});
}
}

View File

@ -11,7 +11,9 @@
namespace Flarum\Install\Prerequisite;
class PhpVersion extends AbstractPrerequisite
use Illuminate\Support\Collection;
class PhpVersion implements PrerequisiteInterface
{
protected $minVersion;
@ -20,13 +22,17 @@ class PhpVersion extends AbstractPrerequisite
$this->minVersion = $minVersion;
}
public function check()
public function problems(): Collection
{
$collection = new Collection;
if (version_compare(PHP_VERSION, $this->minVersion, '<')) {
$this->errors[] = [
$collection->push([
'message' => "PHP $this->minVersion is required.",
'detail' => 'You are running version '.PHP_VERSION.'. Talk to your hosting provider about upgrading to the latest PHP version.',
];
'detail' => 'You are running version '.PHP_VERSION.'. You might want to talk to your system administrator about upgrading to the latest PHP version.',
]);
}
return $collection;
}
}

View File

@ -11,9 +11,18 @@
namespace Flarum\Install\Prerequisite;
use Illuminate\Support\Collection;
interface PrerequisiteInterface
{
public function check();
public function getErrors();
/**
* Verify that this prerequisite is fulfilled.
*
* If everything is okay, this method should return an empty Collection
* instance. When problems are detected, it should return a Collection of
* arrays, each having at least a "message" and optionally a "detail" key.
*
* @return Collection
*/
public function problems(): Collection;
}

View File

@ -11,7 +11,9 @@
namespace Flarum\Install\Prerequisite;
class WritablePaths extends AbstractPrerequisite
use Illuminate\Support\Collection;
class WritablePaths implements PrerequisiteInterface
{
protected $paths;
@ -20,21 +22,36 @@ class WritablePaths extends AbstractPrerequisite
$this->paths = $paths;
}
public function check()
public function problems(): Collection
{
foreach ($this->paths as $path) {
if (! file_exists($path)) {
$this->errors[] = [
return $this->getMissingPaths()
->concat($this->getNonWritablePaths());
}
private function getMissingPaths(): Collection
{
return (new Collection($this->paths))
->reject(function ($path) {
return file_exists($path);
})->map(function ($path) {
return [
'message' => 'The '.$this->getAbsolutePath($path).' directory doesn\'t exist',
'detail' => 'This directory is necessary for the installation. Please create the folder.',
];
} elseif (! is_writable($path)) {
$this->errors[] = [
});
}
private function getNonWritablePaths(): Collection
{
return (new Collection($this->paths))
->filter(function ($path) {
return file_exists($path) && ! is_writable($path);
})->map(function ($path) {
return [
'message' => 'The '.$this->getAbsolutePath($path).' directory is not writable.',
'detail' => 'Please chmod this directory'.($path !== public_path() ? ' and its contents' : '').' to 0775.'
];
}
}
});
}
private function getAbsolutePath($path)

View File

@ -0,0 +1,17 @@
<?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;
interface ReversibleStep
{
public function revert();
}

33
src/Install/Step.php Normal file
View File

@ -0,0 +1,33 @@
<?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;
interface Step
{
/**
* A one-line status message summarizing what's happening in this step.
*
* @return string
*/
public function getMessage();
/**
* Do the work that constitutes this step.
*
* This method should raise a `StepFailed` exception whenever something goes
* wrong that should result in the entire installation being reverted.
*
* @return void
* @throws StepFailed
*/
public function run();
}

View File

@ -0,0 +1,18 @@
<?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 Exception;
class StepFailed extends Exception
{
}

View File

@ -0,0 +1,62 @@
<?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\Steps;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\Step;
use Illuminate\Database\Connectors\MySqlConnector;
use Illuminate\Database\MySqlConnection;
use RangeException;
class ConnectToDatabase implements Step
{
private $dbConfig;
private $store;
public function __construct(DatabaseConfig $dbConfig, callable $store)
{
$this->dbConfig = $dbConfig;
$this->store = $store;
}
public function getMessage()
{
return 'Connecting to database';
}
public function run()
{
$config = $this->dbConfig->toArray();
$pdo = (new MySqlConnector)->connect($config);
$version = $pdo->query('SELECT VERSION()')->fetchColumn();
if (str_contains($version, 'MariaDB')) {
if (version_compare($version, '10.0.5', '<')) {
throw new RangeException('MariaDB version too low. You need at least MariaDB 10.0.5');
}
} else {
if (version_compare($version, '5.6.0', '<')) {
throw new RangeException('MySQL version too low. You need at least MySQL 5.6.');
}
}
($this->store)(
new MySqlConnection(
$pdo,
$config['database'],
$config['prefix'],
$config
)
);
}
}

View File

@ -0,0 +1,53 @@
<?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\Steps;
use Flarum\Group\Group;
use Flarum\Install\AdminUser;
use Flarum\Install\Step;
use Illuminate\Database\ConnectionInterface;
class CreateAdminUser implements Step
{
/**
* @var ConnectionInterface
*/
private $database;
/**
* @var AdminUser
*/
private $admin;
public function __construct(ConnectionInterface $database, AdminUser $admin)
{
$this->database = $database;
$this->admin = $admin;
}
public function getMessage()
{
return 'Creating admin user '.$this->admin->getUsername();
}
public function run()
{
$uid = $this->database->table('users')->insertGetId(
$this->admin->getAttributes()
);
$this->database->table('group_user')->insert([
'user_id' => $uid,
'group_id' => Group::ADMINISTRATOR_ID,
]);
}
}

View File

@ -0,0 +1,122 @@
<?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\Steps;
use Flarum\Database\DatabaseMigrationRepository;
use Flarum\Database\Migrator;
use Flarum\Extension\Extension;
use Flarum\Install\Step;
use Flarum\Settings\DatabaseSettingsRepository;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;
class EnableBundledExtensions implements Step
{
/**
* @var ConnectionInterface
*/
private $database;
/**
* @var string
*/
private $basePath;
/**
* @var string
*/
private $assetPath;
public function __construct(ConnectionInterface $database, $basePath, $assetPath)
{
$this->database = $database;
$this->basePath = $basePath;
$this->assetPath = $assetPath;
}
public function getMessage()
{
return 'Enabling bundled extensions';
}
public function run()
{
$extensions = $this->loadExtensions();
foreach ($extensions as $extension) {
$extension->migrate($this->getMigrator());
$extension->copyAssetsTo(
new Filesystem(new Local($this->assetPath))
);
}
(new DatabaseSettingsRepository($this->database))->set(
'extensions_enabled',
$extensions->keys()->toJson()
);
}
const EXTENSION_WHITELIST = [
'flarum-approval',
'flarum-bbcode',
'flarum-emoji',
'flarum-lang-english',
'flarum-flags',
'flarum-likes',
'flarum-lock',
'flarum-markdown',
'flarum-mentions',
'flarum-statistics',
'flarum-sticky',
'flarum-subscriptions',
'flarum-suspend',
'flarum-tags',
];
/**
* @return \Illuminate\Support\Collection
*/
private function loadExtensions()
{
$json = file_get_contents("$this->basePath/vendor/composer/installed.json");
return (new Collection(json_decode($json, true)))
->filter(function ($package) {
return Arr::get($package, 'type') == 'flarum-extension';
})->filter(function ($package) {
return ! empty(Arr::get($package, 'name'));
})->map(function ($package) {
$extension = new Extension($this->basePath.'/vendor/'.Arr::get($package, 'name'), $package);
$extension->setVersion(Arr::get($package, 'version'));
return $extension;
})->filter(function (Extension $extension) {
return in_array($extension->getId(), self::EXTENSION_WHITELIST);
})->sortBy(function (Extension $extension) {
return $extension->composerJsonAttribute('extra.flarum-extension.title');
})->mapWithKeys(function (Extension $extension) {
return [$extension->getId() => $extension];
});
}
private function getMigrator()
{
return $this->migrator = $this->migrator ?? new Migrator(
new DatabaseMigrationRepository($this->database, 'migrations'),
$this->database,
new \Illuminate\Filesystem\Filesystem
);
}
}

View File

@ -0,0 +1,58 @@
<?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\Steps;
use Flarum\Install\ReversibleStep;
use Flarum\Install\Step;
use Illuminate\Filesystem\Filesystem;
class PublishAssets implements Step, ReversibleStep
{
/**
* @var string
*/
private $basePath;
/**
* @var string
*/
private $assetPath;
public function __construct($basePath, $assetPath)
{
$this->basePath = $basePath;
$this->assetPath = $assetPath;
}
public function getMessage()
{
return 'Publishing all assets';
}
public function run()
{
(new Filesystem)->copyDirectory(
"$this->basePath/vendor/components/font-awesome/webfonts",
$this->targetPath()
);
}
public function revert()
{
(new Filesystem)->deleteDirectory($this->targetPath());
}
private function targetPath()
{
return "$this->assetPath/fonts";
}
}

View File

@ -0,0 +1,60 @@
<?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\Steps;
use Flarum\Database\DatabaseMigrationRepository;
use Flarum\Database\Migrator;
use Flarum\Install\Step;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Filesystem\Filesystem;
class RunMigrations implements Step
{
/**
* @var ConnectionInterface
*/
private $database;
/**
* @var string
*/
private $path;
public function __construct(ConnectionInterface $database, $path)
{
$this->database = $database;
$this->path = $path;
}
public function getMessage()
{
return 'Running migrations';
}
public function run()
{
$migrator = $this->getMigrator();
$migrator->getRepository()->createRepository();
$migrator->run($this->path);
}
private function getMigrator()
{
$repository = new DatabaseMigrationRepository(
$this->database, 'migrations'
);
$files = new Filesystem;
return new Migrator($repository, $this->database, $files);
}
}

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\Steps;
use Flarum\Install\DatabaseConfig;
use Flarum\Install\ReversibleStep;
use Flarum\Install\Step;
class StoreConfig implements Step, ReversibleStep
{
private $debugMode;
private $dbConfig;
private $baseUrl;
private $configFile;
public function __construct($debugMode, DatabaseConfig $dbConfig, $baseUrl, $configFile)
{
$this->debugMode = $debugMode;
$this->dbConfig = $dbConfig;
$this->baseUrl = $baseUrl;
$this->configFile = $configFile;
}
public function getMessage()
{
return 'Writing config file';
}
public function run()
{
file_put_contents(
$this->configFile,
'<?php return '.var_export($this->buildConfig(), true).';'
);
}
public function revert()
{
@unlink($this->configFile);
}
private function buildConfig()
{
return [
'debug' => $this->debugMode,
'database' => $this->dbConfig->toArray(),
'url' => $this->baseUrl,
'paths' => $this->getPathsConfig(),
];
}
private function getPathsConfig()
{
return [
'api' => 'api',
'admin' => 'admin',
];
}
}

View File

@ -0,0 +1,80 @@
<?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\Steps;
use Flarum\Foundation\Application;
use Flarum\Install\Step;
use Flarum\Settings\DatabaseSettingsRepository;
use Illuminate\Database\ConnectionInterface;
class WriteSettings implements Step
{
/**
* @var ConnectionInterface
*/
private $database;
/**
* @var array
*/
private $custom;
public function __construct(ConnectionInterface $database, array $custom)
{
$this->database = $database;
$this->custom = $custom;
}
public function getMessage()
{
return 'Writing default settings';
}
public function run()
{
$repo = new DatabaseSettingsRepository($this->database);
$repo->set('version', Application::VERSION);
foreach ($this->getSettings() as $key => $value) {
$repo->set($key, $value);
}
}
private function getSettings()
{
return $this->custom + $this->getDefaults();
}
private function getDefaults()
{
return [
'allow_post_editing' => 'reply',
'allow_renaming' => '10',
'allow_sign_up' => '1',
'custom_less' => '',
'default_locale' => 'en',
'default_route' => '/all',
'extensions_enabled' => '[]',
'forum_title' => 'A new Flarum forum',
'forum_description' => '',
'mail_driver' => 'mail',
'mail_from' => 'noreply@localhost',
'theme_colored_header' => '0',
'theme_dark_mode' => '0',
'theme_primary_color' => '#4D698E',
'theme_secondary_color' => '#4D698E',
'welcome_message' => 'This is beta software and you should not use it in production.',
'welcome_title' => 'Welcome to Flarum',
];
}
}

View File

@ -0,0 +1,18 @@
<?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 Exception;
class ValidationFailed extends Exception
{
}

View File

@ -26,14 +26,8 @@ class LocaleServiceProvider extends AbstractServiceProvider
*/
public function boot(Dispatcher $events)
{
$locales = $this->app->make('flarum.locales');
$locales->addLocale($this->getDefaultLocale(), 'Default');
$events->dispatch(new ConfigureLocales($locales));
$events->listen(ClearingCache::class, function () use ($locales) {
$locales->clearCache();
$events->listen(ClearingCache::class, function () {
$this->app->make('flarum.locales')->clearCache();
});
}
@ -43,10 +37,16 @@ class LocaleServiceProvider extends AbstractServiceProvider
public function register()
{
$this->app->singleton(LocaleManager::class, function () {
return new LocaleManager(
$locales = new LocaleManager(
$this->app->make('translator'),
$this->getCacheDir()
);
$locales->addLocale($this->getDefaultLocale(), 'Default');
event(new ConfigureLocales($locales));
return $locales;
});
$this->app->alias(LocaleManager::class, 'flarum.locales');

View File

@ -18,12 +18,12 @@ use Flarum\Post\Event\Posted;
use Flarum\Post\Event\Restored;
use Flarum\Post\Event\Revised;
use Flarum\User\User;
use Psr\Http\Message\ServerRequestInterface;
/**
* A standard comment in a discussion.
*
* @property string $parsed_content
* @property string $content_html
*/
class CommentPost extends Post
{
@ -166,11 +166,12 @@ class CommentPost extends Post
/**
* Get the content rendered as HTML.
*
* @param ServerRequestInterface $request
* @return string
*/
public function getContentHtmlAttribute()
public function formatContent(ServerRequestInterface $request)
{
return static::$formatter->render($this->attributes['content'], $this);
return static::$formatter->render($this->attributes['content'], $this, $request);
}
/**

View File

@ -26,14 +26,14 @@ class PostServiceProvider extends AbstractServiceProvider
$this->registerPostTypes();
$events = $this->app->make('events');
$events->subscribe('Flarum\Post\PostPolicy');
$events->subscribe(PostPolicy::class);
}
public function registerPostTypes()
{
$models = [
'Flarum\Post\CommentPost',
'Flarum\Post\DiscussionRenamedPost'
CommentPost::class,
DiscussionRenamedPost::class
];
$this->app->make('events')->fire(

View File

@ -11,9 +11,19 @@
namespace Flarum\Search;
use Flarum\Discussion\Search\DiscussionSearcher;
use Flarum\Discussion\Search\Gambit\AuthorGambit;
use Flarum\Discussion\Search\Gambit\CreatedGambit;
use Flarum\Discussion\Search\Gambit\FulltextGambit as DiscussionFulltextGambit;
use Flarum\Discussion\Search\Gambit\HiddenGambit;
use Flarum\Discussion\Search\Gambit\UnreadGambit;
use Flarum\Event\ConfigureDiscussionGambits;
use Flarum\Event\ConfigureUserGambits;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\User\Search\Gambit\EmailGambit;
use Flarum\User\Search\Gambit\FulltextGambit as UserFulltextGambit;
use Flarum\User\Search\Gambit\GroupGambit;
use Flarum\User\Search\UserSearcher;
use Illuminate\Contracts\Container\Container;
class SearchServiceProvider extends AbstractServiceProvider
@ -25,11 +35,6 @@ class SearchServiceProvider extends AbstractServiceProvider
*/
public function register()
{
$this->app->bind(
'Flarum\Discussion\Search\Fulltext\DriverInterface',
'Flarum\Discussion\Search\Fulltext\MySqlFulltextDriver'
);
$this->registerDiscussionGambits();
$this->registerUserGambits();
@ -37,14 +42,14 @@ class SearchServiceProvider extends AbstractServiceProvider
public function registerUserGambits()
{
$this->app->when('Flarum\User\Search\UserSearcher')
->needs('Flarum\Search\GambitManager')
$this->app->when(UserSearcher::class)
->needs(GambitManager::class)
->give(function (Container $app) {
$gambits = new GambitManager($app);
$gambits->setFulltextGambit('Flarum\User\Search\Gambit\FulltextGambit');
$gambits->add('Flarum\User\Search\Gambit\EmailGambit');
$gambits->add('Flarum\User\Search\Gambit\GroupGambit');
$gambits->setFulltextGambit(UserFulltextGambit::class);
$gambits->add(EmailGambit::class);
$gambits->add(GroupGambit::class);
$app->make('events')->fire(
new ConfigureUserGambits($gambits)
@ -56,16 +61,16 @@ class SearchServiceProvider extends AbstractServiceProvider
public function registerDiscussionGambits()
{
$this->app->when('Flarum\Discussion\Search\DiscussionSearcher')
->needs('Flarum\Search\GambitManager')
$this->app->when(DiscussionSearcher::class)
->needs(GambitManager::class)
->give(function (Container $app) {
$gambits = new GambitManager($app);
$gambits->setFulltextGambit('Flarum\Discussion\Search\Gambit\FulltextGambit');
$gambits->add('Flarum\Discussion\Search\Gambit\AuthorGambit');
$gambits->add('Flarum\Discussion\Search\Gambit\CreatedGambit');
$gambits->add('Flarum\Discussion\Search\Gambit\HiddenGambit');
$gambits->add('Flarum\Discussion\Search\Gambit\UnreadGambit');
$gambits->setFulltextGambit(DiscussionFulltextGambit::class);
$gambits->add(AuthorGambit::class);
$gambits->add(CreatedGambit::class);
$gambits->add(HiddenGambit::class);
$gambits->add(UnreadGambit::class);
$app->make('events')->fire(
new ConfigureDiscussionGambits($gambits)

View File

@ -12,6 +12,7 @@
namespace Flarum\Settings;
use Flarum\Foundation\AbstractServiceProvider;
use Illuminate\Database\ConnectionInterface;
class SettingsServiceProvider extends AbstractServiceProvider
{
@ -23,7 +24,7 @@ class SettingsServiceProvider extends AbstractServiceProvider
$this->app->singleton(SettingsRepositoryInterface::class, function () {
return new MemoryCacheSettingsRepository(
new DatabaseSettingsRepository(
$this->app->make('Illuminate\Database\ConnectionInterface')
$this->app->make(ConnectionInterface::class)
)
);
});

View File

@ -23,10 +23,11 @@ class UpdateServiceProvider extends AbstractServiceProvider
public function register()
{
$this->app->singleton('flarum.update.routes', function () {
return new RouteCollection;
});
$routes = new RouteCollection;
$this->populateRoutes($routes);
$this->loadViewsFrom(__DIR__.'/../../views/install', 'flarum.update');
return $routes;
});
}
/**
@ -34,7 +35,7 @@ class UpdateServiceProvider extends AbstractServiceProvider
*/
public function boot()
{
$this->populateRoutes($this->app->make('flarum.update.routes'));
$this->loadViewsFrom(__DIR__.'/../../views/install', 'flarum.update');
}
/**

View File

@ -0,0 +1,105 @@
<?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\User;
use Flarum\Http\UrlGenerator;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\Event\Registered;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Translation\Translator;
use Illuminate\Mail\Message;
class AccountActivationMailer
{
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
/**
* @var Mailer
*/
protected $mailer;
/**
* @var UrlGenerator
*/
protected $url;
/**
* @var Translator
*/
protected $translator;
/**
* @param \Flarum\Settings\SettingsRepositoryInterface $settings
* @param Mailer $mailer
* @param UrlGenerator $url
* @param Translator $translator
*/
public function __construct(SettingsRepositoryInterface $settings, Mailer $mailer, UrlGenerator $url, Translator $translator)
{
$this->settings = $settings;
$this->mailer = $mailer;
$this->url = $url;
$this->translator = $translator;
}
public function handle(Registered $event)
{
$user = $event->user;
if ($user->is_email_confirmed) {
return;
}
$data = $this->getEmailData($user, $user->email);
$body = $this->translator->trans('core.email.activate_account.body', $data);
$this->mailer->raw($body, function (Message $message) use ($user, $data) {
$message->to($user->email);
$message->subject('['.$data['{forum}'].'] '.$this->translator->trans('core.email.activate_account.subject'));
});
}
/**
* @param User $user
* @param string $email
* @return EmailToken
*/
protected function generateToken(User $user, $email)
{
$token = EmailToken::generate($email, $user->id);
$token->save();
return $token;
}
/**
* Get the data that should be made available to email templates.
*
* @param User $user
* @param string $email
* @return array
*/
protected function getEmailData(User $user, $email)
{
$token = $this->generateToken($user, $email);
return [
'{username}' => $user->display_name,
'{url}' => $this->url->to('forum')->route('confirmEmail', ['token' => $token->token]),
'{forum}' => $this->settings->get('forum_title')
];
}
}

View File

@ -14,8 +14,6 @@ namespace Flarum\User;
use Flarum\Http\UrlGenerator;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\Event\EmailChangeRequested;
use Flarum\User\Event\Registered;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Translation\Translator;
use Illuminate\Mail\Message;
@ -42,12 +40,6 @@ class EmailConfirmationMailer
*/
protected $translator;
/**
* @param \Flarum\Settings\SettingsRepositoryInterface $settings
* @param Mailer $mailer
* @param UrlGenerator $url
* @param Translator $translator
*/
public function __construct(SettingsRepositoryInterface $settings, Mailer $mailer, UrlGenerator $url, Translator $translator)
{
$this->settings = $settings;
@ -56,40 +48,7 @@ class EmailConfirmationMailer
$this->translator = $translator;
}
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(Registered::class, [$this, 'whenUserWasRegistered']);
$events->listen(EmailChangeRequested::class, [$this, 'whenUserEmailChangeWasRequested']);
}
/**
* @param \Flarum\User\Event\Registered $event
*/
public function whenUserWasRegistered(Registered $event)
{
$user = $event->user;
if ($user->is_email_confirmed) {
return;
}
$data = $this->getEmailData($user, $user->email);
$body = $this->translator->trans('core.email.activate_account.body', $data);
$this->mailer->raw($body, function (Message $message) use ($user, $data) {
$message->to($user->email);
$message->subject('['.$data['{forum}'].'] '.$this->translator->trans('core.email.activate_account.subject'));
});
}
/**
* @param \Flarum\User\Event\EmailChangeRequested $event
*/
public function whenUserEmailChangeWasRequested(EmailChangeRequested $event)
public function handle(EmailChangeRequested $event)
{
$email = $event->email;
$data = $this->getEmailData($event->user, $email);

View File

@ -400,4 +400,16 @@ class Gate implements GateContract
{
// TODO: Implement abilities() method.
}
/**
* Get the raw result from the authorization callback.
*
* @param string $ability
* @param array|mixed $arguments
* @return mixed
*/
public function raw($ability, $arguments = [])
{
// TODO: Implement raw() method.
}
}

View File

@ -14,24 +14,15 @@ namespace Flarum\User;
use Flarum\Group\Group;
use Flarum\User\Event\Saving;
use Flarum\User\Exception\PermissionDeniedException;
use Illuminate\Contracts\Events\Dispatcher;
class SelfDemotionGuard
{
/**
* @param Dispatcher $events
*/
public function subscribe(Dispatcher $events)
{
$events->listen(Saving::class, [$this, 'whenUserWillBeSaved']);
}
/**
* Prevent an admin from removing their admin permission via the API.
* @param Saving $event
* @throws PermissionDeniedException
*/
public function whenUserWillBeSaved(Saving $event)
public function handle(Saving $event)
{
// Non-admin users pose no problem
if (! $event->actor->isAdmin()) {

View File

@ -753,7 +753,7 @@ class User extends AbstractModel
*/
public function refreshCommentCount()
{
$this->comment_count = $this->posts()->count();
$this->comment_count = $this->posts()->where('type', 'comment')->count();
return $this;
}

View File

@ -14,7 +14,13 @@ namespace Flarum\User;
use Flarum\Event\ConfigureUserPreferences;
use Flarum\Event\GetPermission;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\User\Event\EmailChangeRequested;
use Flarum\User\Event\Registered;
use Flarum\User\Event\Saving;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Filesystem\Factory;
use League\Flysystem\FilesystemInterface;
use RuntimeException;
class UserServiceProvider extends AbstractServiceProvider
@ -36,18 +42,18 @@ class UserServiceProvider extends AbstractServiceProvider
});
});
$this->app->alias('flarum.gate', 'Illuminate\Contracts\Auth\Access\Gate');
$this->app->alias('flarum.gate', GateContract::class);
$this->app->alias('flarum.gate', Gate::class);
}
protected function registerAvatarsFilesystem()
{
$avatarsFilesystem = function (Container $app) {
return $app->make('Illuminate\Contracts\Filesystem\Factory')->disk('flarum-avatars')->getDriver();
return $app->make(Factory::class)->disk('flarum-avatars')->getDriver();
};
$this->app->when(AvatarUploader::class)
->needs('League\Flysystem\FilesystemInterface')
->needs(FilesystemInterface::class)
->give($avatarsFilesystem);
}
@ -83,8 +89,10 @@ class UserServiceProvider extends AbstractServiceProvider
$events = $this->app->make('events');
$events->subscribe(SelfDemotionGuard::class);
$events->subscribe(EmailConfirmationMailer::class);
$events->listen(Saving::class, SelfDemotionGuard::class);
$events->listen(Registered::class, AccountActivationMailer::class);
$events->listen(EmailChangeRequested::class, EmailConfirmationMailer::class);
$events->subscribe(UserMetadataUpdater::class);
$events->subscribe(UserPolicy::class);

View File

@ -1,40 +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\Tests\Api\Controller;
use Flarum\Api\Controller\DeleteDiscussionController;
use Flarum\Discussion\Discussion;
class DeleteDiscussionControllerTest extends ApiControllerTestCase
{
protected $controller = DeleteDiscussionController::class;
protected $discussion;
protected function init()
{
$this->discussion = Discussion::start(__CLASS__, $this->getNormalUser());
$this->discussion->save();
}
/**
* @test
*/
public function admin_can_delete()
{
$this->actor = $this->getAdminUser();
$response = $this->callWith([], ['id' => $this->discussion->id]);
$this->assertEquals(204, $response->getStatusCode());
}
}

View File

@ -1,50 +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\Tests\Api\Controller;
use Flarum\Api\Controller\ListDiscussionsController;
use Flarum\Discussion\Discussion;
class ListDiscussionControllerTest extends ApiControllerTestCase
{
protected $controller = ListDiscussionsController::class;
/**
* @test
*/
public function shows_index_for_guest()
{
$response = $this->callWith();
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getBody()->getContents(), true);
$this->assertEquals(Discussion::count(), count($data['data']));
}
/**
* @test
*/
public function can_search_for_author()
{
$user = $this->getNormalUser();
$response = $this->callWith([], [
'filter' => [
'q' => 'author:'.$user->username.' foo'
],
'include' => 'mostRelevantPost'
]);
$this->assertEquals(200, $response->getStatusCode());
}
}

View File

@ -1,86 +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\Tests\Api\Controller;
use Flarum\Api\Controller\ShowDiscussionController;
use Flarum\Discussion\Discussion;
use Flarum\Tests\Test\Concerns\ManagesContent;
class ShowDiscussionControllerTest extends ApiControllerTestCase
{
use ManagesContent;
protected $controller = ShowDiscussionController::class;
/**
* @var Discussion
*/
protected $discussion;
protected function init()
{
$this->discussion = Discussion::start(__CLASS__, $this->getNormalUser());
}
/**
* @test
*/
public function author_can_see_discussion()
{
$this->discussion->save();
$this->actor = $this->getNormalUser();
$response = $this->callWith([], ['id' => $this->discussion->id]);
$this->assertEquals(200, $response->getStatusCode());
}
/**
* @test
* @expectedException \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function guest_cannot_see_empty_discussion()
{
$this->discussion->save();
$response = $this->callWith([], ['id' => $this->discussion->id]);
$this->assertEquals(200, $response->getStatusCode());
}
/**
* @test
*/
public function guest_can_see_discussion()
{
$this->discussion->save();
$this->addPostByNormalUser();
$response = $this->callWith([], ['id' => $this->discussion->id]);
$this->assertEquals(200, $response->getStatusCode());
}
/**
* @test
* @expectedException \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function guests_cannot_see_private_discussion()
{
$this->discussion->is_private = true;
$this->discussion->save();
$this->callWith([], ['id' => $this->discussion->id]);
}
}

Some files were not shown because too many files have changed in this diff Show More