Merge branch 'master' into 1236-database-changes

This commit is contained in:
Toby Zerner 2018-08-24 17:03:50 +09:30
commit 7d46e130d2
26 changed files with 263 additions and 143 deletions

View File

@ -6,6 +6,6 @@ about: "If you have a question, please check out our forum or Discord!"
We primarily use GitHub as an issue tracker; for usage and support questions, please check out these resources below. Thanks!
* Flarum Community: https://discuss.flarum.org
* Discord Chat: https://flarum.org/discord
* Twitter: https://twitter.com/flarum
* Flarum Community: https://discuss.flarum.org/
* Discord Chat: https://flarum.org/discord/
* Twitter: https://twitter.com/Flarum

View File

@ -1,5 +1,5 @@
<!--
IMPORTANT: We applaud pull requests, they excite us every single time. As we have an obligation to maintain a healthy code standard and quality, we take sufficient time for reviews.
IMPORTANT: We applaud pull requests, they excite us every single time. As we have an obligation to maintain a healthy code standard and quality, we take sufficient time for reviews. Please do create a separate pull request per change/issue/feature; we will ask you to split bundled pull requests.
-->
**Fixes #0000**

View File

@ -1,6 +1,6 @@
# Flarum Core
This repository contains Flarum's core code. If you want to set up a forum, visit the [main Flarum repository](http://github.com/flarum/flarum).
This repository contains Flarum's core code. If you want to set up a forum, visit the [main Flarum repository](https://github.com/flarum/flarum).
## Contributing

View File

@ -2,7 +2,7 @@
"name": "flarum/core",
"description": "Delightfully simple forum software.",
"keywords": ["forum", "discussion"],
"homepage": "http://flarum.org",
"homepage": "https://flarum.org/",
"license": "MIT",
"authors": [
{
@ -17,7 +17,7 @@
"support": {
"issues": "https://github.com/flarum/core/issues",
"source": "https://github.com/flarum/core",
"docs": "http://flarum.org/docs"
"docs": "https://flarum.org/docs/"
},
"require": {
"php": ">=7.1",
@ -57,7 +57,7 @@
"symfony/translation": "^3.3",
"symfony/yaml": "^3.3",
"tobscure/json-api": "^0.3.0",
"zendframework/zend-diactoros": "^1.7",
"zendframework/zend-diactoros": "^1.8.4",
"zendframework/zend-stratigility": "^3.0"
},
"require-dev": {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -46,11 +46,14 @@ export default class ExtensionsPage extends Page {
{controls}
</Dropdown>
) : ''}
<label className="ExtensionListItem-title">
<input type="checkbox" checked={this.isEnabled(extension.id)} onclick={this.toggle.bind(this, extension.id)}/> {' '}
{extension.extra['flarum-extension'].title}
</label>
<div className="ExtensionListItem-version">{extension.version}</div>
<div className="ExtensionListItem-main">
<label className="ExtensionListItem-title">
<input type="checkbox" checked={this.isEnabled(extension.id)} onclick={this.toggle.bind(this, extension.id)}/> {' '}
{extension.extra['flarum-extension'].title}
</label>
<div className="ExtensionListItem-version">{extension.version}</div>
<div className="ExtensionListItem-description">{extension.description}</div>
</div>
</div>
</li>;
})}

View File

