Add ability to upload a logo + favicon, and add custom header HTML

Closes . Not going to bother with a preview SVG or anything fancy for now – we can think about that as part of . Right now it's just good to finally get this functionality in!

Also need to think about apple-touch-icon, msTile stuff, and social sharing image. Not sure if this is all too much for core, but it's definitely too much for the current Appearance page layout. Again, something to think about as part of .

Code is a bit rough around the edges, but figured there's not much point in using the command bus properly since .
This commit is contained in:
Toby Zerner 2016-06-04 18:05:46 +09:30
parent 01a6dccb83
commit feffe53a86
17 changed files with 811 additions and 92 deletions

314
js/admin/dist/app.js vendored

@ -17488,10 +17488,10 @@ System.register('flarum/components/AlertManager', ['flarum/Component', 'flarum/c
});;
'use strict';
System.register('flarum/components/AppearancePage', ['flarum/components/Page', 'flarum/components/Button', 'flarum/components/Switch', 'flarum/components/EditCustomCssModal', 'flarum/utils/saveSettings'], function (_export, _context) {
System.register('flarum/components/AppearancePage', ['flarum/components/Page', 'flarum/components/Button', 'flarum/components/Switch', 'flarum/components/EditCustomCssModal', 'flarum/components/EditCustomHeaderModal', 'flarum/components/UploadImageButton', 'flarum/utils/saveSettings'], function (_export, _context) {
"use strict";
var Page, Button, Switch, EditCustomCssModal, saveSettings, AppearancePage;
var Page, Button, Switch, EditCustomCssModal, EditCustomHeaderModal, UploadImageButton, saveSettings, AppearancePage;
return {
setters: [function (_flarumComponentsPage) {
Page = _flarumComponentsPage.default;
@ -17501,6 +17501,10 @@ System.register('flarum/components/AppearancePage', ['flarum/components/Page', '
Switch = _flarumComponentsSwitch.default;
}, function (_flarumComponentsEditCustomCssModal) {
EditCustomCssModal = _flarumComponentsEditCustomCssModal.default;
}, function (_flarumComponentsEditCustomHeaderModal) {
EditCustomHeaderModal = _flarumComponentsEditCustomHeaderModal.default;
}, function (_flarumComponentsUploadImageButton) {
UploadImageButton = _flarumComponentsUploadImageButton.default;
}, function (_flarumUtilsSaveSettings) {
saveSettings = _flarumUtilsSaveSettings.default;
}],
@ -17572,6 +17576,57 @@ System.register('flarum/components/AppearancePage', ['flarum/components/Page', '
})
)
),
m(
'fieldset',
null,
m(
'legend',
null,
app.translator.trans('core.admin.appearance.logo_heading')
),
m(
'div',
{ className: 'helpText' },
app.translator.trans('core.admin.appearance.logo_text')
),
m(UploadImageButton, { name: 'logo' })
),
m(
'fieldset',
null,
m(
'legend',
null,
app.translator.trans('core.admin.appearance.favicon_heading')
),
m(
'div',
{ className: 'helpText' },
app.translator.trans('core.admin.appearance.favicon_text')
),
m(UploadImageButton, { name: 'favicon' })
),
m(
'fieldset',
null,
m(
'legend',
null,
app.translator.trans('core.admin.appearance.custom_header_heading')
),
m(
'div',
{ className: 'helpText' },
app.translator.trans('core.admin.appearance.custom_header_text')
),
Button.component({
className: 'Button',
children: app.translator.trans('core.admin.appearance.edit_header_button'),
onclick: function onclick() {
return app.modal.show(new EditCustomHeaderModal());
}
})
),
m(
'fieldset',
null,
@ -18232,21 +18287,17 @@ System.register('flarum/components/Dropdown', ['flarum/Component', 'flarum/helpe
});;
'use strict';
System.register('flarum/components/EditCustomCssModal', ['flarum/components/Modal', 'flarum/components/Button', 'flarum/utils/saveSettings'], function (_export, _context) {
System.register('flarum/components/EditCustomCssModal', ['flarum/components/SettingsModal'], function (_export, _context) {
"use strict";
var Modal, Button, saveSettings, EditCustomCssModal;
var SettingsModal, EditCustomCssModal;
return {
setters: [function (_flarumComponentsModal) {
Modal = _flarumComponentsModal.default;
}, function (_flarumComponentsButton) {
Button = _flarumComponentsButton.default;
}, function (_flarumUtilsSaveSettings) {
saveSettings = _flarumUtilsSaveSettings.default;
setters: [function (_flarumComponentsSettingsModal) {
SettingsModal = _flarumComponentsSettingsModal.default;
}],
execute: function () {
EditCustomCssModal = function (_Modal) {
babelHelpers.inherits(EditCustomCssModal, _Modal);
EditCustomCssModal = function (_SettingsModal) {
babelHelpers.inherits(EditCustomCssModal, _SettingsModal);
function EditCustomCssModal() {
babelHelpers.classCallCheck(this, EditCustomCssModal);
@ -18254,11 +18305,6 @@ System.register('flarum/components/EditCustomCssModal', ['flarum/components/Moda
}
babelHelpers.createClass(EditCustomCssModal, [{
key: 'init',
value: function init() {
this.customLess = m.prop(app.data.settings.custom_less || '');
}
}, {
key: 'className',
value: function className() {
return 'EditCustomCssModal Modal--large';
@ -18269,53 +18315,26 @@ System.register('flarum/components/EditCustomCssModal', ['flarum/components/Moda
return app.translator.trans('core.admin.edit_css.title');
}
}, {
key: 'content',
value: function content() {
return m(
key: 'form',
value: function form() {
return [m(
'p',
null,
app.translator.trans('core.admin.edit_css.customize_text', { a: m('a', { href: 'https://github.com/flarum/core/tree/master/less', target: '_blank' }) })
), m(
'div',
{ className: 'Modal-body' },
m(
'p',
null,
app.translator.trans('core.admin.edit_css.customize_text', { a: m('a', { href: 'https://github.com/flarum/core/tree/master/less', target: '_blank' }) })
),
m(
'div',
{ className: 'Form' },
m(
'div',
{ className: 'Form-group' },
m('textarea', { className: 'FormControl', rows: '30', value: this.customLess(), onchange: m.withAttr('value', this.customLess) })
),
m(
'div',
{ className: 'Form-group' },
Button.component({
className: 'Button Button--primary',
type: 'submit',
children: app.translator.trans('core.admin.edit_css.submit_button'),
loading: this.loading
})
)
)
);
{ className: 'Form-group' },
m('textarea', { className: 'FormControl', rows: '30', bidi: this.setting('custom_less') })
)];
}
}, {
key: 'onsubmit',
value: function onsubmit(e) {
e.preventDefault();
this.loading = true;
saveSettings({
custom_less: this.customLess()
}).then(function () {
return window.location.reload();
});
key: 'onsaved',
value: function onsaved() {
window.location.reload();
}
}]);
return EditCustomCssModal;
}(Modal);
}(SettingsModal);
_export('default', EditCustomCssModal);
}
@ -18323,6 +18342,61 @@ System.register('flarum/components/EditCustomCssModal', ['flarum/components/Moda
});;
'use strict';
System.register('flarum/components/EditCustomHeaderModal', ['flarum/components/SettingsModal'], function (_export, _context) {
"use strict";
var SettingsModal, EditCustomHeaderModal;
return {
setters: [function (_flarumComponentsSettingsModal) {
SettingsModal = _flarumComponentsSettingsModal.default;
}],
execute: function () {
EditCustomHeaderModal = function (_SettingsModal) {
babelHelpers.inherits(EditCustomHeaderModal, _SettingsModal);
function EditCustomHeaderModal() {
babelHelpers.classCallCheck(this, EditCustomHeaderModal);
return babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(EditCustomHeaderModal).apply(this, arguments));
}
babelHelpers.createClass(EditCustomHeaderModal, [{
key: 'className',
value: function className() {
return 'EditCustomHeaderModal Modal--large';
}
}, {
key: 'title',
value: function title() {
return app.translator.trans('core.admin.edit_header.title');
}
}, {
key: 'form',
value: function form() {
return [m(
'p',
null,
app.translator.trans('core.admin.edit_header.customize_text')
), m(
'div',
{ className: 'Form-group' },
m('textarea', { className: 'FormControl', rows: '30', bidi: this.setting('custom_header') })
)];
}
}, {
key: 'onsaved',
value: function onsaved() {
window.location.reload();
}
}]);
return EditCustomHeaderModal;
}(SettingsModal);
_export('default', EditCustomHeaderModal);
}
};
});;
'use strict';
System.register('flarum/components/EditGroupModal', ['flarum/components/Modal', 'flarum/components/Button', 'flarum/components/Badge', 'flarum/models/Group'], function (_export, _context) {
"use strict";
@ -20651,7 +20725,12 @@ System.register('flarum/components/SettingsModal', ['flarum/components/Modal', '
this.loading = true;
saveSettings(this.dirty()).then(this.hide.bind(this), this.loaded.bind(this));
saveSettings(this.dirty()).then(this.onsaved.bind(this), this.loaded.bind(this));
}
}, {
key: 'onsaved',
value: function onsaved() {
this.hide();
}
}]);
return SettingsModal;
@ -20767,6 +20846,122 @@ System.register('flarum/components/Switch', ['flarum/components/Checkbox'], func
}
};
});;
'use strict';
System.register('flarum/components/UploadImageButton', ['flarum/components/Button'], function (_export, _context) {
"use strict";
var Button, UploadImageButton;
return {
setters: [function (_flarumComponentsButton) {
Button = _flarumComponentsButton.default;
}],
execute: function () {
UploadImageButton = function (_Button) {
babelHelpers.inherits(UploadImageButton, _Button);
function UploadImageButton() {
babelHelpers.classCallCheck(this, UploadImageButton);
return babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(UploadImageButton).apply(this, arguments));
}
babelHelpers.createClass(UploadImageButton, [{
key: 'init',
value: function init() {
this.loading = false;
}
}, {
key: 'view',
value: function view() {
this.props.loading = this.loading;
this.props.className = (this.props.className || '') + ' Button';
if (app.data.settings[this.props.name + '_path']) {
this.props.onclick = this.remove.bind(this);
this.props.children = app.translator.trans('core.admin.upload_image.remove_button');
return m(
'div',
null,
m(
'p',
null,
m('img', { src: app.forum.attribute(this.props.name + 'Url'), alt: '' })
),
m(
'p',
null,
babelHelpers.get(Object.getPrototypeOf(UploadImageButton.prototype), 'view', this).call(this)
)
);
} else {
this.props.onclick = this.upload.bind(this);
this.props.children = app.translator.trans('core.admin.upload_image.upload_button');
}
return babelHelpers.get(Object.getPrototypeOf(UploadImageButton.prototype), 'view', this).call(this);
}
}, {
key: 'upload',
value: function upload() {
var _this2 = this;
if (this.loading) return;
var $input = $('<input type="file">');
$input.appendTo('body').hide().click().on('change', function (e) {
var data = new FormData();
data.append(_this2.props.name, $(e.target)[0].files[0]);
_this2.loading = true;
m.redraw();
app.request({
method: 'POST',
url: _this2.resourceUrl(),
serialize: function serialize(raw) {
return raw;
},
data: data
}).then(_this2.success.bind(_this2), _this2.failure.bind(_this2));
});
}
}, {
key: 'remove',
value: function remove() {
this.loading = true;
m.redraw();
app.request({
method: 'DELETE',
url: this.resourceUrl()
}).then(this.success.bind(this), this.failure.bind(this));
}
}, {
key: 'resourceUrl',
value: function resourceUrl() {
return app.forum.attribute('apiUrl') + '/' + this.props.name;
}
}, {
key: 'success',
value: function success(response) {
window.location.reload();
}
}, {
key: 'failure',
value: function failure(response) {
this.loading = false;
m.redraw();
}
}]);
return UploadImageButton;
}(Button);
_export('default', UploadImageButton);
}
};
});;
"use strict";
System.register("flarum/extend", [], function (_export, _context) {
@ -22435,6 +22630,7 @@ System.register('flarum/Translator', ['flarum/models/User', 'flarum/helpers/user
case 'vi':
case 'zh':
return 0;
case 'af':
case 'az':
case 'bn':

@ -2,6 +2,8 @@ import Page from 'flarum/components/Page';
import Button from 'flarum/components/Button';
import Switch from 'flarum/components/Switch';
import EditCustomCssModal from 'flarum/components/EditCustomCssModal';
import EditCustomHeaderModal from 'flarum/components/EditCustomHeaderModal';
import UploadImageButton from 'flarum/components/UploadImageButton';
import saveSettings from 'flarum/utils/saveSettings';
export default class AppearancePage extends Page {
@ -51,6 +53,34 @@ export default class AppearancePage extends Page {
</fieldset>
</form>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.logo_heading')}</legend>
<div className="helpText">
{app.translator.trans('core.admin.appearance.logo_text')}
</div>
<UploadImageButton name="logo"/>
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.favicon_heading')}</legend>
<div className="helpText">
{app.translator.trans('core.admin.appearance.favicon_text')}
</div>
<UploadImageButton name="favicon"/>
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_header_heading')}</legend>
<div className="helpText">
{app.translator.trans('core.admin.appearance.custom_header_text')}
</div>
{Button.component({
className: 'Button',
children: app.translator.trans('core.admin.appearance.edit_header_button'),
onclick: () => app.modal.show(new EditCustomHeaderModal())
})}
</fieldset>
<fieldset>
<legend>{app.translator.trans('core.admin.appearance.custom_styles_heading')}</legend>
<div className="helpText">

@ -0,0 +1,24 @@
import SettingsModal from 'flarum/components/SettingsModal';
export default class EditCustomHeaderModal extends SettingsModal {
className() {
return 'EditCustomHeaderModal Modal--large';
}
title() {
return app.translator.trans('core.admin.edit_header.title');
}
form() {
return [
<p>{app.translator.trans('core.admin.edit_header.customize_text')}</p>,
<div className="Form-group">
<textarea className="FormControl" rows="30" bidi={this.setting('custom_header')}/>
</div>
];
}
onsaved() {
window.location.reload();
}
}

@ -0,0 +1,97 @@
import Button from 'flarum/components/Button';
export default class UploadImageButton extends Button {
init() {
this.loading = false;
}
view() {
this.props.loading = this.loading;
this.props.className = (this.props.className || '') + ' Button';
if (app.data.settings[this.props.name + '_path']) {
this.props.onclick = this.remove.bind(this);
this.props.children = app.translator.trans('core.admin.upload_image.remove_button');
return (
<div>
<p><img src={app.forum.attribute(this.props.name+'Url')} alt=""/></p>
<p>{super.view()}</p>
</div>
);
} else {
this.props.onclick = this.upload.bind(this);
this.props.children = app.translator.trans('core.admin.upload_image.upload_button');
}
return super.view();
}
/**
* Prompt the user to upload an image.
*/
upload() {
if (this.loading) return;
const $input = $('<input type="file">');
$input.appendTo('body').hide().click().on('change', e => {
const data = new FormData();
data.append(this.props.name, $(e.target)[0].files[0]);
this.loading = true;
m.redraw();
app.request({
method: 'POST',
url: this.resourceUrl(),
serialize: raw => raw,
data
}).then(
this.success.bind(this),
this.failure.bind(this)
);
});
}
/**
* Remove the logo.
*/
remove() {
this.loading = true;
m.redraw();
app.request({
method: 'DELETE',
url: this.resourceUrl()
}).then(
this.success.bind(this),
this.failure.bind(this)
);
}
resourceUrl() {
return app.forum.attribute('apiUrl') + '/' + this.props.name;
}
/**
* After a successful upload/removal, reload the page.
*
* @param {Object} response
* @protected
*/
success(response) {
window.location.reload();
}
/**
* If upload/removal fails, stop loading.
*
* @param {Object} response
* @protected
*/
failure(response) {
this.loading = false;
m.redraw();
}
}

@ -162,7 +162,7 @@ export default class AvatarEditor extends Component {
* @param {Object} response
* @protected
*/
failure() {
failure(response) {
this.loading = false;
m.redraw();
}

@ -28,7 +28,9 @@
margin-bottom: 15px;
}
.EditCustomCssModal textarea {
font-family: monospace;
line-height: 1;
.EditCustomCssModal, .EditCustomHeaderModal {
textarea {
font-family: monospace;
line-height: 1;
}
}

@ -168,6 +168,10 @@
padding: 0;
list-style: none;
}
.Header-logo {
max-height: 30px;
vertical-align: middle;
}
// On phones, the header is displayed inside of the drawer. We lay its
// contents out vertically.

@ -92,7 +92,7 @@
}
.LoadingIndicator {
color: inherit;
margin: 0 -10px 0 -15px;
margin: 0 -5px 0 -15px;
}
&.loading {
.Button-label {

@ -361,6 +361,34 @@ class ApiServiceProvider extends AbstractServiceProvider
$route->toController(Controller\SetPermissionController::class)
);
// Upload a logo
$routes->post(
'/logo',
'logo',
$route->toController(Controller\UploadLogoController::class)
);
// Remove the logo
$routes->delete(
'/logo',
'logo.delete',
$route->toController(Controller\DeleteLogoController::class)
);
// Upload a favicon
$routes->post(
'/favicon',
'favicon',
$route->toController(Controller\UploadFaviconController::class)
);
// Remove the favicon
$routes->delete(
'/favicon',
'favicon.delete',
$route->toController(Controller\DeleteFaviconController::class)
);
$this->app->make('events')->fire(
new ConfigureApiRoutes($routes, $route)
);

@ -0,0 +1,63 @@
<?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\Api\Controller;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Foundation\Application;
use Flarum\Settings\SettingsRepositoryInterface;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\EmptyResponse;
class DeleteFaviconController extends AbstractDeleteController
{
use AssertPermissionTrait;
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
/**
* @var Application
*/
protected $app;
/**
* @param SettingsRepositoryInterface $settings
*/
public function __construct(SettingsRepositoryInterface $settings, Application $app)
{
$this->settings = $settings;
$this->app = $app;
}
/**
* {@inheritdoc}
*/
protected function delete(ServerRequestInterface $request)
{
$this->assertAdmin($request->getAttribute('actor'));
$path = $this->settings->get('favicon_path');
$this->settings->set('favicon_path', null);
$uploadDir = new Filesystem(new Local($this->app->publicPath().'/assets'));
if ($uploadDir->has($path)) {
$uploadDir->delete($path);
}
return new EmptyResponse(204);
}
}

@ -0,0 +1,63 @@
<?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\Api\Controller;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Foundation\Application;
use Flarum\Settings\SettingsRepositoryInterface;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\EmptyResponse;
class DeleteLogoController extends AbstractDeleteController
{
use AssertPermissionTrait;
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
/**
* @var Application
*/
protected $app;
/**
* @param SettingsRepositoryInterface $settings
*/
public function __construct(SettingsRepositoryInterface $settings, Application $app)
{
$this->settings = $settings;
$this->app = $app;
}
/**
* {@inheritdoc}
*/
protected function delete(ServerRequestInterface $request)
{
$this->assertAdmin($request->getAttribute('actor'));
$path = $this->settings->get('logo_path');
$this->settings->set('logo_path', null);
$uploadDir = new Filesystem(new Local($this->app->publicPath().'/assets'));
if ($uploadDir->has($path)) {
$uploadDir->delete($path);
}
return new EmptyResponse(204);
}
}

@ -0,0 +1,90 @@
<?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\Api\Controller;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Foundation\Application;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Support\Str;
use Intervention\Image\ImageManager;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;
use League\Flysystem\MountManager;
use Psr\Http\Message\ServerRequestInterface;
use Tobscure\JsonApi\Document;
class UploadFaviconController extends ShowForumController
{
use AssertPermissionTrait;
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
/**
* @var Application
*/
protected $app;
/**
* @param SettingsRepositoryInterface $settings
*/
public function __construct(SettingsRepositoryInterface $settings, Application $app)
{
$this->settings = $settings;
$this->app = $app;
}
/**
* {@inheritdoc}
*/
public function data(ServerRequestInterface $request, Document $document)
{
$this->assertAdmin($request->getAttribute('actor'));
$file = array_get($request->getUploadedFiles(), 'favicon');
$tmpFile = tempnam($this->app->storagePath().'/tmp', 'favicon');
$file->moveTo($tmpFile);
$extension = pathinfo($file->getClientFilename(), PATHINFO_EXTENSION);
if ($extension !== 'ico') {
$manager = new ImageManager;
$encodedImage = $manager->make($tmpFile)->resize(64, 64, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->encode('png');
file_put_contents($tmpFile, $encodedImage);
$extension = 'png';
}
$mount = new MountManager([
'source' => new Filesystem(new Local(pathinfo($tmpFile, PATHINFO_DIRNAME))),
'target' => new Filesystem(new Local($this->app->publicPath().'/assets')),
]);
if (($path = $this->settings->get('favicon_path')) && $mount->has($file = "target://$path")) {
$mount->delete($file);
}
$uploadName = 'favicon-'.Str::lower(Str::quickRandom(8)).'.'.$extension;
$mount->move('source://'.pathinfo($tmpFile, PATHINFO_BASENAME), "target://$uploadName");
$this->settings->set('favicon_path', $uploadName);
return parent::data($request, $document);
}
}

@ -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\Api\Controller;
use Flarum\Core\Access\AssertPermissionTrait;
use Flarum\Foundation\Application;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Support\Str;
use Intervention\Image\ImageManager;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;
use League\Flysystem\MountManager;
use Psr\Http\Message\ServerRequestInterface;
use Tobscure\JsonApi\Document;
class UploadLogoController extends ShowForumController
{
use AssertPermissionTrait;
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
/**
* @var Application
*/
protected $app;
/**
* @param SettingsRepositoryInterface $settings
*/
public function __construct(SettingsRepositoryInterface $settings, Application $app)
{
$this->settings = $settings;
$this->app = $app;
}
/**
* {@inheritdoc}
*/
public function data(ServerRequestInterface $request, Document $document)
{
$this->assertAdmin($request->getAttribute('actor'));
$file = array_get($request->getUploadedFiles(), 'logo');
$tmpFile = tempnam($this->app->storagePath().'/tmp', 'logo');
$file->moveTo($tmpFile);
$manager = new ImageManager;
$encodedImage = $manager->make($tmpFile)->heighten(60, function ($constraint) {
$constraint->upsize();
})->encode('png');
file_put_contents($tmpFile, $encodedImage);
$mount = new MountManager([
'source' => new Filesystem(new Local(pathinfo($tmpFile, PATHINFO_DIRNAME))),
'target' => new Filesystem(new Local($this->app->publicPath().'/assets')),
]);
if (($path = $this->settings->get('logo_path')) && $mount->has($file = "target://$path")) {
$mount->delete($file);
}
$uploadName = 'logo-'.Str::lower(Str::quickRandom(8)).'.png';
$mount->move('source://'.pathinfo($tmpFile, PATHINFO_BASENAME), "target://$uploadName");
$this->settings->set('logo_path', $uploadName);
return parent::data($request, $document);
}
}

@ -11,6 +11,7 @@
namespace Flarum\Api\Serializer;
use Flarum\Core\Access\Gate;
use Flarum\Forum\UrlGenerator;
use Flarum\Foundation\Application;
use Flarum\Settings\SettingsRepositoryInterface;
@ -21,11 +22,6 @@ class ForumSerializer extends AbstractSerializer
*/
protected $type = 'forums';
/**
* @var Gate
*/
protected $gate;
/**
* @var Application
*/
@ -37,15 +33,20 @@ class ForumSerializer extends AbstractSerializer
protected $settings;
/**
* @param Gate $gate
* @var UrlGenerator
*/
protected $url;
/**
* @param Application $app
* @param SettingsRepositoryInterface $settings
* @param UrlGenerator $url
*/
public function __construct(Gate $gate, Application $app, SettingsRepositoryInterface $settings)
public function __construct(Application $app, SettingsRepositoryInterface $settings, UrlGenerator $url)
{
$this->gate = $gate;
$this->app = $app;
$this->settings = $settings;
$this->url = $url;
}
/**
@ -61,25 +62,27 @@ class ForumSerializer extends AbstractSerializer
*/
protected function getDefaultAttributes($model)
{
$gate = $this->gate->forUser($this->actor);
$attributes = [
'title' => $this->settings->get('forum_title'),
'description' => $this->settings->get('forum_description'),
'baseUrl' => $url = $this->app->url(),
'basePath' => parse_url($url, PHP_URL_PATH) ?: '',
'debug' => $this->app->inDebugMode(),
'apiUrl' => $this->app->url('api'),
'welcomeTitle' => $this->settings->get('welcome_title'),
'welcomeMessage' => $this->settings->get('welcome_message'),
'themePrimaryColor' => $this->settings->get('theme_primary_color'),
'allowSignUp' => (bool) $this->settings->get('allow_sign_up'),
'defaultRoute' => $this->settings->get('default_route'),
'canViewDiscussions' => $gate->allows('viewDiscussions'),
'canStartDiscussion' => $gate->allows('startDiscussion')
'title' => $this->settings->get('forum_title'),
'description' => $this->settings->get('forum_description'),
'baseUrl' => $url = $this->app->url(),
'basePath' => parse_url($url, PHP_URL_PATH) ?: '',
'debug' => $this->app->inDebugMode(),
'apiUrl' => $this->app->url('api'),
'welcomeTitle' => $this->settings->get('welcome_title'),
'welcomeMessage' => $this->settings->get('welcome_message'),
'themePrimaryColor' => $this->settings->get('theme_primary_color'),
'themeSecondaryColor' => $this->settings->get('theme_secondary_color'),
'logoUrl' => $this->getLogoUrl(),
'faviconUrl' => $this->getFaviconUrl(),
'headerHtml' => $this->settings->get('custom_header'),
'allowSignUp' => (bool) $this->settings->get('allow_sign_up'),
'defaultRoute' => $this->settings->get('default_route'),
'canViewDiscussions' => $this->actor->can('viewDiscussions'),
'canStartDiscussion' => $this->actor->can('startDiscussion')
];
if ($gate->allows('administrate')) {
if ($this->actor->can('administrate')) {
$attributes['adminUrl'] = $this->app->url('admin');
$attributes['version'] = $this->app->version();
}
@ -94,4 +97,24 @@ class ForumSerializer extends AbstractSerializer
{
return $this->hasMany($model, 'Flarum\Api\Serializer\GroupSerializer');
}
/**
* @return null|string
*/
protected function getLogoUrl()
{
$logoPath = $this->settings->get('logo_path');
return $logoPath ? $this->url->toPath('assets/'.$logoPath) : null;
}
/**
* @return null|string
*/
protected function getFaviconUrl()
{
$faviconPath = $this->settings->get('favicon_path');
return $faviconPath ? $this->url->toPath('assets/'.$faviconPath) : null;
}
}

@ -9,7 +9,12 @@
<div class="container">
<h1 class="Header-title">
<a href="{{ array_get($forum, 'attributes.baseUrl') }}">
{{ array_get($forum, 'attributes.title') }}
<?php $title = array_get($forum, 'attributes.title'); ?>
@if ($logo = array_get($forum, 'attributes.logoUrl'))
<img src="{{ $logo }}" alt="{{ $title }}" class="Header-logo">
@else
{{ $title }}
@endif
</a>
</h1>
<div id="header-primary" class="Header-primary"></div>

@ -11,10 +11,16 @@
<link rel="stylesheet" href="{{ $url }}">
@endforeach
@if ($faviconUrl = array_get($forum, 'attributes.faviconUrl'))
<link href="{{ $faviconUrl }}" rel="shortcut icon">
@endif
{!! $head !!}
</head>
<body>
{!! array_get($forum, 'attributes.headerHtml') !!}
{!! $layout !!}
<div id="modal"></div>

@ -1,12 +1,12 @@
<?php
/**
* Forum WebApp Template
* Forum Web App Template
*
* NOTE: You shouldn't edit this file directly. Your changes will be overwritten
* when you update Flarum. See flarum.org/docs/templates to learn how to
* customize your forum's layout.
*
* Flarum's JavaScript client mounts various components into key elements in
* Flarum's JavaScript app mounts various components into key elements in
* this template. They are distinguished by their ID attributes:
*
* - #app
@ -32,7 +32,12 @@
<div class="container">
<h1 class="Header-title">
<a href="{{ array_get($forum, 'attributes.baseUrl') }}" id="home-link">
{{ array_get($forum, 'attributes.title') }}
<?php $title = array_get($forum, 'attributes.title'); ?>
@if ($logo = array_get($forum, 'attributes.logoUrl'))
<img src="{{ $logo }}" alt="{{ $title }}" class="Header-logo">
@else
{{ $title }}
@endif
</a>
</h1>
<div id="header-primary" class="Header-primary"></div>