1
0
mirror of https://github.com/flarum/core.git synced 2025-10-10 14:34:30 +02:00

Merge branch 'master' into 1236-database-changes

This commit is contained in:
Toby Zerner
2018-08-24 17:03:50 +09:30
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! 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 * Flarum Community: https://discuss.flarum.org/
* Discord Chat: https://flarum.org/discord * Discord Chat: https://flarum.org/discord/
* Twitter: https://twitter.com/flarum * 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** **Fixes #0000**

View File

@@ -1,6 +1,6 @@
# Flarum Core # 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 ## Contributing

View File

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

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

4
js/dist/forum.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -46,11 +46,14 @@ export default class ExtensionsPage extends Page {
{controls} {controls}
</Dropdown> </Dropdown>
) : ''} ) : ''}
<div className="ExtensionListItem-main">
<label className="ExtensionListItem-title"> <label className="ExtensionListItem-title">
<input type="checkbox" checked={this.isEnabled(extension.id)} onclick={this.toggle.bind(this, extension.id)}/> {' '} <input type="checkbox" checked={this.isEnabled(extension.id)} onclick={this.toggle.bind(this, extension.id)}/> {' '}
{extension.extra['flarum-extension'].title} {extension.extra['flarum-extension'].title}
</label> </label>
<div className="ExtensionListItem-version">{extension.version}</div> <div className="ExtensionListItem-version">{extension.version}</div>
<div className="ExtensionListItem-description">{extension.description}</div>
</div>
</div> </div>
</li>; </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. * Make a request to the API to find record(s) of a specific type.
* *
* @param {String} type The resource 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 * Alternatively, if an object is passed, it will be handled as the
* `query` parameter. * `query` parameter.
* @param {Object} [query] * @param {Object} [query]

View File

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

View File

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

View File

@@ -37,7 +37,7 @@ export default class ForumApplication extends Application {
/** /**
* The page's search component instance. * The page's search component instance.
* *
* @type {SearchBox} * @type {Search}
*/ */
search = new 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. * Clear and reload the discussion.
*/ */
@@ -160,7 +166,6 @@ export default class DiscussionPage extends Page {
this.discussion = discussion; this.discussion = discussion;
app.history.push('discussion', discussion.title()); app.history.push('discussion', discussion.title());
app.setTitle(discussion.title());
app.setTitleCount(0); app.setTitleCount(0);
// When the API responds with a discussion, it will also include a number of // 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[]} * @type {SearchSource[]}
*/ */
this.sources = this.sourceItems().toArray(); this.sources = null;
/** /**
* The number of sources that are still loading results. * The number of sources that are still loading results.
@@ -74,6 +74,15 @@ export default class Search extends Component {
this.value(currentSearch || ''); 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 ( return (
<div className={'Search ' + classList({ <div className={'Search ' + classList({
open: this.value() && this.hasFocus, open: this.value() && this.hasFocus,
@@ -210,8 +219,8 @@ export default class Search extends Component {
sourceItems() { sourceItems() {
const items = new ItemList(); const items = new ItemList();
items.add('discussions', new DiscussionsSearchSource()); if (app.forum.attribute('canViewDiscussions')) items.add('discussions', new DiscussionsSearchSource());
items.add('users', new UsersSearchSource()); if (app.forum.attribute('canViewUserList')) items.add('users', new UsersSearchSource());
return items; return items;
} }

View File

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

View File

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

View File

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

94
src/Extend/Frontend.php Normal file
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) public function extend(Container $app)
{ {
$bootstrapper = $this->getBootstrapperPath(); foreach ($this->getExtenders() as $extender) {
// If an extension has not yet switched to the new extend.php
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
// format, it might return a function (or more of them). We wrap // format, it might return a function (or more of them). We wrap
// these in a Compat extender to enjoy an unique interface. // these in a Compat extender to enjoy an unique interface.
if ($extender instanceof \Closure || is_string($extender)) { if ($extender instanceof \Closure || is_string($extender)) {
@@ -274,9 +260,39 @@ class Extension implements Arrayable
return $this->path; 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) public function js(SourceCollector $sources)
{ {
if ($this->js) { 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 () { $sources->addString(function () {
$name = $this->extension->getId(); $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) public function localeJs(SourceCollector $sources, string $locale)
{ {
} }

View File

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

View File

@@ -23,12 +23,37 @@ class WritablePaths extends AbstractPrerequisite
public function check() public function check()
{ {
foreach ($this->paths as $path) { foreach ($this->paths as $path) {
if (! is_writable($path)) { if (! file_exists($path)) {
$this->errors[] = [ $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.' '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); $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 // 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. // in the policy and return a result for all rules defined in the class.
if (! is_null($result)) { if (! is_null($result)) {

View File

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