=====Advanced Category Action===== //Installed as an **[[WikkaBetaFeatures alpha feature]]** on this server as of 2005-06-12.// >>**See also:** //official// ~-CategoryActionInfo //background and history// ~-PageAndCategoryDivisionInACategory ~-CategorySystemOverhaul //supporting code// ~-ArrayToList ~-ArrayToColumns ~-GenerateUniqueId ~-CompatibilityCode ~-AdvancedFormatter //related// ~-AdvancedBacklinksAction >>This is the development page for a "more advanced" version of the [[Docs:CategoryActionInfo category action]]. The version presented on this page is a further development of the beta version already running on this site (with as its main feature a more structural and readable columnar layout), borrows ideas from NilsLindenberg's PageAndCategoryDivisionInACategory, while adding new functionality and flexibility. In fact, it takes the possibilities of the current category system to its practical limits in what it is capable of for **category navigation**. While a [[CategorySystemOverhaul category system overhaul]] definitely will be needed to support new functionality, this should help to tide us over until we have designed and implemented a new system.::c:: ====Improvements and new functionality==== ===Features=== ~-Choose output **format** with the **##type##** parameter: ~~//category context:// ~~~-##cols## produces a columnar output, with columns constructed as left-floated ##div##s ~~~-##list## produces an unordered list ~~//page context:// ~~~-##related## is a special option that displays "related pages": pages in the same categories as the **current** page; this automatically sets some other options, such as list-format output and showing only pages--- ~-Choose **what** to show with the **##show##** parameter: ~~-##all## shows both subcategories and pages, but separated ~~-##categories## shows subcategories only ~~-##pages## shows member pages only ~~-##members## shows all, but without separation between subcategories and pages (automatic when ##catnames## is off (0) and the operation of the 1.1.6.0. category action) ~-Select a **category**: ~~//category context:// ~~~-choose what category to show members of with the **##cat##** parameter; default is the **current category page** ~~//page context:// ~~~-choose a single category to show related pages for with the **##cat##** parameter (the current page must actually be a member of that category); default is **all** categories the **current page** belongs to ~-Template pages (meant only to clone from and thus not real "content") are filtered out by default (except for a category for templates); override this with the **##inctpl##** parameter ~-The assumption is that categories are named with 'Category' at the start; if not, this can be indicated with the **##catnames##** parameter; when 0, this will automatically select **##show##** = 'members' and disable other features like "related pages" that depend on this naming convention ~-Output is wrapped in a div with class "categorycols", "categorylist" or "categoryrel" as an easy hook for styling; use the **##class##** parameter to add extra classes ~-The automatically-generated (main) heading (also with an id) can be overridden with the **##head##** parameter ~-Backwards-compatible with the version 1.1.6.0 parameters (##page##, ##col## and ##compact##); the new parameters override the old ones if both are present ===Syntax=== ""{{category [cat="categoryname"] [show="all|categories|pages|members"] [type="cols|list|related"] [cols="n"] [catnames="0|1"] [inctpl="0|1"] [class="class"] [head="main heading"]}}"" ===Use cases=== //later// ====The code==== This code completely replaces the current **##./actions/category.php##** file (make a backup if you want to try it out). Although it uses some new parameters it is still completely compatible with the current (1.1.6.0) category action; the new parameters take precence over the old ones though. //Note that this is still 'somewhat' beta code and currently contains (commented out) some debug code; this will be cleaned up later.// %%(php;1)used on any page; if not a category page, the cat * parameter must be specified in order to list category members (otherwise the action will revert to * list the content of the top-level category). * - a content page for the 'related pages' feature. To list related pages, specify type='related' and * (optionally) the category to list (other) member pages of. If no category is specified, all * categories of the current page are considered; if a category is specified, it will be considered * only if it is actually one of the categories the current page belongs to. * * Several features are dependent on a naming convention where each category ("category page") starts * with 'Category'. While it's (technically) possible to have categories without this naming convention, * this makes features like separating subcategories from content pages in a listing impossible. * The default is to assume the naming convention is used; if not, override this behavior with a * catnames="0" parameter: the action will then ignore any contradictory parameters and revert to just * listing 'members' of the current or requested category. * * Template pages (created specifically to clone from) are normally not "real" content pages; the default * is to filter these pages (so only actual content pages are listed), which will only work if the * naming convention for them is followed by ending the page name with 'Template'. This behavior can * be overridden by specifying an inctpl="1" parameter. One exception: page names that start with * 'Category' and end with 'Template' are considered a proper category, intended to contain templates: * all members of such a 'template category' are considered even when the 'inctpl' parameter is set to 0. * * Note: the list view (type='list' or type='related') is nice for a sidebar while the columnar view * (type='cols') is more suited as content for a category page. * * Syntax: * {{category [cat="categoryname"] [show="all|categories|pages|members"] [type="cols|list|related"] [cols="n"] [catnames="0|1"] [inctpl="0|1"] [class="class"] [head="main heading"]}} * * Old (version 1) parameters are supported for compatibility but new ones take precedence. * * @todo - possible? use a single list also for columns, using CSS to visually split up into columns - JW 2005-01-19 * * @package Actions * @subpackage SystemContent * @name Category * * @author {@link http://wikka.jsnx.com/JsnX JsnX} * @author {@link http://wikka.jsnx.com/NilsLindenberg NilsLindenberg} (separation into subcategories and pages) * @author {@link http://wikka.jsnx.com/JavaWoman JavaWoman} (complete rewrite, filtering, table-less columns and 'related pages' functionality) * @copyright Copyright © 2005, Marjolein Katsma * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @since Wikka 1.0.0 * @version 2.5beta * * @input string $cat optional: category to list members of; default: current page or CategoryCategory * You can specify '/' as a shortcut for CategoryCategory * @input string $show optional: all|categories|pages|members; default: all; or members if $catnames * is 0. * @input string $type optional: cols|list|related; default: cols. * - cols: multi-column display, list everything. (Useful for category pages) * - list: single list, strips 'Category' from category (page) names. * (Useful for sidebar) * - related: "related pages": list(s) with pages in the same categories as * the current page, or in requested category only; excluding the current page. * (useful for sidebar) * * @input integer $catnames optional: consider only category names (pages) that start with 'Category' as a * category (the name test is case-insensitive); default: 1 * set to 0 if there is no naming convention for categories! * @input integer $inctpl optional: include "template" pages (1) or not (0); default: 0. * @input integer $cols optional: number of columns to use; default: 1 (only relevant for type"cols") * @input integer $class optional: class(es) to determine styling of the output list or columns * @input string $head optional: override of built-in main heading; you can specify a '%s' placeholder * for the requested category in a category members listing (type="cols|list") * * @input string $page DEPRECATED (optional): superseded by $cat (synonym) * @input integer $compact DEPRECATED (optional): use columns (0) or list (1); superseded by $type * @input integer $col DEPRECATED (optional): superseded by $cols * * @output string list of pages belonging to the specified or top-level category, formatted as a list * or columns of items * * @uses CheckMySQLVersion() * @uses getCatMembers() * @uses Link() * @uses makeId() * @uses makeMemberList() * @uses makeMemberCols() */ // ----------------- utility functions ------------------ if (!function_exists('buildMemberList')) { function buildMemberList($member,&$list,$cat,$bIncTpl) { global $wakka; if ( (FALSE === $bIncTpl && preg_match('/(.+)Template$/',$member)) ||# template filter ('CategoryCategory' == $member) || # do not list top-level category as member ($member == $cat) # do not list requested category as member ) { #echo '(Members) Filtered: '.$member.'
'; // do nothing } else { $list[] = $wakka->Link($member); } } } if (!function_exists('buildCatList')) { function buildCatList($member,&$list,$cat,$type) { global $wakka; if ( (!preg_match('/^Category/',$member)) || # only category pages count ('CategoryCategory' == $member) || # do not list top-level category as member ($member == $cat) # do not list requested category as member ) { #echo '(Cats) Filtered: '.$member.'
'; // do nothing } else { if ('cols' == $type) { $list[] = $wakka->Link($member); } elseif ('list' == $type) { $list[] = $wakka->Link($member,'',preg_replace('/^Category/','',$member)); } } } } if (!function_exists('buildPageList')) { function buildPageList($member,&$list,$cat,$bIncTpl,$type,$page) { global $wakka; if ( (preg_match('/^Category/',$member)) || # categories filter (only pages count) (FALSE === $bIncTpl && preg_match('/Template$/',$member)) || # template filter ('related' == $type && $member == $page) # current page filter ) { #echo '(Pages) Filtered: '.$member.'
'; // do nothing } else { if ('related' == $type) { #echo "adding member $member for cat $cat
"; $list[$cat][] = $wakka->Link($member); } else { #echo 'adding '.$member.'
'; $list[] = $wakka->Link($member); } } } } // ----------------- constants and variables ------------------ // constants $aShow = array('all','categories','pages','members'); $aType = array('cols','list','related'); if (!defined('PAT_PAGENAME_CHARS')) define('PAT_PAGENAME_CHARS','A-Za-z0-9ÄÖÜßäöü'); # NOT within [] so we can use if for a negative class as well // set defaults $lCat = NULL; # no predefined default $lShow = 'all'; # both categories and pages $lType = 'cols'; # default display type $bCatNames = TRUE; # only pages starting with 'Category' are considered category pages $bIncTpl = FALSE; # do not show template pages or treat a template as a category $lCols = 1; # one column for columnar layout $lClass = ''; # no class $lHead = NULL; # specified heading (may contain place holder for category) // initializations $bCatDefined = FALSE; $bCategoryPage = preg_match('/^Category/',$this->tag); // User-interface strings if (!defined('HD_COL')) define('HD_COL','Members of the %s Category'); if (!defined('HD_LST')) define('HD_LST','%s'); if (!defined('HD_REL')) define('HD_REL','Related pages'); if (!defined('COL_MEMBERS_FOUND')) define('COL_MEMBERS_FOUND','Category %2$s consists of the following %1$d members:'); if (!defined('COL_CATS_FOUND')) define('COL_CATS_FOUND','The following %1$d categories belong to %2$s:'); if (!defined('COL_PAGES_FOUND')) define('COL_PAGES_FOUND','The following %1$d pages belong to %2$s:'); if (!defined('HD_LST_MEMBERS')) define('HD_LST_MEMBERS','Members'); if (!defined('HD_LST_CATS')) define('HD_LST_CATS','Categories'); if (!defined('HD_LST_PAGES')) define('HD_LST_PAGES','Pages'); if (!defined('LST_MEMBERS_FOUND')) define('LST_MEMBERS_FOUND','%d Members:'); if (!defined('LST_CATS_FOUND')) define('LST_CATS_FOUND','%d Categories:'); if (!defined('LST_PAGES_FOUND')) define('LST_PAGES_FOUND','%d Pages:'); if (!defined('REL_PAGES_FOUND')) define('REL_PAGES_FOUND','in %2$s (%1$d):'); # note (required) parameter order! if (!defined('COL_NONE_FOUND')) define('COL_NONE_FOUND','No items found for %s.'); if (!defined('COL_NO_CATS_FOUND')) define('COL_NO_CATS_FOUND','No subcategories found in %s.'); if (!defined('COL_NO_PAGES_FOUND')) define('COL_NO_PAGES_FOUND','No pages found in %s.'); if (!defined('LST_NONE_FOUND')) define('LST_NONE_FOUND','No items found.'); if (!defined('LST_NO_CATS_FOUND')) define('LST_NO_CATS_FOUND','No subcategories found.'); if (!defined('LST_NO_PAGES_FOUND')) define('LST_NO_PAGES_FOUND','No pages found.'); if (!defined('REL_NONE_FOUND')) define('REL_NONE_FOUND','None found.'); // --------------------------- processsing -------------------------- // --------------- get parameters ---------------- if (is_array($vars)) { foreach ($vars as $param => $value) { switch ($param) { // 1 - context case 'cat': if ($this->existsPage($value)) $tCat = $value; break; case 'page': if ($this->existsPage($value)) $tPage = $value; break; // 2 - what case 'show': if (in_array($value,$aShow)) $tShow = $value; break; // 3 - display type case 'type': if (in_array($value,$aType)) $tType = $value; # #if (isset($tType)) echo '(valid) input type: '.$tType.'
'; # break; case 'compact': if ($value === (string)(int)$value) $tCompact = (int)$value; break; // 4 - filters case 'catnames': if ($value == 0) $tCatNames = FALSE; # #if (isset($tCatNames)) echo 'CatNames FALSE
'; # break; case 'inctpl': if ($value == 1) $tIncTpl = TRUE; # #if ($value == 1) echo 'include template pages
'; # break; // 5 - presentation case 'cols': if ($value === (string)(int)$value) $tCols = abs((int)$value); break; case 'col': if ($value === (string)(int)$value) $tCol = abs((int)$value); break; case 'class': $tClass = trim(strip_tags($value)); break; case 'head': $tHead = trim(strip_tags($value)); } } } // ------------- process parameters -------------- // filters if (isset($tCatNames)) $bCatNames = $tCatNames; # #if ($bCatNames) echo 'catnames on
'; else echo 'catnames not on: cannot distinguish pages from categories!!
'; # // determine which category/categories to look at if (isset($tCat)) { $bCatDefined = TRUE; $tempCat = ('/' == $tCat) ? 'CategoryCategory' : $tCat; # '/' = shortcut for top-level category } elseif (isset($tPage)) # DEPRECATED { $bCatDefined = TRUE; $tempCat = ('/' == $tPage) ? 'CategoryCategory' : $tPage; # '/' = shortcut for top-level category } else { $tempCat = $this->tag; # fallback current page for no specified category } // derive display type if (isset($tType)) { if ('related' == $tType && (!$bCatNames)) { $lType = 'list'; # fallback if category not defined or we have no naming convention } else { $lType = $tType; } } elseif (isset($tCompact)) # DEPRECATED { if (0 == $tCompact) { $lType = 'cols'; } elseif (1 == $tCompact) { $lType = 'list'; } } //else default 'cols' // final category (or array of categories) to consider // verify whether we have a valid category (page) if ($bCatNames && 'related' != $lType && !preg_match('/^Category/i',$tempCat)) { $lCat = 'CategoryCategory'; # fallback category } // find categories to consider for 'related' elseif ($bCatNames && 'related' == $lType) { $aCats = array(); if (!$bCatDefined) { # #$relstart = getmicrotime(); # // replace every character not allowed in a page name by a space and build word array from the page $aWords = preg_split('/\s/',preg_replace('/[^'.PAT_PAGENAME_CHARS.']/',' ',$this->page['body'])); // collect category names foreach ($aWords as $word) { // a word is only a category name if 'Category' is followed by something (case-insensitive check) if (preg_match('/^Category(.+)$/i',$word)) $aCats[] = $word; } $aCats = array_unique($aCats); # /* printf('Page categories found in %.6f seconds
',(getmicrotime() - $relstart)); echo 'categories for the current page:
';
print_r($aCats);
echo '
'; */ # } else { // one category defined: use (only) that IF the specified category occurs on the page! if (preg_match('/\b'.$tempCat.'\b/',$this->page['body'])) $aCats[] = $tempCat; } } else { $lCat = $tempCat; } // include templates? may depend on Category selected if (isset($tIncTpl)) { $bIncTpl = $tIncTpl; } elseif (preg_match('/^Category(.*)Template$/',$lCat)) # 'template category' { $bIncTpl = TRUE; # do show templates in a 'template category' } // else default FALSE (don't show templates) // derive what to show if ($bCatNames) # assume naming convention { if ('related' == $lType) # overrides 'show' parameter for type 'related' { $lShow = 'pages'; } elseif (isset($tShow)) { $lShow = $tShow; } // else default 'all' } else # cannot distinguish between pages and categories! { $lShow = 'members'; } $bShowMixed = ('members' == $lShow); $bShowCats = ('all' == $lShow || 'categories' == $lShow) ? TRUE : FALSE; $bShowPages = ('all' == $lShow || 'pages' == $lShow) ? TRUE : FALSE; // --- presentation parameters // columns if ('cols' == $lType) { if (isset($tCols)) # overrides 'col' parameter { $lCols = $tCols; } elseif (isset($tCol)) { $lCols = $tCol; } } // class if (isset($tClass) && '' != $tClass) $lClass = $tClass; // main heading override if (isset($tHead) && '' != $tHead) { $lHead = $tHead; } else { switch ($lType) { case 'cols': $lHead = HD_COL; break; case 'list': $lHead = HD_LST; break; case 'related': $lHead = HD_REL; break; } } // ---------------- gather data ------------------ // get the category content if ('related' == $lType) { $pagelist = array(); foreach ($aCats as $cat) { # #echo '- Looking at category: '.$cat.'
'; # $results = $this->getCatMembers($cat); if ($results) { foreach ($results as $cpage) { $member = $cpage['tag']; # #echo 'considering member: '.$member.'
'; # buildPageList($member,$pagelist,$cat,$bIncTpl,$lType,$this->tag); } } } ksort($pagelist); } else { # #echo '- Looking at category: '.$lCat.'
'; # $results = $this->getCatMembers($lCat); // gather what we show AS content of the requested category $memberlist = array(); $catlist = array(); $pagelist = array(); if ($results) { foreach ($results as $cpage) { $member = $cpage['tag']; # #echo 'considering member: '.$member.'
'; # if ($bShowMixed) { buildMemberList($member,$memberlist,$lCat,$bIncTpl); } if ($bShowCats) { buildCatList($member,$catlist,$lCat,$lType); } if ($bShowPages) { buildPageList($member,$pagelist,$lCat,$bIncTpl,$lType,$this->tag); } } sort($memberlist); sort($catlist); sort($pagelist); } } // ------------------ output --------------------- // show resulting list(s) of items belonging to selected category/categories $str =''; switch ($lType) { case 'cols': $attrClass = ('' != $lClass) ? ' class="categorycols '.$lClass.'"' : ' class="categorycols"'; $head = ($bCatNames) ? sprintf($lHead,preg_replace('/^Category/','',$lCat)) : sprintf($lHead,$lCat); // start wrapper $str .= ''."\n"; $str .= '
'.$head.'
'."\n"; // members (undifferentiated) if ($bShowMixed) { $hd = sprintf(COL_MEMBERS_FOUND,count($memberlist),$lCat); $str .= $this->makeMemberCols($memberlist,$lCols,$hd,'category members',sprintf(COL_NONE_FOUND,$lCat)); } // categories if ($bShowCats) { $hd = sprintf(COL_CATS_FOUND,count($catlist),$lCat); $str .= $this->makeMemberCols($catlist,$lCols,$hd,'category members',sprintf(COL_NO_CATS_FOUND,$lCat)); } // pages if ($bShowPages) { $hd = sprintf(COL_PAGES_FOUND,count($pagelist),$lCat); $str .= $this->makeMemberCols($pagelist,$lCols,$hd,'category members',sprintf(COL_NO_PAGES_FOUND,$lCat)); } // end wrapper $str .= "\n"; break; case 'list': $attrClass = ('' != $lClass) ? ' class="categorylist '.$lClass.'"' : ' class="categorylist"'; $head = sprintf($lHead,$lCat); // start wrapper $str .= ''."\n"; $str .= '
'.$head.'
'."\n"; // members (undifferentiated) if ($bShowMixed) { $hd = LST_MEMBERS_FOUND; $str .= $this->makeMemberList($memberlist,$hd,'category members','memberlist',LST_NONE_FOUND,'menu'); } // categories if ($bShowCats) { $hd = LST_CATS_FOUND; $str .= $this->makeMemberList($catlist,$hd,'category members','catlist',LST_NO_CATS_FOUND,'menu'); } // pages if ($bShowPages) { $hd = LST_PAGES_FOUND; $str .= $this->makeMemberList($pagelist,$hd,'category members','pagelist',LST_NO_PAGES_FOUND,'menu'); } // end wrapper $str .= "\n"; break; case 'related': $attrClass = ('' != $lClass) ? ' class="categoryrel '.$lClass.'"' : ' class="categoryrel"'; $head = $lHead; // start wrapper $str .= ''."\n"; $str .= '
'.$head.'
'."\n"; // data lists if (count($pagelist) > 0) { foreach ($pagelist as $cat => $memberlist) { sort($memberlist); $hd = sprintf(REL_PAGES_FOUND,count($memberlist),$cat); $str .= $this->makeMemberList($memberlist,$hd,'related pages','rellist',REL_NONE_FOUND,'menu'); } } else { $str .= '

