Merge branch 'develop'
This commit is contained in:
commit
d7f22dc797
|
|
@ -1,3 +1,7 @@
|
|||
* **Build 13x** (2014-08-xx)
|
||||
- List widget has been refactored to improve efficiency.
|
||||
- Added new list column type `nameFrom` (take name from X attribute) as an alternative to `select`.
|
||||
|
||||
* **Build 137** (2014-08-14)
|
||||
- Lists now support Filters (see Backend > Lists docs).
|
||||
- Numerous hard coded phrases converted to localized strings.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,14 @@ class ListColumn
|
|||
public $sortable = true;
|
||||
|
||||
/**
|
||||
* @var string Custom SQL for selecting this record value.
|
||||
* @var string Model attribute to use for the display value, this will
|
||||
* override any $sqlSelect definition.
|
||||
*/
|
||||
public $nameFrom;
|
||||
|
||||
/**
|
||||
* @var string Custom SQL for selecting this record display value,
|
||||
* the @ symbol is replaced with the table name.
|
||||
*/
|
||||
public $sqlSelect;
|
||||
|
||||
|
|
@ -103,6 +110,7 @@ class ListColumn
|
|||
if (isset($config['searchable'])) $this->searchable = $config['searchable'];
|
||||
if (isset($config['sortable'])) $this->sortable = $config['sortable'];
|
||||
if (isset($config['invisible'])) $this->invisible = $config['invisible'];
|
||||
if (isset($config['nameFrom'])) $this->nameFrom = $config['nameFrom'];
|
||||
if (isset($config['select'])) $this->sqlSelect = $config['select'];
|
||||
if (isset($config['relation'])) $this->relation = $config['relation'];
|
||||
if (isset($config['format'])) $this->format = $config['format'];
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ return [
|
|||
'menu_label' => 'Dashboard',
|
||||
'widget_label' => 'Widget',
|
||||
'widget_width' => 'Width',
|
||||
'full_width' => 'full width',
|
||||
'add_widget' => 'Add widget',
|
||||
'widget_inspector_title' => 'Widget configuration',
|
||||
'widget_inspector_description' => 'Configure the report widget',
|
||||
|
|
@ -119,15 +120,13 @@ return [
|
|||
'setup_title' => 'List Setup',
|
||||
'setup_help' => 'Use checkboxes to select columns you want to see in the list. You can change position of columns by dragging them up or down.',
|
||||
'records_per_page' => 'Records per page',
|
||||
'records_per_page_help' => 'Select the number of records per page to display. Please note that high number of records on a single page can reduce performance.',
|
||||
'apply_changes' => 'Apply changes',
|
||||
'cancel' => 'Cancel'
|
||||
'records_per_page_help' => 'Select the number of records per page to display. Please note that high number of records on a single page can reduce performance.'
|
||||
],
|
||||
'fileupload' => [
|
||||
'attachment' => 'Attachment',
|
||||
'help' => 'Add a title and description for this attachment.',
|
||||
'title_label' => 'Title',
|
||||
'description_label' => 'Description'
|
||||
'attachment' => 'Attachment',
|
||||
'help' => 'Add a title and description for this attachment.',
|
||||
'title_label' => 'Title',
|
||||
'description_label' => 'Description'
|
||||
],
|
||||
'form' => [
|
||||
'create_title' => "New :name",
|
||||
|
|
@ -163,6 +162,7 @@ return [
|
|||
'select' => 'Select',
|
||||
'select_all' => 'all',
|
||||
'select_none' => 'none',
|
||||
'select_placeholder' => 'please select',
|
||||
'insert_row' => 'Insert Row',
|
||||
'delete_row' => 'Delete Row'
|
||||
],
|
||||
|
|
|
|||
|
|
@ -113,9 +113,7 @@ return [
|
|||
'setup_title' => 'Configura elenco',
|
||||
'setup_help' => 'Utilizza le checkbox per selezionare le colonne che vuoi visualizzare nell\'elenco. Puoi cambiare la posizione delle colonne trascinandole verso l\'alto o il basso.',
|
||||
'records_per_page' => 'Record per pagina',
|
||||
'records_per_page_help' => 'Seleziona il numero di record da visualizzare su ogni pagina. Ricorda che un numero elevato di record in una singola pagina può ridurre le prestazioni.',
|
||||
'apply_changes' => 'Applica modifiche',
|
||||
'cancel' => 'Annulla'
|
||||
'records_per_page_help' => 'Seleziona il numero di record da visualizzare su ogni pagina. Ricorda che un numero elevato di record in una singola pagina può ridurre le prestazioni.'
|
||||
],
|
||||
'form' => [
|
||||
'create_title' => "Nuovo :name",
|
||||
|
|
|
|||
|
|
@ -265,9 +265,10 @@ class Lists extends WidgetBase
|
|||
protected function prepareModel()
|
||||
{
|
||||
$query = $this->model->newQuery();
|
||||
$selects = [$this->model->getTable().'.*'];
|
||||
$tables = ['base'=>$this->model->getTable()];
|
||||
$primaryTable = $this->model->getTable();
|
||||
$selects = [$primaryTable.'.*'];
|
||||
$joins = [];
|
||||
$withs = [];
|
||||
|
||||
/*
|
||||
* Extensibility
|
||||
|
|
@ -276,68 +277,129 @@ class Lists extends WidgetBase
|
|||
$this->fireEvent('list.extendQueryBefore', [$query]);
|
||||
|
||||
/*
|
||||
* Related custom selects, must come first
|
||||
* Prepare searchable column names
|
||||
*/
|
||||
foreach ($this->getVisibleListColumns() as $column) {
|
||||
if (!isset($column->relation) || !isset($column->sqlSelect))
|
||||
continue;
|
||||
$primarySearchable = [];
|
||||
$relationSearchable = [];
|
||||
|
||||
if (!$this->model->hasRelation($column->relation))
|
||||
throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', ['class'=>get_class($this->model), 'relation'=>$column->relation]));
|
||||
$columnsToSearch = [];
|
||||
if (!empty($this->searchTerm) && ($searchableColumns = $this->getSearchableColumns())) {
|
||||
foreach ($searchableColumns as $column) {
|
||||
/*
|
||||
* Related
|
||||
*/
|
||||
if ($this->isColumnRelated($column)) {
|
||||
$table = $this->model->makeRelation($column->relation)->getTable();
|
||||
$columnName = isset($column->sqlSelect)
|
||||
? DbDongle::raw($this->parseTableName($column->sqlSelect, $table))
|
||||
: $table . '.' . $column->nameFrom;
|
||||
|
||||
$alias = Db::getQueryGrammar()->wrap($column->columnName);
|
||||
$table = $this->model->makeRelation($column->relation)->getTable();
|
||||
$relationType = $this->model->getRelationType($column->relation);
|
||||
$sqlSelect = $this->parseTableName($column->sqlSelect, $table);
|
||||
$relationSearchable[$column->relation][] = $columnName;
|
||||
}
|
||||
/*
|
||||
* Primary
|
||||
*/
|
||||
else {
|
||||
$columnName = isset($column->sqlSelect)
|
||||
? DbDongle::raw($this->parseTableName($column->sqlSelect, $primaryTable))
|
||||
: $primaryTable . '.' . $column->columnName;
|
||||
|
||||
if (in_array($relationType, ['hasMany', 'belongsToMany', 'morphToMany', 'morphedByMany', 'morphMany', 'attachMany', 'hasManyThrough']))
|
||||
$selects[] = DbDongle::raw("group_concat(" . $sqlSelect . " separator ', ') as ". $alias);
|
||||
else
|
||||
$selects[] = DbDongle::raw($sqlSelect . ' as '. $alias);
|
||||
|
||||
$joins[] = $column->relation;
|
||||
$tables[$column->relation] = $table;
|
||||
$primarySearchable[] = $columnName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($joins)
|
||||
$query->joinWith(array_unique($joins), false);
|
||||
/*
|
||||
* Prepare related eager loads (withs) and custom selects (joins)
|
||||
*/
|
||||
foreach ($this->getVisibleListColumns() as $column) {
|
||||
|
||||
if (!$this->isColumnRelated($column) || (!isset($column->sqlSelect) && !isset($column->nameFrom)))
|
||||
continue;
|
||||
|
||||
if (isset($column->nameFrom))
|
||||
$withs[] = $column->relation;
|
||||
|
||||
$joins[] = $column->relation;
|
||||
}
|
||||
|
||||
/*
|
||||
* Include any relation constraints
|
||||
*/
|
||||
if ($joins) {
|
||||
foreach (array_unique($joins) as $join) {
|
||||
/*
|
||||
* Apply a supplied search term for relation columns and
|
||||
* constrain the query only if there is something to search for
|
||||
*/
|
||||
$columnsToSearch = array_get($relationSearchable, $join, []);
|
||||
|
||||
if (count($columnsToSearch) > 0) {
|
||||
$query->whereHas($join, function($_query) use ($columnsToSearch) {
|
||||
$_query->searchWhere($this->searchTerm, $columnsToSearch);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add eager loads to the query
|
||||
*/
|
||||
if ($withs) {
|
||||
$query->with(array_unique($withs));
|
||||
}
|
||||
|
||||
/*
|
||||
* Custom select queries
|
||||
*/
|
||||
foreach ($this->getVisibleListColumns() as $column) {
|
||||
if (!isset($column->sqlSelect) || isset($column->relation))
|
||||
if (!isset($column->sqlSelect))
|
||||
continue;
|
||||
|
||||
$alias = Db::getQueryGrammar()->wrap($column->columnName);
|
||||
$sqlSelect = $this->parseTableName($column->sqlSelect, $tables['base']);
|
||||
$selects[] = DbDongle::raw($sqlSelect . ' as '. $alias);
|
||||
|
||||
/*
|
||||
* Relation column
|
||||
*/
|
||||
if (isset($column->relation)) {
|
||||
$table = $this->model->makeRelation($column->relation)->getTable();
|
||||
$relationType = $this->model->getRelationType($column->relation);
|
||||
$sqlSelect = $this->parseTableName($column->sqlSelect, $table);
|
||||
|
||||
/*
|
||||
* Manipulate a count query for the sub query
|
||||
*/
|
||||
$relationObj = $this->model->{$column->relation}();
|
||||
$countQuery = $relationObj->getRelationCountQuery($relationObj->getRelated()->newQuery(), $query);
|
||||
|
||||
$joinSql = $this->isColumnRelated($column, true)
|
||||
? DbDongle::raw("group_concat(" . $sqlSelect . " separator ', ')")
|
||||
: DbDongle::raw($sqlSelect);
|
||||
|
||||
$joinSql = $countQuery->select($joinSql)->toSql();
|
||||
|
||||
$selects[] = Db::raw("(".$joinSql.") as ".$alias);
|
||||
}
|
||||
/*
|
||||
* Primary column
|
||||
*/
|
||||
else {
|
||||
$sqlSelect = $this->parseTableName($column->sqlSelect, $primaryTable);
|
||||
$selects[] = DbDongle::raw($sqlSelect . ' as '. $alias);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a supplied search term
|
||||
* Apply a supplied search term for primary columns
|
||||
*/
|
||||
if (!empty($this->searchTerm) && ($searchableColumns = $this->getSearchableColumns())) {
|
||||
$query->orWhere(function($innerQuery) use ($searchableColumns, $tables) {
|
||||
$columnsToSearch = [];
|
||||
foreach ($searchableColumns as $column) {
|
||||
|
||||
if (isset($column->sqlSelect)) {
|
||||
$table = (isset($column->relation)) ? $tables[$column->relation] : 'base';
|
||||
$columnName = DbDongle::raw($this->parseTableName($column->sqlSelect, $table));
|
||||
}
|
||||
else
|
||||
$columnName = $tables['base'] . '.' . $column->columnName;
|
||||
|
||||
$columnsToSearch[] = $columnName;
|
||||
}
|
||||
|
||||
$innerQuery->searchWhere($this->searchTerm, $columnsToSearch);
|
||||
if (count($primarySearchable) > 0) {
|
||||
$query->orWhere(function($innerQuery) use ($primarySearchable) {
|
||||
$innerQuery->searchWhere($this->searchTerm, $primarySearchable);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle sorting
|
||||
* Apply sorting
|
||||
*/
|
||||
if ($sortColumn = $this->getSortColumn()) {
|
||||
$query->orderBy($sortColumn, $this->sortDirection);
|
||||
|
|
@ -356,9 +418,7 @@ class Lists extends WidgetBase
|
|||
Event::fire('backend.list.extendQuery', [$this, $query]);
|
||||
$this->fireEvent('list.extendQuery', [$query]);
|
||||
|
||||
// Grouping due to the joinWith() call
|
||||
$query->select($selects);
|
||||
$query->groupBy($this->model->getQualifiedKeyName());
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
|
@ -555,16 +615,33 @@ class Lists extends WidgetBase
|
|||
*/
|
||||
public function getColumnValue($record, $column)
|
||||
{
|
||||
|
||||
$columnName = $column->columnName;
|
||||
|
||||
/*
|
||||
* If the column is a relation, it will be a custom select,
|
||||
* Handle taking name from model attribute.
|
||||
*/
|
||||
if ($column->nameFrom) {
|
||||
if (!array_key_exists($columnName, $record->getRelations()))
|
||||
$value = null;
|
||||
elseif ($this->isColumnRelated($column, true))
|
||||
$value = implode(', ', $record->{$columnName}->lists($column->nameFrom));
|
||||
elseif ($this->isColumnRelated($column))
|
||||
$value = $record->{$columnName}->{$column->nameFrom};
|
||||
else
|
||||
$value = $record->{$column->nameFrom};
|
||||
}
|
||||
/*
|
||||
* Otherwise, if the column is a relation, it will be a custom select,
|
||||
* so prevent the Model from attempting to load the relation
|
||||
* if the value is NULL.
|
||||
*/
|
||||
$columnName = $column->columnName;
|
||||
if ($record->hasRelation($columnName) && array_key_exists($columnName, $record->attributes))
|
||||
$value = $record->attributes[$columnName];
|
||||
else
|
||||
$value = $record->{$columnName};
|
||||
else {
|
||||
if ($record->hasRelation($columnName) && array_key_exists($columnName, $record->attributes))
|
||||
$value = $record->attributes[$columnName];
|
||||
else
|
||||
$value = $record->{$columnName};
|
||||
}
|
||||
|
||||
if (method_exists($this, 'eval'. studly_case($column->type) .'TypeValue'))
|
||||
$value = $this->{'eval'. studly_case($column->type) .'TypeValue'}($record, $column, $value);
|
||||
|
|
@ -962,4 +1039,38 @@ class Lists extends WidgetBase
|
|||
return $this->onRefresh();
|
||||
}
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
/**
|
||||
* Check if column refers to a relation of the model
|
||||
* @param ListColumn $column List column object
|
||||
* @param boolean $multi If set, returns true only if the relation is a "multiple relation type"
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isColumnRelated($column, $multi = false)
|
||||
{
|
||||
if (!isset($column->relation))
|
||||
return false;
|
||||
|
||||
if (!$this->model->hasRelation($column->relation))
|
||||
throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', ['class'=>get_class($this->model), 'relation'=>$column->relation]));
|
||||
|
||||
if (!$multi)
|
||||
return true;
|
||||
|
||||
$relationType = $this->model->getRelationType($column->relation);
|
||||
|
||||
return in_array($relationType, [
|
||||
'hasMany',
|
||||
'belongsToMany',
|
||||
'morphToMany',
|
||||
'morphedByMany',
|
||||
'morphMany',
|
||||
'attachMany',
|
||||
'hasManyThrough'
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -117,7 +117,7 @@ class ReportContainer extends WidgetBase
|
|||
{
|
||||
$sizes = [];
|
||||
for ($i = 1; $i <= 10; $i++)
|
||||
$sizes[$i] = $i < 10 ? $i : $i.' (full width)';
|
||||
$sizes[$i] = $i < 10 ? $i : $i.' (' . Lang::get('backend::lang.dashboard.full_width') . ')';
|
||||
|
||||
$this->vars['sizes'] = $sizes;
|
||||
$this->vars['widgets'] = WidgetManager::instance()->listReportWidgets();
|
||||
|
|
|
|||
|
|
@ -54,13 +54,13 @@
|
|||
data-request="<?= $this->getEventHandler('onApplySetup') ?>"
|
||||
data-dismiss="popup"
|
||||
data-stripe-load-indicator>
|
||||
<?= e(trans('backend::lang.list.apply_changes')) ?>
|
||||
<?= e(trans('backend::lang.form.apply')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
data-dismiss="popup">
|
||||
<?= e(trans('backend::lang.list.cancel')) ?>
|
||||
<?= e(trans('backend::lang.form.cancel')) ?>
|
||||
</button>
|
||||
</div>
|
||||
<?= Form::close() ?>
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label><?= e(trans('backend::lang.dashboard.widget_label')) ?></label>
|
||||
<select class="form-control custom-select" name="className" data-placeholder="please select">
|
||||
<select class="form-control custom-select" name="className" data-placeholder="<?= e(trans('backend::lang.form.select_placeholder')) ?>">
|
||||
<option></option>
|
||||
<?php foreach ($widgets as $className => $widgetInfo):?>
|
||||
<option value="<?= e($className) ?>"><?= isset($widgetInfo['label']) ? e(trans($widgetInfo['label'])) : $className ?></option>
|
||||
|
|
|
|||
|
|
@ -255,7 +255,10 @@ class Controller extends BaseController
|
|||
{
|
||||
$this->loader = new TwigLoader();
|
||||
|
||||
$options = ['auto_reload' => true];
|
||||
$options = [
|
||||
'auto_reload' => true,
|
||||
'debug' => Config::get('app.debug', false),
|
||||
];
|
||||
if (!Config::get('cms.twigNoCache'))
|
||||
$options['cache'] = storage_path().'/twig';
|
||||
|
||||
|
|
@ -548,7 +551,18 @@ class Controller extends BaseController
|
|||
*/
|
||||
public function renderPage()
|
||||
{
|
||||
return $this->pageContents;
|
||||
$contents = $this->pageContents;
|
||||
|
||||
/*
|
||||
* Extensibility
|
||||
*/
|
||||
if ($event = $this->fireEvent('page.render', [$contents], true))
|
||||
return $event;
|
||||
|
||||
if ($event = Event::fire('cms.page.render', [$this, $contents], true))
|
||||
return $event;
|
||||
|
||||
return $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class RequestLogs extends Controller
|
|||
public function onEmptyLog()
|
||||
{
|
||||
RequestLog::truncate();
|
||||
Flash::success(Lang::get('system::lang.event_log.empty_success'));
|
||||
Flash::success(Lang::get('system::lang.request_log.empty_success'));
|
||||
return $this->listRefresh();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,13 +38,21 @@ trait AssetMaker
|
|||
if ($type != null) $type = strtolower($type);
|
||||
$result = null;
|
||||
$reserved = ['build'];
|
||||
$pathCache = [];
|
||||
|
||||
if ($type == null || $type == 'css'){
|
||||
foreach ($this->assets['css'] as $asset) {
|
||||
|
||||
/*
|
||||
* Prevent duplicates
|
||||
*/
|
||||
$path = $this->getAssetEntryBuildPath($asset);
|
||||
if (isset($pathCache[$path])) continue;
|
||||
$pathCache[$path] = true;
|
||||
|
||||
$attributes = HTML::attributes(array_merge([
|
||||
'rel' => 'stylesheet',
|
||||
'href' => $this->getAssetEntryBuildPath($asset)
|
||||
'href' => $path
|
||||
],
|
||||
array_except($asset['attributes'], $reserved)
|
||||
));
|
||||
|
|
@ -56,9 +64,16 @@ trait AssetMaker
|
|||
if ($type == null || $type == 'rss'){
|
||||
foreach ($this->assets['rss'] as $asset) {
|
||||
|
||||
/*
|
||||
* Prevent duplicates
|
||||
*/
|
||||
$path = $this->getAssetEntryBuildPath($asset);
|
||||
if (isset($pathCache[$path])) continue;
|
||||
$pathCache[$path] = true;
|
||||
|
||||
$attributes = HTML::attributes(array_merge([
|
||||
'rel' => 'alternate',
|
||||
'href' => $this->getAssetEntryBuildPath($asset),
|
||||
'href' => $path,
|
||||
'title' => 'RSS',
|
||||
'type' => 'application/rss+xml'
|
||||
],
|
||||
|
|
@ -72,8 +87,15 @@ trait AssetMaker
|
|||
if ($type == null || $type == 'js') {
|
||||
foreach ($this->assets['js'] as $asset) {
|
||||
|
||||
/*
|
||||
* Prevent duplicates
|
||||
*/
|
||||
$path = $this->getAssetEntryBuildPath($asset);
|
||||
if (isset($pathCache[$path])) continue;
|
||||
$pathCache[$path] = true;
|
||||
|
||||
$attributes = HTML::attributes(array_merge([
|
||||
'src' => $this->getAssetEntryBuildPath($asset)
|
||||
'src' => $path
|
||||
],
|
||||
array_except($asset['attributes'], $reserved)
|
||||
));
|
||||
|
|
|
|||
Loading…
Reference in New Issue