@ -75,7 +75,7 @@ export default class Store {
* Make a request to the API to find record(s) of a specific type.
*
* @param {String} type The resource type.
* @param {Integer|Integer[]|Object} [id] The ID(s) of the model(s) to retreive.
* @param {Integer|Integer[]|Object} [id] The ID(s) of the model(s) to retrieve.
* Alternatively, if an object is passed, it will be handled as the
* `query` parameter.
* @param {Object} [query]

View File

@ -19,7 +19,9 @@ export default class LoadingIndicator extends Component {
return <div {...attrs}>{m.trust('&nbsp;')}</div>;
}
config() {
config(isInitialized) {
if (isInitialized) return;
const options = { zIndex: 'auto', color: this.$().css('color') };
switch (this.props.size) {

View File

@ -49,7 +49,7 @@ export default class ModalManager extends Component {
this.showing = true;
this.component = component;
app.current.retain = true;
if (app.current) app.current.retain = true;
m.redraw(true);

View File

@ -37,7 +37,7 @@ export default class ForumApplication extends Application {
/**
* The page's search component instance.
*
* @type {SearchBox}
* @type {Search}
*/
search = new Search();

View File

@ -115,6 +115,12 @@ export default class DiscussionPage extends Page {
);
}
config() {
if (this.discussion) {
app.setTitle(this.discussion.title());
}
}
/**
* Clear and reload the discussion.
*/
@ -160,7 +166,6 @@ export default class DiscussionPage extends Page {
this.discussion = discussion;
app.history.push('discussion', discussion.title());
app.setTitle(discussion.title());
app.setTitleCount(0);
// When the API responds with a discussion, it will also include a number of

View File

@ -38,7 +38,7 @@ export default class Search extends Component {
*
* @type {SearchSource[]}
*/
this.sources = this.sourceItems().toArray();
this.sources = null;
/**
* The number of sources that are still loading results.
@ -74,6 +74,15 @@ export default class Search extends Component {
this.value(currentSearch || '');
}
// Initialize search sources in the view rather than the constructor so
// that we have access to app.forum.
if (!this.sources) {
this.sources = this.sourceItems().toArray();
}
// Hide the search view if no sources were loaded
if (!this.sources.length) return <div></div>;
return (
<div className={'Search ' + classList({
open: this.value() && this.hasFocus,
@ -210,8 +219,8 @@ export default class Search extends Component {
sourceItems() {
const items = new ItemList();
items.add('discussions', new DiscussionsSearchSource());
items.add('users', new UsersSearchSource());
if (app.forum.attribute('canViewDiscussions')) items.add('discussions', new DiscussionsSearchSource());
if (app.forum.attribute('canViewUserList')) items.add('users', new UsersSearchSource());
return items;
}

View File

@ -24,9 +24,10 @@
.clearfix();
> li {
float: left;
text-align: left;
position: relative;
border-radius: 4px;
transition: background .2s;
}
}
.ExtensionListItem.disabled {
@ -39,45 +40,59 @@
}
}
.ExtensionListItem {
width: 120px;
height: 160px;
margin-right: 15px;
margin-bottom: 15px;
padding: 10px;
}
.ExtensionListItem-title {
display: block;
font-size: 13px;
font-weight: bold;
margin: 8px 0 0;
white-space: nowrap;
.ExtensionListItem:hover {
background: @control-bg;
}
.ExtensionListItem-content {
padding: 0 50px;
min-height: 40px;
}
.ExtensionListItem-main {
overflow: hidden;
text-overflow: ellipsis;
}
.ExtensionListItem-title {
display: inline-block;
font-size: 13px;
font-weight: bold;
white-space: nowrap;
cursor: pointer;
padding-right: 10px;
}
.ExtensionListItem-version {
color: @muted-more-color;
font-size: 11px;
font-weight: normal;
display: inline-flex;
}
.ExtensionListItem-controls {
float: right;
display: none;
margin-right: -5px;
margin-right: -50px;
margin-top: 1px;
.ExtensionListItem:hover &, &.open {
display: block;
}
}
.ExtensionListItem-description {
font-size: 11px;
font-weight: normal;
text-align: justify;
}
.ExtensionIcon {
width: 120px;
height: 120px;
width: 40px;
height: 40px;
background: @control-bg;
color: @control-color;
border-radius: 6px;
display: inline-block;
font-size: 60px;
line-height: 120px;
font-size: 20px;
line-height: 40px;
text-align: center;
margin-left: -50px;
position: absolute;
}

View File

@ -304,10 +304,9 @@
@media @desktop-up {
.Composer:not(.fullScreen) {
.App--index & {
margin-left: 220px;
margin-right: -20px;
}
margin-left: 220px;
margin-right: -20px;
.App--discussion & {
margin-left: -20px;
margin-right: 205px;

View File

@ -15,7 +15,9 @@ use Flarum\Extension\ExtensionManager;
use Flarum\Frontend\Content\ContentInterface;
use Flarum\Frontend\HtmlDocument;
use Flarum\Group\Permission;
use Flarum\Settings\Event\Deserializing;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\ConnectionInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
@ -40,17 +42,25 @@ class AdminPayload implements ContentInterface
* @param SettingsRepositoryInterface $settings
* @param ExtensionManager $extensions
* @param ConnectionInterface $db
* @param Dispatcher $events
*/
public function __construct(SettingsRepositoryInterface $settings, ExtensionManager $extensions, ConnectionInterface $db)
public function __construct(SettingsRepositoryInterface $settings, ExtensionManager $extensions, ConnectionInterface $db, Dispatcher $events)
{
$this->settings = $settings;
$this->extensions = $extensions;
$this->db = $db;
$this->events = $events;
}
public function populate(HtmlDocument $document, Request $request)
{
$document->payload['settings'] = $this->settings->all();
$settings = $this->settings->all();
$this->events->dispatch(
new Deserializing($settings)
);
$document->payload['settings'] = $settings;
$document->payload['permissions'] = Permission::map();
$document->payload['extensions'] = $this->extensions->getExtensions()->toArray();

View File

@ -1,64 +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\Extend;
use Flarum\Extension\Extension;
use Flarum\Frontend\Asset\ExtensionAssets;
use Flarum\Frontend\CompilerFactory;
use Illuminate\Contracts\Container\Container;
class Assets implements ExtenderInterface
{
protected $frontend;
protected $css = [];
protected $js;
public function __construct($frontend)
{
$this->frontend = $frontend;
}
public function css($path)
{
$this->css[] = $path;
return $this;
}
/**
* @deprecated
*/
public function asset($path)
{
return $this->css($path);
}
public function js($path)
{
$this->js = $path;
return $this;
}
public function __invoke(Container $container, Extension $extension = null)
{
$container->resolving(
"flarum.$this->frontend.assets",
function (CompilerFactory $assets) use ($extension) {
$assets->add(function () use ($extension) {
return new ExtensionAssets($extension, $this->css, $this->js);
});
}
);
}
}

View File

@ -0,0 +1,94 @@
<?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\Extend;
use Flarum\Extension\Extension;
use Flarum\Frontend\Asset\ExtensionAssets;
use Flarum\Frontend\CompilerFactory;
use Flarum\Http\RouteHandlerFactory;
use Illuminate\Contracts\Container\Container;
class Frontend implements ExtenderInterface
{
protected $frontend;
protected $css = [];
protected $js;
protected $routes = [];
public function __construct($frontend)
{
$this->frontend = $frontend;
}
public function css($path)
{
$this->css[] = $path;
return $this;
}
public function js($path)
{
$this->js = $path;
return $this;
}
public function route($path, $name, $content = null)
{
$this->routes[] = compact('path', 'name', 'content');
return $this;
}
public function __invoke(Container $container, Extension $extension = null)
{
$this->registerAssets($container, $extension);
$this->registerRoutes($container);
}
private function registerAssets(Container $container, Extension $extension)
{
if (empty($this->css) && empty($this->js)) {
return;
}
$container->resolving(
"flarum.$this->frontend.assets",
function (CompilerFactory $assets) use ($extension) {
$assets->add(function () use ($extension) {
return new ExtensionAssets(
$extension, $this->css, $this->js
);
});
}
);
}
private function registerRoutes(Container $container)
{
if (empty($this->routes)) {
return;
}
$routes = $container->make("flarum.$this->frontend.routes");
$factory = $container->make(RouteHandlerFactory::class);
foreach ($this->routes as $route) {
$routes->get(
$route['path'], $route['name'],
$factory->toForum($route['content'])
);
}
}
}

View File

@ -113,22 +113,8 @@ class Extension implements Arrayable
public function extend(Container $app)
{
$bootstrapper = $this->getBootstrapperPath();
if (! file_exists($bootstrapper)) {
return;
}
$extenders = require $bootstrapper;
if (! is_array($extenders)) {
$extenders = [$extenders];
}
$extenders = array_flatten($extenders);
foreach ($extenders as $extender) {
// If an extension has not yet switched to the new bootstrap.php
foreach ($this->getExtenders() as $extender) {
// If an extension has not yet switched to the new extend.php
// format, it might return a function (or more of them). We wrap
// these in a Compat extender to enjoy an unique interface.
if ($extender instanceof \Closure || is_string($extender)) {
@ -274,9 +260,39 @@ class Extension implements Arrayable
return $this->path;
}
public function getBootstrapperPath()
private function getExtenders(): array
{
return "{$this->path}/bootstrap.php";
$extenderFile = $this->getExtenderFile();
if (! $extenderFile) {
return [];
}
$extenders = require $extenderFile;
if (! is_array($extenders)) {
$extenders = [$extenders];
}
return array_flatten($extenders);
}
private function getExtenderFile(): ?string
{
$filename = "{$this->path}/extend.php";
if (file_exists($filename)) {
return $filename;
}
// To give extension authors some time to migrate to the new extension
// format, we will also fallback to the old bootstrap.php name. Consider
// this feature deprecated.
$deprecatedFilename = "{$this->path}/bootstrap.php";
if (file_exists($deprecatedFilename)) {
return $deprecatedFilename;
}
}
/**

View File

@ -46,10 +46,20 @@ class ExtensionAssets implements AssetInterface
public function js(SourceCollector $sources)
{
if ($this->js) {
$sources->addString(function () {
return 'var module={}';
});
if (is_callable($this->js)) {
$sources->addString($this->js);
} else {
$sources->addFile($this->js);
}
$sources->addString(function () {
$name = $this->extension->getId();
return 'var module={};'.$this->getContent($this->js).";flarum.extensions['$name']=module.exports";
return "flarum.extensions['$name']=module.exports";
});
}
}
@ -65,11 +75,6 @@ class ExtensionAssets implements AssetInterface
}
}
private function getContent($asset)
{
return is_callable($asset) ? $asset() : file_get_contents($asset);
}
public function localeJs(SourceCollector $sources, string $locale)
{
}

View File

@ -40,6 +40,7 @@ class InstallServiceProvider extends AbstractServiceProvider
'mbstring',
'openssl',
'pdo_mysql',
'tokenizer',
]),
new WritablePaths([
base_path(),
@ -53,8 +54,6 @@ class InstallServiceProvider extends AbstractServiceProvider
$this->app->singleton('flarum.install.routes', function () {
return new RouteCollection;
});
$this->loadViewsFrom(__DIR__.'/../../views/install', 'flarum.install');
}
/**
@ -62,6 +61,8 @@ class InstallServiceProvider extends AbstractServiceProvider
*/
public function boot()
{
$this->loadViewsFrom(__DIR__.'/../../views/install', 'flarum.install');
$this->populateRoutes($this->app->make('flarum.install.routes'));
}

View File

@ -23,12 +23,37 @@ class WritablePaths extends AbstractPrerequisite
public function check()
{
foreach ($this->paths as $path) {
if (! is_writable($path)) {
if (! file_exists($path)) {
$this->errors[] = [
'message' => 'The '.realpath($path).' directory is not writable.',
'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[] = [
'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)
{
$path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
$absolutes = [];
foreach ($parts as $part) {
if ('.' == $part) {
continue;
}
if ('..' == $part) {
array_pop($absolutes);
} else {
$absolutes[] = $part;
}
}
return (substr($path, 0, 1) == '/' ? '/' : '').implode(DIRECTORY_SEPARATOR, $absolutes);
}
}

View File

@ -277,7 +277,7 @@ class Gate implements GateContract
$result = call_user_func_array([$instance, 'before'], $beforeArguments);
// If we recieved a non-null result from the before method, we will return it
// If we received a non-null result from the before method, we will return it
// as the result of a check. This allows developers to override the checks
// in the policy and return a result for all rules defined in the class.
if (! is_null($result)) {

View File

@ -38,7 +38,7 @@ class UserPolicy extends AbstractPolicy
*/
public function find(User $actor, Builder $query)
{
if ($actor->cannot('viewDiscussions')) {
if ($actor->cannot('viewUserList')) {
$query->whereRaw('FALSE');
}
}