Server : Apache System : Linux indy02.toastserver.com 3.10.0-962.3.2.lve1.5.85.el7.x86_64 #1 SMP Thu Apr 18 15:18:36 UTC 2024 x86_64 User : palandch ( 1163) PHP Version : 7.1.33 Disable Function : NONE Directory : /home/palandch/www/core/model/modx/ |
<?php /** * @package modx */ /** * Represents a category for organizing modElement instances. * * @property int $parent The parent category ID, if set. Otherwise defaults to 0. * @property string $category The name of the Category. * @package modx */ class modCategory extends modAccessibleSimpleObject { /** * @var boolean Monitors whether parent has been changed. * @access protected */ protected $_parentChanged = false; /** * @var array A list of invalid characters in the name of an Element. * @access protected */ protected $_invalidCharacters = array('!','@','#','$','%','^','&','*', '(',')','+','=','[',']','{','}','\'','"',':',';','\\','/','<','>','?' ,',','`','~'); /** * Overrides xPDOObject::set to strip invalid characters from element names. * * {@inheritDoc} */ public function set($k, $v= null, $vType= '') { $set = false; switch ($k) { case 'category': $v = str_replace($this->_invalidCharacters,'',$v); default: $oldParentId = $this->get('parent'); $set = parent::set($k,$v,$vType); if ($set && $k == 'parent' && $v != $oldParentId && !$this->isNew()) { $this->_parentChanged = true; } } return $set; } /** * Overrides xPDOObject::save to fire modX-specific events * * {@inheritDoc} */ public function save($cacheFlag = null) { $isNew = $this->isNew(); if ($this->xpdo instanceof modX) { $this->xpdo->invokeEvent('OnCategoryBeforeSave',array( 'mode' => $isNew ? modSystemEvent::MODE_NEW : modSystemEvent::MODE_UPD, 'category' => &$this, 'cacheFlag' => $cacheFlag, )); } $saved = parent :: save($cacheFlag); /* if a new board */ if ($saved && $isNew) { $this->buildClosure(); } /* if parent changed on existing object, rebuild closure table */ if (!$isNew && $this->_parentChanged) { $this->rebuildClosure(); } if ($saved && $this->xpdo instanceof modX) { $this->xpdo->invokeEvent('OnCategorySave',array( 'mode' => $isNew ? modSystemEvent::MODE_NEW : modSystemEvent::MODE_UPD, 'category' => &$this, 'cacheFlag' => $cacheFlag, )); } return $saved; } /** * Overrides xPDOObject::remove to reset all Element categories back to 0 * and fire modX-specific events. * * {@inheritDoc} */ public function remove(array $ancestors = array()) { if ($this->xpdo instanceof modX) { $this->xpdo->invokeEvent('OnCategoryBeforeRemove',array( 'category' => &$this, 'ancestors' => $ancestors, )); } $removed = parent :: remove($ancestors); if ($removed && $this->xpdo instanceof modX) { $elementClasses = array( 'modChunk', 'modPlugin', 'modSnippet', 'modTemplate', 'modTemplateVar', ); foreach ($elementClasses as $classKey) { $elements = $this->xpdo->getCollection($classKey,array('category' => $this->get('id'))); foreach ($elements as $element) { $element->set('category',0); $element->save(); } } $this->xpdo->invokeEvent('OnCategoryRemove',array( 'category' => &$this, 'ancestors' => $ancestors, )); } return $removed; } /** * Loads the access control policies applicable to this category. * * {@inheritdoc} */ public function findPolicy($context = '') { $policy = array(); $enabled = true; $context = !empty($context) ? $context : $this->xpdo->context->get('key'); if ($context === $this->xpdo->context->get('key')) { $enabled = (boolean) $this->xpdo->getOption('access_category_enabled', null, true); } elseif ($this->xpdo->getContext($context)) { $enabled = (boolean) $this->xpdo->contexts[$context]->getOption('access_category_enabled', true); } if ($enabled) { if (empty($this->_policies) || !isset($this->_policies[$context])) { $aclSelectColumns = $this->xpdo->getSelectColumns('modAccessCategory','modAccessCategory','',array('id','target','principal','authority','policy')); $c = $this->xpdo->newQuery('modAccessCategory'); $c->select($aclSelectColumns); $c->select($this->xpdo->getSelectColumns('modAccessPolicy','Policy','',array('data'))); $c->leftJoin('modAccessPolicy','Policy'); $c->innerJoin('modCategoryClosure','CategoryClosure',array( 'CategoryClosure.descendant:=' => $this->get('id'), 'modAccessCategory.principal_class:=' => 'modUserGroup', 'CategoryClosure.ancestor = modAccessCategory.target', array( 'modAccessCategory.context_key:=' => $context, 'OR:modAccessCategory.context_key:=' => null, 'OR:modAccessCategory.context_key:=' => '', ), )); $c->groupby($aclSelectColumns); $c->sortby($this->xpdo->getSelectColumns('modCategoryClosure','CategoryClosure','',array('depth')).' DESC, '.$this->xpdo->getSelectColumns('modAccessCategory','modAccessCategory','',array('authority')).' ASC',''); $acls = $this->xpdo->getIterator('modAccessCategory',$c); foreach ($acls as $acl) { $policy['modAccessCategory'][$acl->get('target')][] = array( 'principal' => $acl->get('principal'), 'authority' => $acl->get('authority'), 'policy' => $acl->get('data') ? $this->xpdo->fromJSON($acl->get('data'), true) : array(), ); } $this->_policies[$context] = $policy; } else { $policy = $this->_policies[$context]; } } return $policy; } /** * Build the closure table for this instance. * * @return boolean True unless building the closure fails and instance is removed. */ public function buildClosure() { $id = $this->get('id'); $parent = $this->get('parent'); /* create self closure */ $cl = $this->xpdo->newObject('modCategoryClosure'); $cl->set('ancestor',$id); $cl->set('descendant',$id); if ($cl->save() === false) { $this->remove(); return false; } /* create closures and calculate rank */ $c = $this->xpdo->newQuery('modCategoryClosure'); $c->where(array( 'descendant' => $parent, )); $c->sortby('depth','DESC'); $gparents = $this->xpdo->getCollection('modCategoryClosure',$c); $cgps = count($gparents); $i = $cgps - 1; $gps = array(); foreach ($gparents as $gparent) { $depth = 0; $ancestor = $gparent->get('ancestor'); if ($ancestor != 0) $depth = $i; $obj = $this->xpdo->newObject('modCategoryClosure'); $obj->set('ancestor',$ancestor); $obj->set('descendant',$id); $obj->set('depth',$depth); $obj->save(); $i--; $gps[] = $ancestor; } /* handle 0 ancestor closure */ $rootcl = $this->xpdo->getObject('modCategoryClosure',array( 'ancestor' => 0, 'descendant' => $id, )); if (!$rootcl) { $rootcl = $this->xpdo->newObject('modCategoryClosure'); $rootcl->set('ancestor',0); $rootcl->set('descendant',$id); $rootcl->set('depth',0); $rootcl->save(); } return true; } /** * Rebuild closure table records for this instance, i.e. parent changed. */ public function rebuildClosure() { /* first remove old tree path */ $this->xpdo->removeCollection('modCategoryClosure',array( 'descendant' => $this->get('id'), 'ancestor:!=' => $this->get('id'), )); /* now create new tree path from new parent */ $newParentId = $this->get('parent'); $c = $this->xpdo->newQuery('modCategoryClosure'); $c->where(array( 'descendant' => $newParentId, )); $c->sortby('depth','DESC'); $ancestors= $this->xpdo->getCollection('modCategoryClosure',$c); $grandParents = array(); foreach ($ancestors as $ancestor) { $depth = $ancestor->get('depth'); $grandParentId = $ancestor->get('ancestor'); /* if already has a depth, inc by 1 */ if ($depth > 0) $depth++; /* if is the new parent node, set depth to 1 */ if ($grandParentId == $newParentId && $newParentId != 0) { $depth = 1; } if ($grandParentId != 0) { $grandParents[] = $grandParentId; } $cl = $this->xpdo->newObject('modCategoryClosure'); $cl->set('ancestor',$ancestor->get('ancestor')); $cl->set('descendant',$this->get('id')); $cl->set('depth',$depth); $cl->save(); } /* if parent is root, make sure to set the root closure */ if ($newParentId == 0) { $cl = $this->xpdo->newObject('modCategoryClosure'); $cl->set('ancestor',0); $cl->set('descendant',$this->get('id')); $cl->save(); } } }