'.REL_NONE_FOUND.'

'."\n"; } // end wrapper $str .= "\n"; break; } echo $str; ?> %% ====Supporting code==== As can be seen from the docblock (and the rest of the code) this //new// category action requires several bits of supporting code, already posted on other pages: ===Constants=== The code makes use of a number of constants. See ArrayToList for the code and how to add it where. ===##""getCatMembers()""##=== This is used to build a list of members belonging to a particular category - see CompatibilityCode for the code and where to insert it. ===##""makeId()""##=== Used here to generate a unique id for headings; see GenerateUniqueId for the code and where to insert it. See also AdvancedFormatter for a bit of background an context for this method. ===##""makeMemberCols()""##=== Used to build a columnar display of category members - see ArrayToColumns for the code and further details. ===##""makeMemberList()""##=== Used to build a list of category members - see ArrayToList for the code and further details. ---- === Minor code modifications === Thought I'd toss these changes up here just in case anyone else is trying to make similar adjustments. I changed the code so it would remove "Category" when it appears in front of a category name for show="all|categories|pages" (it doesn't work yet for show="members", though I would like to adjust that as well): line 139: %%(php;0) $list[] = $wakka->Link($member); %% to: %%(php;0) $list[] = $wakka->Link($member,'',preg_replace('/^Category/','',$member)); %% Then, to put spaces in CamelCase words in category displays, I made these changes to the code (requires UrlBeautify changes): line 117: %%(php;0) $list[] = $wakka->Link($member); %% to: %%(php;0) $list[] = $wakka->Link($member,"",addspaceincamelcase($member)); %% lines 139 and 143: %%(php;0) $list[] = $wakka->Link($member,'',preg_replace('/^Category/','',$member)); %% to: %%(php;0) $list[] = $wakka->Link($member,'',preg_replace('/^Category/','',addspaceincamelcase($member))); %% lines 167 and 172: %%(php;0) $list[$cat][] = $wakka->Link($member); %% to: %%(php;0) $list[] = $wakka->Link($member,"",addspaceincamelcase($member)); %% --MovieLady ---- CategoryDevelopmentActions