1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-09 16:26:59 +02:00

Refactoring of core Template class to improve, optimize and simplify it, plus add new dedicated methods for childTemplates(), parentTemplates(), and allowNewPages().

This commit is contained in:
Ryan Cramer
2020-03-20 14:13:33 -04:00
parent 00b89f71b3
commit 542e138e42
2 changed files with 354 additions and 145 deletions

View File

@@ -198,6 +198,14 @@ class Template extends WireData implements Saveable, Exportable {
*/ */
protected $_roles = null; protected $_roles = null;
/**
* Loaded state
*
* @var bool
*
*/
protected $loaded = true;
/** /**
* The template's settings, as they relate to database schema * The template's settings, as they relate to database schema
* *
@@ -274,6 +282,22 @@ class Template extends WireData implements Saveable, Exportable {
'ns' => '', // namespace found in the template file, or blank if not determined 'ns' => '', // namespace found in the template file, or blank if not determined
); );
/**
* Get or set loaded state
*
* When loaded state is false, we bypass some internal validations/checks that dont need to run while booting
*
* #pw-internal
*
* @param bool|null $loaded
* @return bool
* @since 3.0.153
*
*/
public function loaded($loaded = null) {
if($loaded !== null) $this->loaded = (bool) $loaded;
return $this->loaded;
}
/** /**
* Get a Template property * Get a Template property
@@ -298,6 +322,28 @@ class Template extends WireData implements Saveable, Exportable {
return isset($this->settings[$key]) ? $this->settings[$key] : parent::get($key); return isset($this->settings[$key]) ? $this->settings[$key] : parent::get($key);
} }
/**
* Given different ways to refer to a role type return array of type, property and permission name
*
* @param string|Permission $type
* @return array Returns array of [ typeName, propertyName, permissionName ]
* @since 3.0.153
*
*/
protected function roleTypeNames($type) {
if(is_object($type) && $type instanceof Page) $type = $type->name;
if($type === 'view' || $type === 'roles' || $type === 'viewRoles' || $type === 'page-view') {
return array('view', 'roles', 'page-view');
} else if($type === 'edit' || $type === 'page-edit' || $type === 'editRoles') {
return array('edit', 'editRoles', 'page-edit');
} else if($type === 'create' || $type === 'page-create' || $type === 'createRoles') {
return array('create', 'createRoles', 'page-create');
} else if($type === 'add' || $type === 'page-add' || $type === 'addRoles') {
return array('add', 'addRoles', 'page-add');
}
return array('','','');
}
/** /**
* Get the role pages that are part of this template * Get the role pages that are part of this template
* *
@@ -316,22 +362,15 @@ class Template extends WireData implements Saveable, Exportable {
*/ */
public function getRoles($type = 'view') { public function getRoles($type = 'view') {
if(strpos($type, 'page-') === 0) $type = str_replace('page-', '', $type);
if($type !== 'view') { if($type !== 'view') {
$roleIDs = null; list($name, $propertyName, /*permissionName*/) = $this->roleTypeNames($type);
if($type === 'edit') { if($name !== 'view') {
$roleIDs = $this->editRoles; if(empty($name)) throw new WireException("Unknown roles type: $type");
} else if($type === 'create') { $roleIDs = $this->$propertyName;
$roleIDs = $this->createRoles;
} else if($type === 'add') {
$roleIDs = $this->addRoles;
} else {
throw new WireException("Unknown roles type: $type");
}
if(empty($roleIDs)) return $this->wire('pages')->newPageArray(); if(empty($roleIDs)) return $this->wire('pages')->newPageArray();
return $this->wire('pages')->getById($roleIDs); return $this->wire('pages')->getById($roleIDs);
} }
}
// type=view assumed from this point forward // type=view assumed from this point forward
@@ -384,11 +423,10 @@ class Template extends WireData implements Saveable, Exportable {
* *
*/ */
public function hasRole($role, $type = 'view') { public function hasRole($role, $type = 'view') {
list($type, $property, /*permissionName*/) = $this->roleTypeNames($type);
$has = false; $has = false;
$roles = $this->getRoles(); $roles = $this->getRoles();
$rolePage = null; $rolePage = null;
if(is_object($type) && $type instanceof Page) $type = $type->name;
if(strpos($type, 'page-') === 0) $type = str_replace('page-', '', $type);
if(is_string($role)) { if(is_string($role)) {
$has = $roles->has("name=$role"); $has = $roles->has("name=$role");
} else if(is_int($role)) { } else if(is_int($role)) {
@@ -398,17 +436,11 @@ class Template extends WireData implements Saveable, Exportable {
$has = $roles->has($role); $has = $roles->has($role);
$rolePage = $role; $rolePage = $role;
} }
if($type == 'view') return $has; if($type === 'view') return $has;
if(!$has) return false; // page-view is a pre-requisite if(!$has) return false; // page-view is a pre-requisite
if(!$rolePage || !$rolePage->id) $rolePage = $this->wire('roles')->get($role); if(!$rolePage || !$rolePage->id) $rolePage = $this->wire('roles')->get($role);
if(!$rolePage->id) return false; if(!$rolePage->id) return false;
if($type === 'edit') { $has = $property ? in_array($rolePage->id, $this->$property) : false;
$has = in_array($rolePage->id, $this->editRoles);
} else if($type === 'create') {
$has = in_array($rolePage->id, $this->createRoles);
} else if($type == 'add') {
$has = in_array($rolePage->id, $this->addRoles);
}
return $has; return $has;
} }
@@ -428,34 +460,93 @@ class Template extends WireData implements Saveable, Exportable {
* *
*/ */
public function setRoles($value, $type = 'view') { public function setRoles($value, $type = 'view') {
if($type === 'view' || $type === 'page-view') {
list($type, $property, /* permissionName */) = $this->roleTypeNames($type);
if(empty($property)) {
// @todo Some other $type, delegate to permissionByRole
return;
}
if($type === 'view') {
if(is_array($value) || $value instanceof PageArray) { if(is_array($value) || $value instanceof PageArray) {
$this->_roles = $value; $this->_roles = $value;
} }
} else if(WireArray::iterable($value)) { return;
}
if(!WireArray::iterable($value)) $value = array();
$roleIDs = array(); $roleIDs = array();
$roles = null;
foreach($value as $v) { foreach($value as $v) {
$id = 0;
if(is_int($v)) { if(is_int($v)) {
$id = $v; $id = $v;
} else if(is_string($v) && ctype_digit($v)) { } else if(is_string($v)) {
if(ctype_digit($v)) {
$id = (int) $v; $id = (int) $v;
} else {
if($roles === null) $roles = $this->wire('roles');
$id = $roles ? (int) $roles->get($v)->id : 0;
if(!$id && $this->_importMode && $this->useRoles) {
$this->error("Unable to load role '$v' for '$this.$type'");
}
}
} else if($v instanceof Page) { } else if($v instanceof Page) {
$id = $v->id; $id = (int) $v->id;
} else {
continue;
} }
$roleIDs[] = $id; if($id) $roleIDs[] = $id;
} }
if($type == 'edit' || $type == 'page-edit') {
$this->set('editRoles', $roleIDs); parent::set($property, $roleIDs);
} else if($type == 'create' || $type == 'page-create') { }
$this->set('createRoles', $roleIDs);
} else if($type == 'add' || $type == 'page-add') { /**
$this->set('addRoles', $roleIDs); * Set roles/permissions
} else { *
// @todo Some other $type, delegate to permissionByRole * #pw-internal
*
* @param array $value
* @since 3.0.153
*
*/
protected function setRolesPermissions($value) {
if(!is_array($value)) $value = array();
$a = array();
foreach($value as $roleID => $permissionIDs) {
if(!ctype_digit("$roleID")) {
// convert role name to ID
$roleID = $this->wire('roles')->get("name=$roleID")->id;
}
if(!$roleID) continue;
foreach($permissionIDs as $permissionID) {
$test = ltrim($permissionID, '-');
if(!ctype_digit($test)) {
// convert permission name to ID
$revoke = strpos($permissionID, '-') === 0;
$permissionID = $this->wire('permissions')->get("name=$test")->id;
if(!$permissionID) continue;
if($revoke) $permissionID = "-$permissionID";
}
// we force these as strings so that they can portable in JSON
$roleID = (string) ((int) $roleID);
$permissionID = (string) ((int) $permissionID);
if(!isset($a[$roleID])) $a[$roleID] = array();
$a[$roleID][] = $permissionID;
} }
} }
parent::set('rolesPermissions', $a);
} }
/** /**
@@ -582,29 +673,7 @@ class Template extends WireData implements Saveable, Exportable {
$this->setFlags($value); $this->setFlags($value);
} else if(isset($this->settings[$key])) { } else if(isset($this->settings[$key])) {
$this->setSetting($key, $value);
if($key == 'id') {
$value = (int) $value;
} else if($key == 'name') {
$value = $this->wire('sanitizer')->name($value);
} else if($key == 'fieldgroups_id' && $value) {
$fieldgroup = $this->wire('fieldgroups')->get($value);
if($fieldgroup) $this->setFieldgroup($fieldgroup);
else $this->error("Unable to load fieldgroup '$value' for template $this->name");
return $this;
} else if($key == 'cache_time') {
$value = (int) $value;
} else {
$value = '';
}
if($this->settings[$key] != $value) {
if($this->settings[$key] && ($this->settings['flags'] & Template::flagSystem) && in_array($key, array('id', 'name'))) {
throw new WireException("Template '$this' has the system flag and you may not change its 'id' or 'name' fields. ");
}
$this->trackChange($key, $this->settings[$key], $value);
}
$this->settings[$key] = $value;
} else if($key == 'fieldgroup' || $key == 'fields') { } else if($key == 'fieldgroup' || $key == 'fields') {
$this->setFieldgroup($value); $this->setFieldgroup($value);
@@ -612,9 +681,6 @@ class Template extends WireData implements Saveable, Exportable {
} else if($key == 'filename') { } else if($key == 'filename') {
$this->filename($value); $this->filename($value);
} else if($key == 'roles') {
$this->setRoles($value);
} else if($key == 'childrenTemplatesID') { // this can eventaully be removed } else if($key == 'childrenTemplatesID') { // this can eventaully be removed
if($value < 0) { if($value < 0) {
parent::set('noChildren', 1); parent::set('noChildren', 1);
@@ -628,81 +694,26 @@ class Template extends WireData implements Saveable, Exportable {
$value = $this->wire('pages')->sortfields()->decode($value, ''); $value = $this->wire('pages')->sortfields()->decode($value, '');
parent::set($key, $value); parent::set($key, $value);
} else if(in_array($key, array('addRoles', 'editRoles', 'createRoles'))) { } else if($key === 'roles' || $key === 'addRoles' || $key === 'editRoles' || $key === 'createRoles') {
if(!is_array($value)) $value = array(); $this->setRoles($value, $key);
foreach($value as $k => $v) {
if(!is_int($v)) { } else if($key === 'rolesPermissions') {
if(is_object($v)) { $this->setRolesPermissions($value);
$v = $v->id;
} else if(is_string($v) && !ctype_digit("$v")) { } else if($key === 'childTemplates' || $key === 'parentTemplates') {
$role = $this->wire('roles')->get($v); if($this->loaded) {
if(!$role->id && $this->_importMode && $this->useRoles) $this->error("Unable to load role: $v"); $this->familyTemplates($key, $value);
$v = $role->id; } else {
}
}
if($v) $value[(int) $k] = (int) $v;
}
parent::set($key, $value); parent::set($key, $value);
}
} else if($key == 'rolesPermissions') { } else if($key === 'noChildren' || $key === 'noParents') {
if(!is_array($value)) $value = array();
$_value = array();
foreach($value as $roleID => $permissionIDs) {
// if any one of these happened to be a role name or permission name, convert to IDs
if(!ctype_digit("$roleID")) $roleID = $this->wire('roles')->get("name=$roleID")->id;
if(!$roleID) continue;
foreach($permissionIDs as $permissionID) {
if(!ctype_digit(ltrim($permissionID, '-'))) {
$revoke = strpos($permissionID, '-') === 0;
$permissionID = $this->wire('permissions')->get("name=" . ltrim($permissionID, '-'))->id;
if(!$permissionID) continue;
if($revoke) $permissionID = "-$permissionID";
}
// we force these as strings so that they can portable in JSON
$roleID = (string) ((int) $roleID);
$permissionID = (string) ((int) $permissionID);
if(!isset($_value[$roleID])) $_value[$roleID] = array();
$_value[$roleID][] = $permissionID;
}
}
parent::set($key, $_value);
} else if(in_array($key, array('childTemplates', 'parentTemplates'))) {
if(!is_array($value)) $value = array();
foreach($value as $k => $v) {
if(!is_int($v)) {
if(is_object($v)) {
$v = $v->id;
} else if(!ctype_digit("$v")) {
$t = $this->wire('templates')->get($v);
if(!$t && $this->_importMode) {
$this->error("Unable to load template '$v' for '$this->name.$key'");
}
$v = $t ? $t->id : 0;
}
}
if($v) $value[(int)$k] = (int) $v;
}
parent::set($key, $value);
} else if(in_array($key, array('noChildren', 'noParents'))) {
$value = (int) $value; $value = (int) $value;
if(!$value) $value = null; // enforce null over 0 if(!$value) $value = null; // enforce null over 0
parent::set($key, $value); parent::set($key, $value);
} else if($key == 'cacheExpirePages') { } else if($key == 'cacheExpirePages') {
if(!is_array($value)) $value = array(); $this->setCacheExpirePages($value);
foreach($value as $k => $v) {
if(is_object($v)) {
$v = $v->id;
} else if(!ctype_digit("$v")) {
$p = $this->wire('pages')->get($v);
if(!$p->id) $this->error("Unable to load page: $v");
$v = $p->id;
}
$value[(int) $k] = (int) $v;
}
parent::set($key, $value);
} else if($key == 'icon') { } else if($key == 'icon') {
$this->setIcon($value); $this->setIcon($value);
@@ -720,6 +731,72 @@ class Template extends WireData implements Saveable, Exportable {
return $this; return $this;
} }
/**
* Set a setting
*
* #pw-internal
*
* @param string $key
* @param mixed $value
* @since 3.0.153
* @throws WireException
*
*/
protected function setSetting($key, $value) {
if($key == 'id') {
$value = (int) $value;
} else if($key == 'name') {
$value = $this->loaded ? $this->wire('sanitizer')->name($value) : $value;
} else if($key == 'fieldgroups_id' && $value) {
$fieldgroup = $this->wire('fieldgroups')->get($value);
if($fieldgroup) {
$this->setFieldgroup($fieldgroup);
} else {
$this->error("Unable to load fieldgroup '$value' for template $this->name");
}
return;
} else if($key == 'cache_time') {
$value = (int) $value;
} else {
// unknown or invalid setting
$value = '';
}
if($this->loaded && $this->settings[$key] != $value) {
if(($key === 'id' || $key === 'name') && $this->settings[$key] && ($this->settings['flags'] & Template::flagSystem)) {
throw new WireException("Template '$this' has the system flag and you may not change its 'id' or 'name' settings.");
}
$this->trackChange($key, $this->settings[$key], $value);
}
$this->settings[$key] = $value;
}
/**
* Set the cacheExpirePages property
*
* @param array $value
*
*/
protected function setCacheExpirePages($value) {
if(!is_array($value)) $value = array();
foreach($value as $k => $v) {
if(is_object($v)) {
$v = $v->id;
} else if(!ctype_digit("$v")) {
$p = $this->wire('pages')->get($v);
if(!$p->id) $this->error("Unable to load page: $v");
$v = $p->id;
}
$value[(int) $k] = (int) $v;
}
parent::set('cacheExpirePages', $value);
}
/** /**
* Get or set allowed URL segments * Get or set allowed URL segments
* *
@@ -817,24 +894,30 @@ class Template extends WireData implements Saveable, Exportable {
*/ */
public function setFieldgroup(Fieldgroup $fieldgroup) { public function setFieldgroup(Fieldgroup $fieldgroup) {
if(is_null($this->fieldgroup) || $fieldgroup->id != $this->fieldgroup->id) $this->trackChange('fieldgroup', $this->fieldgroup, $fieldgroup); if($this->fieldgroup === null || $fieldgroup->id != $this->fieldgroup->id) {
if($this->loaded) $this->trackChange('fieldgroup', $this->fieldgroup, $fieldgroup);
}
if($this->fieldgroup && $fieldgroup->id != $this->fieldgroup->id) { if($this->fieldgroup && $fieldgroup->id != $this->fieldgroup->id) {
// save record of the previous fieldgroup so that unused fields can be deleted during save() // save record of the previous fieldgroup so that unused fields can be deleted during save()
$this->fieldgroupPrevious = $this->fieldgroup; $this->fieldgroupPrevious = $this->fieldgroup;
if($this->flags & Template::flagSystem) if($this->flags & Template::flagSystem) {
throw new WireException("Can't change fieldgroup for template '{$this}' because it is a system template."); throw new WireException("Can't change fieldgroup for template '{$this}' because it is a system template.");
}
$hasPermanentFields = false; $hasPermanentFields = false;
foreach($this->fieldgroup as $field) { foreach($this->fieldgroup as $field) {
if($field->flags & Field::flagPermanent) $hasPermanentFields = true; if($field->flags & Field::flagPermanent) $hasPermanentFields = true;
} }
if($this->id && $hasPermanentFields) throw new WireException("Fieldgroup for template '{$this}' may not be changed because it has permanent fields."); if($this->id && $hasPermanentFields) {
throw new WireException("Fieldgroup for template '{$this}' may not be changed because it has permanent fields.");
}
} }
$this->fieldgroup = $fieldgroup; $this->fieldgroup = $fieldgroup;
$this->settings['fieldgroups_id'] = $fieldgroup->id; $this->settings['fieldgroups_id'] = $fieldgroup->id;
return $this; return $this;
} }
@@ -1047,9 +1130,107 @@ class Template extends WireData implements Saveable, Exportable {
return $this->name; return $this->name;
} }
/**
* Get or set parent templates (templates allowed for parent pages of pages using this template)
*
* - May be specified as template IDs or names in an array, or Template objects in a TemplatesArray.
* - To allow any template as parent, specify a blank array.
* - To disallow any parents (other than whats already in use) set the `$template->noParents` property to 1.
*
* #pw-group-family
*
* @param array|TemplatesArray|null $setValue Specify only when setting, an iterable value containing Template objects, IDs or names
* @return TemplatesArray
* @since 3.0.153
*
*/
public function parentTemplates($setValue = null) {
return $this->familyTemplates('parentTemplates', $setValue);
}
/** /**
* Return the parent page that this template assumes new pages are added to . * Get or set child templates (templates allowed for children of pages using this template)
*
* - May be specified as template IDs or names in an array, or Template objects in a TemplatesArray.
* - To allow any template to be used for children, specify a blank array.
* - To disallow any children (other than whats already in use) set the `$template->noChildren` property to 1.
*
* #pw-group-family
*
* @param array|TemplatesArray|null $setValue Specify only when setting, an iterable value containing Template objects, IDs or names
* @return TemplatesArray
* @since 3.0.153
*
*/
public function childTemplates($setValue = null) {
return $this->familyTemplates('childTemplates', $setValue);
}
/**
* Get or set childTemplates or parentTemplates
*
* #pw-internal
*
* @param string $property Specify either 'childTemplates' or 'parentTemplates'
* @param array|TemplatesArray|null $setValue Iterable value containing Template objects, IDs or names
* @return TemplatesArray
* @since 3.0.153
*
*/
protected function familyTemplates($property, $setValue = null) {
/** @var Templates $templates */
$templates = $this->wire('templates');
$value = new TemplatesArray();
if($setValue !== null && WireArray::iterable($setValue)) {
// set
$ids = array();
foreach($setValue as $v) {
$template = $v instanceof Template ? $v : $templates->get($v);
if($template) {
$ids[$template->id] = $template->id;
$value->add($template);
} else if($this->_importMode) {
$this->error("Unable to load template '$v' for '$this->name.$property'");
}
}
parent::set($property, array_values($ids));
} else {
// get
foreach($this->$property as $id) {
$template = $templates->get((int) $id);
if($template) $value->add($template);
}
}
return $value;
}
/**
* Allow new pages that use this template?
*
* #pw-group-family
*
* @return bool
* @since 3.0.153
*
*/
public function allowNewPages() {
$pages = $this->wire('pages'); /** @var Pages $pages */
$noParents = (int) $this->noParents;
if($noParents === 1) {
// no new pages may be created
return false;
} else if($noParents === -1) {
// only one may exist
if($pages->has("template=$this")) return false;
}
return true;
}
/**
* Return the parent page that this template assumes new pages are added to
* *
* This is based on family settings, when applicable. * This is based on family settings, when applicable.
* It also takes into account user access, if requested (see arg 1). * It also takes into account user access, if requested (see arg 1).

View File

@@ -24,12 +24,16 @@ class Templates extends WireSaveableItems {
/** /**
* Reference to all the Fieldgroups * Reference to all the Fieldgroups
* *
* @var Fieldgroups
*
*/ */
protected $fieldgroups = null; protected $fieldgroups = null;
/** /**
* WireArray of all Template instances * WireArray of all Template instances
* *
* @var TemplatesArray
*
*/ */
protected $templatesArray; protected $templatesArray;
@@ -82,6 +86,30 @@ class Templates extends WireSaveableItems {
return $this->templatesArray; return $this->templatesArray;
} }
/**
* Make an item and populate with given data
*
* #pw-internal
*
* @param array $a Associative array of data to populate
* @return Saveable|Wire
* @since 3.0.146
*
*/
public function makeItem(array $a = array()) {
/** @var Template $item */
$template = $this->wire(new Template());
$template->loaded(false);
foreach($a as $key => $value) {
$template->set($key, $value);
}
$template->loaded(true);
$template->resetTrackChanges(true);
return $template;
}
/** /**
* Return a new blank item * Return a new blank item
* *