mirror of
https://github.com/processwire/processwire.git
synced 2025-08-08 07:47:00 +02:00
Upgrade core to have lazy-loading option for Fields, Templates, Fieldgroups. Collaboration with @thetuningspoon for boot performance improvement on installations with large quantities of fields/templates/fieldgroups. Lazy loading option is enabled by default but can be disabled by setting $config->useLazyLoading=false; in your /site/config.php file.
Co-authored-by: thetuningspoon <mspooner@hey.com>
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
* You may also make up your own configuration options by assigning them
|
||||
* in /site/config.php
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
*
|
||||
@@ -76,12 +76,15 @@ $config->debugIf = '';
|
||||
/**
|
||||
* Tools, and their order, to show in debug mode (admin)
|
||||
*
|
||||
* Options include: pages, api, session, modules, hooks, database, db, timers, user, input, cache, autoload
|
||||
* Options include: pages, api, session, modules, hooks, database, db, timers, user, input, cache, autoload, lazyload
|
||||
*
|
||||
* Note: when used, lazyload option should be placed first beause its results can be affected by later debug tools.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
$config->debugTools = array(
|
||||
//'lazyload',
|
||||
'pages',
|
||||
'api',
|
||||
'session',
|
||||
@@ -194,6 +197,23 @@ $config->useMarkupRegions = false;
|
||||
*/
|
||||
$config->usePageClasses = false;
|
||||
|
||||
/**
|
||||
* Use lazy loading of fields (plus templates and fieldgroups) for faster boot times?
|
||||
*
|
||||
* This delays loading of fields, templates and fieldgroups until they are requested.
|
||||
* This can improve performance on systems with hundreds of fields or templates, as
|
||||
* individual fields, templates/fieldgroups won't get constructed until they are needed.
|
||||
*
|
||||
* Specify `true` to use lazy loading for all types, `false` to disable all lazy loading,
|
||||
* or specify array with one or more of the following for lazy loading only certain types:
|
||||
* `[ 'fields', 'templates', 'fieldgroups' ]`
|
||||
*
|
||||
* @var bool|array
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
$config->useLazyLoading = true;
|
||||
|
||||
/**
|
||||
* Disable all HTTPS requirements?
|
||||
*
|
||||
|
@@ -148,6 +148,7 @@
|
||||
* @property bool $usePoweredBy Use the x-powered-by header? Set to false to disable. #pw-group-system
|
||||
* @property bool $useFunctionsAPI Allow most API variables to be accessed as functions? (see /wire/core/FunctionsAPI.php) #pw-group-system
|
||||
* @property bool $useMarkupRegions Enable support for front-end markup regions? #pw-group-system
|
||||
* @property bool|array $useLazyLoading Delay loading of fields (and templates/fieldgroups) till requested? Can improve performance on systems with lots of fields or templates. #pw-group-system @since 3.0.193
|
||||
* @property bool $usePageClasses Use custom Page classes in `/site/classes/[TemplateName]Page.php`? #pw-group-system @since 3.0.152
|
||||
* @property int $lazyPageChunkSize Chunk size for for $pages->findMany() calls. #pw-group-system
|
||||
*
|
||||
|
@@ -12,7 +12,7 @@
|
||||
* #pw-body Field objects are managed by the `$fields` API variable.
|
||||
* #pw-use-constants
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
* @property int $id Numeric ID of field in the database #pw-group-properties
|
||||
@@ -256,7 +256,6 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*/
|
||||
static protected $lowercaseTables = null;
|
||||
|
||||
|
||||
/**
|
||||
* Set a native setting or a dynamic data property for this Field
|
||||
*
|
||||
@@ -270,40 +269,27 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
|
||||
if($key == 'name') {
|
||||
return $this->setName($value);
|
||||
} else if($key == 'type' && $value) {
|
||||
return $this->setFieldtype($value);
|
||||
} else if($key == 'prevTable') {
|
||||
$this->prevTable = $value;
|
||||
return $this;
|
||||
} else if($key == 'prevName') {
|
||||
$this->prevName = $value;
|
||||
return $this;
|
||||
} else if($key == 'prevFieldtype') {
|
||||
$this->prevFieldtype = $value;
|
||||
return $this;
|
||||
} else if($key == 'flags') {
|
||||
$this->setFlags($value);
|
||||
return $this;
|
||||
} else if($key == 'flagsAdd') {
|
||||
return $this->addFlag($value);
|
||||
} else if($key == 'flagsDel') {
|
||||
return $this->removeFlag($value);
|
||||
} else if($key == 'id') {
|
||||
$value = (int) $value;
|
||||
|
||||
switch($key) {
|
||||
case 'id': $this->settings['id'] = (int) $value; return $this;
|
||||
case 'name': return $this->setName($value);
|
||||
case 'data': return empty($value) ? $this : parent::set($key, $value);
|
||||
case 'type': return ($value ? $this->setFieldtype($value) : $this);
|
||||
case 'label': $this->settings['label'] = $value; return $this;
|
||||
case 'prevTable': $this->prevTable = $value; return $this;
|
||||
case 'prevName': $this->prevName = $value; return $this;
|
||||
case 'prevFieldtype': $this->prevFieldtype = $value; return $this;
|
||||
case 'flags': $this->setFlags($value); return $this;
|
||||
case 'flagsAdd': return $this->addFlag($value);
|
||||
case 'flagsDel': return $this->removeFlag($value);
|
||||
case 'icon': $this->setIcon($value); return $this;
|
||||
case 'editRoles': $this->setRoles('edit', $value); return $this;
|
||||
case 'viewRoles': $this->setRoles('view', $value); return $this;
|
||||
}
|
||||
|
||||
|
||||
if(isset($this->settings[$key])) {
|
||||
$this->settings[$key] = $value;
|
||||
} else if($key == 'icon') {
|
||||
$this->setIcon($value);
|
||||
} else if($key == 'editRoles') {
|
||||
$this->setRoles('edit', $value);
|
||||
} else if($key == 'viewRoles') {
|
||||
$this->setRoles('view', $value);
|
||||
} else if($key == 'useRoles') {
|
||||
} else if($key === 'useRoles') {
|
||||
$flags = $this->flags;
|
||||
if($value) {
|
||||
$flags = $flags | self::flagAccess; // add flag
|
||||
@@ -318,6 +304,26 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set raw setting or other value with no validation/processing
|
||||
*
|
||||
* This is for use when a field is loading and needs no validation.
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function setRawSetting($key, $value) {
|
||||
if($key === 'data') {
|
||||
if(!empty($value)) parent::set($key, $value);
|
||||
} else {
|
||||
$this->settings[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bitmask of flags for the field
|
||||
*
|
||||
@@ -391,29 +397,41 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*
|
||||
*/
|
||||
public function get($key) {
|
||||
|
||||
if($key === 'type' && isset($this->settings['type'])) {
|
||||
$value = $this->settings['type'];
|
||||
if($value) $value->setLastAccessField($this);
|
||||
return $value;
|
||||
|
||||
if($key === 'type') {
|
||||
if(!empty($this->settings['type'])) {
|
||||
$value = $this->settings['type'];
|
||||
if($value) $value->setLastAccessField($this);
|
||||
return $value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
switch($key) {
|
||||
case 'id':
|
||||
case 'name':
|
||||
case 'type':
|
||||
case 'flags':
|
||||
case 'label': return $this->settings[$key];
|
||||
case 'table': return $this->getTable();
|
||||
case 'flagsStr': return $this->wire()->fields->getFlagNames($this->settings['flags'], true);
|
||||
case 'viewRoles':
|
||||
case 'editRoles': return $this->$key;
|
||||
case 'useRoles': return ($this->settings['flags'] & self::flagAccess) ? true : false;
|
||||
case 'prevTable':
|
||||
case 'prevName':
|
||||
case 'prevFieldtype': return $this->$key;
|
||||
case 'icon': return $this->getIcon(true);
|
||||
case 'tags': return $this->getTags(true);
|
||||
case 'tagList': return $this->getTags();
|
||||
}
|
||||
if($key == 'viewRoles') return $this->viewRoles;
|
||||
else if($key == 'editRoles') return $this->editRoles;
|
||||
else if($key == 'table') return $this->getTable();
|
||||
else if($key == 'prevTable') return $this->prevTable;
|
||||
else if($key == 'prevName') return $this->prevName;
|
||||
else if($key == 'prevFieldtype') return $this->prevFieldtype;
|
||||
else if(isset($this->settings[$key])) return $this->settings[$key];
|
||||
else if($key == 'icon') return $this->getIcon(true);
|
||||
else if($key == 'useRoles') return ($this->settings['flags'] & self::flagAccess) ? true : false;
|
||||
else if($key == 'flags') return $this->settings['flags'];
|
||||
else if($key == 'flagsStr') return $this->wire('fields')->getFlagNames($this->settings['flags'], true);
|
||||
else if($key == 'tagList') return $this->getTags();
|
||||
else if($key == 'tags') return $this->getTags(true);
|
||||
|
||||
if(isset($this->settings[$key])) return $this->settings[$key];
|
||||
$value = parent::get($key);
|
||||
|
||||
if($key === 'allowContexts' && !is_array($value)) $value = array();
|
||||
if(is_array($this->trackGets)) $this->trackGets($key);
|
||||
if($this->trackGets && is_array($this->trackGets)) $this->trackGets($key);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
@@ -610,21 +628,25 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*
|
||||
*/
|
||||
public function setName($name) {
|
||||
$name = $this->wire('sanitizer')->fieldName($name);
|
||||
|
||||
if($this->wire('fields')->isNative($name)) {
|
||||
throw new WireException("Field may not be named '$name' because it is a reserved word");
|
||||
$fields = $this->wire()->fields;
|
||||
|
||||
if($fields) {
|
||||
if(!ctype_alnum("$name")) {
|
||||
$name = $this->wire()->sanitizer->fieldName($name);
|
||||
}
|
||||
if($fields->isNative($name)) {
|
||||
throw new WireException("Field may not be named '$name' because it is a reserved word");
|
||||
}
|
||||
if(($f = $fields->get($name)) && $f->id != $this->id) {
|
||||
throw new WireException("Field may not be named '$name' because it is already used by another field ($f->id: $f->name)");
|
||||
}
|
||||
if(strpos($name, '__') !== false) {
|
||||
throw new WireException("Field name '$name' may not have double underscores because this usage is reserved by the core");
|
||||
}
|
||||
}
|
||||
|
||||
if($this->wire('fields') && ($f = $this->wire('fields')->get($name)) && $f->id != $this->id) {
|
||||
throw new WireException("Field may not be named '$name' because it is already used by another field");
|
||||
}
|
||||
|
||||
if(strpos($name, '__') !== false) {
|
||||
throw new WireException("Field name '$name' may not have double underscores because this usage is reserved by the core");
|
||||
}
|
||||
|
||||
if($this->settings['name'] != $name) {
|
||||
|
||||
if(!empty($this->settings['name']) && $this->settings['name'] != $name) {
|
||||
if($this->settings['name'] && ($this->settings['flags'] & Field::flagSystem)) {
|
||||
throw new WireException("You may not change the name of field '{$this->settings['name']}' because it is a system field.");
|
||||
}
|
||||
@@ -657,8 +679,8 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
|
||||
} else if(is_string($type)) {
|
||||
$typeStr = $type;
|
||||
$fieldtypes = $this->wire('fieldtypes'); /** @var Fieldtypes $fieldtypes */
|
||||
if(!$type = $fieldtypes->get($type)) {
|
||||
$type = $this->wire()->fieldtypes->get($type);
|
||||
if(!$type) {
|
||||
$this->error("Fieldtype '$typeStr' does not exist");
|
||||
return $this;
|
||||
}
|
||||
@@ -666,10 +688,13 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
throw new WireException("Invalid field type in call to Field::setFieldType");
|
||||
}
|
||||
|
||||
if(!$this->type || ($this->type->name != $type->name)) {
|
||||
$this->trackChange("type:{$type->name}");
|
||||
if($this->type) $this->prevFieldtype = $this->type;
|
||||
$thisType = $this->settings['type'];
|
||||
|
||||
if($thisType && "$thisType" != "$type") {
|
||||
if($this->trackChanges) $this->trackChange("type:$type");
|
||||
$this->prevFieldtype = $thisType;
|
||||
}
|
||||
|
||||
$this->settings['type'] = $type;
|
||||
|
||||
return $this;
|
||||
@@ -682,7 +707,7 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*
|
||||
* #pw-group-retrieval
|
||||
*
|
||||
* @return Fieldtype|null
|
||||
* @return Fieldtype|null|string
|
||||
* @since 3.0.16 Added for consistency, but all versions can still use $field->type.
|
||||
*
|
||||
*/
|
||||
@@ -796,7 +821,7 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
}
|
||||
}
|
||||
if($type == 'view') {
|
||||
$guestID = $this->wire('config')->guestUserRolePageID;
|
||||
$guestID = $this->wire()->config->guestUserRolePageID;
|
||||
// if guest is present, then that's inclusive of all, no need to store others in viewRoles
|
||||
if(in_array($guestID, $ids)) $ids = array($guestID);
|
||||
if($this->viewRoles != $ids) {
|
||||
@@ -829,7 +854,7 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*
|
||||
*/
|
||||
public function ___viewable(Page $page = null, User $user = null) {
|
||||
return $this->wire('fields')->_hasPermission($this, 'view', $page, $user);
|
||||
return $this->wire()->fields->_hasPermission($this, 'view', $page, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -848,7 +873,7 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*
|
||||
*/
|
||||
public function ___editable(Page $page = null, User $user = null) {
|
||||
return $this->wire('fields')->_hasPermission($this, 'edit', $page, $user);
|
||||
return $this->wire()->fields->_hasPermission($this, 'edit', $page, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -862,11 +887,9 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*
|
||||
*/
|
||||
public function save() {
|
||||
$fields = $this->wire('fields');
|
||||
return $fields->save($this);
|
||||
return $this->wire()->fields->save($this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the number of Fieldgroups this field is used in.
|
||||
*
|
||||
@@ -891,18 +914,29 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*
|
||||
*/
|
||||
public function getFieldgroups($getCount = false) {
|
||||
$fieldgroups = $getCount ? null : $this->wire(new FieldgroupsArray());
|
||||
|
||||
$fieldgroups = $this->wire()->fieldgroups;
|
||||
$items = $getCount ? null : $this->wire(new FieldgroupsArray()); /** @var FieldgroupsArray $items */
|
||||
$count = 0;
|
||||
foreach($this->wire()->fieldgroups as $fieldgroup) {
|
||||
|
||||
/*
|
||||
* note: all fieldgroups load on the foreach($fieldgroups) so this code doesn't seem necessary?
|
||||
if($fieldgroups->useLazy()) {
|
||||
$fieldgroups->loadLazyItemsByValue('fields_id', $this->settings['id']);
|
||||
}
|
||||
*/
|
||||
|
||||
foreach($fieldgroups as $fieldgroup) {
|
||||
foreach($fieldgroup as $field) {
|
||||
if($field->id == $this->id) {
|
||||
if($fieldgroups) $fieldgroups->add($fieldgroup);
|
||||
if($items) $items->add($fieldgroup);
|
||||
$count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $getCount ? $count : $fieldgroups;
|
||||
|
||||
return $getCount ? $count : $items;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -915,24 +949,26 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*
|
||||
*/
|
||||
public function getTemplates($getCount = false) {
|
||||
$templates = $this->wire()->templates;
|
||||
if($getCount) {
|
||||
$count = 0;
|
||||
foreach($this->templates as $template) {
|
||||
foreach($templates as $template) {
|
||||
if($template->hasField($this)) $count++;
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
$templates = $this->wire(new TemplatesArray());
|
||||
/** @var TemplatesArray $items */
|
||||
$items = $this->wire(new TemplatesArray());
|
||||
$fieldgroups = $this->getFieldgroups();
|
||||
foreach($this->templates as $template) {
|
||||
foreach($templates as $template) {
|
||||
foreach($fieldgroups as $fieldgroup) {
|
||||
if($template->fieldgroups_id == $fieldgroup->id) {
|
||||
$templates->add($template);
|
||||
$items->add($template);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $templates;
|
||||
return $items;
|
||||
}
|
||||
|
||||
|
||||
@@ -1196,7 +1232,9 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*
|
||||
*/
|
||||
public function getTable() {
|
||||
if(is_null(self::$lowercaseTables)) self::$lowercaseTables = $this->config->dbLowercaseTables ? true : false;
|
||||
if(self::$lowercaseTables === null) {
|
||||
self::$lowercaseTables = $this->wire()->config->dbLowercaseTables ? true : false;
|
||||
}
|
||||
if(!empty($this->setTable)) {
|
||||
$table = $this->setTable;
|
||||
} else {
|
||||
@@ -1217,7 +1255,7 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*
|
||||
*/
|
||||
public function setTable($table = null) {
|
||||
$table = empty($table) ? '' : $this->wire('sanitizer')->fieldName($table);
|
||||
$table = empty($table) ? '' : $this->wire()->sanitizer->fieldName($table);
|
||||
$this->setTable = $table;
|
||||
}
|
||||
|
||||
@@ -1390,10 +1428,12 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
*/
|
||||
public function setIcon($icon) {
|
||||
// store the non-prefixed version
|
||||
if(strpos($icon, 'icon-') === 0) $icon = str_replace('icon-', '', $icon);
|
||||
if(strpos($icon, 'fa-') === 0) $icon = str_replace('fa-', '', $icon);
|
||||
$icon = $this->wire('sanitizer')->pageName($icon);
|
||||
parent::set('icon', $icon);
|
||||
if(strlen("$icon")) {
|
||||
if(strpos($icon, 'icon-') === 0) $icon = str_replace('icon-', '', $icon);
|
||||
if(strpos($icon, 'fa-') === 0) $icon = str_replace('fa-', '', $icon);
|
||||
$icon = $this->wire()->sanitizer->pageName($icon);
|
||||
}
|
||||
parent::set('icon', "$icon");
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -1446,7 +1486,7 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
if($this->tagList !== $tagList) {
|
||||
$this->tagList = $tagList;
|
||||
parent::set('tags', implode(' ', $tagList));
|
||||
$this->wire('fields')->getTags('reset');
|
||||
$this->wire()->fields->getTags('reset');
|
||||
}
|
||||
return $tagList;
|
||||
}
|
||||
@@ -1509,9 +1549,9 @@ class Field extends WireData implements Saveable, Exportable {
|
||||
if(is_string($options)) $options = array('find' => $options);
|
||||
if(is_bool($options)) $options = array('http' => $options);
|
||||
if(!is_array($options)) $options = array();
|
||||
$url = $this->wire('config')->urls(empty($options['http']) ? 'admin' : 'httpAdmin');
|
||||
$url = $this->wire()->config->urls(empty($options['http']) ? 'admin' : 'httpAdmin');
|
||||
$url .= "setup/field/edit?id=$this->id";
|
||||
if(!empty($options['find'])) $url .= '#find-' . $this->wire('sanitizer')->fieldName($options['find']);
|
||||
if(!empty($options['find'])) $url .= '#find-' . $this->wire()->sanitizer->fieldName($options['find']);
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
@@ -13,7 +13,7 @@
|
||||
* are separated in the API in case want want to have fieldgroups used by
|
||||
* multiple templates in the future (like ProcessWire 1.x).
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
* @property int $id Fieldgroup database ID #pw-group-retrieval
|
||||
@@ -37,7 +37,7 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
|
||||
protected $settings = array(
|
||||
'id' => 0,
|
||||
'name' => '',
|
||||
);
|
||||
);
|
||||
|
||||
/**
|
||||
* Any fields that were removed from this instance are noted so that Fieldgroups::save() can delete unused data
|
||||
@@ -122,10 +122,12 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
|
||||
*
|
||||
*/
|
||||
public function add($field) {
|
||||
if(!is_object($field)) $field = $this->wire('fields')->get($field);
|
||||
if(!is_object($field)) $field = $this->wire()->fields->get($field);
|
||||
|
||||
if($field && $field instanceof Field) {
|
||||
if(!$field->id) throw new WireException("You must save field '$field' before adding to Fieldgroup '{$this->name}'");
|
||||
if(!$field->id) {
|
||||
throw new WireException("You must save field '$field' before adding to Fieldgroup '$this->name'");
|
||||
}
|
||||
parent::add($field);
|
||||
} else {
|
||||
// throw new WireException("Unable to add field '$field' to Fieldgroup '{$this->name}'");
|
||||
@@ -371,7 +373,10 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
|
||||
if($item) $this->add($item);
|
||||
if(!empty($row['data'])) {
|
||||
// set field context for this fieldgroup
|
||||
$this->fieldContexts[(int)$item] = wireDecodeJSON($row['data']);
|
||||
$data = $row['data'];
|
||||
if(is_string($data)) $data = wireDecodeJSON($data);
|
||||
if(!is_array($data)) $row['data'] = array();
|
||||
$this->fieldContexts[(int) "$item"] = $data;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
@@ -392,15 +397,17 @@ class Fieldgroup extends WireArray implements Saveable, Exportable, HasLookupIte
|
||||
|
||||
if($key == 'data') return $this; // we don't have a data field here
|
||||
|
||||
if($key == 'id') {
|
||||
if($key === 'id') {
|
||||
$value = (int) $value;
|
||||
|
||||
} else if($key == 'name') {
|
||||
$value = $this->wire('sanitizer')->name($value);
|
||||
} else if($key === 'name') {
|
||||
$value = $this->wire()->sanitizer->templateName($value);
|
||||
}
|
||||
|
||||
if(isset($this->settings[$key])) {
|
||||
if($this->settings[$key] !== $value) $this->trackChange($key, $this->settings[$key], $value);
|
||||
if($this->trackChanges && $this->settings[$key] !== $value) {
|
||||
$this->trackChange($key, $this->settings[$key], $value);
|
||||
}
|
||||
$this->settings[$key] = $value;
|
||||
|
||||
} else {
|
||||
|
@@ -23,20 +23,19 @@
|
||||
class Fieldgroups extends WireSaveableItemsLookup {
|
||||
|
||||
/**
|
||||
* Instances of FieldgroupsArray
|
||||
* Instance of FieldgroupsArray
|
||||
*
|
||||
* @var FieldgroupsArray
|
||||
*
|
||||
*/
|
||||
protected $fieldgroupsArray;
|
||||
protected $fieldgroupsArray = null;
|
||||
|
||||
/**
|
||||
* Init
|
||||
*
|
||||
*/
|
||||
public function init() {
|
||||
$this->fieldgroupsArray = $this->wire(new FieldgroupsArray());
|
||||
$this->load($this->fieldgroupsArray);
|
||||
$this->getWireArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +47,7 @@ class Fieldgroups extends WireSaveableItemsLookup {
|
||||
*/
|
||||
protected function getLoadQuery($selectors = null) {
|
||||
$query = parent::getLoadQuery($selectors);
|
||||
$lookupTable = $this->wire('database')->escapeTable($this->getLookupTable());
|
||||
$lookupTable = $this->wire()->database->escapeTable($this->getLookupTable());
|
||||
$query->select("$lookupTable.data"); // QA
|
||||
return $query;
|
||||
}
|
||||
@@ -76,6 +75,19 @@ class Fieldgroups extends WireSaveableItemsLookup {
|
||||
*
|
||||
*/
|
||||
public function getAll() {
|
||||
return $this->getWireArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WireArray|FieldgroupsArray
|
||||
*
|
||||
*/
|
||||
public function getWireArray() {
|
||||
if($this->fieldgroupsArray === null) {
|
||||
$this->fieldgroupsArray = new FieldgroupsArray();
|
||||
$this->wire($this->fieldgroupsArray);
|
||||
$this->load($this->fieldgroupsArray);
|
||||
}
|
||||
return $this->fieldgroupsArray;
|
||||
}
|
||||
|
||||
@@ -119,7 +131,14 @@ class Fieldgroups extends WireSaveableItemsLookup {
|
||||
*
|
||||
*/
|
||||
public function getNumTemplates(Fieldgroup $fieldgroup) {
|
||||
return count($this->getTemplates($fieldgroup));
|
||||
$templates = $this->wire()->templates;
|
||||
$num = 0;
|
||||
|
||||
foreach($templates->getAllValues('fieldgroups_id', 'id') as $templateId => $fieldgroupId) {
|
||||
if($fieldgroupId == $fieldgroup->id) $num++;
|
||||
}
|
||||
|
||||
return $num;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,11 +149,59 @@ class Fieldgroups extends WireSaveableItemsLookup {
|
||||
*
|
||||
*/
|
||||
public function getTemplates(Fieldgroup $fieldgroup) {
|
||||
$templates = $this->wire(new TemplatesArray());
|
||||
foreach($this->wire('templates') as $tpl) {
|
||||
if($tpl->fieldgroup->id == $fieldgroup->id) $templates->add($tpl);
|
||||
$templates = $this->wire()->templates;
|
||||
$items = $this->wire(new TemplatesArray()); /** @var TemplatesArray $items */
|
||||
|
||||
foreach($templates->getAllValues('fieldgroups_id', 'id') as $templateId => $fieldgroupId) {
|
||||
if($fieldgroupId == $fieldgroup->id) {
|
||||
$template = $templates->get($templateId);
|
||||
$items->add($template);
|
||||
}
|
||||
}
|
||||
return $templates;
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all field names used by given fieldgroup
|
||||
*
|
||||
* Use this when you want to identify the field names (or IDs) without loading the fieldgroup or fields in it.
|
||||
*
|
||||
* @param string|int|Fieldgroup $fieldgroup Fieldgroup name, ID or object
|
||||
* @return array Returned array of field names indexed by field ID
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function getFieldNames($fieldgroup) {
|
||||
$fieldNames = array();
|
||||
$useLazy = $this->useLazy();
|
||||
if(!$useLazy && !is_object($fieldgroup)) $fieldgroup = $this->get($fieldgroup);
|
||||
if($fieldgroup instanceof Fieldgroup) {
|
||||
foreach($fieldgroup as $field) {
|
||||
$fieldNames[$field->id] = $field->name;
|
||||
}
|
||||
return $fieldNames;
|
||||
}
|
||||
$fieldIds = array();
|
||||
if(ctype_digit("$fieldgroup") && $useLazy) {
|
||||
foreach(array_keys($this->lazyItems) as $key) {
|
||||
$row = &$this->lazyItems[$key];
|
||||
if("$row[id]" === "$fieldgroup" && $row['fields_id']) {
|
||||
$fieldIds[] = (int) $row['fields_id'];
|
||||
}
|
||||
}
|
||||
} else if($fieldgroup) {
|
||||
foreach(array_keys($this->lazyItems) as $key) {
|
||||
$row = &$this->lazyItems[$key];
|
||||
if("$row[name]" === "$fieldgroup" && $row['fields_id']) {
|
||||
$fieldIds[] = (int) $row['fields_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if(count($fieldIds)) {
|
||||
$fieldNames = $this->wire()->fields->getAllValues('name', 'id', 'id', $fieldIds);
|
||||
}
|
||||
return $fieldNames;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -5,7 +5,7 @@
|
||||
*
|
||||
* Manages collection of ALL Field instances, not specific to any particular Fieldgroup
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
* #pw-summary Manages all custom fields in ProcessWire, independently of any Fieldgroup.
|
||||
@@ -34,6 +34,8 @@ class Fields extends WireSaveableItems {
|
||||
|
||||
/**
|
||||
* Instance of FieldsArray
|
||||
*
|
||||
* @var FieldsArray
|
||||
*
|
||||
*/
|
||||
protected $fieldsArray = null;
|
||||
@@ -96,6 +98,12 @@ class Fields extends WireSaveableItems {
|
||||
'_custom',
|
||||
);
|
||||
|
||||
/**
|
||||
* Flag names in format [ flagInt => 'flagName' ]
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $flagNames = array();
|
||||
|
||||
/**
|
||||
@@ -120,12 +128,18 @@ class Fields extends WireSaveableItems {
|
||||
*/
|
||||
protected $tableTools = null;
|
||||
|
||||
/**
|
||||
* @var Fieldtypes|null
|
||||
*
|
||||
*/
|
||||
protected $fieldtypes = null;
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->fieldsArray = new FieldsArray();
|
||||
parent::__construct();
|
||||
$this->flagNames = array(
|
||||
Field::flagAutojoin => 'autojoin',
|
||||
Field::flagGlobal => 'global',
|
||||
@@ -138,7 +152,9 @@ class Fields extends WireSaveableItems {
|
||||
Field::flagSystemOverride => 'system-override',
|
||||
);
|
||||
// convert so that keys are names so that isset() can be used rather than in_array()
|
||||
if(isset(self::$nativeNamesSystem[0])) self::$nativeNamesSystem = array_flip(self::$nativeNamesSystem);
|
||||
if(isset(self::$nativeNamesSystem[0])) {
|
||||
self::$nativeNamesSystem = array_flip(self::$nativeNamesSystem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,8 +164,7 @@ class Fields extends WireSaveableItems {
|
||||
*
|
||||
*/
|
||||
public function init() {
|
||||
$this->wire($this->fieldsArray);
|
||||
$this->load($this->fieldsArray);
|
||||
$this->getWireArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,44 +189,94 @@ class Fields extends WireSaveableItems {
|
||||
public function makeItem(array $a = array()) {
|
||||
|
||||
if(empty($a['type'])) return parent::makeItem($a);
|
||||
|
||||
/** @var Fieldtypes $fieldtypes */
|
||||
$fieldtypes = $this->wire('fieldtypes');
|
||||
if(!$fieldtypes) return parent::makeItem($a);
|
||||
if($this->fieldtypes === null) $this->fieldtypes = $this->wire()->fieldtypes;
|
||||
if(!$this->fieldtypes) return parent::makeItem($a);
|
||||
|
||||
/** @var Fieldtype $fieldtype */
|
||||
$fieldtype = $fieldtypes->get($a['type']);
|
||||
if(!$fieldtype) return parent::makeItem($a);
|
||||
$fieldtype = $this->fieldtypes->get($a['type']);
|
||||
if(!$fieldtype) {
|
||||
if($this->useLazy) {
|
||||
$this->error("Fieldtype module '$a[type]' for field '$a[name]' is missing");
|
||||
$fieldtype = $this->fieldtypes->get('FieldtypeText');
|
||||
} else {
|
||||
return parent::makeItem($a);
|
||||
}
|
||||
}
|
||||
|
||||
$class = $fieldtype->getFieldClass($a);
|
||||
if(empty($class) || $class === 'Field') return parent::makeItem($a);
|
||||
|
||||
if(strpos($class, "\\") === false) $class = wireClassName($class, true);
|
||||
if(!class_exists($class)) return parent::makeItem($a);
|
||||
$a['type'] = $fieldtype;
|
||||
$a['id'] = (int) $a['id'];
|
||||
$a['flags'] = (int) $a['flags'];
|
||||
|
||||
$class = $fieldtype->getFieldClass($a);
|
||||
|
||||
if(empty($class) || $class === 'Field') {
|
||||
$class = '';
|
||||
} else if(strpos($class, "\\") === false) {
|
||||
$class = wireClassName($class, true);
|
||||
if(!class_exists($class)) $class = '';
|
||||
}
|
||||
|
||||
if(empty($class)) {
|
||||
$field = new Field();
|
||||
} else {
|
||||
$field = new $class(); /** @var Field $field */
|
||||
}
|
||||
|
||||
/** @var Field $field */
|
||||
$field = new $class();
|
||||
$this->wire($field);
|
||||
$field->setTrackChanges(false);
|
||||
|
||||
foreach($a as $key => $value) {
|
||||
$field->$key = $value;
|
||||
$field->setRawSetting($key, $value);
|
||||
}
|
||||
|
||||
$field->resetTrackChanges(true);
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Saveable item from a raw array ($row) and add it to $items
|
||||
*
|
||||
* @param array $row
|
||||
* @param WireArray|null $items
|
||||
* @return Saveable|WireData|Wire
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
protected function initItem(array &$row, WireArray $items = null) {
|
||||
/** @var Field $item */
|
||||
$item = parent::initItem($row, $items);
|
||||
$fieldtype = $item ? $item->type : null;
|
||||
if($fieldtype) $fieldtype->initField($item);
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Per WireSaveableItems interface, return all available Field instances
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @return FieldsArray
|
||||
* @return FieldsArray|WireArray
|
||||
*
|
||||
*/
|
||||
public function getAll() {
|
||||
return $this->fieldsArray;
|
||||
return $this->getWireArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WireArray container that items are stored in
|
||||
*
|
||||
* @return WireArray
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function getWireArray() {
|
||||
if($this->fieldsArray === null) {
|
||||
$this->fieldsArray = new FieldsArray();
|
||||
$this->wire($this->fieldsArray);
|
||||
$this->load($this->fieldsArray);
|
||||
}
|
||||
return $this->fieldsArray;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,22 +419,29 @@ class Fields extends WireSaveableItems {
|
||||
*/
|
||||
public function ___delete(Saveable $item) {
|
||||
|
||||
if(!$this->fieldsArray->isValidItem($item)) throw new WireException("Fields::delete(item) only accepts items of type Field");
|
||||
if(!$this->getWireArray()->isValidItem($item)) {
|
||||
throw new WireException("Fields::delete(item) only accepts items of type Field");
|
||||
}
|
||||
|
||||
// if the field doesn't have an ID, so it's not one that came from the DB
|
||||
if(!$item->id) throw new WireException("Unable to delete from '" . $item->getTable() . "' for field that doesn't exist in fields table");
|
||||
if(!$item->id) {
|
||||
$table = $item->getTable();
|
||||
throw new WireException("Unable to delete from '$table' for field that doesn't exist in fields table");
|
||||
}
|
||||
|
||||
// if it's in use by any fieldgroups, then we don't allow it to be deleted
|
||||
if($item->numFieldgroups()) {
|
||||
$names = $item->getFieldgroups()->implode("', '", (string) "name");
|
||||
throw new WireException("Unable to delete field '{$item->name}' because it is in use by these fieldgroups: '$names'");
|
||||
throw new WireException("Unable to delete field '$item->name' because it is in use by these fieldgroups: '$names'");
|
||||
}
|
||||
|
||||
// if it's a system field, it may not be deleted
|
||||
if($item->flags & Field::flagSystem) throw new WireException("Unable to delete field '{$item->name}' because it is a system field.");
|
||||
if($item->flags & Field::flagSystem) {
|
||||
throw new WireException("Unable to delete field '$item->name' because it is a system field.");
|
||||
}
|
||||
|
||||
// delete entries in fieldgroups_fields table. Not really necessary since the above exception prevents this, but here in case that changes.
|
||||
$this->wire('fieldgroups')->deleteField($item);
|
||||
$this->wire()->fieldgroups->deleteField($item);
|
||||
|
||||
// drop the field's table
|
||||
if($item->type) $item->type->deleteField($item);
|
||||
@@ -990,6 +1062,102 @@ class Fields extends WireSaveableItems {
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find fields by type
|
||||
*
|
||||
* @param string|Fieldtype $type Fieldtype class name or object
|
||||
* @param array $options
|
||||
* - `inherit` (bool): Also find types that inherit from given type? (default=true)
|
||||
* - `valueType` (string): Value type to return, one of 'field', 'id', or 'name' (default='field')
|
||||
* - `indexType` (string): Index type to use, one of 'name', 'id', or '' blank for non-associative array (default='name')
|
||||
* @return array|Field[]
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function findByType($type, array $options = array()) {
|
||||
|
||||
$defaults = array(
|
||||
'inherit' => true, // also find fields using type inherited from given type or interface?
|
||||
'valueType' => 'field', // one of 'field', 'id', or 'name'
|
||||
'indexType' => 'name', // one of 'name', 'id', or '' blank for non associative array
|
||||
);
|
||||
|
||||
$options = array_merge($defaults, $options);
|
||||
$valueType = $options['valueType'];
|
||||
$indexType = $options['indexType'];
|
||||
$inherit = $options['inherit'];
|
||||
$matchTypes = array();
|
||||
$matches = array();
|
||||
|
||||
if($inherit) {
|
||||
$typeName = wireClassName($type, true);
|
||||
foreach($this->wire()->fieldtypes as $fieldtype) {
|
||||
if($fieldtype instanceof $typeName) $matchTypes[$fieldtype->className()] = true;
|
||||
}
|
||||
} else {
|
||||
$typeName = wireClassName($type);
|
||||
$matchTypes[$typeName] = true;
|
||||
}
|
||||
|
||||
foreach($this->getWireArray() as $field) {
|
||||
$fieldtype = $field->type;
|
||||
|
||||
if(!$fieldtype) continue;
|
||||
if(!isset($matchTypes[$fieldtype->className()])) continue;
|
||||
|
||||
if($valueType === 'field') {
|
||||
$value = $field;
|
||||
} else if($valueType === 'name') {
|
||||
$value = $field->name;
|
||||
} else {
|
||||
$value = $field->id;
|
||||
}
|
||||
if($indexType) {
|
||||
$index = $field->get($options['indexType']);
|
||||
$matches[$index] = $value;
|
||||
} else {
|
||||
$matches[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if($this->useLazy()) {
|
||||
foreach(array_keys($this->lazyItems) as $key) {
|
||||
if(!isset($this->lazyItems[$key])) continue;
|
||||
$row = $this->lazyItems[$key];
|
||||
if(empty($row['type'])) continue;
|
||||
$type = $row['type'];
|
||||
if(!isset($matchTypes[$type])) continue;
|
||||
if($valueType === 'field') {
|
||||
$value = $this->getLazy((int) $row['id']);
|
||||
} else if($valueType === 'name') {
|
||||
$value = $row['name'];
|
||||
} else {
|
||||
$value = $row['id'];
|
||||
}
|
||||
if($indexType) {
|
||||
$index = isset($data[$indexType]) ? $row[$indexType] : $row['id'];
|
||||
$matches[$index] = $value;
|
||||
} else {
|
||||
$matches[] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all field names
|
||||
*
|
||||
* @param string $indexType One of 'name', 'id' or blank string for no index (default='')
|
||||
* @return array
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function getAllNames($indexType = '') {
|
||||
return $this->getAllValues('name', $indexType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all flag names or get all flag names for given flags or Field
|
||||
*
|
||||
|
@@ -99,6 +99,7 @@ abstract class Fieldtype extends WireData implements Module {
|
||||
*
|
||||
*/
|
||||
public function init() { }
|
||||
public function ready() { }
|
||||
|
||||
/**
|
||||
* Set last access field
|
||||
@@ -774,6 +775,18 @@ abstract class Fieldtype extends WireData implements Module {
|
||||
if($query && $table && $field && $subfield && $desc) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when field of this type is initialized at boot or after lazy loaded
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param Field $field
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function initField(Field $field) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new field table in the database.
|
||||
|
@@ -291,6 +291,7 @@ class ProcessWire extends Wire {
|
||||
$config->setWire($this);
|
||||
|
||||
$this->debug = $config->debug;
|
||||
if($this->debug) Debug::timer('all');
|
||||
$this->instanceID = self::addInstance($this);
|
||||
$this->setWire($this);
|
||||
|
||||
@@ -552,9 +553,11 @@ class ProcessWire extends Wire {
|
||||
$pages = $this->wire('pages', new Pages($this), true);
|
||||
|
||||
$this->initVar('fieldtypes', $fieldtypes);
|
||||
if($this->debug) Debug::timer('init.fields.templates.fieldgroups');
|
||||
$this->initVar('fields', $fields);
|
||||
$this->initVar('fieldgroups', $fieldgroups);
|
||||
$this->initVar('templates', $templates);
|
||||
if($this->debug) Debug::saveTimer('init.fields.templates.fieldgroups');
|
||||
$this->initVar('pages', $pages);
|
||||
|
||||
if($this->debug) Debug::timer('boot.load.permissions');
|
||||
|
@@ -781,6 +781,8 @@ class Template extends WireData implements Saveable, Exportable {
|
||||
/**
|
||||
* Set setting value without processing
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @since 3.0.194
|
||||
|
@@ -5,7 +5,7 @@
|
||||
*
|
||||
* Manages and provides access to all the Template instances
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
* #pw-summary Manages and provides access to all the Templates.
|
||||
@@ -36,7 +36,7 @@ class Templates extends WireSaveableItems {
|
||||
* @var TemplatesArray
|
||||
*
|
||||
*/
|
||||
protected $templatesArray;
|
||||
protected $templatesArray = null;
|
||||
|
||||
/**
|
||||
* Templates that had changed files during this request
|
||||
@@ -61,9 +61,9 @@ class Templates extends WireSaveableItems {
|
||||
*
|
||||
*/
|
||||
public function __construct(Fieldgroups $fieldgroups) {
|
||||
parent::__construct();
|
||||
$fieldgroups->wire($this);
|
||||
$this->fieldgroups = $fieldgroups;
|
||||
$this->templatesArray = $this->wire(new TemplatesArray());
|
||||
$this->fieldgroups = $fieldgroups;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,8 +73,7 @@ class Templates extends WireSaveableItems {
|
||||
*
|
||||
*/
|
||||
public function init() {
|
||||
$this->wire($this->templatesArray);
|
||||
$this->load($this->templatesArray);
|
||||
$this->getWireArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,6 +83,23 @@ class Templates extends WireSaveableItems {
|
||||
*
|
||||
*/
|
||||
public function getAll() {
|
||||
return $this->getWireArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WireArray container that items are stored in
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @return WireArray
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function getWireArray() {
|
||||
if($this->templatesArray === null) {
|
||||
$this->templatesArray = $this->wire(new TemplatesArray());
|
||||
$this->load($this->templatesArray);
|
||||
}
|
||||
return $this->templatesArray;
|
||||
}
|
||||
|
||||
@@ -99,18 +115,47 @@ class Templates extends WireSaveableItems {
|
||||
*/
|
||||
public function makeItem(array $a = array()) {
|
||||
|
||||
/** @var Template $item */
|
||||
/** @var Template $template */
|
||||
$template = $this->wire(new Template());
|
||||
$template->loaded(false);
|
||||
|
||||
if(!empty($a['data'])) {
|
||||
if(is_string($a['data'])) $a['data'] = $this->decodeData($a['data']);
|
||||
} else {
|
||||
unset($a['data']);
|
||||
}
|
||||
|
||||
foreach(array('id', 'name', 'fieldgroups_id', 'flags', 'cache_time') as $key) {
|
||||
if(!isset($a[$key])) continue;
|
||||
$value = $key === 'name' ? $a[$key] : (int) $a[$key];
|
||||
$template->setRaw($key, $value);
|
||||
unset($a[$key]);
|
||||
}
|
||||
|
||||
foreach($a as $key => $value) {
|
||||
$template->set($key, $value);
|
||||
}
|
||||
|
||||
$template->loaded(true);
|
||||
$template->resetTrackChanges(true);
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all lazy items
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function loadAllLazyItems() {
|
||||
if(!$this->useLazy()) return;
|
||||
$this->wire()->fieldgroups->loadAllLazyItems();
|
||||
parent::loadAllLazyItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new blank item
|
||||
*
|
||||
@@ -200,8 +245,8 @@ class Templates extends WireSaveableItems {
|
||||
*
|
||||
*/
|
||||
public function get($key) {
|
||||
if($key == 'path') return $this->wire('config')->paths->templates;
|
||||
$value = $this->templatesArray->get($key);
|
||||
if($key === 'path') return $this->wire()->config->paths->templates;
|
||||
$value = $this->getWireArray()->get($key);
|
||||
if(is_null($value)) $value = parent::get($key);
|
||||
return $value;
|
||||
}
|
||||
@@ -224,14 +269,22 @@ class Templates extends WireSaveableItems {
|
||||
|
||||
$isNew = $item->id < 1;
|
||||
|
||||
if(!$item->fieldgroup) throw new WireException("Template '$item' cannot be saved because it has no fieldgroup assigned");
|
||||
if(!$item->fieldgroup->id) throw new WireException("You must save Fieldgroup '{$item->fieldgroup->name}' before adding to Template '{$item}'");
|
||||
if(!$item->fieldgroup) {
|
||||
throw new WireException("Template '$item' cannot be saved because it has no fieldgroup assigned");
|
||||
}
|
||||
if(!$item->fieldgroup->id) {
|
||||
throw new WireException("You must save Fieldgroup '{$item->fieldgroup->name}' before adding to Template '{$item}'");
|
||||
}
|
||||
|
||||
$rolesChanged = $item->isChanged('useRoles');
|
||||
|
||||
if($this->wire('pages')->get("/")->template->id == $item->id) {
|
||||
if(!$item->useRoles) throw new WireException("Template '{$item}' is used by the homepage and thus must manage access");
|
||||
if(!$item->hasRole("guest")) throw new WireException("Template '{$item}' is used by the homepage and thus must have the 'guest' role assigned.");
|
||||
if($this->wire()->pages->get('/')->template->id == $item->id) {
|
||||
if(!$item->useRoles) {
|
||||
throw new WireException("Template '{$item}' is used by the homepage and thus must manage access");
|
||||
}
|
||||
if(!$item->hasRole('guest')) {
|
||||
throw new WireException("Template '{$item}' is used by the homepage and thus must have the 'guest' role assigned.");
|
||||
}
|
||||
}
|
||||
|
||||
if(!$item->isChanged('modified')) $item->modified = time();
|
||||
|
@@ -44,11 +44,27 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
*/
|
||||
abstract public function makeBlankItem();
|
||||
|
||||
/**
|
||||
* Get WireArray container that items are stored in
|
||||
*
|
||||
* This is the same as the getAll() method except that it is guaranteed not to load
|
||||
* additional items as part of the call.
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @return WireArray
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function getWireArray() {
|
||||
return $this->getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an item and populate with given data
|
||||
*
|
||||
* @param array $a Associative array of data to populate
|
||||
* @return Saveable|Wire
|
||||
* @return Saveable|WireData|Wire
|
||||
* @throws WireException
|
||||
* @since 3.0.146
|
||||
*
|
||||
@@ -71,7 +87,6 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
*/
|
||||
abstract public function getTable();
|
||||
|
||||
|
||||
/**
|
||||
* Return the default name of the field that load() should sort by (default is none)
|
||||
*
|
||||
@@ -93,7 +108,7 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
*/
|
||||
protected function getLoadQuerySelectors($selectors, DatabaseQuerySelect $query) {
|
||||
|
||||
$database = $this->wire('database');
|
||||
$database = $this->wire()->database;
|
||||
|
||||
if(is_object($selectors) && $selectors instanceof Selectors) {
|
||||
// iterable selectors
|
||||
@@ -165,7 +180,7 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
|
||||
$item = $this->makeBlankItem();
|
||||
$fields = array_keys($item->getTableData());
|
||||
$database = $this->wire('database');
|
||||
$database = $this->wire()->database;
|
||||
|
||||
$table = $database->escapeTable($this->getTable());
|
||||
|
||||
@@ -195,23 +210,24 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
*/
|
||||
protected function ___load(WireArray $items, $selectors = null) {
|
||||
|
||||
/** @var WireDatabasePDO $database */
|
||||
$database = $this->wire('database');
|
||||
$useLazy = $this->useLazy();
|
||||
$database = $this->wire()->database;
|
||||
$sql = $this->getLoadQuery($selectors)->getQuery();
|
||||
|
||||
$query = $database->prepare($sql);
|
||||
|
||||
$query = $database->prepare($sql);
|
||||
$query->execute();
|
||||
|
||||
while($row = $query->fetch(\PDO::FETCH_ASSOC)) {
|
||||
if(isset($row['data'])) {
|
||||
if($row['data']) {
|
||||
$row['data'] = $this->decodeData($row['data']);
|
||||
} else {
|
||||
unset($row['data']);
|
||||
}
|
||||
$rows = $query->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$n = 0;
|
||||
|
||||
foreach($rows as $row) {
|
||||
if($useLazy) {
|
||||
$this->lazyItems[$n] = $row;
|
||||
$this->lazyNameIndex[$row['name']] = $n;
|
||||
$this->lazyIdIndex[$row['id']] = $n;
|
||||
$n++;
|
||||
} else {
|
||||
$this->initItem($row, $items);
|
||||
}
|
||||
$item = $this->makeItem($row);
|
||||
if($item) $items->add($item);
|
||||
}
|
||||
|
||||
$query->closeCursor();
|
||||
@@ -220,6 +236,35 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Saveable item from a raw array ($row) and add it to $items
|
||||
*
|
||||
* @param array $row
|
||||
* @param WireArray|null $items
|
||||
* @return Saveable|WireData|Wire
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
protected function initItem(array &$row, WireArray $items = null) {
|
||||
|
||||
if(!empty($row['data'])) {
|
||||
if(is_string($row['data'])) $row['data'] = $this->decodeData($row['data']);
|
||||
} else {
|
||||
unset($row['data']);
|
||||
}
|
||||
|
||||
if($items === null) $items = $this->getWireArray();
|
||||
|
||||
$item = $this->makeItem($row);
|
||||
|
||||
if($item) {
|
||||
if($this->useLazy() && $item->id) $this->unsetLazy($item);
|
||||
$items->add($item);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the given item key/field be saved in the database?
|
||||
*
|
||||
@@ -230,7 +275,7 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
*
|
||||
*/
|
||||
protected function saveItemKey($key) {
|
||||
if($key == 'id') return false;
|
||||
if($key === 'id') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -299,7 +344,7 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
$result = $query->execute();
|
||||
if($result) {
|
||||
$item->id = (int) $database->lastInsertId();
|
||||
$this->getAll()->add($item);
|
||||
$this->getWireArray()->add($item);
|
||||
$this->added($item);
|
||||
}
|
||||
}
|
||||
@@ -334,7 +379,7 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
$database = $this->wire('database');
|
||||
|
||||
$this->deleteReady($item);
|
||||
$this->getAll()->remove($item);
|
||||
$this->getWireArray()->remove($item);
|
||||
$table = $database->escapeTable($this->getTable());
|
||||
|
||||
$query = $database->prepare("DELETE FROM `$table` WHERE id=:id LIMIT 1");
|
||||
@@ -397,32 +442,143 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
*
|
||||
*/
|
||||
public function ___find($selectors) {
|
||||
if($this->useLazy()) $this->loadAllLazyItems();
|
||||
return $this->getAll()->find($selectors);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getIterator() {
|
||||
if($this->useLazy()) $this->loadAllLazyItems();
|
||||
return $this->getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an item
|
||||
*
|
||||
* @param string|int $key
|
||||
* @return array|mixed|null|Page|Saveable|Wire|WireData
|
||||
*
|
||||
*/
|
||||
public function get($key) {
|
||||
return $this->getAll()->get($key);
|
||||
$value = $this->getWireArray()->get($key);
|
||||
if($value === null && $this->useLazy() && $key !== null) $value = $this->getLazy($key);
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function __get($key) {
|
||||
$value = $this->get($key);
|
||||
if(is_null($value)) $value = parent::__get($key);
|
||||
if($value === null) $value = parent::__get($key);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do we have the given item or item by given key?
|
||||
*
|
||||
* @param string|int|Saveable|WireData $item
|
||||
* @return bool
|
||||
*
|
||||
*/
|
||||
public function has($item) {
|
||||
return $this->getAll()->has($item);
|
||||
if($this->useLazy() && !empty($this->lazyItems)) $this->get($item); // ensure lazy item present
|
||||
return $this->getAll()->has($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Isset
|
||||
*
|
||||
* @param string|int $key
|
||||
* @return bool
|
||||
*
|
||||
*/
|
||||
public function __isset($key) {
|
||||
return $this->get($key) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all property values for items
|
||||
*
|
||||
* This is useful for getting all property values without triggering lazy loaded items to load.
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string $valueType|array Name of property value you want to get, or array of them, i.e. 'id', 'name', etc. (default='id')
|
||||
* @param string $indexType One of 'name', 'id' or blank string for no index (default='')
|
||||
* @param string $matchType Optionally match this property, also requires $matchValue argument (default='')
|
||||
* @param string|int|array $matchValue Match this value for $matchType property, use array for OR values (default=null)
|
||||
* @return array
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function getAllValues($valueType = 'id', $indexType = '', $matchType = '', $matchValue = null) {
|
||||
|
||||
$values = array();
|
||||
$useValueArray = is_array($valueType);
|
||||
$matchArray = is_array($matchValue) ? array_flip($matchValue) : false;
|
||||
$items = $this->getWireArray();
|
||||
|
||||
if($this->useLazy()) {
|
||||
foreach($this->lazyItems as $row) {
|
||||
$index = null;
|
||||
if($matchValue !== null) {
|
||||
if($matchArray) {
|
||||
$v = isset($row[$matchType]) ? $row[$matchType] : null;
|
||||
if(!$v === null || !isset($matchArray[$v])) continue;
|
||||
} else {
|
||||
if($row[$matchType] != $matchValue) continue;
|
||||
}
|
||||
}
|
||||
if($indexType) {
|
||||
$index = isset($row[$indexType]) ? $row[$indexType] : $row['id'];
|
||||
}
|
||||
if($useValueArray) {
|
||||
/** @var array $valueType */
|
||||
$value = array();
|
||||
foreach($valueType as $key) {
|
||||
$value[$key] = isset($row[$key]) ? $row[$key] : null;
|
||||
}
|
||||
} else {
|
||||
$value = isset($row[$valueType]) ? $row[$valueType] : null;
|
||||
}
|
||||
if($index !== null) {
|
||||
$values[$index] = $value;
|
||||
} else {
|
||||
$values[] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach($items as $field) {
|
||||
$index = null;
|
||||
if($matchValue !== null) {
|
||||
if($matchArray) {
|
||||
$v = $field->get($matchType);
|
||||
if($v === null || !isset($matchArray[$v])) continue;
|
||||
} else {
|
||||
if($field->get($matchType) != $matchValue) continue;
|
||||
}
|
||||
}
|
||||
if($indexType) {
|
||||
$index = $field->get($indexType);
|
||||
}
|
||||
if($useValueArray) {
|
||||
/** @var array $valueType */
|
||||
$value = array();
|
||||
foreach($valueType as $key) {
|
||||
$value[$key] = $field->get($key);
|
||||
}
|
||||
} else {
|
||||
$value = $field->get($valueType);
|
||||
}
|
||||
if($index !== null) {
|
||||
$values[$index] = $value;
|
||||
} else {
|
||||
$values[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the 'data' portion of the table.
|
||||
*
|
||||
@@ -459,6 +615,11 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
public function useFuel($useFuel = null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**************************************************************************************
|
||||
* HOOKERS
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Hook that runs right before item is to be saved.
|
||||
@@ -561,6 +722,12 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
$this->log("Renamed $oldName to $newName", $item);
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************************
|
||||
* OTHER
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enables use of $apivar('name') or wire()->apivar('name')
|
||||
*
|
||||
@@ -603,5 +770,190 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate {
|
||||
return parent::error($text, $flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* debugInfo PHP 5.6+ magic method
|
||||
*
|
||||
* This is used when you print_r() an object instance.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function __debugInfo() {
|
||||
$info = array(); // parent::__debugInfo();
|
||||
$info['loaded'] = array();
|
||||
$info['notLoaded'] = array();
|
||||
foreach($this->getWireArray() as $item) {
|
||||
/** @var WireData|Saveable $item */
|
||||
$when = $item->get('_lazy');
|
||||
$value = $item->get('name|id');
|
||||
$value = $value ? "$value ($when)" : $item;
|
||||
$info['loaded'][] = $value;
|
||||
}
|
||||
foreach($this->lazyItems as $row) {
|
||||
$value = null;
|
||||
if(isset($row['name'])) $value = $row['name'];
|
||||
if(!$value && isset($row['id'])) $value = $row['id'];
|
||||
if(!$value) $value = &$row;
|
||||
$info['notLoaded'][] = $value;
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**************************************************************************************
|
||||
* LAZY LOADING
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Lazy loaded raw item data from database
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $lazyItems = array(); // [ 0 => [ ... ], 1 => [ ... ], etc. ]
|
||||
protected $lazyNameIndex = array(); // [ 'name' => 123 ] where 123 is key in $lazyItems
|
||||
protected $lazyIdIndex = array(); // [ 3 => 123 ] where 3 is ID and 123 is key in $lazyItems
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*
|
||||
*/
|
||||
protected $useLazy = null;
|
||||
|
||||
|
||||
/**
|
||||
* Use lazy loading for this type?
|
||||
*
|
||||
* @return bool
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function useLazy() {
|
||||
if($this->useLazy !== null) return $this->useLazy;
|
||||
$this->useLazy = $this->wire()->config->useLazyLoading;
|
||||
if(is_array($this->useLazy)) $this->useLazy = in_array(strtolower($this->className()), $this->useLazy);
|
||||
return $this->useLazy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from lazy loading data/indexes
|
||||
*
|
||||
* @param Saveable $item
|
||||
* @return bool
|
||||
*
|
||||
*/
|
||||
protected function unsetLazy(Saveable $item) {
|
||||
if(!isset($this->lazyIdIndex[$item->id])) return false;
|
||||
$key = $this->lazyIdIndex[$item->id];
|
||||
unset($this->lazyItems[$key], $this->lazyNameIndex[$item->name], $this->lazyIdIndex[$item->id]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all pending lazy-loaded items
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
*/
|
||||
public function loadAllLazyItems() {
|
||||
|
||||
if(!$this->useLazy()) return;
|
||||
|
||||
$debug = $this->wire()->config->debug;
|
||||
$items = $this->getWireArray();
|
||||
|
||||
foreach(array_keys($this->lazyItems) as $key) {
|
||||
if(!isset($this->lazyItems[$key])) continue; // required
|
||||
$row = &$this->lazyItems[$key];
|
||||
$item = $this->initItem($row, $items);
|
||||
if($debug) $item->setQuietly('_lazy', '*');
|
||||
}
|
||||
|
||||
$this->lazyItems = array();
|
||||
$this->lazyNameIndex = array();
|
||||
$this->lazyIdIndex = array();
|
||||
|
||||
// if you want to identify what triggered a “load all”, uncomment below:
|
||||
// bd(Debug::backtrace());
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy load items by property value
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string $key i.e. fieldgroups_id
|
||||
* @param string|int $value
|
||||
* @todo I don't think we need this method, but leaving it here temporarily for reference
|
||||
* @deprecated
|
||||
*
|
||||
*/
|
||||
private function loadLazyItemsByValue($key, $value) {
|
||||
|
||||
$debug = $this->wire()->config->debug;
|
||||
$items = $this->getWireArray();
|
||||
|
||||
foreach($this->lazyItems as $lazyKey => $lazyItem) {
|
||||
if($lazyItem[$key] != $value) continue;
|
||||
$item = $this->initItem($lazyItem, $items);
|
||||
unset($this->lazyItems[$lazyKey]);
|
||||
if($debug) $item->setQuietly('_lazy', '=');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a lazy loaded item, companion to get() method
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param string|int $value
|
||||
* @return Saveable|Wire|WireData|null
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
protected function getLazy($value) {
|
||||
|
||||
$property = ctype_digit("$value") ? 'id' : 'name';
|
||||
$value = $property === 'id' ? (int) $value : "$value";
|
||||
$item = null;
|
||||
$lazyItem = null;
|
||||
$lazyKey = null;
|
||||
|
||||
if(!empty($this->lazyIdIndex)) {
|
||||
if($property === 'id') {
|
||||
$index = &$this->lazyIdIndex;
|
||||
} else {
|
||||
$index = &$this->lazyNameIndex;
|
||||
}
|
||||
if(isset($index[$value])) {
|
||||
$lazyKey = $index[$value];
|
||||
$lazyItem = $this->lazyItems[$lazyKey];
|
||||
}
|
||||
} else {
|
||||
foreach($this->lazyItems as $key => $row) {
|
||||
if(!isset($row[$property]) || $row[$property] != $value) continue;
|
||||
$lazyKey = $key;
|
||||
$lazyItem = $row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if($lazyItem) {
|
||||
$item = $this->initItem($lazyItem);
|
||||
$this->getWireArray()->add($item);
|
||||
unset($this->lazyItems[$lazyKey]);
|
||||
if($this->wire()->config->debug) $item->setQuietly('_lazy', '1');
|
||||
}
|
||||
|
||||
if($item === null && $property === 'name' && !ctype_alnum($value)) {
|
||||
if(Selectors::stringHasOperator("$value") || strpos("$value", '|')) {
|
||||
$this->loadAllLazyItems();
|
||||
$item = $this->getWireArray()->get($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -18,6 +18,14 @@ abstract class WireSaveableItemsLookup extends WireSaveableItems {
|
||||
*/
|
||||
abstract public function getLookupTable();
|
||||
|
||||
/**
|
||||
* Cache of value returned from getLookupField() method
|
||||
*
|
||||
* @var string|null
|
||||
*
|
||||
*/
|
||||
protected $lookupField = null;
|
||||
|
||||
/**
|
||||
* If a lookup table should be left joined, this method returns the name of the array field in $data that contains multiple values
|
||||
*
|
||||
@@ -26,9 +34,11 @@ abstract class WireSaveableItemsLookup extends WireSaveableItems {
|
||||
*
|
||||
*/
|
||||
public function getLookupField() {
|
||||
if($this->lookupField) return $this->lookupField;
|
||||
$lookupTable = $this->getLookupTable();
|
||||
if(!$lookupTable) return '';
|
||||
return preg_replace('/_?' . $this->getTable() . '_?/', '', $lookupTable) . '_id';
|
||||
$this->lookupField = preg_replace('/_?' . $this->getTable() . '_?/', '', $lookupTable) . '_id';
|
||||
return $this->lookupField;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,43 +72,84 @@ abstract class WireSaveableItemsLookup extends WireSaveableItems {
|
||||
*/
|
||||
protected function ___load(WireArray $items, $selectors = null) {
|
||||
|
||||
$useLazy = $this->useLazy();
|
||||
$database = $this->wire()->database;
|
||||
$query = $this->getLoadQuery($selectors);
|
||||
$sql = $query->getQuery();
|
||||
|
||||
$this->getLookupField(); // preload
|
||||
|
||||
$stmt = $database->prepare($sql);
|
||||
$stmt->execute();
|
||||
$lookupField = $this->getLookupField();
|
||||
|
||||
while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
|
||||
/** @var HasLookupItems $item */
|
||||
$item = $this->makeBlankItem();
|
||||
$lookupValue = $row[$lookupField];
|
||||
unset($row[$lookupField]);
|
||||
$item->addLookupItem($lookupValue, $row);
|
||||
|
||||
foreach($row as $field => $value) {
|
||||
$item->$field = $value;
|
||||
}
|
||||
|
||||
if($items->has($item)) {
|
||||
// LEFT JOIN is adding more elements of the same item, i.e. from lookup table
|
||||
// if the item is already present in $items, then use the existing one rather
|
||||
// and throw out the one we just created
|
||||
$item = $items->get($item);
|
||||
$item->addLookupItem($lookupValue, $row);
|
||||
$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// note: non-use of lazyNameIndex/lazyIdIndex is intentional
|
||||
|
||||
foreach($rows as $row) {
|
||||
if($useLazy) {
|
||||
$this->lazyItems[] = $row;
|
||||
} else {
|
||||
// add a new item
|
||||
$items->add($item);
|
||||
/** @var HasLookupItems $item */
|
||||
$this->initItem($row, $items);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$stmt->closeCursor();
|
||||
$items->setTrackChanges(true);
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Saveable/Lookup item from a raw array ($row) and add it to $items
|
||||
*
|
||||
* @param array $row
|
||||
* @param WireArray|null $items
|
||||
* @return Saveable|HasLookupItems|WireData|Wire
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
protected function initItem(array &$row, WireArray $items = null) {
|
||||
|
||||
$lookupField = $this->getLookupField();
|
||||
$lookupValue = $row[$lookupField];
|
||||
$item = $this->makeBlankItem(); /** @var HasLookupItems $item */
|
||||
|
||||
if($items === null) $items = $this->getWireArray();
|
||||
|
||||
unset($row[$lookupField]);
|
||||
|
||||
$item->addLookupItem($lookupValue, $row);
|
||||
|
||||
foreach($row as $key => $value) {
|
||||
$item->$key = $value;
|
||||
}
|
||||
|
||||
if($this->useLazy) {
|
||||
$items->add($item);
|
||||
foreach($this->lazyItems as $key => $a) {
|
||||
if($a['id'] != $row['id']) continue;
|
||||
if(!isset($a[$lookupField])) continue;
|
||||
$lookupValue = $a[$lookupField];
|
||||
unset($a[$lookupField]);
|
||||
$item->addLookupItem($lookupValue, $a);
|
||||
unset($this->lazyItems[$key]);
|
||||
}
|
||||
|
||||
} else if($items->has($item)) {
|
||||
// LEFT JOIN is adding more elements of the same item, i.e. from lookup table
|
||||
// if the item is already present in $items, then use the existing one rather
|
||||
// and throw out the one we just created
|
||||
$item = $items->get($item);
|
||||
$item->addLookupItem($lookupValue, $row);
|
||||
} else {
|
||||
// add a new item
|
||||
$items->add($item);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the given item key/field be saved in the database?
|
||||
*
|
||||
@@ -178,4 +229,20 @@ abstract class WireSaveableItemsLookup extends WireSaveableItems {
|
||||
$query->execute();
|
||||
return parent::___delete($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* debugInfo PHP 5.6+ magic method
|
||||
*
|
||||
* This is used when you print_r() an object instance.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function __debugInfo() {
|
||||
$info = parent::__debugInfo();
|
||||
$info['loaded'] = array_unique($info['loaded']);
|
||||
$info['notLoaded'] = array_unique($info['notLoaded']);
|
||||
return $info;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@
|
||||
* /wire/core/Fieldtype.php
|
||||
* /wire/core/FieldtypeMulti.php
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
* @todo: automatic sorting.
|
||||
@@ -53,6 +53,30 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
const loadingAll = 1;
|
||||
const loadingOff = 2;
|
||||
|
||||
/**
|
||||
* Field names used by repeaters in format [ PW_instanceNum => [ 'field_name', 'field_name2' ] ];
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
static protected $fieldsUsedInRepeaters = array();
|
||||
|
||||
/**
|
||||
* Template IDs used by repeaters in format [ PW_instanceNum => [ 123, 456, 789 ] ]
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
static protected $templatesUsedByRepeaters = array();
|
||||
|
||||
/**
|
||||
* Has ready method been called? [ PW_instanceNum => true | false ]
|
||||
*
|
||||
* @var bool
|
||||
*
|
||||
*/
|
||||
static protected $isReady = false;
|
||||
|
||||
/**
|
||||
* When non-zero, a deletePageField function call occurred and we shouldn't re-create any repeater parents
|
||||
*
|
||||
@@ -75,6 +99,14 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
*/
|
||||
protected $ajaxFieldName = '';
|
||||
|
||||
/**
|
||||
* Use lazy loading mode?
|
||||
*
|
||||
* @var null|bool
|
||||
*
|
||||
*/
|
||||
protected $useLazy = null;
|
||||
|
||||
/**
|
||||
* Construct the Repeater Fieldtype
|
||||
*
|
||||
@@ -93,8 +125,8 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
*/
|
||||
public function init() {
|
||||
$this->wire()->pages->addHookAfter('deleteReady', $this, 'hookPagesDelete');
|
||||
$this->useLazy = $this->wire()->config->useLazyLoading;
|
||||
parent::init();
|
||||
// $this->initFields();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,23 +134,26 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
*
|
||||
*/
|
||||
public function ready() {
|
||||
// if(in_array('WirePageEditor', wireClassImplements((string) $this->wire('page')->process))) { // @todo
|
||||
// $this->initFields(); // intentionally repeated from init()
|
||||
|
||||
// make sure that all templates used by repeater pages enforce a Page type of RepeaterPage
|
||||
foreach($this->wire()->fields as $field) {
|
||||
$fieldtype = $field->type;
|
||||
if(!$fieldtype || !$fieldtype instanceof FieldtypeRepeater) continue;
|
||||
/** @var FieldtypeRepeater $fieldtype */
|
||||
$template = $fieldtype->getRepeaterTemplate($field);
|
||||
if(!$template) continue;
|
||||
$class = $fieldtype->getPageClass();
|
||||
if(__NAMESPACE__ && $class) $class = wireClassName($class);
|
||||
$_class = $template->get('pageClass');
|
||||
if($class === $_class) continue;
|
||||
$template->set('pageClass', $class);
|
||||
$template->save();
|
||||
}
|
||||
parent::ready();
|
||||
|
||||
$instanceNum = $this->wire()->getInstanceNum();
|
||||
if(!empty(self::$isReady[$instanceNum])) return; // ensures everything below only runs only once (for extending types)
|
||||
self::$isReady[$instanceNum] = true;
|
||||
|
||||
if(!$this->useLazy) {
|
||||
// make sure that all templates used by repeater pages enforce a Page type of RepeaterPage
|
||||
// this was necessary when lazy loading option was disabled
|
||||
$this->useLazy = true;
|
||||
$repeaterFields = $this->wire()->fields->findByType('FieldtypeRepeater', array(
|
||||
'inherit' => true,
|
||||
'valueType' => 'field',
|
||||
'indexType' => '',
|
||||
));
|
||||
foreach($repeaterFields as $field) {
|
||||
$this->initField($field);
|
||||
}
|
||||
$this->useLazy = false;
|
||||
}
|
||||
|
||||
$page = $this->wire()->page;
|
||||
$process = $page->process; /** @var Process|null $process */
|
||||
@@ -161,35 +196,32 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
|
||||
$this->addHookBefore('PageFinder::getQuery', $this, 'hookPageFinderGetQuery');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize repeater fields at boot
|
||||
*
|
||||
protected function initFields() {
|
||||
|
||||
static $initFields = array();
|
||||
$className = $this->className();
|
||||
if(isset($initFields[$className])) return;
|
||||
|
||||
$fields = $this->wire('fields');
|
||||
if(!$fields) return;
|
||||
|
||||
// make sure that all templates used by repeater pages enforce a Page type of RepeaterPage
|
||||
foreach($fields as $field) {
|
||||
if(!$field->type || $field->type->className() != $className) continue;
|
||||
$template = $this->getRepeaterTemplate($field);
|
||||
if(!$template) continue;
|
||||
if(__NAMESPACE__) {
|
||||
$template->setQuietly('pageClass', wireClassName($this->getPageClass()));
|
||||
} else {
|
||||
$template->setQuietly('pageClass', $this->getPageClass());
|
||||
}
|
||||
}
|
||||
|
||||
$initFields[$className] = 1;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called when field of this type is initialized at boot or after lazy loaded
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param Field $field
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function initField(Field $field) {
|
||||
if(!$this->useLazy) return;
|
||||
parent::initField($field);
|
||||
/** @var FieldtypeRepeater $fieldtype */
|
||||
$fieldtype = $field->type;
|
||||
if(!$fieldtype instanceof FieldtypeRepeater) return;
|
||||
$template = $fieldtype->getRepeaterTemplate($field);
|
||||
if(!$template) return;
|
||||
$class = $fieldtype->getPageClass();
|
||||
if(__NAMESPACE__ && $class) $class = wireClassName($class);
|
||||
$_class = $template->get('pageClass');
|
||||
if($class === $_class) return;
|
||||
$template->set('pageClass', $class);
|
||||
$template->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class name to use Field objects of this type (must be class that extends Field class)
|
||||
*
|
||||
@@ -305,27 +337,33 @@ class FieldtypeRepeater extends Fieldtype implements ConfigurableModule {
|
||||
*/
|
||||
public function hookPageFinderGetQuery(HookEvent $event) {
|
||||
|
||||
static $fieldsUsedInRepeaters = null;
|
||||
static $templatesUsedByRepeaters = array();
|
||||
|
||||
/** @var Selectors $selectors */
|
||||
$selectors = $event->arguments[0];
|
||||
/** @var PageFinder $pageFinder */
|
||||
$pageFinder = $event->object;
|
||||
$pageFinderOptions = $pageFinder->getOptions();
|
||||
$instanceNum = $this->wire()->getInstanceNum();
|
||||
|
||||
// determine which fields are used in repeaters
|
||||
if(is_null($fieldsUsedInRepeaters)) {
|
||||
$fieldsUsedInRepeaters = array('title'); // title used by admin template (repeater parents)
|
||||
foreach($this->wire()->templates as $template) {
|
||||
if(strpos($template->name, self::templateNamePrefix) === 0) {
|
||||
$templatesUsedByRepeaters[] = $template->id;
|
||||
foreach($template->fieldgroup as $field) {
|
||||
$fieldsUsedInRepeaters[] = $field->name;
|
||||
}
|
||||
if(!isset(self::$fieldsUsedInRepeaters[$instanceNum])) {
|
||||
$fieldNames = array('title' => 'title'); // title used by admin template (repeater parents)
|
||||
$templates = $this->wire()->templates;
|
||||
$templateIds = array();
|
||||
$allTemplateNames = $templates->getAllValues('name', 'id');
|
||||
$fieldgroups = $this->wire()->fieldgroups;
|
||||
foreach($allTemplateNames as $templateId => $templateName) {
|
||||
if(strpos($templateName, self::templateNamePrefix) !== 0) continue;
|
||||
$templateIds[$templateName] = $templateId;
|
||||
foreach($fieldgroups->getFieldNames($templateName) as $fieldId => $fieldName) {
|
||||
$fieldNames[$fieldName] = $fieldName;
|
||||
}
|
||||
}
|
||||
self::$fieldsUsedInRepeaters[$instanceNum] = array_values($fieldNames);
|
||||
self::$templatesUsedByRepeaters[$instanceNum] = array_values($templateIds);
|
||||
}
|
||||
|
||||
$fieldsUsedInRepeaters = self::$fieldsUsedInRepeaters[$instanceNum];
|
||||
$templatesUsedByRepeaters = self::$templatesUsedByRepeaters[$instanceNum];
|
||||
|
||||
// did we find a field used by a repeater in the selector?
|
||||
$found = false;
|
||||
|
@@ -5,7 +5,7 @@
|
||||
*
|
||||
* This module is the front door to all the other language modules and files.
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
* @property int $languagesPageID
|
||||
@@ -120,10 +120,16 @@ class LanguageSupport extends WireData implements Module, ConfigurableModule {
|
||||
*
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
$pages = $this->wire()->pages;
|
||||
$user = $this->wire()->user;
|
||||
$config = $this->wire()->config;
|
||||
$templates = $this->wire()->templates;
|
||||
$modules = $this->wire()->modules;
|
||||
|
||||
// document which pages were already cached at this point, as their values may need
|
||||
// to be reloaded to account for language fields.
|
||||
foreach($this->wire('pages')->getCache() as $id => $value) $this->earlyCachedPages[$id] = $value;
|
||||
foreach($pages->getCache() as $id => $value) $this->earlyCachedPages[$id] = $value;
|
||||
|
||||
// prevent possible double init
|
||||
if($this->initialized) return;
|
||||
@@ -134,17 +140,18 @@ class LanguageSupport extends WireData implements Module, ConfigurableModule {
|
||||
$defaultLanguagePageID = $this->defaultLanguagePageID;
|
||||
|
||||
// create the $languages API var
|
||||
$languageTemplate = $this->templates->get('language');
|
||||
$languageTemplate = $templates->get('language');
|
||||
if(!$languageTemplate) return;
|
||||
|
||||
if(!$this->languagesPageID) {
|
||||
// fallback if LanguageSupport config lost or not accessible for some reason
|
||||
$this->languagesPageID = $this->wire('pages')->get("template=admin, name=languages");
|
||||
$this->languagesPageID = $pages->get("template=admin, name=languages");
|
||||
}
|
||||
|
||||
// prevent fields like 'title' from autojoining until languages are fully loaded
|
||||
$this->wire('pages')->setAutojoin(false);
|
||||
|
||||
$pages->setAutojoin(false);
|
||||
|
||||
/** @var Languages $languages */
|
||||
$languages = $this->wire(new Languages($this->wire('wire'), $languageTemplate, $this->languagesPageID));
|
||||
$_default = null; // just in case
|
||||
|
||||
@@ -153,7 +160,7 @@ class LanguageSupport extends WireData implements Module, ConfigurableModule {
|
||||
foreach($languages as $language) {
|
||||
if($language->id == $defaultLanguagePageID) {
|
||||
$this->defaultLanguagePage = $language;
|
||||
} else if($language->name == 'default') {
|
||||
} else if($language->name === 'default') {
|
||||
$_default = $language; // backup plan
|
||||
} else {
|
||||
$numOtherLanguages++;
|
||||
@@ -169,6 +176,7 @@ class LanguageSupport extends WireData implements Module, ConfigurableModule {
|
||||
$this->defaultLanguagePage = $languages->getAll()->first();
|
||||
}
|
||||
}
|
||||
|
||||
$this->defaultLanguagePage->setIsDefaultLanguage();
|
||||
$languages->setDefault($this->defaultLanguagePage);
|
||||
|
||||
@@ -176,14 +184,14 @@ class LanguageSupport extends WireData implements Module, ConfigurableModule {
|
||||
$this->wire('languages', $languages);
|
||||
|
||||
// identify the current language from the user, or set one if it's not already
|
||||
if($this->user->language && $this->user->language->id) {
|
||||
if($user->language && $user->language->id) {
|
||||
// $language = $this->user->language;
|
||||
} else {
|
||||
$language = $this->defaultLanguagePage;
|
||||
$this->user->language = $language;
|
||||
$user->language = $language;
|
||||
}
|
||||
|
||||
$this->wire('config')->dateFormat = $this->_('Y-m-d H:i:s'); // Sortable date format used in the admin
|
||||
$config->dateFormat = $this->_('Y-m-d H:i:s'); // Sortable date format used in the admin
|
||||
$locale = $this->_('C'); // Value to pass to PHP's setlocale(LC_ALL, 'value') function when initializing this language // Default is 'C'. Specify '0' to skip the setlocale() call (and carry on system default). Specify CSV string of locales to try multiple locales in order.
|
||||
if($locale != '0') $languages->setLocale(LC_ALL, $locale);
|
||||
|
||||
@@ -201,10 +209,10 @@ class LanguageSupport extends WireData implements Module, ConfigurableModule {
|
||||
$this->addHook('Page::getLanguageValue', $this, 'hookPageGetLanguageValue');
|
||||
|
||||
|
||||
if($this->wire('modules')->isInstalled('LanguageSupportFields')) {
|
||||
$this->LanguageSupportFields = $this->wire('modules')->get('LanguageSupportFields');
|
||||
if($modules->isInstalled('LanguageSupportFields')) {
|
||||
$this->LanguageSupportFields = $modules->get('LanguageSupportFields');
|
||||
$this->LanguageSupportFields->LS_init();
|
||||
if($languages->getPageEditPermissions('none') && !$this->user->hasPermission('page-edit-lang-none')) {
|
||||
if($languages->getPageEditPermissions('none') && !$user->hasPermission('page-edit-lang-none')) {
|
||||
$this->addHookBefore('InputfieldWrapper::renderInputfield', $this, 'hookInputfieldWrapperBeforeRenderInputfield');
|
||||
}
|
||||
}
|
||||
@@ -214,7 +222,7 @@ class LanguageSupport extends WireData implements Module, ConfigurableModule {
|
||||
}
|
||||
|
||||
// restore autojoin state for pages
|
||||
$this->wire('pages')->setAutojoin(true);
|
||||
$pages->setAutojoin(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,29 +230,31 @@ class LanguageSupport extends WireData implements Module, ConfigurableModule {
|
||||
*
|
||||
*/
|
||||
public function ready() {
|
||||
$page = $this->wire()->page;
|
||||
|
||||
// styles used by our Inputfield hooks
|
||||
if($this->wire('page')->template == 'admin') {
|
||||
$this->config->styles->add($this->config->urls('LanguageSupport') . "LanguageSupport.css");
|
||||
$language = $this->wire('user')->language;
|
||||
if($language) $this->config->js('LanguageSupport', array(
|
||||
if($page->template->name === 'admin') {
|
||||
$config = $this->wire()->config;
|
||||
$config->styles->add($config->urls('LanguageSupport') . "LanguageSupport.css");
|
||||
$language = $this->wire()->user->language;
|
||||
if($language) $config->js('LanguageSupport', array(
|
||||
'language' => array(
|
||||
'id' => $language->id,
|
||||
'name' => $language->name,
|
||||
'title' => (string) $language->title,
|
||||
)
|
||||
)
|
||||
));
|
||||
if($this->wire('modules')->isInstalled('LanguageTabs')) {
|
||||
$this->languageTabs = $this->wire('modules')->get('LanguageTabs');
|
||||
$modules = $this->wire()->modules;
|
||||
if($modules->isInstalled('LanguageTabs')) {
|
||||
$this->languageTabs = $modules->get('LanguageTabs');
|
||||
}
|
||||
}
|
||||
|
||||
// if languageSupportFields is here, then we have to deal with pages that loaded before this module did
|
||||
if($this->LanguageSupportFields) {
|
||||
$fieldNames = array();
|
||||
// save the names of all fields that support languages
|
||||
foreach($this->wire('fields') as $field) {
|
||||
if($field->type instanceof FieldtypeLanguageInterface) $fieldNames[] = $field->name;
|
||||
}
|
||||
$fieldNames = $this->LanguageSupportFields->getMultilangFieldNames();
|
||||
|
||||
// unset the values from all the early cached pages since they didn't recognize languages
|
||||
// this will force them to reload when accessed
|
||||
foreach($this->earlyCachedPages as $id => $p) {
|
||||
@@ -254,11 +264,11 @@ class LanguageSupport extends WireData implements Module, ConfigurableModule {
|
||||
if($t) $p->setTrackChanges(true);
|
||||
}
|
||||
}
|
||||
|
||||
// release this as we don't need it anymore
|
||||
$this->earlyCachedPages = array();
|
||||
|
||||
if($this->LanguageSupportFields) $this->LanguageSupportFields->LS_ready();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* Multi-language support fields module
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2022 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
* @method void languageAdded(Page $language) #pw-hooker
|
||||
@@ -29,13 +29,13 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
'singular' => true,
|
||||
'requires' => array(
|
||||
'LanguageSupport'
|
||||
),
|
||||
),
|
||||
'installs' => array(
|
||||
'FieldtypePageTitleLanguage',
|
||||
'FieldtypeTextareaLanguage',
|
||||
'FieldtypeTextLanguage',
|
||||
)
|
||||
);
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +57,6 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
*
|
||||
*/
|
||||
public function __construct() {
|
||||
|
||||
// load other required classes
|
||||
$dirname = dirname(__FILE__);
|
||||
require_once($dirname . '/LanguagesValueInterface.php');
|
||||
@@ -81,31 +80,32 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
*
|
||||
*/
|
||||
public function LS_init() {
|
||||
|
||||
$fields = $this->wire()->fields;
|
||||
|
||||
$this->addHookBefore('FieldtypeLanguageInterface::sleepValue', $this, 'fieldtypeSleepValue');
|
||||
$this->addHookBefore('PageFinder::getQuery', $this, 'pageFinderGetQuery');
|
||||
$this->addHookBefore('Fieldtype::formatValue', $this, 'hookFieldtypeFormatValue');
|
||||
|
||||
$languageNames = array();
|
||||
$fieldNames = array();
|
||||
$fieldNames = $fields->getAllNames('name');
|
||||
|
||||
foreach($this->wire('languages') as $language) {
|
||||
foreach($this->wire()->languages as $language) {
|
||||
$languageNames[] = $language->name;
|
||||
}
|
||||
|
||||
// keep track of which fields are multilanguage
|
||||
foreach($this->wire('fields') as $field) {
|
||||
if($field->type instanceof FieldtypeLanguageInterface) {
|
||||
$this->multilangFields[] = $field->name;
|
||||
}
|
||||
$fieldNames[] = $field->name;
|
||||
}
|
||||
|
||||
$this->multilangFields = $fields->findByType('FieldtypeLanguageInterface', array(
|
||||
'inherit' => true,
|
||||
'valueType' => 'name',
|
||||
'indexType' => '',
|
||||
));
|
||||
|
||||
// determine which fields have language alternates, i.e. 'title_es' is an alternate for 'title'
|
||||
foreach($fieldNames as $fieldName) {
|
||||
foreach($languageNames as $languageName) {
|
||||
$altName = $fieldName . '_' . $languageName;
|
||||
if(in_array($altName, $fieldNames)) {
|
||||
if(isset($fieldNames[$altName])) {
|
||||
if(!isset($this->multilangAlternateFields[$fieldName])) $this->multilangAlternateFields[$fieldName] = array();
|
||||
$this->multilangAlternateFields[$fieldName][] = $altName;
|
||||
}
|
||||
@@ -118,8 +118,9 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
*
|
||||
*/
|
||||
public function LS_ready() {
|
||||
$this->languages->addHook('added', $this, 'hookLanguageAdded');
|
||||
$this->languages->addHook('deleted', $this, 'hookLanguageDeleted');
|
||||
$languages = $this->wire()->languages;
|
||||
$languages->addHook('added', $this, 'hookLanguageAdded');
|
||||
$languages->addHook('deleted', $this, 'hookLanguageDeleted');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,9 +136,9 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
|
||||
/** @var Field $field */
|
||||
$field = $event->arguments[1];
|
||||
if($field->name == 'language') return;
|
||||
if($field->name === 'language') return;
|
||||
|
||||
$language = $this->wire('user')->get('language');
|
||||
$language = $this->wire()->user->get('language');
|
||||
if(!$language || !$language->id || $language->isDefault()) return;
|
||||
|
||||
// exit quickly if we can determine now we don't need to continue
|
||||
@@ -179,12 +180,13 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
*/
|
||||
public function hookLanguageAdded(HookEvent $event) {
|
||||
|
||||
$fields = $this->wire()->fields;
|
||||
$language = $event->arguments[0];
|
||||
|
||||
if($language->template->name != LanguageSupport::languageTemplateName) return;
|
||||
|
||||
foreach($this->multilangFields as $name) {
|
||||
$field = $this->wire('fields')->get($name);
|
||||
$field = $fields->get($name);
|
||||
if($field) $this->fieldLanguageAdded($field, $language);
|
||||
}
|
||||
|
||||
@@ -209,11 +211,12 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
*/
|
||||
public function hookLanguageDeleted(HookEvent $event) {
|
||||
|
||||
$fields = $this->wire()->fields;
|
||||
$language = $event->arguments[0];
|
||||
if($language->template->name != LanguageSupport::languageTemplateName) return;
|
||||
|
||||
foreach($this->multilangFields as $name) {
|
||||
$field = $this->wire('fields')->get($name);
|
||||
$field = $fields->get($name);
|
||||
if($field) $this->fieldLanguageRemoved($field, $language);
|
||||
}
|
||||
|
||||
@@ -245,7 +248,7 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
if(!($field->type instanceof FieldtypeLanguageInterface)) return;
|
||||
|
||||
$schema = $field->type->getDatabaseSchema($field);
|
||||
$database = $this->wire('database');
|
||||
$database = $this->wire()->database;
|
||||
$table = $database->escapeTable($field->table);
|
||||
|
||||
foreach($schema as $name => $value) {
|
||||
@@ -284,7 +287,7 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
if(!($field->type instanceof FieldtypeLanguageInterface)) return;
|
||||
|
||||
$schema = $field->type->getDatabaseSchema($field);
|
||||
$database = $this->wire('database');
|
||||
$database = $this->wire()->database;
|
||||
$table = $database->escapeTable($field->table);
|
||||
|
||||
foreach($schema as $name => $value) {
|
||||
@@ -309,9 +312,9 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
*/
|
||||
public function pageFinderGetQuery(HookEvent $event) {
|
||||
|
||||
$user = $this->wire('user');
|
||||
$user = $this->wire()->user;
|
||||
$language = $user->language;
|
||||
$database = $this->wire('database');
|
||||
$database = $this->wire()->database;
|
||||
|
||||
if(!$language || !$language->id || $language->isDefault()) return;
|
||||
|
||||
@@ -373,6 +376,7 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
$value = $event->return;
|
||||
if($value instanceof LanguagesPageFieldValue) return;
|
||||
$v = new LanguagesPageFieldValue($page, $field, $value);
|
||||
$this->wire($v);
|
||||
$event->return = $v;
|
||||
}
|
||||
|
||||
@@ -396,6 +400,7 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
// good
|
||||
} else if(is_array($value)) {
|
||||
$value = new LanguagesPageFieldValue($page, $field, $value);
|
||||
$this->wire($value);
|
||||
$value->setTrackChanges(true);
|
||||
$value->setField($field);
|
||||
$event->return = $value;
|
||||
@@ -421,7 +426,7 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
|
||||
if(!$value instanceof LanguagesPageFieldValue) return;
|
||||
|
||||
foreach($this->wire('languages') as $language) {
|
||||
foreach($this->wire()->languages as $language) {
|
||||
if($language->isDefault()) $key = 'data';
|
||||
else $key = 'data' . $language->id;
|
||||
$values[$key] = $value->getLanguageValue($language->id);
|
||||
@@ -444,17 +449,21 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
|
||||
public function fieldtypeGetConfigInputfields(HookEvent $event) {
|
||||
|
||||
/** @var Field $field */
|
||||
$field = $event->arguments(0);
|
||||
$inputfields = $event->return;
|
||||
|
||||
$f = $this->wire('modules')->get('InputfieldRadios');
|
||||
/** @var InputfieldWrapper $inputfields */
|
||||
$inputfields = $event->return;
|
||||
|
||||
/** @var InputfieldRadios $f */
|
||||
$f = $this->wire()->modules->get('InputfieldRadios');
|
||||
$f->attr('name', 'langBlankInherit');
|
||||
$f->label = $this->_('Language Support / Blank Behavior');
|
||||
$f->description = $this->_("What should happen when this field's value is blank?");
|
||||
$f->notes = $this->_('Applies only to non-default language values on the front-end of your site.');
|
||||
$f->addOption(LanguagesPageFieldValue::langBlankInheritDefault, $this->_('Inherit value from default language'));
|
||||
$f->addOption(LanguagesPageFieldValue::langBlankInheritNone, $this->_('Remain blank'));
|
||||
$f->attr('value', (int) $field->langBlankInherit);
|
||||
$f->attr('value', (int) $field->get('langBlankInherit'));
|
||||
$f->collapsed = Inputfield::collapsedBlank;
|
||||
$inputfields->add($f);
|
||||
}
|
||||
@@ -469,7 +478,9 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
*
|
||||
*/
|
||||
public function getAlternateFields($fieldName) {
|
||||
if(isset($this->multilangAlternateFields[$fieldName])) return $this->multilangAlternateFields[$fieldName];
|
||||
if(isset($this->multilangAlternateFields[$fieldName])) {
|
||||
return $this->multilangAlternateFields[$fieldName];
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
@@ -485,13 +496,11 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
$pos = strrpos($altFieldName, '_');
|
||||
if(!$pos) return '';
|
||||
$parentName = substr($altFieldName, 0, $pos);
|
||||
// $this->message($parentName);
|
||||
if(isset($this->multilangAlternateFields[$parentName]) && in_array($altFieldName, $this->multilangAlternateFields[$parentName])) {
|
||||
if(!$returnLanguage) return $parentName;
|
||||
$languageName = substr($altFieldName, $pos+1);
|
||||
return $this->wire('languages')->get($languageName);
|
||||
}
|
||||
return '';
|
||||
if(!isset($this->multilangAlternateFields[$parentName])) return '';
|
||||
if(!in_array($altFieldName, $this->multilangAlternateFields[$parentName])) return '';
|
||||
if(!$returnLanguage) return $parentName;
|
||||
$languageName = substr($altFieldName, $pos+1);
|
||||
return $this->wire()->languages->get($languageName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -505,6 +514,17 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
return $this->getAlternateFieldParent($altFieldName, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multi-language field names (fields that implement FieldtypeLanguageInterface)
|
||||
*
|
||||
* @return array
|
||||
* @since 3.0.194
|
||||
*
|
||||
*/
|
||||
public function getMultilangFieldNames() {
|
||||
return $this->multilangFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given field name a language alternate field?
|
||||
*
|
||||
@@ -544,10 +564,12 @@ class LanguageSupportFields extends WireData implements Module {
|
||||
public function ___uninstall() {
|
||||
// first check if there are any fields using the LanguageInterface
|
||||
$errors = '';
|
||||
foreach($this->wire('fields') as $field) {
|
||||
foreach($this->wire()->fields as $field) {
|
||||
if($field->type instanceof FieldtypeLanguageInterface) $errors .= $field->name . ", ";
|
||||
}
|
||||
if($errors) throw new WireException("Can't uninstall because these fields use the language interface: " . rtrim($errors, ", "));
|
||||
if($errors) {
|
||||
throw new WireException("Can't uninstall because these fields use the language interface: " . rtrim($errors, ", "));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
<?php namespace ProcessWire;
|
||||
<?php namespace ProcessWire;
|
||||
|
||||
/** @var ProcessWire $wire */
|
||||
/** @var Config $config */
|
||||
/** @var Pages $pages */
|
||||
/** @var Sanitizer $sanitizer */
|
||||
@@ -14,7 +15,9 @@
|
||||
/** @var WireCache $cache */
|
||||
/** @var WireClassLoader $classLoader */
|
||||
|
||||
if(!defined("PROCESSWIRE")) die();
|
||||
if(!defined("PROCESSWIRE")) die();
|
||||
Debug::saveTimer('all');
|
||||
Debug::saveTimer("page.$page.render");
|
||||
$debugToolsLabel = __('Debug Mode Tools', __FILE__);
|
||||
|
||||
echo
|
||||
@@ -412,6 +415,43 @@ Debug::saveTimer('timer-name', 'optional notes'); // stop and save timer
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if($tool == 'lazyload' && $config->useLazyLoading): ?>
|
||||
<?php
|
||||
$o = "<table class=''><thead><th>Class</th><th>Loaded</th><th>Not Loaded</th><tbody>";
|
||||
$numTotal = 0;
|
||||
$numLoadedTotal = 0;
|
||||
$numNotLoadedTotal = 0;
|
||||
foreach(array('fields', 'templates', 'fieldgroups') as $key) {
|
||||
/** @var WireSaveableItems $var */
|
||||
$var = $wire->$key;
|
||||
if(empty($var)) continue;
|
||||
$debugInfo = $var->__debugInfo();
|
||||
$numLoaded = count($debugInfo['loaded']);
|
||||
$numNotLoaded = count($debugInfo['notLoaded']);
|
||||
$numEither = $numLoaded + $numNotLoaded;
|
||||
$numTotal += $numEither;
|
||||
$numLoadedTotal += $numLoaded;
|
||||
$numNotLoadedTotal += $numNotLoaded;
|
||||
sort($debugInfo['loaded']);
|
||||
sort($debugInfo['notLoaded']);
|
||||
$o .=
|
||||
"<tr>" .
|
||||
"<td>" . $var->className() . " <span class='detail'>($numLoaded/$numNotLoaded/$numEither)</span></td>" .
|
||||
"<td>" . implode("<br />", $debugInfo['loaded']) . "</td>" .
|
||||
"<td>" . implode("<br />", $debugInfo['notLoaded']) . "</td>" .
|
||||
"</tr>";
|
||||
}
|
||||
$o .= "</tbody></table><br />";
|
||||
?>
|
||||
<div class="container">
|
||||
<h3><a href='#'><?php echo __('Lazy load') . " <span class='ui-priority-secondary'>($numLoadedTotal/$numNotLoadedTotal/$numTotal)</span>"; ?></a></h3>
|
||||
<div>
|
||||
<?php echo $o; unset($o, $var, $debugInfo, $numLoaded, $numNotLoaded, $numEither, $numTotal, $numLoadedTotal, $numNotLoadedTotal); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
||||
<p class='detail' style='margin: 0; padding: 0.25em 1em;'>
|
||||
|
Reference in New Issue
Block a user