/core/model/modx/modcategory.class.php
PHP | 280 lines | 255 code | 2 blank | 23 comment | 0 complexity | 453d1faa54a06ed95fa1ddbea9027cfd MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, BSD-3-Clause, LGPL-2.1
- <?php
- /*
- * This file is part of MODX Revolution.
- *
- * Copyright (c) MODX, LLC. All Rights Reserved.
- *
- * For complete copyright and license information, see the COPYRIGHT and LICENSE
- * files found in the top-level directory of this distribution.
- */
-
- /**
- * 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();
- }
- }
- }