commit
61fcd5fd76
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -1,3 +1,16 @@
|
|||
* **Build 125** (2014-07-24)
|
||||
- Theme support added.
|
||||
- Added new Theme picker to the backend via Settings > Front-end theme
|
||||
- New shorthand method for `$this->getClassExtension('Backend.Behaviors.FormController')` becomes `$this->asExtension('FormController')`.
|
||||
- Buttons inside a popup support new `data-popup-load-indicator` attribute.
|
||||
- Added a new config item to disable core updates completely (see config cms.disableCoreUpdates).
|
||||
- Added a unique alternate favicon to the Back-end area.
|
||||
|
||||
* **Build 124** (2014-07-17)
|
||||
- Improvements to Twig functions and filters.
|
||||
- URL, HTML and Form helpers are now available in Twig.
|
||||
- The DataGrid form widget has been moved to a standard widget called Grid.
|
||||
|
||||
* **Build 122** (2014-07-15)
|
||||
- Restyled the CMS tabs
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,20 @@ return array(
|
|||
*/
|
||||
'disablePlugins' => [],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Prevents application updates
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If using composer or git to download updates to the core files, set this
|
||||
| value to 'true' to prevent the update gateway from trying to download
|
||||
| these files again as part of the application update process. Plugins
|
||||
| and themes will still be downloaded.
|
||||
|
|
||||
*/
|
||||
|
||||
'disableCoreUpdates' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Back-end URI prefix
|
||||
|
|
|
|||
|
|
@ -48,7 +48,10 @@ Log::useFiles(storage_path().'/logs/system.log');
|
|||
|
||||
App::error(function(Exception $exception, $code)
|
||||
{
|
||||
Log::error($exception);
|
||||
/*
|
||||
* October uses a custom error handler, see
|
||||
* System\Classes\ErrorHandler::handleException
|
||||
*/
|
||||
});
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ class ServiceProvider extends ModuleServiceProvider
|
|||
'category' => 'My Settings',
|
||||
'icon' => 'icon-code',
|
||||
'url' => Backend::URL('backend/editorpreferences'),
|
||||
'sort' => 200,
|
||||
'order' => 600,
|
||||
'context' => 'mysettings'
|
||||
],
|
||||
'backend_preferences' => [
|
||||
|
|
@ -91,7 +91,7 @@ class ServiceProvider extends ModuleServiceProvider
|
|||
'category' => 'My Settings',
|
||||
'icon' => 'icon-laptop',
|
||||
'class' => 'Backend\Models\BackendPreferences',
|
||||
'sort' => 200,
|
||||
'order' => 500,
|
||||
'context' => 'mysettings'
|
||||
],
|
||||
'myaccount' => [
|
||||
|
|
@ -100,7 +100,7 @@ class ServiceProvider extends ModuleServiceProvider
|
|||
'category' => 'My Settings',
|
||||
'icon' => 'icon-user',
|
||||
'url' => Backend::URL('backend/users/myaccount'),
|
||||
'sort' => 200,
|
||||
'order' => 400,
|
||||
'context' => 'mysettings'
|
||||
],
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -7271,6 +7271,7 @@ body {
|
|||
/* The html and body elements cannot have any padding or margin. */
|
||||
}
|
||||
body {
|
||||
webkit-font-smoothing: antialiased;
|
||||
background: #fafafa;
|
||||
}
|
||||
#layout-canvas {
|
||||
|
|
@ -7323,6 +7324,9 @@ body {
|
|||
.layout > .layout-row > .layout-cell.min-size {
|
||||
width: 0;
|
||||
}
|
||||
.layout > .layout-row > .layout-cell.min-height {
|
||||
height: 0;
|
||||
}
|
||||
.layout > .layout-row > .layout-cell.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -7363,6 +7367,9 @@ body {
|
|||
.layout > .layout-row > .layout-cell.min-size {
|
||||
width: 0;
|
||||
}
|
||||
.layout > .layout-row > .layout-cell.min-height {
|
||||
height: 0;
|
||||
}
|
||||
.layout > .layout-row > .layout-cell.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -7411,6 +7418,9 @@ body {
|
|||
.layout > .layout-cell.min-size {
|
||||
width: 0;
|
||||
}
|
||||
.layout > .layout-cell.min-height {
|
||||
height: 0;
|
||||
}
|
||||
.layout > .layout-cell.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -10506,6 +10516,19 @@ html.cssanimations .cursor-loading-indicator.hide {
|
|||
.dropdown-menu .dropdown-container > ul li.divider {
|
||||
margin: 0;
|
||||
}
|
||||
.dropdown-menu.pull-right .dropdown-container > ul:after {
|
||||
left: auto;
|
||||
right: 15px;
|
||||
}
|
||||
.dropdown-menu.pull-right .dropdown-container > ul:before {
|
||||
left: auto;
|
||||
right: 14px;
|
||||
}
|
||||
.dropdown-menu.pull-right .dropdown-container > ul li.first-item a:hover:after,
|
||||
.dropdown-menu.pull-right .dropdown-container > ul li.first-item a:focus:after {
|
||||
left: auto;
|
||||
right: 15px;
|
||||
}
|
||||
.dropdown-menu.top .dropdown-container > ul:after {
|
||||
content: '';
|
||||
display: block;
|
||||
|
|
@ -10944,6 +10967,7 @@ body.dropdown-open .dropdown-overlay {
|
|||
.control-tabs.content-tabs > .tab-content > .tab-pane {
|
||||
padding-top: 0;
|
||||
}
|
||||
.control-tabs.content-tabs > .tab-content > .tab-pane div.list-header,
|
||||
.control-tabs.content-tabs > .tab-content > .tab-pane div.padded-container,
|
||||
.control-tabs.content-tabs > .tab-content > .tab-pane div.toolbar-widget {
|
||||
background: #ffffff;
|
||||
|
|
@ -11518,9 +11542,13 @@ ul.status-list li span.status.info {
|
|||
.control-breadcrumb li:last-child:after {
|
||||
content: '';
|
||||
}
|
||||
.control-breadcrumb + .content-tabs {
|
||||
.control-breadcrumb + .content-tabs,
|
||||
.control-breadcrumb + .padded-container {
|
||||
margin-top: -20px;
|
||||
}
|
||||
.control-breadcrumb.no-bottom-margin {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
body.slim-container .control-breadcrumb {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
|
|
@ -101,7 +101,7 @@
|
|||
|
||||
// LOADINDICATOR DATA-API
|
||||
// ==============
|
||||
|
||||
|
||||
$(document)
|
||||
.on('ajaxPromise', '[data-load-indicator]', function() {
|
||||
var
|
||||
|
|
|
|||
|
|
@ -38,6 +38,11 @@
|
|||
this.$modal = this.$target.modal({ show: false, backdrop: false, keyboard: this.options.keyboard })
|
||||
this.isAjax = this.options.handler || this.options.ajax
|
||||
|
||||
/*
|
||||
* Duplicate the popup reference on the .control-popup container
|
||||
*/
|
||||
this.$target.data('oc.popup', this)
|
||||
|
||||
/*
|
||||
* Hook in to BS Modal events
|
||||
*/
|
||||
|
|
@ -50,7 +55,7 @@
|
|||
setTimeout(function() { self.$content.empty() }, 500)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
this.$modal.on('show.bs.modal', function(){
|
||||
self.isOpen = true
|
||||
self.setBackdrop(true)
|
||||
|
|
@ -156,7 +161,7 @@
|
|||
|
||||
this.$backdrop.addClass('in')
|
||||
|
||||
this.$backdrop.append($('<div class="popup-loading-indicator modal-content" />'))
|
||||
this.$backdrop.append($('<div class="modal-content popup-loading-indicator" />'))
|
||||
}
|
||||
else if (!val && this.$backdrop) {
|
||||
this.$backdrop.remove()
|
||||
|
|
@ -168,15 +173,24 @@
|
|||
if (!this.$backdrop)
|
||||
return;
|
||||
|
||||
var self = this;
|
||||
var self = this
|
||||
if (val) {
|
||||
setTimeout(function(){ self.$backdrop.addClass('loading'); }, 100)
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.$backdrop.removeClass('loading');
|
||||
}
|
||||
}
|
||||
|
||||
Popup.prototype.hideLoading = function(val) {
|
||||
this.setLoading(false)
|
||||
|
||||
// Wait for animations to complete
|
||||
var self = this
|
||||
setTimeout(function() { self.setBackdrop(false) }, 250)
|
||||
setTimeout(function() { self.hide() }, 500)
|
||||
}
|
||||
|
||||
Popup.prototype.triggerEvent = function(eventName, params) {
|
||||
if (!params)
|
||||
params = [this.$el, this.$modal]
|
||||
|
|
@ -265,4 +279,15 @@
|
|||
return false
|
||||
});
|
||||
|
||||
$(document)
|
||||
.on('ajaxPromise', '[data-popup-load-indicator]', function() {
|
||||
$(this).closest('.control-popup').removeClass('in').popup('setLoading', true)
|
||||
})
|
||||
.on('ajaxFail', '[data-popup-load-indicator]', function() {
|
||||
$(this).closest('.control-popup').addClass('in').popup('setLoading', false)
|
||||
})
|
||||
.on('ajaxDone', '[data-popup-load-indicator]', function() {
|
||||
$(this).closest('.control-popup').popup('hideLoading')
|
||||
})
|
||||
|
||||
}(window.jQuery);
|
||||
|
|
|
|||
|
|
@ -8,11 +8,14 @@
|
|||
* Supported data attributes:
|
||||
* - data-trigger-type, values: display, hide, enable, disable
|
||||
* - data-trigger: a CSS selector for elements that trigger the action (checkboxes)
|
||||
* - data-trigger-condition, values: checked (more conditions to add later) - determines the condition the elements
|
||||
* specified in the data-trigger should satisfy in order the condition to be considered as "true".
|
||||
* - data-trigger-condition, values:
|
||||
* - checked: determines the condition the elements specified in the data-trigger
|
||||
* should satisfy in order the condition to be considered as "true".
|
||||
* - value[somevalue]: determines if the value of data-trigger equals the specified value (somevalue)
|
||||
* the condition is considered "true".
|
||||
*
|
||||
* Example: <input type="button" class="btn disabled"
|
||||
* data-trigger-type="enable"
|
||||
* data-trigger-type="enable"
|
||||
* data-trigger="#cblist input[type=checkbox]"
|
||||
* data-trigger-condition="checked" ... >
|
||||
*
|
||||
|
|
@ -28,7 +31,7 @@
|
|||
var TriggerOn = function (element, options) {
|
||||
|
||||
var $el = this.$el = $(element);
|
||||
|
||||
|
||||
this.options = options || {};
|
||||
|
||||
if (this.options.triggerCondition === false)
|
||||
|
|
@ -40,10 +43,20 @@
|
|||
if (this.options.triggerType === false)
|
||||
throw new Error('Trigger type is not specified.')
|
||||
|
||||
if (this.options.triggerCondition == 'checked')
|
||||
this.triggerCondition = this.options.triggerCondition
|
||||
|
||||
if (this.options.triggerCondition.indexOf('value') == 0) {
|
||||
var match = this.options.triggerCondition.match(/[^[\]]+(?=])/g)
|
||||
if (match) {
|
||||
this.triggerConditionValue = match
|
||||
this.triggerCondition = 'value'
|
||||
}
|
||||
}
|
||||
|
||||
if (this.triggerCondition == 'checked' || this.triggerCondition == 'value')
|
||||
$(document).on('change', this.options.trigger, $.proxy(this.onConditionChanged, this))
|
||||
|
||||
var self = this;
|
||||
var self = this
|
||||
$el.on('oc.triggerOn.update', function(e){
|
||||
e.stopPropagation()
|
||||
self.onConditionChanged()
|
||||
|
|
@ -53,8 +66,12 @@
|
|||
}
|
||||
|
||||
TriggerOn.prototype.onConditionChanged = function() {
|
||||
if (this.options.triggerCondition == 'checked')
|
||||
this.updateTarget($(this.options.trigger + ':checked').length > 0);
|
||||
if (this.triggerCondition == 'checked') {
|
||||
this.updateTarget($(this.options.trigger + ':checked').length > 0)
|
||||
}
|
||||
else if (this.triggerCondition == 'value') {
|
||||
this.updateTarget($(this.options.trigger).val() == this.triggerConditionValue)
|
||||
}
|
||||
}
|
||||
|
||||
TriggerOn.prototype.updateTarget = function(status) {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
margin: -20px -20px 20px -20px;
|
||||
background-color: @color-breadcrumb-background;
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
|
@ -40,9 +40,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
+ .content-tabs {
|
||||
+ .content-tabs, + .padded-container {
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
&.no-bottom-margin {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
body.slim-container {
|
||||
|
|
@ -50,4 +54,4 @@ body.slim-container {
|
|||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,8 +91,29 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.pull-right {
|
||||
.dropdown-container > ul {
|
||||
&:after {
|
||||
left: auto;
|
||||
right: 15px;
|
||||
}
|
||||
&:before {
|
||||
left: auto;
|
||||
right: 14px;
|
||||
}
|
||||
li.first-item a {
|
||||
&:hover, &:focus {
|
||||
&:after {
|
||||
left: auto;
|
||||
right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.top {
|
||||
.dropdown-container {
|
||||
.dropdown-container {
|
||||
> ul {
|
||||
&:after {
|
||||
.triangle(down, 15px, 8px, @dropdown-bg);
|
||||
|
|
@ -110,7 +131,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.touch .dropdown-menu .dropdown-container > ul li {
|
||||
.touch .dropdown-menu .dropdown-container > ul li {
|
||||
a:hover {
|
||||
color: @dropdown-link-color;
|
||||
background: white;
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
background-color: rgba(0,0,0,.2);
|
||||
.opacity(1);
|
||||
|
||||
.popup-loading-indicator {
|
||||
.popup-loading-indicator {
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
|
|
|
|||
|
|
@ -293,6 +293,7 @@
|
|||
> .tab-content > .tab-pane {
|
||||
padding-top: 0;
|
||||
|
||||
div.list-header,
|
||||
div.padded-container,
|
||||
div.toolbar-widget {
|
||||
background: @color-tab-content-active-bg;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ body {
|
|||
}
|
||||
|
||||
body {
|
||||
webkit-font-smoothing: antialiased;
|
||||
background: @color-body-bg;
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +69,10 @@ body {
|
|||
width: 0;
|
||||
}
|
||||
|
||||
&.min-height {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
&.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,9 +70,6 @@ class UserPreferencesModel extends SettingsModel
|
|||
*/
|
||||
public function beforeModelSave()
|
||||
{
|
||||
// Purge the field values from the attributes
|
||||
$this->model->attributes = array_diff_key($this->model->attributes, $this->fieldValues);
|
||||
|
||||
$preferences = UserPreferences::forUser();
|
||||
list($namespace, $group, $item) = $preferences->parseKey($this->recordCode);
|
||||
$this->model->item = $item;
|
||||
|
|
|
|||
|
|
@ -379,7 +379,7 @@ class Controller extends Extendable
|
|||
return Response::make(Lang::get('backend::lang.model.mass_assignment_failed', ['attribute' => $ex->getMessage()]), 500);
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
return Response::make($ex->getMessage(), 500);
|
||||
return Response::make(sprintf('"%s" on line %s of %s', $ex->getMessage(), $ex->getLine(), $ex->getFile()), 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -494,8 +494,11 @@ class Controller extends Extendable
|
|||
* @param array $params Extra parameters
|
||||
* @return string
|
||||
*/
|
||||
public function makeHintPartial($name, $partial, array $params = [])
|
||||
public function makeHintPartial($name, $partial = null, array $params = [])
|
||||
{
|
||||
if (!$partial)
|
||||
$partial = $name;
|
||||
|
||||
return $this->makeLayoutPartial('hint', [
|
||||
'hintName' => $name,
|
||||
'hintPartial' => $partial,
|
||||
|
|
|
|||
|
|
@ -254,11 +254,16 @@ class FormField
|
|||
|
||||
/**
|
||||
* Returns a value suitable for the field name property.
|
||||
* @param string $arrayName Specify a custom array name
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
public function getName($arrayName = null)
|
||||
{
|
||||
if ($this->arrayName)
|
||||
return $this->arrayName.'['.implode('][', Str::evalHtmlArray($this->columnName)).']';
|
||||
if ($arrayName === null)
|
||||
$arrayName = $this->arrayName;
|
||||
|
||||
if ($arrayName)
|
||||
return $arrayName.'['.implode('][', Str::evalHtmlArray($this->columnName)).']';
|
||||
else
|
||||
return $this->columnName;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=0">
|
||||
<link rel="icon" type="image/png" href="<?= URL::asset('modules/backend/assets/images/favicon.png') ?>" />
|
||||
<title data-title-template="<?= empty($this->pageTitleTemplate) ? '%s | October CMS' : e($this->pageTitleTemplate) ?>">
|
||||
<?= $this->pageTitle ?> | October CMS
|
||||
</title>
|
||||
|
|
|
|||
|
|
@ -51,15 +51,25 @@ class Grid extends WidgetBase
|
|||
protected $disableToolbar = false;
|
||||
|
||||
/**
|
||||
* @var mixed Array of data, or callable for data source.
|
||||
* @var array Provided data set, cannot use with dataLocker or useDataSource.
|
||||
*/
|
||||
protected $dataSource;
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @var string HTML element that can [re]store the grid data.
|
||||
* @var string HTML element that can [re]store the grid data, cannot use with data or useDataSource.
|
||||
*/
|
||||
protected $dataLocker;
|
||||
|
||||
/**
|
||||
* @var boolean Get data from AJAX callback (onDataSource), cannot use with data or dataLocker.
|
||||
*/
|
||||
protected $useDataSource = false;
|
||||
|
||||
/**
|
||||
* @var boolean Sends an AJAX callback (onDataChanged) any time a field is changed.
|
||||
*/
|
||||
protected $monitorChanges = true;
|
||||
|
||||
/**
|
||||
* Initialize the widget, called by the constructor and free from its parameters.
|
||||
*/
|
||||
|
|
@ -70,8 +80,10 @@ class Grid extends WidgetBase
|
|||
$this->allowInsert = $this->getConfig('allowInsert', $this->allowInsert);
|
||||
$this->allowRemove = $this->getConfig('allowRemove', $this->allowRemove);
|
||||
$this->disableToolbar = $this->getConfig('disableToolbar', $this->disableToolbar);
|
||||
$this->data = $this->getConfig('data', $this->data);
|
||||
$this->dataLocker = $this->getConfig('dataLocker', $this->dataLocker);
|
||||
$this->dataSource = $this->getConfig('dataSource', $this->dataSource);
|
||||
$this->useDataSource = $this->getConfig('useDataSource', $this->useDataSource);
|
||||
$this->monitorChanges = $this->getConfig('monitorChanges', $this->monitorChanges);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -97,7 +109,10 @@ class Grid extends WidgetBase
|
|||
$this->vars['allowInsert'] = $this->allowInsert;
|
||||
$this->vars['allowRemove'] = $this->allowRemove;
|
||||
$this->vars['disableToolbar'] = $this->disableToolbar;
|
||||
$this->vars['data'] = $this->data;
|
||||
$this->vars['dataLocker'] = $this->dataLocker;
|
||||
$this->vars['useDataSource'] = $this->useDataSource;
|
||||
$this->vars['monitorChanges'] = $this->monitorChanges;
|
||||
}
|
||||
|
||||
protected function makeToolbarWidget()
|
||||
|
|
@ -129,12 +144,32 @@ class Grid extends WidgetBase
|
|||
return ['result' => $result];
|
||||
}
|
||||
|
||||
public function onDataSource()
|
||||
public function onDataChanged()
|
||||
{
|
||||
if ($this->dataLocker)
|
||||
if (!$this->monitorChanges)
|
||||
return;
|
||||
|
||||
$result = $this->dataSource;
|
||||
/*
|
||||
* Changes array, each array item will contain:
|
||||
* ['rowData' => [...], 'keyName' => 'changedColumn', 'oldValue' => 'was', 'newValue' => 'is']
|
||||
*/
|
||||
$changes = post('changes');
|
||||
$this->fireEvent('grid.dataChanged', [$changes]);
|
||||
}
|
||||
|
||||
public function onDataSource()
|
||||
{
|
||||
if (!$this->useDataSource)
|
||||
return;
|
||||
|
||||
$result = [];
|
||||
|
||||
if ($_result = $this->fireEvent('grid.dataSource', [], true))
|
||||
$result = $_result;
|
||||
|
||||
if (!is_array($result))
|
||||
$result = [];
|
||||
|
||||
return ['result' => $result];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@
|
|||
|
||||
}
|
||||
|
||||
|
||||
// FORM WIDGET PLUGIN DEFINITION
|
||||
// ============================
|
||||
|
||||
|
|
@ -97,7 +96,7 @@
|
|||
|
||||
// FORM WIDGET DATA-API
|
||||
// ==============
|
||||
|
||||
|
||||
$(document).render(function(){
|
||||
$('[data-control="formwidget"]').formWidget();
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
class="form-control"
|
||||
autocomplete="off"
|
||||
maxlength="255"
|
||||
pattern="\d+"
|
||||
<?= HTML::attributes($field->attributes) ?>
|
||||
/>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,16 @@
|
|||
*
|
||||
* Data attributes:
|
||||
* - data-control="datagrid" - enables the plugin on an element
|
||||
* - data-option="value" - an option with a value
|
||||
* - data-allow-remove="true" - allow rows to be removed
|
||||
* - data-autocomplete-handler="onAutocomplete" - AJAX handler for autocomplete values
|
||||
* - data-data-locker="input#locker" - Input element to store and restore grid data as JSON
|
||||
* - data-source-handler="onGetData" - AJAX handler for obtaining grid data
|
||||
*
|
||||
* JavaScript API:
|
||||
* $('a#someElement').dataGrid({ option: 'value' })
|
||||
* $('div#someElement').dataGrid({ option: 'value' })
|
||||
*
|
||||
* Dependences:
|
||||
* - Some other plugin (filename.js)
|
||||
* Dependences:
|
||||
* - Handsontable (handsontable.js)
|
||||
*/
|
||||
|
||||
+function ($) { "use strict";
|
||||
|
|
@ -22,13 +25,16 @@
|
|||
this.options = options
|
||||
this.$el = $(element)
|
||||
|
||||
this.columnHeaders = this.options.columnHeaders
|
||||
this.staticWidths = this.options.columnWidths
|
||||
this.gridInstance = null
|
||||
this.columns = validateColumns(this.options.columns)
|
||||
|
||||
// Init
|
||||
var handsontableOptions = {
|
||||
colHeaders: this.options.columnHeaders,
|
||||
colHeaders: function(columnIndex) {
|
||||
return self.columnHeaders[columnIndex]
|
||||
},
|
||||
colWidths: function(columnIndex) {
|
||||
return self.staticWidths[columnIndex]
|
||||
},
|
||||
|
|
@ -40,6 +46,19 @@
|
|||
// rowHeaders: false,
|
||||
// manualColumnMove: true,
|
||||
// manualRowMove: true,
|
||||
afterChange: function(changes, source) {
|
||||
if (source === 'loadData')
|
||||
return
|
||||
|
||||
/*
|
||||
* changes - is a 2D array containing information about each of the edited cells
|
||||
* [ [row, prop, oldVal, newVal], ... ].
|
||||
*
|
||||
* source - is one of the strings: "alter", "empty", "edit", "populateFromArray",
|
||||
* "loadData", "autofill", "paste".
|
||||
*/
|
||||
self.$el.trigger('datagrid.change', [changes, source])
|
||||
},
|
||||
fillHandle: false,
|
||||
multiSelect: false,
|
||||
removeRowPlugin: this.options.allowRemove
|
||||
|
|
@ -48,15 +67,24 @@
|
|||
if (this.options.autoInsertRows)
|
||||
handsontableOptions.minSpareRows = 1
|
||||
|
||||
if (this.options.dataLocker) {
|
||||
/*
|
||||
* Data provided
|
||||
*/
|
||||
if (this.options.data) {
|
||||
handsontableOptions.data = this.options.data
|
||||
}
|
||||
/*
|
||||
* Data from a data locker
|
||||
*/
|
||||
else if (this.options.dataLocker) {
|
||||
/*
|
||||
* Event to update the data locker
|
||||
*/
|
||||
this.$dataLocker = $(this.options.dataLocker)
|
||||
handsontableOptions.afterChange = function(changes, source) {
|
||||
self.$el.on('datagrid.change', function(event, eventData) {
|
||||
if (!self.gridInstance) return
|
||||
self.$dataLocker.val(JSON.stringify(self.getData()))
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* Populate existing data
|
||||
|
|
@ -68,14 +96,44 @@
|
|||
delete handsontableOptions.data
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Data from an AJAX data source
|
||||
*/
|
||||
else if (this.options.sourceHandler) {
|
||||
$.request(self.options.sourceHandler, {
|
||||
success: function(data, textStatus, jqXHR){
|
||||
self.gridInstance.loadData(data.result)
|
||||
self.refreshDataSource()
|
||||
}
|
||||
|
||||
/*
|
||||
* Monitor for data changes
|
||||
*/
|
||||
if (this.options.changeHandler) {
|
||||
self.$el.on('datagrid.change', function(event, changes, source) {
|
||||
var changeData = [];
|
||||
|
||||
$.each(changes, function(index, change){
|
||||
var changeObj = {}
|
||||
changeObj.keyName = change[1]
|
||||
changeObj.oldValue = change[2]
|
||||
changeObj.newValue = change[3]
|
||||
|
||||
if (changeObj.oldValue == changeObj.newValue)
|
||||
return; // continue
|
||||
|
||||
changeObj.rowData = self.getDataAtRow(change[0])
|
||||
changeData.push(changeObj)
|
||||
})
|
||||
|
||||
if (changeData.length > 0) {
|
||||
self.$el.request(self.options.changeHandler, {
|
||||
data: { changes: changeData }
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Create up Handson table and validate columns
|
||||
*/
|
||||
this.$el.handsontable(handsontableOptions)
|
||||
this.gridInstance = this.$el.handsontable('getInstance')
|
||||
|
||||
|
|
@ -103,6 +161,9 @@
|
|||
return columns
|
||||
}
|
||||
|
||||
/*
|
||||
* Auto complete
|
||||
*/
|
||||
var autocompleteLastQuery = '',
|
||||
autocompleteInterval = 300,
|
||||
autocompleteInputTimer
|
||||
|
|
@ -137,7 +198,10 @@
|
|||
}
|
||||
|
||||
DataGrid.DEFAULTS = {
|
||||
data: null,
|
||||
dataLocker: null,
|
||||
sourceHandler: null,
|
||||
changeHandler: null,
|
||||
startRows: 1,
|
||||
minRows: 1,
|
||||
autoInsertRows: false,
|
||||
|
|
@ -145,11 +209,14 @@
|
|||
columnWidths: null,
|
||||
columns: null,
|
||||
autocompleteHandler: null,
|
||||
sourceHandler: null,
|
||||
allowRemove: true,
|
||||
confirmMessage: 'Are you sure?'
|
||||
}
|
||||
|
||||
DataGrid.prototype.setHeaderValue = function(index, value) {
|
||||
this.columnHeaders[index] = value
|
||||
}
|
||||
|
||||
DataGrid.prototype.getDataAtRow = function(row) {
|
||||
if (!row && row !== 0)
|
||||
row = this.getSelectedRow()
|
||||
|
|
@ -157,6 +224,18 @@
|
|||
return $.extend(true, {}, this.gridInstance.getDataAtRow(row))
|
||||
}
|
||||
|
||||
DataGrid.prototype.refreshDataSource = function() {
|
||||
var self = this
|
||||
this.$el.request(self.options.sourceHandler, {
|
||||
success: function(data, textStatus, jqXHR){
|
||||
self.setData(data.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
DataGrid.prototype.setData = function(data) {
|
||||
this.gridInstance.loadData(data)
|
||||
}
|
||||
|
||||
DataGrid.prototype.getData = function() {
|
||||
var self = this,
|
||||
results = [],
|
||||
|
|
|
|||
|
|
@ -124,9 +124,6 @@
|
|||
.handsontable tbody th:last-of-type {
|
||||
border-right: 1px solid #e2e2e2 !important;
|
||||
}
|
||||
.handsontable tbody td:first-of-type.currentRow {
|
||||
border-left: 3px solid #ff9933;
|
||||
}
|
||||
.handsontable th.active {
|
||||
/*background-color: #CCC;*/
|
||||
color: #666;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,12 @@
|
|||
* - Use autocomplete plugin instead of typeahead
|
||||
* - Custom checkboxes
|
||||
* - Removed native scrollbars
|
||||
*
|
||||
*
|
||||
* @todo
|
||||
* - Add a Column strategy for even distribution of 0 width columns
|
||||
* - Replace dragdealer with October scrollbars
|
||||
* - Explore mobile support, currently non-existent
|
||||
*
|
||||
*/
|
||||
|
||||
var Handsontable = { //class namespace
|
||||
|
|
|
|||
|
|
@ -147,9 +147,13 @@
|
|||
border-right: 1px solid @color-handsontable-border !important;
|
||||
}
|
||||
|
||||
td:first-of-type.currentRow {
|
||||
border-left: 3px solid @color-list-stripe-active;
|
||||
}
|
||||
//td:first-of-type {
|
||||
// border-left: 3px solid transparent;
|
||||
//}
|
||||
|
||||
// td:first-of-type.currentRow {
|
||||
// border-left: 3px solid @color-list-stripe-active;
|
||||
// }
|
||||
}
|
||||
|
||||
th.active {
|
||||
|
|
|
|||
|
|
@ -4,20 +4,22 @@
|
|||
<?php endif ?>
|
||||
|
||||
<div
|
||||
id="<?= $this->getId('grid') ?>"
|
||||
id="<?= $this->getId() ?>"
|
||||
style="width:100%"
|
||||
class="control-datagrid"
|
||||
data-control="datagrid"
|
||||
data-allow-remove="<?= $allowRemove ? 'true' : 'false' ?>"
|
||||
<?php if ($dataLocker): ?>data-data-locker="<?= $dataLocker ?>"<?php endif ?>
|
||||
data-autocomplete-handler="<?= $this->getEventHandler('onAutocomplete') ?>"
|
||||
data-source-handler="<?= $this->getEventHandler('onDataSource') ?>"
|
||||
<?php if ($dataLocker): ?>data-data-locker="<?= $dataLocker ?>"<?php endif ?>
|
||||
<?php if ($useDataSource): ?>data-source-handler="<?= $this->getEventHandler('onDataSource') ?>"<?php endif ?>
|
||||
<?php if ($monitorChanges): ?>data-change-handler="<?= $this->getEventHandler('onDataChanged') ?>"<?php endif ?>
|
||||
></div>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
$('#<?= $this->getId('grid') ?>')
|
||||
$('#<?= $this->getId() ?>')
|
||||
.data('columns', <?= json_encode($columnDefinitions) ?>)
|
||||
.data('columnHeaders', <?= json_encode($columnHeaders) ?>)
|
||||
.data('columnWidths', <?= json_encode($columnWidths) ?>)
|
||||
<?php if ($data): ?>.data('data', <?= json_encode($data) ?>)<?php endif ?>
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use BackendMenu;
|
|||
use BackendAuth;
|
||||
use Backend\Classes\WidgetManager;
|
||||
use October\Rain\Support\ModuleServiceProvider;
|
||||
use System\Classes\SettingsManager;
|
||||
|
||||
class ServiceProvider extends ModuleServiceProvider
|
||||
{
|
||||
|
|
@ -89,6 +90,7 @@ class ServiceProvider extends ModuleServiceProvider
|
|||
'cms.manage_pages' => ['label' => 'Manage pages', 'tab' => 'Cms'],
|
||||
'cms.manage_layouts' => ['label' => 'Manage layouts', 'tab' => 'Cms'],
|
||||
'cms.manage_partials' => ['label' => 'Manage partials', 'tab' => 'Cms'],
|
||||
'cms.manage_themes' => ['label' => 'Manage themes', 'tab' => 'Cms']
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -99,6 +101,21 @@ class ServiceProvider extends ModuleServiceProvider
|
|||
$manager->registerFormWidget('Cms\FormWidgets\Components');
|
||||
});
|
||||
|
||||
/*
|
||||
* Register settings
|
||||
*/
|
||||
SettingsManager::instance()->registerCallback(function($manager){
|
||||
$manager->registerSettingItems('October.Cms', [
|
||||
'theme' => [
|
||||
'label' => 'cms::lang.theme.settings_menu',
|
||||
'description' => 'cms::lang.theme.settings_menu_description',
|
||||
'category' => 'CMS',
|
||||
'icon' => 'icon-picture-o',
|
||||
'url' => Backend::URL('cms/themes'),
|
||||
'order' => 200
|
||||
]
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
.theme-selector-layout .layout-cell {
|
||||
padding: 24px;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.theme-selector-layout .theme-thumbnail {
|
||||
width: 288px;
|
||||
background: #ecf0f1;
|
||||
border-bottom: 1px solid #e3e7e9;
|
||||
}
|
||||
.theme-selector-layout .theme-thumbnail img {
|
||||
opacity: 0.6;
|
||||
filter: alpha(opacity=60);
|
||||
width: 240px;
|
||||
}
|
||||
.theme-selector-layout .theme-description {
|
||||
border-bottom: 1px solid #f2f3f4;
|
||||
}
|
||||
.theme-selector-layout .theme-description h3,
|
||||
.theme-selector-layout .theme-description p {
|
||||
opacity: 0.6;
|
||||
filter: alpha(opacity=60);
|
||||
}
|
||||
.theme-selector-layout .theme-description h3 {
|
||||
margin: 0 0 25px 0;
|
||||
font-size: 28px;
|
||||
color: #2b3e50;
|
||||
display: inline-block;
|
||||
}
|
||||
.theme-selector-layout .theme-description p.author {
|
||||
font-size: 13px;
|
||||
display: inline-block;
|
||||
color: #808c8d;
|
||||
}
|
||||
.theme-selector-layout .theme-description p.description {
|
||||
color: #2b3e50;
|
||||
font-size: 14px;
|
||||
line-height: 180%;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.theme-selector-layout .theme-description .controls button i.icon-star {
|
||||
margin-right: 5px;
|
||||
color: #f1a84e;
|
||||
font-size: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .theme-thumbnail {
|
||||
background: #bdc3c7;
|
||||
border-bottom-color: #bdc3c7;
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .thumbnail-container {
|
||||
position: relative;
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .thumbnail-container:after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 14px solid transparent;
|
||||
border-bottom: 14px solid transparent;
|
||||
border-left: 15px solid #bdc3c7;
|
||||
position: absolute;
|
||||
right: -35px;
|
||||
top: 50%;
|
||||
margin-top: -14px;
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .theme-description h3,
|
||||
.theme-selector-layout .layout-row:hover .theme-description h3,
|
||||
.theme-selector-layout .layout-row.active .theme-description p,
|
||||
.theme-selector-layout .layout-row:hover .theme-description p {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .theme-thumbnail img,
|
||||
.theme-selector-layout .layout-row:hover .theme-thumbnail img {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.theme-selector-layout .layout-row.last .theme-description,
|
||||
.theme-selector-layout .layout-row.last .theme-thumbnail {
|
||||
border-bottom: none!important;
|
||||
}
|
||||
.theme-selector-layout .find-more-themes {
|
||||
background: #ecf0f1;
|
||||
color: #2b3e50;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 20px;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.theme-selector-layout .find-more-themes:hover {
|
||||
background: #1795f1;
|
||||
color: white;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.theme-selector-layout .layout-cell,
|
||||
.theme-selector-layout .layout-row {
|
||||
display: block!important;
|
||||
width: auto!important;
|
||||
height: auto!important;
|
||||
}
|
||||
.theme-selector-layout .theme-thumbnail img {
|
||||
width: 100%;
|
||||
}
|
||||
.theme-selector-layout .layout-row.links .theme-thumbnail {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
|
|
@ -0,0 +1,112 @@
|
|||
.theme-selector-layout .layout-cell {
|
||||
padding: 24px;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.theme-selector-layout .theme-thumbnail {
|
||||
width: 288px;
|
||||
background: #ecf0f1;
|
||||
border-bottom: 1px solid #e3e7e9;
|
||||
}
|
||||
.theme-selector-layout .theme-thumbnail img {
|
||||
opacity: 0.6;
|
||||
filter: alpha(opacity=60);
|
||||
width: 240px;
|
||||
}
|
||||
.theme-selector-layout .theme-description {
|
||||
border-bottom: 1px solid #f2f3f4;
|
||||
}
|
||||
.theme-selector-layout .theme-description h3,
|
||||
.theme-selector-layout .theme-description p {
|
||||
opacity: 0.6;
|
||||
filter: alpha(opacity=60);
|
||||
}
|
||||
.theme-selector-layout .theme-description h3 {
|
||||
margin: 0 0 25px 0;
|
||||
font-size: 28px;
|
||||
color: #2b3e50;
|
||||
display: inline-block;
|
||||
}
|
||||
.theme-selector-layout .theme-description p.author {
|
||||
font-size: 13px;
|
||||
display: inline-block;
|
||||
color: #808c8d;
|
||||
}
|
||||
.theme-selector-layout .theme-description p.description {
|
||||
color: #2b3e50;
|
||||
font-size: 14px;
|
||||
line-height: 180%;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.theme-selector-layout .theme-description .controls button i.icon-star {
|
||||
margin-right: 5px;
|
||||
color: #f1a84e;
|
||||
font-size: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .theme-thumbnail {
|
||||
background: #bdc3c7;
|
||||
border-bottom-color: #bdc3c7;
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .thumbnail-container {
|
||||
position: relative;
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .thumbnail-container:after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 14px solid transparent;
|
||||
border-bottom: 14px solid transparent;
|
||||
border-left: 15px solid #bdc3c7;
|
||||
position: absolute;
|
||||
right: -35px;
|
||||
top: 50%;
|
||||
margin-top: -14px;
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .theme-description h3,
|
||||
.theme-selector-layout .layout-row:hover .theme-description h3,
|
||||
.theme-selector-layout .layout-row.active .theme-description p,
|
||||
.theme-selector-layout .layout-row:hover .theme-description p {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.theme-selector-layout .layout-row.active .theme-thumbnail img,
|
||||
.theme-selector-layout .layout-row:hover .theme-thumbnail img {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.theme-selector-layout .layout-row.last .theme-description,
|
||||
.theme-selector-layout .layout-row.last .theme-thumbnail {
|
||||
border-bottom: none!important;
|
||||
}
|
||||
.theme-selector-layout .find-more-themes {
|
||||
background: #ecf0f1;
|
||||
color: #2b3e50;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 20px;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.theme-selector-layout .find-more-themes:hover {
|
||||
background: #1795f1;
|
||||
color: white;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.theme-selector-layout .layout-cell,
|
||||
.theme-selector-layout .layout-row {
|
||||
display: block!important;
|
||||
width: auto!important;
|
||||
height: auto!important;
|
||||
}
|
||||
.theme-selector-layout .theme-thumbnail img {
|
||||
width: 100%;
|
||||
}
|
||||
.theme-selector-layout .layout-row.links .theme-thumbnail {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
@import "../../../backend/assets/less/core/boot.less";
|
||||
|
||||
.theme-selector-layout {
|
||||
.layout-cell {
|
||||
padding: 24px;
|
||||
.box-sizing(border-box);
|
||||
}
|
||||
|
||||
.theme-thumbnail {
|
||||
width: 288px;
|
||||
background: #ecf0f1;
|
||||
border-bottom: 1px solid #e3e7e9;
|
||||
|
||||
img {
|
||||
.opacity(0.6);
|
||||
width: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-description {
|
||||
border-bottom: 1px solid #f2f3f4;
|
||||
|
||||
h3, p {
|
||||
.opacity(0.6);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0 0 25px 0;
|
||||
font-size: 28px;
|
||||
color: #2b3e50;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
p.author {
|
||||
font-size: 13px;
|
||||
display: inline-block;
|
||||
color: #808c8d;
|
||||
}
|
||||
|
||||
p.description {
|
||||
color: #2b3e50;
|
||||
font-size: 14px;
|
||||
line-height: 180%;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
button i.icon-star {
|
||||
margin-right: 5px;
|
||||
color: #f1a84e;
|
||||
font-size: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-row.active {
|
||||
.theme-thumbnail {
|
||||
background: #bdc3c7;
|
||||
border-bottom-color: #bdc3c7;
|
||||
|
||||
}
|
||||
|
||||
.thumbnail-container {
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
.triangle(right, 15px, 28px, #bdc3c7);
|
||||
position: absolute;
|
||||
right: -35px;
|
||||
top: 50%;
|
||||
margin-top: -14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-row {
|
||||
&.active, &:hover {
|
||||
.theme-description {
|
||||
h3, p {
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-thumbnail {
|
||||
img {
|
||||
.opacity(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.last {
|
||||
.theme-description, .theme-thumbnail {
|
||||
border-bottom: none!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.find-more-themes {
|
||||
background: #ecf0f1;
|
||||
color: #2b3e50;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 20px;
|
||||
.border-radius(4px);
|
||||
|
||||
&:hover {
|
||||
background: @link-color;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Screen specific
|
||||
//
|
||||
|
||||
@media (max-width: @screen-sm) {
|
||||
.theme-selector-layout {
|
||||
.layout-cell, .layout-row {
|
||||
display: block!important;
|
||||
width: auto!important;
|
||||
height: auto!important;
|
||||
}
|
||||
|
||||
.theme-thumbnail {
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-row.links {
|
||||
.theme-thumbnail {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -93,6 +93,9 @@ class Controller extends BaseController
|
|||
public function __construct($theme = null)
|
||||
{
|
||||
$this->theme = $theme ? $theme : Theme::getActiveTheme();
|
||||
if (!$this->theme)
|
||||
throw new CmsException(Lang::get('cms::lang.theme.active.not_found'));
|
||||
|
||||
$this->assetPath = Config::get('cms.themesDir').'/'.$this->theme->getDirName();
|
||||
$this->router = new Router($this->theme);
|
||||
$this->initTwigEnvironment();
|
||||
|
|
|
|||
|
|
@ -205,8 +205,12 @@ class Router
|
|||
*/
|
||||
$pages = $this->theme->listPages();
|
||||
$map = [];
|
||||
foreach ($pages as $page)
|
||||
foreach ($pages as $page) {
|
||||
if (!$page->url)
|
||||
continue;
|
||||
|
||||
$map[] = ['file' => $page->getFileName(), 'pattern' => $page->url];
|
||||
}
|
||||
|
||||
self::$urlMap = $map;
|
||||
if ($cacheable)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
<?php namespace Cms\Classes;
|
||||
|
||||
use URL;
|
||||
use File;
|
||||
use Lang;
|
||||
use Cache;
|
||||
use Event;
|
||||
use Config;
|
||||
use October\Rain\Support\Yaml;
|
||||
use System\Models\Parameters;
|
||||
use System\Classes\SystemException;
|
||||
use DirectoryIterator;
|
||||
|
||||
/**
|
||||
* This class represents the CMS theme.
|
||||
|
|
@ -21,6 +26,11 @@ class Theme
|
|||
*/
|
||||
protected $dirName;
|
||||
|
||||
/**
|
||||
* @var mixed Keeps the cached configuration file values.
|
||||
*/
|
||||
protected $configCache = null;
|
||||
|
||||
/**
|
||||
* Loads the theme.
|
||||
*/
|
||||
|
|
@ -58,7 +68,7 @@ class Theme
|
|||
*/
|
||||
public static function exists($dirName)
|
||||
{
|
||||
$theme = new self;
|
||||
$theme = new static;
|
||||
$path = $theme->getPath($dirName);
|
||||
|
||||
return File::isDirectory($path);
|
||||
|
|
@ -84,8 +94,17 @@ class Theme
|
|||
*/
|
||||
public static function getActiveTheme()
|
||||
{
|
||||
$paramKey = 'cms::theme.active';
|
||||
$activeTheme = Config::get('cms.activeTheme');
|
||||
|
||||
$dbResult = Parameters::findRecord($paramKey)
|
||||
->remember(1440, $paramKey)
|
||||
->pluck('value')
|
||||
;
|
||||
|
||||
if ($dbResult !== null)
|
||||
$activeTheme = $dbResult;
|
||||
|
||||
$apiResult = Event::fire('cms.activeTheme', [], true);
|
||||
if ($apiResult !== null)
|
||||
$activeTheme = $apiResult;
|
||||
|
|
@ -93,7 +112,7 @@ class Theme
|
|||
if (!strlen($activeTheme))
|
||||
throw new SystemException(Lang::get('cms::lang.theme.active.not_set'));
|
||||
|
||||
$theme = new self();
|
||||
$theme = new static;
|
||||
$theme->load($activeTheme);
|
||||
if (!File::isDirectory($theme->getPath()))
|
||||
return null;
|
||||
|
|
@ -101,6 +120,18 @@ class Theme
|
|||
return $theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active theme.
|
||||
* The active theme code is stored in the database and overrides the configuration cms.activeTheme parameter.
|
||||
* @param string $code Specifies the active theme code.
|
||||
*/
|
||||
public static function setActiveTheme($code)
|
||||
{
|
||||
$paramKey = 'cms::theme.active';
|
||||
Parameters::set($paramKey, $code);
|
||||
Cache::forget($paramKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the edit theme.
|
||||
* By default the edit theme is loaded from the cms.editTheme parameter,
|
||||
|
|
@ -123,11 +154,81 @@ class Theme
|
|||
if (!strlen($editTheme))
|
||||
throw new SystemException(Lang::get('cms::lang.theme.edit.not_set'));
|
||||
|
||||
$theme = new self();
|
||||
$theme = new static;
|
||||
$theme->load($editTheme);
|
||||
if (!File::isDirectory($theme->getPath()))
|
||||
return null;
|
||||
|
||||
return $theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all themes.
|
||||
* @return array Returns an array of the Theme objects.
|
||||
*/
|
||||
public static function all()
|
||||
{
|
||||
$path = base_path().Config::get('cms.themesDir');
|
||||
|
||||
$it = new DirectoryIterator($path);
|
||||
$it->rewind();
|
||||
|
||||
$result = [];
|
||||
foreach ($it as $fileinfo) {
|
||||
if (!$fileinfo->isDir() || $fileinfo->isDot())
|
||||
continue;
|
||||
|
||||
$theme = new static;
|
||||
$theme->load($fileinfo->getFilename());
|
||||
$result[] = $theme;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the theme.yaml file and returns the theme configuration values.
|
||||
* @return array Returns the parsed configuration file values.
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
if ($this->configCache !== null)
|
||||
return $this->configCache;
|
||||
|
||||
$path = $this->getPath().'/theme.yaml';
|
||||
if (!File::exists($path))
|
||||
return $this->configCache = [];
|
||||
|
||||
return $this->configCache = Yaml::parseFile($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value from the theme configuration file by its name.
|
||||
* @param string $name Specifies the configuration parameter name.
|
||||
* @param mixed $default Specifies the default value to return in case if the parameter doesn't exist in the configuration file.
|
||||
* @return mixed Returns the parameter value or a default value
|
||||
*/
|
||||
public function getConfigValue($name, $default = null)
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
if (isset($config[$name]))
|
||||
return $config[$name];
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the theme preview image URL.
|
||||
* If the image file doesn't exist returns the placeholder image URL.
|
||||
* @return string Returns the image URL.
|
||||
*/
|
||||
public function getPreviewImageUrl()
|
||||
{
|
||||
$previewPath = '/assets/images/theme-preview.png';
|
||||
$path = $this->getPath().$previewPath;
|
||||
if (!File::exists($path))
|
||||
return URL::asset('modules/cms/assets/images/default-theme-preview.png');
|
||||
|
||||
return URL::asset('themes/'.$this->getDirName().$previewPath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
<?php namespace Cms\Controllers;
|
||||
|
||||
use Lang;
|
||||
use Config;
|
||||
use BackendMenu;
|
||||
use Input;
|
||||
use Backend\Classes\Controller;
|
||||
use Cms\Classes\Theme as CmsTheme;
|
||||
|
||||
/**
|
||||
* Theme selector controller
|
||||
*
|
||||
* @package october\backend
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*
|
||||
*/
|
||||
class Themes extends Controller
|
||||
{
|
||||
public $requiredPermissions = ['cms.manage_themes'];
|
||||
|
||||
public $bodyClass = 'slim-container';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->addCss('/modules/cms/assets/css/october.theme-selector.css', 'core');
|
||||
|
||||
$this->pageTitle = Lang::get('cms::lang.theme.settings_menu');
|
||||
BackendMenu::setContext('October.System', 'system', 'settings');
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function index_onSetActiveTheme()
|
||||
{
|
||||
CmsTheme::setActiveTheme(Input::get('theme'));
|
||||
|
||||
return [
|
||||
'#theme-list' => $this->makePartial('theme_list')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
$themes = Cms\Classes\Theme::all();
|
||||
$activeTheme = Cms\Classes\Theme::getActiveTheme();
|
||||
$cnt = count($themes);
|
||||
|
||||
foreach ($themes as $index=>$theme):
|
||||
$isLast = $index == $cnt-1;
|
||||
$isActive = $activeTheme->getDirName() == $theme->getDirName();
|
||||
$author = $theme->getConfigValue('author');
|
||||
?>
|
||||
<div class="layout-row <?= $isLast ? 'last' : null ?> min-size <?= $isActive ? 'active' : null ?>">
|
||||
<div class="layout-cell min-height theme-thumbnail">
|
||||
<div class="thumbnail-container"><img src="<?= $theme->getPreviewImageUrl() ?>"/></div>
|
||||
</div>
|
||||
<div class="layout-cell min-height theme-description">
|
||||
<h3><?= e($theme->getConfigValue('name', $theme->getDirName())) ?></h3>
|
||||
<?php if (strlen($author)): ?>
|
||||
<p class="author">by <a href="<?= e($theme->getConfigValue('authorUrl', '#')) ?>"><?= e($author) ?></a></p>
|
||||
<?php endif ?>
|
||||
<p class="description"><?= e($theme->getConfigValue('description', 'The theme description is not provided.')) ?></p>
|
||||
<div class="controls">
|
||||
<?php if ($isActive): ?>
|
||||
<button
|
||||
type="submit"
|
||||
disabled
|
||||
class="btn btn-disabled">
|
||||
<i class="icon-star"></i>
|
||||
<?= e(trans('cms::lang.theme.active_button')) ?>
|
||||
</button>
|
||||
<?php else: ?>
|
||||
<button
|
||||
type="submit"
|
||||
data-request="onSetActiveTheme"
|
||||
data-request-data="theme: '<?= e($theme->getDirName()) ?>'"
|
||||
data-stripe-load-indicator
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('cms::lang.theme.activate_button')) ?>
|
||||
</button>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
<div class="layout-row links">
|
||||
<div class="layout-cell theme-thumbnail">
|
||||
|
||||
</div>
|
||||
<div class="layout-cell theme-description">
|
||||
<a class="find-more-themes" href="http://octobercms.com/themes"><?= e(trans('cms::lang.theme.find_more_themes')) ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?= Block::put('body') ?>
|
||||
<div class="layout">
|
||||
<div class="layout-row min-size">
|
||||
<div class="control-breadcrumb no-bottom-margin">
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('system/settings') ?>"><?= e(trans('system::lang.settings.menu_label')) ?></a></li>
|
||||
<li><?= e($this->pageTitle) ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= Form::open(['onsubmit'=>'return false']) ?>
|
||||
<div class="layout theme-selector-layout" id="theme-list">
|
||||
<?= $this->makePartial('theme_list') ?>
|
||||
</div>
|
||||
<?= Form::close() ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?= Block::endPut() ?>
|
||||
|
|
@ -15,12 +15,18 @@ return [
|
|||
'theme' => [
|
||||
'active' => [
|
||||
'not_set' => "The active theme is not set.",
|
||||
'not_found' => "The active theme is not found.",
|
||||
],
|
||||
'edit' => [
|
||||
'not_set' => "The edit theme is not set.",
|
||||
'not_found' => "The edit theme is not found.",
|
||||
'not_match' => "The object you're trying to access doesn't belong to the theme being edited. Please reload the page."
|
||||
]
|
||||
],
|
||||
'settings_menu' => 'Front-end theme',
|
||||
'settings_menu_description' => 'Preview the list of installed themes and select an active theme.',
|
||||
'find_more_themes' => 'Find more themes on OctoberCMS Theme Marketplace.',
|
||||
'activate_button' => 'Activate',
|
||||
'active_button' => 'Activate',
|
||||
],
|
||||
'page' => [
|
||||
'not_found' => [
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ class AssetList extends WidgetBase
|
|||
{
|
||||
$this->addCss('css/assetlist.css', 'core');
|
||||
$this->addJs('js/assetlist.js', 'core');
|
||||
$this->addJs('/modules/backend/widgets/form/assets/js/form.js', 'core');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ class ServiceProvider extends ModuleServiceProvider
|
|||
'category' => 'System',
|
||||
'icon' => 'icon-envelope',
|
||||
'class' => 'System\Models\MailSettings',
|
||||
'sort' => 100
|
||||
'order' => 400,
|
||||
],
|
||||
'mail_templates' => [
|
||||
'label' => 'system::lang.mail_templates.menu_label',
|
||||
|
|
@ -237,7 +237,7 @@ class ServiceProvider extends ModuleServiceProvider
|
|||
'category' => 'System',
|
||||
'icon' => 'icon-envelope-square',
|
||||
'url' => Backend::url('system/mailtemplates'),
|
||||
'sort' => 100
|
||||
'order' => 400,
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@
|
|||
.control-updatelist h5:first-of-type {
|
||||
border-top: none;
|
||||
}
|
||||
.control-updatelist h5 i {
|
||||
margin-right: 7px;
|
||||
color: #405261;
|
||||
}
|
||||
.control-updatelist h5 small {
|
||||
text-transform: none;
|
||||
float: right;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@
|
|||
border-top: none;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: 7px;
|
||||
color: @color-text-title;
|
||||
}
|
||||
|
||||
small {
|
||||
text-transform: none;
|
||||
float: right;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use System\Classes\ApplicationException;
|
|||
*
|
||||
* Usage:
|
||||
*
|
||||
* In the model class definition:
|
||||
* In the model class definition:
|
||||
*
|
||||
* public $implement = ['System.Behaviors.SettingsModel'];
|
||||
* public $settingsCode = 'author_plugin_code';
|
||||
|
|
@ -200,4 +200,4 @@ class SettingsModel extends ModelBehavior
|
|||
{
|
||||
return $this->fieldConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class SettingsManager
|
|||
'icon' => null,
|
||||
'url' => null,
|
||||
'permissions' => [],
|
||||
'order' => 100,
|
||||
'order' => 500,
|
||||
'context' => 'system',
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -206,6 +206,16 @@ class UpdateManager
|
|||
}
|
||||
$result['plugins'] = $plugins;
|
||||
|
||||
/*
|
||||
* Strip out themes that have been installed before
|
||||
*/
|
||||
$themes = [];
|
||||
foreach (array_get($result, 'themes', []) as $code => $info) {
|
||||
if (!$this->isThemeInstalled($code))
|
||||
$themes[$code] = $info;
|
||||
}
|
||||
$result['themes'] = $themes;
|
||||
|
||||
Parameters::set('system::update.count', array_get($result, 'update', 0));
|
||||
|
||||
return $result;
|
||||
|
|
@ -433,6 +443,57 @@ class UpdateManager
|
|||
@unlink($filePath);
|
||||
}
|
||||
|
||||
//
|
||||
// Themes
|
||||
//
|
||||
|
||||
/**
|
||||
* Downloads a theme from the update server.
|
||||
* @param string $name Theme name.
|
||||
* @param string $hash Expected file hash.
|
||||
* @return self
|
||||
*/
|
||||
public function downloadTheme($name, $hash)
|
||||
{
|
||||
$fileCode = $name . $hash;
|
||||
$this->requestServerFile('theme/get', $fileCode, $hash, ['name' => $name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a theme after it has been downloaded.
|
||||
*/
|
||||
public function extractTheme($name, $hash)
|
||||
{
|
||||
$fileCode = $name . $hash;
|
||||
$filePath = $this->getFilePath($fileCode);
|
||||
|
||||
if (!Zip::extract($filePath, $this->baseDirectory . '/themes/'))
|
||||
throw new ApplicationException(Lang::get('system::lang.zip.extract_failed', ['file' => $filePath]));
|
||||
|
||||
@unlink($filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a theme has ever been installed before.
|
||||
* @param string $name Theme code
|
||||
* @return boolean
|
||||
*/
|
||||
public function isThemeInstalled($name)
|
||||
{
|
||||
return array_key_exists($name, Parameters::get('system::theme.history', []));
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags a theme as being installed, so it is not downloaded twice.
|
||||
* @param string $name Theme code
|
||||
*/
|
||||
public function setThemeInstalled($name)
|
||||
{
|
||||
$history = Parameters::get('system::theme.history', []);
|
||||
$history[$name] = Carbon::now()->timestamp;
|
||||
Parameters::set('system::theme.history', $history);
|
||||
}
|
||||
|
||||
//
|
||||
// Notes
|
||||
//
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?php namespace System\Console;
|
||||
|
||||
use Str;
|
||||
use Config;
|
||||
use Illuminate\Console\Command;
|
||||
use System\Classes\UpdateManager;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
|
@ -35,9 +36,29 @@ class OctoberUpdate extends Command
|
|||
$this->output->writeln('<info>Updating October...</info>');
|
||||
$manager = UpdateManager::instance()->resetNotes();
|
||||
$forceUpdate = $this->option('force');
|
||||
$pluginsOnly = $this->option('plugins');
|
||||
$coreOnly = $this->option('core');
|
||||
|
||||
/*
|
||||
* Check for disabilities
|
||||
*/
|
||||
$disableCore = $disablePlugins = $disableThemes = false;
|
||||
|
||||
if ($this->option('plugins')) {
|
||||
$disableCore = true;
|
||||
$disableThemes = true;
|
||||
}
|
||||
|
||||
if ($this->option('core')) {
|
||||
$disablePlugins = true;
|
||||
$disableThemes = true;
|
||||
}
|
||||
|
||||
if (Config::get('cms.disableCoreUpdates', false)) {
|
||||
$disableCore = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform update
|
||||
*/
|
||||
$updateList = $manager->requestUpdateList($forceUpdate);
|
||||
$updates = (int)array_get($updateList, 'update', 0);
|
||||
|
||||
|
|
@ -49,7 +70,7 @@ class OctoberUpdate extends Command
|
|||
$this->output->writeln(sprintf('<info>Found %s new %s!</info>', $updates, Str::plural('update', $updates)));
|
||||
}
|
||||
|
||||
$coreHash = $pluginsOnly ? null : array_get($updateList, 'core.hash');
|
||||
$coreHash = $disableCore ? null : array_get($updateList, 'core.hash');
|
||||
$coreBuild = array_get($updateList, 'core.build');
|
||||
|
||||
if ($coreHash) {
|
||||
|
|
@ -57,7 +78,7 @@ class OctoberUpdate extends Command
|
|||
$manager->downloadCore($coreHash);
|
||||
}
|
||||
|
||||
$plugins = $coreOnly ? [] : array_get($updateList, 'plugins');
|
||||
$plugins = $disablePlugins ? [] : array_get($updateList, 'plugins');
|
||||
foreach ($plugins as $code => $plugin) {
|
||||
$pluginName = array_get($plugin, 'name');
|
||||
$pluginHash = array_get($plugin, 'hash');
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use Str;
|
|||
use Lang;
|
||||
use File;
|
||||
use Flash;
|
||||
use Config;
|
||||
use Backend;
|
||||
use Redirect;
|
||||
use BackendMenu;
|
||||
|
|
@ -31,6 +32,11 @@ class Updates extends Controller
|
|||
|
||||
public $listConfig = ['list' => 'config_list.yaml', 'manage' => 'config_manage_list.yaml'];
|
||||
|
||||
/**
|
||||
* @var boolean If set to true, core updates will not be downloaded or extracted.
|
||||
*/
|
||||
protected $disableCoreUpdates = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
|
@ -38,6 +44,8 @@ class Updates extends Controller
|
|||
$this->addCss('/modules/system/assets/css/updates.css', 'core');
|
||||
|
||||
BackendMenu::setContext('October.System', 'system', 'updates');
|
||||
|
||||
$this->disableCoreUpdates = Config::get('cms.disableCoreUpdates', false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -98,10 +106,12 @@ class Updates extends Controller
|
|||
|
||||
switch ($stepCode) {
|
||||
case 'downloadCore':
|
||||
if ($this->disableCoreUpdates) return;
|
||||
$manager->downloadCore(post('hash'));
|
||||
break;
|
||||
|
||||
case 'extractCore':
|
||||
if ($this->disableCoreUpdates) return;
|
||||
$manager->extractCore(post('hash'), post('build'));
|
||||
break;
|
||||
|
||||
|
|
@ -109,10 +119,18 @@ class Updates extends Controller
|
|||
$manager->downloadPlugin(post('name'), post('hash'));
|
||||
break;
|
||||
|
||||
case 'downloadTheme':
|
||||
$manager->downloadTheme(post('name'), post('hash'));
|
||||
break;
|
||||
|
||||
case 'extractPlugin':
|
||||
$manager->extractPlugin(post('name'), post('hash'));
|
||||
break;
|
||||
|
||||
case 'extractTheme':
|
||||
$manager->extractTheme(post('name'), post('hash'));
|
||||
break;
|
||||
|
||||
case 'completeUpdate':
|
||||
$manager->update();
|
||||
Flash::success(Lang::get('system::lang.updates.update_success'));
|
||||
|
|
@ -148,7 +166,8 @@ class Updates extends Controller
|
|||
|
||||
$this->vars['core'] = array_get($result, 'core', false);
|
||||
$this->vars['hasUpdates'] = array_get($result, 'update', false);
|
||||
$this->vars['updateList'] = array_get($result, 'plugins', []);
|
||||
$this->vars['pluginList'] = array_get($result, 'plugins', []);
|
||||
$this->vars['themeList'] = array_get($result, 'themes', []);
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
$this->handleError($ex);
|
||||
|
|
@ -204,18 +223,20 @@ class Updates extends Controller
|
|||
public function onApplyUpdates()
|
||||
{
|
||||
try {
|
||||
$plugins = post('plugins', []);
|
||||
if (!is_array($plugins))
|
||||
$plugins = [];
|
||||
|
||||
$coreHash = post('hash');
|
||||
$coreBuild = post('build');
|
||||
$core = [$coreHash, $coreBuild];
|
||||
|
||||
$plugins = post('plugins', []);
|
||||
if (!is_array($plugins)) $plugins = [];
|
||||
|
||||
$themes = post('themes', []);
|
||||
if (!is_array($themes)) $themes = [];
|
||||
|
||||
/*
|
||||
* Update steps
|
||||
*/
|
||||
$updateSteps = $this->buildUpdateSteps($core, $plugins);
|
||||
$updateSteps = $this->buildUpdateSteps($core, $plugins, $themes);
|
||||
|
||||
/*
|
||||
* Finish up
|
||||
|
|
@ -234,13 +255,16 @@ class Updates extends Controller
|
|||
return $this->makePartial('execute');
|
||||
}
|
||||
|
||||
private function buildUpdateSteps($core, $plugins)
|
||||
private function buildUpdateSteps($core, $plugins, $themes)
|
||||
{
|
||||
if (!is_array($core))
|
||||
$core = [null, null];
|
||||
|
||||
if (!is_array($plugins))
|
||||
$plugins = [];
|
||||
|
||||
if (!is_array($core))
|
||||
$core = [null, null];
|
||||
if (!is_array($themes))
|
||||
$themes = [];
|
||||
|
||||
$updateSteps = [];
|
||||
list($coreHash, $coreBuild) = $core;
|
||||
|
|
@ -265,6 +289,15 @@ class Updates extends Controller
|
|||
];
|
||||
}
|
||||
|
||||
foreach ($themes as $name => $hash) {
|
||||
$updateSteps[] = [
|
||||
'code' => 'downloadTheme',
|
||||
'label' => Lang::get('system::lang.updates.theme_downloading', compact('name')),
|
||||
'name' => $name,
|
||||
'hash' => $hash
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract
|
||||
*/
|
||||
|
|
@ -286,6 +319,15 @@ class Updates extends Controller
|
|||
];
|
||||
}
|
||||
|
||||
foreach ($themes as $name => $hash) {
|
||||
$updateSteps[] = [
|
||||
'code' => 'extractTheme',
|
||||
'label' => Lang::get('system::lang.updates.theme_extracting', compact('name')),
|
||||
'name' => $name,
|
||||
'hash' => $hash
|
||||
];
|
||||
}
|
||||
|
||||
return $updateSteps;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
<?php if ($core): ?>
|
||||
<h5>
|
||||
<i class="icon-cube"></i>
|
||||
<?= e(trans('system::lang.system.name')) ?>
|
||||
<?php if ($core['old_build']): ?>
|
||||
<?= e(trans('system::lang.updates.core_build_old', ['build'=>$core['old_build']])) ?>
|
||||
|
|
@ -26,8 +27,23 @@
|
|||
<input type="hidden" name="build" value="<?= e($core['build']) ?>" />
|
||||
<?php endif ?>
|
||||
|
||||
<?php foreach ($updateList as $code => $plugin): ?>
|
||||
<?php foreach ($themeList as $code => $theme): ?>
|
||||
<h5>
|
||||
<i class="icon-picture-o"></i>
|
||||
<?= e(array_get($theme, 'name', 'Unknown')) ?>
|
||||
<small><?= e(trans('system::lang.updates.theme_label')) ?></small>
|
||||
</h5>
|
||||
<dl>
|
||||
<dt><?= e(array_get($theme, 'version', 'v1.0.0')) ?></dt>
|
||||
<dd><?= e(trans('system::lang.updates.theme_new_install')) ?></dd>
|
||||
</dl>
|
||||
|
||||
<input type="hidden" name="themes[<?= e($code) ?>]" value="<?= e($theme['hash']) ?>" />
|
||||
<?php endforeach ?>
|
||||
|
||||
<?php foreach ($pluginList as $code => $plugin): ?>
|
||||
<h5>
|
||||
<i class="icon-puzzle-piece"></i>
|
||||
<?= e($plugin['name']) ?>
|
||||
|
||||
<?php if ($plugin['old_version']): ?>
|
||||
|
|
|
|||
|
|
@ -137,6 +137,10 @@ return [
|
|||
'plugin_version_none' => 'New plugin',
|
||||
'plugin_version_old' => 'Current v:version',
|
||||
'plugin_version_new' => 'v:version',
|
||||
'theme_label' => 'Theme',
|
||||
'theme_new_install' => 'New theme installation.',
|
||||
'theme_downloading' => 'Downloading theme: :name',
|
||||
'theme_extracting' => 'Unpacking theme: :name',
|
||||
'update_label' => 'Update software',
|
||||
'update_completing' => 'Finishing update process',
|
||||
'update_loading' => 'Loading available updates...',
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class ThemeTest extends TestCase
|
|||
$it->setMaxDepth(1);
|
||||
$it->rewind();
|
||||
|
||||
while($it->valid()) {
|
||||
while ($it->valid()) {
|
||||
if (!$it->isDot() && !$it->isDir() && $it->getExtension() == 'htm')
|
||||
$result++;
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
|
|
@ -0,0 +1,4 @@
|
|||
name: Demo
|
||||
description: Demo OctoberCMS theme. Demonstrates the basic concepts of the front-end theming: layouts, pages, partials, components, content blocks, AJAX framework.
|
||||
author: OctoberCMS
|
||||
authorUrl: 'http://octobercms.com'
|
||||
Loading…
Reference in New Issue