1
0
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:
Ryan Cramer
2022-02-04 14:51:11 -05:00
parent 9a1cf64e02
commit a5c70a4e7d
16 changed files with 1232 additions and 329 deletions

View File

@@ -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?
*

View File

@@ -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
*

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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;
}
/**

View File

@@ -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
*

View File

@@ -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.

View File

@@ -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');

View File

@@ -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

View File

@@ -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();

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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();
}
/**

View File

@@ -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, ", "));
}
}
}

View File

@@ -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() . "&nbsp;<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;'>