core/model/modx/processors/element/sort.class.php
<?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.
*/
/**
* Sorts elements in the element tree
*
* @param json $data The JSON encoded data from the tree
*
* @package modx
* @subpackage processors.layout.tree.element
*/
class modElementSortProcessor extends modProcessor {
public function checkPermissions() {
return $this->modx->hasPermission('element_tree');
}
public function getLanguageTopics() {
return array('category');
}
public function process() {
$data = $this->getData();
if (empty($data)) return $this->failure();
$error = false;
if (!empty($data['n_category']) && is_array($data['n_category'])) {
$error = $this->handleCategoryDrop($data['n_category']);
} else if (!empty($data)) {
$error = $this->handleSubCategoryDrop($data);
}
if (is_string($error)) return $this->failure($error);
return $this->success();
}
/**
* Get the data formatted and ready for sorting
* @return array
*/
public function getData() {
$data = $this->getProperty('data');
$data = urldecode($data);
$data = $this->modx->fromJSON($data);
$this->sortNodes('modTemplate','template',$data);
$this->sortNodes('modTemplateVar','tv',$data);
$this->sortNodes('modChunk','chunk',$data);
$this->sortNodes('modSnippet','snippet',$data);
$this->sortNodes('modPlugin','plugin',$data);
return $data;
}
/**
* Handle dropping of Elements or Categories onto Categories
*
* @param array $data
* @return boolean|string
*/
public function handleCategoryDrop(array $data) {
/* if dropping an element onto a category, do that here */
foreach ($data as $key => $elements) {
if (!is_array($elements) || empty($elements)) continue;
$key = explode('_',$key);
if (empty($key[1]) || empty($key[2]) || $key[1] != 'category') continue;
foreach ($elements as $elKey => $elArray) {
$elKey = explode('_',$elKey);
if (empty($elKey[1]) || empty($elKey[3])) continue;
$className = 'mod'.ucfirst($elKey[1]);
if ($className == 'modTv') $className = 'modTemplateVar';
/** @var modElement $element */
$element = $this->modx->getObject($className,$elKey[3]);
if ($element) {
$element->set('category',$key[2]);
$element->save();
}
}
}
/* if sorting categories, do that here */
$cdata = array();
$this->getNodesFormatted($cdata,$data);
foreach ($cdata as $categoryArray) {
if ($categoryArray['type'] != 'category') continue;
/** @var modCategory $category */
$category = $this->modx->getObject('modCategory',$categoryArray['id']);
if ($category && $categoryArray['parent'] != $category->get('parent')) {
$exists = $this->modx->getCount('modCategory',array('category' => $category->get('category'), 'parent' => $categoryArray['parent'])) > 0;
if ($exists) return $this->modx->lexicon('category_err_ae');
$category->set('parent',$categoryArray['parent']);
$category->save();
}
}
return true;
}
/**
* Handle dropping of Categories onto other Categories
* @param array $data
* @return boolean
*/
public function handleSubCategoryDrop(array $data) {
$cdata = array();
foreach ($data as $typeKey => $type) {
if (!empty($type)) {
$this->getCategoryNodeDrop($cdata,$type);
}
}
foreach ($cdata as $item) {
if (empty($item['class']) || empty($item['pk'])) continue;
if ($item['class'] == 'modCategory') {
/** @var modCategory $category */
$category = $this->modx->getObject('modCategory',$item['pk']);
if ($category) {
$category->set('parent',$item['category']);
$category->save();
}
}
}
return true;
}
/**
* Properly sort the data
* @param string $xname
* @param string $type
* @param array $data
* @return void
*/
public function sortNodes($xname,$type,$data) {
$s = $data['n_type_'.$type];
if (is_array($s)) {
$this->sortNodesHelper($s,$xname);
}
}
public function sortNodesHelper($objs,$xname,$currentCategoryId = 0) {
foreach ($objs as $objar => $kids) {
$oar = explode('_',$objar);
$nodeArray = $this->processID($oar);
if ($nodeArray['type'] == 'category') {
$this->sortNodesHelper($kids,$xname,$nodeArray['pk']);
} elseif ($nodeArray['type'] == 'element') {
/** @var modElement $element */
$element = $this->modx->getObject($xname,$nodeArray['pk']);
if (empty($element)) continue;
$element->set('category',$currentCategoryId);
if ($element->get('static')) {
switch ($xname) {
case 'modTemplate':
$type = 'templates';
break;
case 'modTemplateVar':
$type = 'tvs';
break;
case 'modChunk':
$type = 'chunks';
break;
case 'modSnippet':
$type = 'snippets';
break;
case 'modPlugin':
$type = 'plugins';
break;
}
$automate = $this->modx->getOption('static_elements_automate_' . $type);
if ($automate) {
$newFilename = $this->setNewStaticElementFilename($type, $element, $currentCategoryId);
$element->set('static_file', $newFilename);
}
}
$element->save();
}
}
}
/**
* Set the static element path after drag and drop.
*
* @param $type
* @parem $element
* @param $currentCategoryId
* @return string
*/
public function setNewStaticElementFilename($type, $element, $currentCategoryId) {
$categoryArray = $this->getCategoryUltimateParent($currentCategoryId);
$list = array();
$list[] = $categoryArray;
/* Get list of child categories. */
$this->includeCategoryChildren($list, $categoryArray['id'], $categoryArray['name']);
/* Convert nested elements to nested directory structure. */
if ($list) {
$categoryName = $list[0]['category'];
foreach ($list as $category) {
if ($category['id'] === (int)$currentCategoryId) {
$categoryName .= $category['name'];
}
}
}
/* Set category path, also for nested categories. */
$categoryName = strtolower(str_replace(' ', '', $categoryName));
$categoryName = preg_replace('/^[\W\-]+$/', '', $categoryName);
$categoryName = str_replace('—', '/', $categoryName);
$pathInfo = pathinfo($element->get('static_file'));
$basePath = $this->modx->getOption('static_elements_basepath');
$filename = rtrim($basePath, '/') . '/' . $type . '/' . $categoryName . '/' . $pathInfo['basename'];
$filename = str_replace('//', '/', $filename);
return $filename;
}
/**
* Retrieve ultimate parent category for current category.
*
* @param $id
* @return mixed
*/
public function getCategoryUltimateParent($id) {
$category = $this->modx->getObject('modCategory', $id);
if ($category->get('parent') > 0) {
return $this->getCategoryUltimateParent((int) $category->get('parent'));
} else {
return $category->toArray();
}
}
/**
* Get a list of nested categories.
*
* @param $list
* @param $nestedId
* @param $nestedName
*/
public function includeCategoryChildren(&$list, $nestedId, $nestedName){
$children = $this->modx->getIterator('modCategory', array('parent' => $nestedId));
if ($children) {
/** @var modCategory $child */
foreach ($children as $child) {
if (!$child->checkPolicy('list')) continue;
$categoryArray = $child->toArray();
$categoryArray['name'] = $nestedName . ' — ' . $child->get('category');
$list[] = $categoryArray;
$this->includeCategoryChildren($list, $categoryArray['id'], $categoryArray['name']);
}
}
}
public function processID($ar) {
return array(
'elementType' => $ar[1],
'type' => $ar[2],
'pk' => $ar[3],
'elementCatId' => isset($ar[4]) ? $ar[4] : 0,
);
}
public function getNodesFormatted(&$ar_nodes,$cur_level,$parent = 0) {
$order = 0;
foreach ($cur_level as $nodeId => $children) {
$ar = explode('_',$nodeId);
if (empty($ar[1]) || empty($ar[2])) continue;
$ar_nodes[] = array(
'id' => $ar[2],
'type' => $ar[1],
'parent' => $parent,
'order' => $order,
);
$order++;
$this->getNodesFormatted($ar_nodes,$children,$ar[2]);
}
}
public function getCategoryNodeDrop(&$cdata,$type = array(),$currentParent = 0) {
foreach ($type as $itemKey => $item) {
$nar = explode('_',$itemKey);
$pk = !empty($nar[3]) ? $nar[3] : 0;
$cdata[] = array(
'type' => $nar[1],
'class' => 'mod'.ucfirst($nar[2]),
'pk' => $pk,
'category' => $currentParent,
);
if (!empty($item)) {
$this->getCategoryNodeDrop($cdata,$item,$pk);
}
}
}
}
return 'modElementSortProcessor';