Source for file CategoryUtil.class.php
Documentation is available at CategoryUtil.class.php
* Zikula Application Framework
* @copyright Robert Gasch
* @link http://www.zikula.org
* @version $Id: CategoryUtil.class.php 24342 2008-06-06 12:03:14Z markwest $
* @license GNU/GPL - http://www.gnu.org/copyleft/gpl.html
* @author Robert Gasch rgasch@gmail.com
* @subpackage CategoryUtil
* Return a category object by ID
* @param cid The category-ID to retrieve
* @return The resulting folder object
if (isset ($cache[$cid])) {
$permFilter[] = array('realm' => 0,
'component_left' => 'Categories',
'component_middle' => '',
'component_right' => 'Category',
'instance_middle' => 'path',
'instance_right' => 'ipath',
'level' => ACCESS_OVERVIEW);
$cache[$cid] = DBUtil::selectObjectByID ('categories_category', (int) $cid, 'id', null, $permFilter);
$cache[$cid]['display_name'] = DataUtil::formatForDisplayHTML(unserialize($cache[$cid]['display_name']));
$cache[$cid]['display_desc'] = DataUtil::formatForDisplayHTML(unserialize($cache[$cid]['display_desc']));
* Return an array of categories objects according the specified where-clause and sort criteria.
* @param where The where clause to use in the select (optional) (default='')
* @param sort The order-by clause to use in the select (optional) (default='')
* @param assocKey The field to use as the associated array key (optional) (default='')
* @return The resulting folder object array
function getCategories ($where= '', $sort= '', $assocKey= '', $enablePermissionFilter= true)
$category_column = $pntables['categories_category_column'];
$sort = "ORDER BY $category_column[sort_value], $category_column[name]";
if ($enablePermissionFilter) {
$permFilter[] = array('realm' => 0,
'component_left' => 'Categories',
'component_middle' => '',
'component_right' => 'Category',
'instance_middle' => 'path',
'instance_right' => 'ipath',
foreach ($arraykeys as $arraykey) {
if ($cats[$arraykey]['display_name']) {
if ($cats[$arraykey]['display_desc']) {
if (!$enablePermissionFilter) {
* Return a folder object by it's path
* @param apath The path to retrieve by (simple path or array of paths)
* @param field The (path) field we search for (either path or ipath) (optional) (default='path')
* @return The resulting folder object
$category_column = $pntables['categories_category_column'];
foreach ($apath as $path) {
if (isset ($cats[0]) && is_array($cats[0])) {
* Return an array of categories by the registry info
* @param registry The registered categories to retrieve
* @return The resulting folder object array
$category_column = $pntables['categories_category_column'];
foreach ($registry as $property => $catID) {
foreach ($registry as $property => $catID) {
if (isset ($cats[$catID])) {
$result[$property] = $cats[$catID];
* Return the direct subcategories of the specified category
* @param id The folder id to retrieve
* @param sort The order-by clause (optional) (default='')
* @param relative whether or not to also generate relative paths (optional) (default=false)
* @param all whether or not to return all (or only active) categories (optional) (default=false)
* @param assocKey The field to use as the associated array key (optional) (default='')
* @param attributes The associative array of attribute field names to filter by (optional) (default=null)
* @return The resulting folder object
$category_column = $pntables['categories_category_column'];
$where .= " AND $category_column[status]='A'";
//if ($attributes && is_array($attributes)) {
// foreach ($attributes as $k=>$v) {
// $where .= " AND $category_column[$k]='$v' ";
if ($cats && $relative) {
foreach ($arraykeys as $key) {
* Return all parent categories starting from id
* @param id The (leaf) folder id to retrieve
* @param assocKey whether or not to return an assocKeyiative array (optional) (default='id')
* @return The resulting folder object array
$category_column = $pntables['categories_category_column'];
if (!$cat || !$cat['parent_id']) {
$cats[$cat[$assocKey]] = $cat;
} while ($cat && $cat['parent_id']);
* Return an array of category objects by path without the root category
* @param apath The path to retrieve categories by
* @param sort The sort field (optional) (default='')
* @param field The the (path) field to use (path or ipath) (optional) (default='ipath')
* @param includeLeaf whether or not to also return leaf nodes (optional) (default=true)
* @param all whether or not to return all (or only active) categories (optional) (default=false)
* @param exclPath The path to exclude from the retrieved categories (optional) (default='')
* @param assocKey The field to use to build an associative key (optional) (default='')
* @param attributes The associative array of attribute field names to filter by (optional) (default=null)
* @return The resulting folder object array
$all= false, $exclPath= '', $assocKey= '', $attributes= null)
$category_column = $pntables['categories_category_column'];
$where .= " AND $category_column[is_leaf]=0";
$where .= " AND $category_column[status]='A'";
//if ($attributes && is_array($attributes)) {
// foreach ($attributes as $k=>$v) {
// $where .= " AND $category_column[$k]='$v' ";
$sort = "ORDER BY $category_column[sort_value], $category_column[path]";
* Return an array of Subcategories for the specified folder
* @param cid The root-category category-id
* @param recurse whether or not to generate a recursive subcategory result set (optional) (default=true)
* @param relative whether or not to generate relative path indexes (optional) (default=true)
* @param includeRoot whether or not to include the root folder in the result set (optional) (default=false)
* @param includeLeaf whether or not to also return leaf nodes (optional) (default=true)
* @param all whether or not to include all (or only active) folders in the result set (optional) (default=false)
* @param excludeCid CategoryID (root folder) to exclude from the result set (optional) (default='')
* @param assocKey The field to use as the associated array key (optional) (default='')
* @param attributes The associative array of attribute field names to filter by (optional) (default=null)
* @return The resulting folder object array
function getSubCategories ($cid, $recurse= true, $relative= true, $includeRoot= false,
$includeLeaf= true, $all= false, $excludeCid= '', $assocKey= '', $attributes= null)
static $catPathCache = array();
$cacheKey = $cid . '_' . (int) $recurse . '_' . (int) $relative . '_' . (int) $includeRoot . '_' . (int) $includeLeaf . '_' . (int) $all . '_' . $excludeCid . '_' . $assocKey;
if (isset ($catPathCache[$cacheKey])) {
return $catPathCache[$cacheKey];
$catPathCache[$cacheKey] = $cats;
* Return an array of Subcategories for the specified folder
* @param apath The path to get categories by
* @param field The (path) field we match by (either path or ipath) (optional) (default='ipath')
* @param recurse whether or not to generate a recursive subcategory result set (optional) (default=true)
* @param relative whether or not to generate relative path indexes (optional) (default=true)
* @param includeRoot whether or not to include the root folder in the result set (optional) (default=false)
* @param includeLeaf whether or not to also return leaf nodes (optional) (default=true)
* @param all whether or not to include all (or only active) folders in the result set (optional) (default=false)
* @param excludeCid CategoryID (root folder) to exclude from the result set (optional) (default='')
* @param assocKey The field to use as the associated array key (optional) (default='')
* @param attributes The associative array of attribute field names to filter by (optional) (default=null)
* @return The resulting folder object array
$includeRoot= false, $includeLeaf= true, $all= false, $excludeCid= '', $assocKey= '', $attributes= null)
static $catPathCache = array();
$cacheKey = $apath . '_' . $field . '_' . (int) $recurse . '_' . (int) $relative . '_' . (int) $includeRoot . '_' . (int) $includeLeaf . '_' . (int) $all . '_' . $excludeCid . '_' . $assocKey;
if (isset ($catPathCache[$cacheKey])) {
return $catPathCache[$cacheKey];
$includeRoot, $includeLeaf, $all, $exclCat, $assocKey, $attributes);
$catPathCache[$cacheKey] = $cats;
* Return an array of Subcategories by for the given category
* @param category The root category to retrieve
* @param recurse whether or not to recruse (if false, only direct subfolders are retrieved) (optional) (default=true)
* @param relative whether or not to also generate relative paths (optional) (default=true)
* @param includeRoot whether or not to include the root folder in the result set (optional) (default=false)
* @param includeLeaf whether or not to also return leaf nodes (optional) (default=true)
* @param all whether or not to return all (or only active) categories (optional) (default=false)
* @param excludeCat The root category of the hierarchy to exclude from the result set (optional) (default='')
* @param assocKey The field to use as the associated array key (optional) (default='')
* @param attributes The associative array of attribute field names to filter by (optional) (default=null)
* @param sortField The field to sort the resulting category array by (optional) (default=null)
* @return The resulting folder object array
$includeLeaf= true, $all= false, $excludeCat= null, $assocKey= '', $attributes= null, $sortField= null)
$ipath = $category['ipath'];
$ipathExcl = ($excludeCat ? $excludeCat['ipath'] : '');
// since array_shift() resets numeric array indexes, we remove the leading element like this
list ($k, $v) = each($cats);
if ($cats && $relative) {
foreach ($arraykeys as $key) {
$_catSortField = $sortField;
usort ($cats, '_sortCategories');
* Delete a category by it's ID
* @param cid The categoryID to delete
* @return The DB result set
$category_table = $pntables['categories_category'];
$category_column = $pntables['categories_category_column'];
* Delete categories by Path
* @param apath The path we wish to delete
* @param field The (path) field we delete from (either path or ipath) (optional) (default='ipath')
* @return The DB result set
$category_table = $pntables['categories_category'];
$category_column = $pntables['categories_category_column'];
$sql = "DELETE FROM $category_table WHERE $category_column[$field] LIKE '". DataUtil::formatForStore($apath). "%'";
* Move categories by ID (recursive move)
* @param cid The categoryID we wish to move
* @param newparent_id The categoryID of the new parent category
* Move SubCategories by Path (recurisve move)
* @param apath The path to move from
* @param newparent_id The categoryID of the new parent category
* @param field The field to use for the path reference (optional) (default='ipath')
* Move Categories by Path (recursive move)
* @param apath The path to move from
* @param newparent_id The categoryID of the new parent category
* @param field The field to use for the path reference (optional) (default='ipath')
* @param includeRoot whether or not to also move the root folder (optional) (default=true)
if (!$newParent || !$cats) {
// since array_shift() resets numeric array indexes, we remove the leading element like this
list ($k, $v) = each($cats);
$newParentIPath = $newParent['ipath'] . '/';
$newParentPath = $newParent['path'] . '/';
$oldParentIPath = $oldParent['ipath'] . '/';
$oldParentPath = $oldParent['path'] . '/';
$category_table = $pntables['categories_category'];
$category_column = $pntables['categories_category_column'];
$pathField = $category_column[$field];
$fpath = $category_column['path'];
$fipath = $category_column['ipath'];
$sql = "UPDATE $category_table SET
$fpath = REPLACE($fpath, '$oldParentPath', '$newParentPath'),
$fipath = REPLACE($fipath, '$oldParentIPath', '$newParentIPath')
* Copy categories by ID (recursive copy)
* @param cid The categoryID we wish to copy
* @param newparent_id The categoryID of the new parent category
* Copy SubCategories by Path (recurisve copy)
* @param apath The path to copy from
* @param newparent_id The categoryID of the new parent category
* @param field The field to use for the path reference (optional) (default='ipath')
* Copy Categories by Path (recurisve copy)
* @param apath The path to copy from
* @param newparent_id The categoryID of the new parent category
* @param field The field to use for the path reference (optional) (default='ipath')
* @param includeRoot whether or not to also move the root folder (optional) (default=true)
if (!$newParent || !$cats) {
$oldToNewID[$cats[0]['parent_id']] = $newParent['id'];
// since array_shift() resets numeric array indexes, we remove the leading element like this
list ($k, $v) = each($cats);
$cat['parent_id'] = $oldToNewID[$cat['parent_id']];
$cat['path'] = $newParent['path'] . '/' . $cat['path_relative'];
$pnCat = new pnCategory ($cat);
$oldToNewID[$oldID] = $pnCat->_objData['id'];
// rebuild iPath since now we have all new PathIDs
* Check whether $cid is a direct subcategory of $root_id
* @param root_id The root/parent ID
* @param cid The categoryID we wish to check for subcategory-ness.
if (isset ($cat['parent_id'])) {
return $cat['parent_id'] == $root_id;
* Check whether $cid is a direct subcategory of $root_id
* @param rootCat The root/parent category
* @param cat The category we wish to check for subcategory-ness.
return $cat['parent_id'] == $rootCat['id'];
* Check whether $cid is a subcategory of $root_id
* @param root_id The ID of the root category we wish to check from
* @param cid The category-id we wish to check for subcategory-ness.
if (!$root_id || !$cid) {
if (!$rootCat || !$cat ) {
* Check whether $cat is a subcategory of $rootCat
* @param rootCat The root/parent category
* @param cat The category we wish to check for subcategory-ness.
$rPath = $rootCat['ipath'] . '/';
return strpos($cPath, $rPath) === 0;
* Check whether the category $cid has subcategories (optional checks for leafe )
* @param cid The parent category
* @param countOnly whether or not to explicitly check for leaf nodes in the subcategories
* @param all whether or not to return all (or only active) subcategories
return (boolean) count($cats);
foreach ($cats as $cat) {
* Get the java-script for the tree menu
* @param cats The categories array to represent in the tree
* @param doReplaceRootCat Whether or not to replace the root category with a localized string (optional) (default=true)
* @return generated tree JS text
$treemid->setMenuStructureString($menuString);
$treemid->parseStructureForMenu('treemenu1');
$treemid->setLibjsdir("javascript/phplayersmenu/libjs");
$treemid->setImgdir("javascript/phplayersmenu/images");
$treemid->setImgwww("javascript/phplayersmenu/images");
$treemenu1 = $treemid->newTreeMenu('treemenu1');
* Return an array of folders the user has at least access/view rights to.
* @param cats The username we wish to get the folder list for
* @return The resulting folder path array
$params['mode'] = 'edit';
// use foreach as folders can come back with folderIDs as keys
foreach ($cats as $k => $v)
// account for the fact that a single slash is a valid root
// path but subfolders only have a single slash as well
for ($j= 0; $j< $depth; $j++ ) {
$params['cid'] = $v['id'];
$menuLine = "$ds|$name|$url||||\n";
$menuString .= $menuLine;
//print (nl2br ($menuString));
* Return the HTML selector code for the given category hierarchy
* @param cats The category hierarchy to generate a HTML selector for
* @param field The field value to return (optional) (default='id')
* @param selected The selected category (optional) (default=0)
* @param name The name of the selector field to generate (optional) (default='category[parent_id]')
* @param defaultValue The default value to present to the user (optional) (default=0)
* @param defaultText The default text to present to the user (optional) (default='')
* @param allValue The value to assign to the "all" option (optional) (default=0)
* @param allText The text to assign to the "all" option (optional) (default='')
* @param submit whether or not to submit the form upon change (optional) (default=false)
* @param displayPath If false, the path is simulated, if true, the full path is shown (optional) (default=false)
* @param doReplaceRootCat Whether or not to replace the root category with a localized string (optional) (default=true)
* @param multipleSize If > 1, a multiple selector box is built, otherwise a normal/single selector box is build (optional) (default=1)
* @return The HTML selector code for the given category hierarchy
function getSelector_Categories ($cats, $field= 'id', $selectedValue= '0', $name= 'category[parent_id]', $defaultValue= 0, $defaultText= '',
$allValue= 0, $allText= '', $submit= false, $displayPath= false, $doReplaceRootCat= true, $multipleSize= 1,
$line = '---------------------------------------------------------------------';
if ($multipleSize > 1 && strpos($name,'[]')=== false) {
$selectedValue = array($selectedValue);
$id = strtr ($name, '[]', '__');
$multiple = $multipleSize > 1 ? ' multiple="multiple"' : '';
$multipleSize = $multipleSize > 1 ? " size=\"$multipleSize\"" : '';
$submit = $submit ? ' onchange="this.form.submit();"' : '';
$html = "<select name=\"$name\" id=\"$id\"{$multipleSize}{$multiple}{$submit}>";
if (!empty($defaultText)) {
$sel = (in_array((string) $defaultValue, $selectedValue) ? ' selected="selected"' : '');
$html .= "<option value=\"$defaultValue\"$sel>$defaultText</option>";
$sel = (in_array((string) $allValue, $selectedValue) ? ' selected="selected"' : '');
$html .= "<option value=\"$allValue\"$sel>$allText</option>";
if (!isset ($cats) || empty($cats)) {
$sel = (in_array((string) $cat['__ATTRIBUTES__'][$field], $selectedValue) ? ' selected="selected"' : '');
$sel = (in_array((string) $cat[$field], $selectedValue) ? ' selected="selected"' : '');
$v = $cat['__ATTRIBUTES__'][$field];
$html .= "<option value=\"$v\"$sel>$cat[path]</option>";
$html .= "<option value=\"$cat[$field]\"$sel>$cat[path]</option>";
$indent = substr ($line, 0, $cslash* 2);
// $indent = '|' . $indent;
// $indent = ' ' . $indent;
if (isset ($cat['display_name'][$lang]) && !empty($cat['display_name'][$lang])) {
$catName = $cat['display_name'][$lang];
$v = $cat['__ATTRIBUTES__'][$field];
$html .= "<option value=\"$v\"$sel>$indent " . DataUtil::formatForDisplayHtml($catName) . "</option>";
$html .= "<option value=\"$cat[$field]\"$sel>$indent " . DataUtil::formatForDisplayHtml($catName) . "</option>";
* Compare function for ML name field
* @param catA 1st category
* @param catB 2nd category
* @return The resulting compare value
if (!$catA['display_name'][$lang]) {
$catA['display_name'][$lang] = $catA['name'];
if ($catA['display_name'][$lang] == $catB['display_name'][$lang]) {
return strcmp($catA['display_name'][$lang], $catB['display_name'][$lang]);
* Compare function for ML description field
* @param catA 1st category
* @param catB 2nd category
* @return The resulting compare value
if ($catA['display_desc'][$lang] == $catB['display_desc'][$lang]) {
return strcmp($catA['display_desc'][$lang], $catB['display_desc'][$lang]);
* Utility function to sort a category array by the current locate of
* either the ML name or description
* @param cats The categories array
* @param func Which compare function to use (determines field to be used for comparison) (optional) (defaylt='cmpName')
* @return The resulting sorted category array (original array altered in place)
* Resequence the sort fields for the given category
* @param cats The categories array
* @param step The counting step/interval (optional) (default=1)
* @return True if something was done, false if an emtpy $cats was passed in
$cats[$k]['sort_value'] = ++ $c* $step;
* Given an array of categories (with the Property-Names being
* the keys of the array) and it corresponding Parent categories (indexed
* with the Property-Names too), return an (identically indexed) array
* of category-paths based on the given field (name or id make sense)
* @param rootCatIDs The root/parent categories ID
* @param cats The associative categories object array
* @param includeRoot If true, the root portion of the path is preserved
* @return The resulting folder path array (which is also altered in place)
foreach ($cats as $prop => $catID) {
if (!isset ($rootCatIDs[$prop]) || !$rootCatIDs[$prop]) {
* Given a category with its parent category, return an (idenically indexed)
* array of category-paths based on the given field (name or id make sense)
* @param rootCategory The root/parent category
* @param cat The category to process
* @param includeRoot If true, the root portion of the path is preserved
* @return The resulting folder path array (which is also altered in place)
// remove the Root Category name of the paths
// because multilanguage names has different lengths
$pos = strpos($rootCategory['path'], '/', 1);
$rootCategory['path'] = substr ($rootCategory['path'], $pos);
$pos = strpos($cat['path'], '/', 1);
$normalizedPath = substr ($cat['path'], $pos);
// process the normalized paths
$ppos = strrpos($rootCategory['path'], '/') + 1;
$ipos = strrpos($rootCategory['ipath'], '/') + 1;
$cat['path_relative'] = substr ($normalizedPath, $ppos);
$cat['ipath_relative'] = substr ($cat['ipath'], $ipos);
$offSlashPath = strpos($cat['path_relative'], '/');
$offSlashIPath = strpos($cat['ipath_relative'], '/');
if ($offSlashPath !== false) {
$cat['path_relative'] = substr ($cat['path_relative'], $offSlashPath+ 1);
if ($offSlashIPath !== false) {
$cat['ipath_relative'] = substr ($cat['ipath_relative'], $offSlashIPath+ 1);
* Given an array of categories (with the category-IDs being
* the keys of the array), return an (idenically indexed) array
* of category-paths based on the given field (name or id make sense)
* @param cats The associative categories object array
* @param field Which field to use the building the path (optional) (default='name')
* @return The resulting folder path array
foreach ($cats as $k => $v) {
$path = "$pcat[$field]/$path";
$pid = $pcat['parent_id'];
* Rebuild the path field for all categories in the database
* @param field The field which we wish to populate (optional) (default='path')
* @param sourceField The field we use to build the path with (optional) (default='name')
* @param leaf_id The leaf-category category-id (ie: we'll rebuild the path of this category and all it's parents) (optional) (default=0)
* Note that field and sourceField go in pairs (that is, if you want sensical results)!
function rebuildPaths ($field= 'path', $sourceField= 'name', $leaf_id= 0)
//$cats = CategoryUtil::getParentCategories ($leaf_id, 'id');
foreach ($cats as $k => $v) {
if ($v[$field] != $paths[$k][$field]) {
// since we're not going through the object layer for this, we must manually serialize the locale fields
$v['display_name'] = serialize ($v['display_name']);
$v['display_desc'] = serialize ($v['display_desc']);
* Check for access to a certain set of categories
* For each category property in the list, check if we have access to that category in that property.
* Check is done as "Categories:Property:$propertyName", "$cat[id]::"
* @param array $categories Array of category data (as returned from ObjectUtil::expandObjectWithCategories).
* @param int $permLevel Required permision level.
* @return bool True if access is allowed to at least one of the categories
// Always allow access to content with no categories associated
if (count($categories) == 0)
// Access is required for all categories
foreach ($categories as $propertyName => $cat)
// Access is required for at least one category
foreach ($categories as $propertyName => $cat)
return ($a[$_catSortField] < $b[$_catSortField]) ? - 1 : 1;
|