<?php

$plugin_info = array(
	'pi_name'        => 'Catagory Lister 2',
	'pi_version'     => '1.01',
	'pi_author'      => 'Jeff Barsky',
	'pi_author_url'  => 'http://www.miasmaofmusings.com',
	'pi_description' => 'Make a more configurable catagory listing.',
	'pi_usage'       => cat_lister::usage()
);

class Cat_lister {  // Open Class

	var $return_data = '';

	function Cat_lister() { // Open Cat_lister Function

      // -- Section 1 -- Variables and Validation

		// Set Globals and Variables
		global $TMPL, $DB, $LOC;
		$return     = '';
		$oops       = '';
		$category   = array();
		$tree       = array();
		$sort_order = '';
	
		// Bring in Parameters and Tag Template
		$custom_order  = (!$TMPL -> fetch_param('custom_order') ) ? "DEF"    : $TMPL -> fetch_param('custom_order')   ;
		$sort_order    = (!$TMPL -> fetch_param('sort_order')   ) ? "DEF"    : $TMPL -> fetch_param('sort_order')     ;
		$sort_field    = (!$TMPL -> fetch_param('sort_field')   ) ? "DEF"    : $TMPL -> fetch_param('sort_field')     ;
		$display_style = (!$TMPL -> fetch_param('display_style')) ? "NLevel" : $TMPL -> fetch_param('display_style')  ;
		$style_def_L   = (!$TMPL -> fetch_param('style_def_L')  ) ? ""       : " ".$TMPL -> fetch_param('style_def_L');
		$style_def_I   = (!$TMPL -> fetch_param('style_def_I')  ) ? ""       : " ".$TMPL -> fetch_param('style_def_I');
      $p_seperator   = (!$TMPL -> fetch_param('p_seperator')  ) ? " - "    : $TMPL -> fetch_param('p_seperator')    ;
      $hide_empties  = (!$TMPL -> fetch_param('hide_empties') ) ? "0"      : $TMPL -> fetch_param('hide_empties')   ;
      $warnings      = (!$TMPL -> fetch_param('warnings')     ) ? "0"      : $TMPL -> fetch_param('warnings')       ;
      $max_depth     = (!$TMPL -> fetch_param('max_depth')    ) ? "0"      : $TMPL -> fetch_param('max_depth')      ;
      $max_length    = (!$TMPL -> fetch_param('max_length')   ) ? "0"      : $TMPL -> fetch_param('max_length')     ;
      $date_format   = (!$TMPL -> fetch_param('date_format')  ) ? "m/d/Y"  : $TMPL -> fetch_param('date_format')    ;
      $tagdata       =   $TMPL -> tagdata;
	
		// Help out "uninformed" users / Explode Custom Order
		$custom_order = ereg_replace('[:space:]', '', $TMPL -> fetch_param('custom_order'));
		$custom_order = ereg_replace('[:punct:]', '|', $custom_order);
		$custom_order = explode('|', $custom_order);

      // -- Section 2 -- Queries and Extracted Data

		// Start weblog_id block
		if (!$TMPL -> fetch_param('weblog')){
			$oops .= "WARNING: No Weblog name provided, using default of 'weblog1'.<br>\n";
			$weblog = "weblog1";
		} else {
			$weblog = $TMPL -> fetch_param('weblog');
		}
		$query = $DB->query("SELECT weblog_id, cat_group FROM exp_weblogs WHERE blog_name = '".$DB->escape_str($weblog)."'");
      if ($query ->num_rows == 0) {
	      // First Bail Out Point
      	$oops .= "FATAL ERROR: Weblog not found, please check to be sure the weblog name you supplied is valid.<br>\n";
      	return $oops;
      }	
		$weblog   = $query -> result[0]['weblog_id'];
		$catgroup = $query -> result[0]['cat_group'];
		// End weblog_id block
		
		// Now we need to get data: Get Catagory Data and start collect
		$sql = "SELECT   cat_id, group_id, parent_id, cat_name, cat_order, cat_description, cat_image
		        FROM     exp_categories 
		        WHERE    group_id = ".$catgroup."
		        ORDER BY parent_id, cat_order";
		
		// Run first query
		$query = $DB->query($sql);
	
		// Collect initial data
		foreach ($query->result as $row) {
			$category[$row['cat_id']] = array( 
				'cid'       => $row['cat_id'],
				'parent'    => $row['parent_id'],
				'order'     => $row['cat_order'],
				'grp'       => $row['group_id'],
				'name'      => $row['cat_name'],
				'cat_desc'  => (($row['cat_description']) ? $row['cat_description'] : "0" ),
				'cat_image' => (($row['cat_image'])       ? $row['cat_image']       : "0" ),
				'total_e'   => "0",
				'total_c'   => "0",
				'l_e_date'  => "0",
				'l_c_date'  => "0",
				'entries'   => "0",
				'comments'  => "0",
				'nest_lvl'  => "0",
				'nest_list' => "0",
				'ps_name'   => "0"
				);
		}

		// Now we need to get even MORE data
		$sql = "SELECT exp_category_posts.entry_id, cat_id, weblog_id, entry_date, comment_total, recent_comment_date
				  FROM   exp_category_posts, exp_weblog_titles
				  WHERE  exp_category_posts.entry_id = exp_weblog_titles.entry_id
				  AND    exp_weblog_titles.weblog_id = ".$weblog ;

		// Run second query
		$query = $DB->query($sql);
	
		// Put this data into the "Category" collection
		foreach($query -> result as $row){
			$category[$row['cat_id']]['entries']++;
			$category[$row['cat_id']]['total_e']++;
			$category[$row['cat_id']]['comments'] += $row['comment_total'];
			$category[$row['cat_id']]['total_c']  += $row['comment_total'];
			$category[$row['cat_id']]['l_e_date'] = 
				($category[$row['cat_id']]['l_e_date'] >= $row['entry_date']) ?
				 $category[$row['cat_id']]['l_e_date'] :  $row['entry_date'];
			$category[$row['cat_id']]['l_c_date'] = 
				($category[$row['cat_id']]['l_c_date'] >= $row['recent_comment_date']) ?
				 $category[$row['cat_id']]['l_c_date'] :  $row['recent_comment_date'];
		}

		// Time to manipulate object.
		foreach ($category as $c){
			$parent = $c['parent'];
			$category[$c['cid']]['nest_lvl']++;
			$category[$c['cid']]['nest_list'] = $category[$c['cid']]['cid'];
			$category[$c['cid']]['ps_name']   = $category[$c['cid']]['name'];
			$tree[$c['parent']][] = $c['cid'];
			while ($parent != 0){
				$category[$parent]['entries']  -= $category[$c['cid']]['total_e'];
				$category[$parent]['comments'] -= $category[$c['cid']]['total_c'];
				$category[$c['cid']]['nest_lvl']++;
				$category[$c['cid']]['nest_list'] = $parent.",".$category[$c['cid']]['nest_list'];
				$category[$c['cid']]['ps_name'] = $category[$parent]['name'].$p_seperator.$category[$c['cid']]['ps_name'];
				$parent = $category[$parent]['parent'];
			}
		}

      // -- Section 3 -- Sorting / Removing Empties
		
      if ($sort_order == "DEF") {
			// Sort Hierarchically
			$this -> jbHsort($parent = array(0), $tree, count($category), $outOrder = array());
			// The $outOrder array is returned by reference
			foreach($outOrder as $key => $val){	$cat[] = $category[$val]; }
			if ($hide_empties){
				$ntemp = array();
				foreach ($cat as $row){
					if ($row['entries'] != 0 or $row['total_e'] != 0) {
						$ntemp[] = $row;
					}
				}
				$category = $ntemp;
				unset($ntemp);
			} else {
				$category = $cat;
			}
      } elseif($sort_order == "CUSTOM"){
		   // Sort Custom Order
		   foreach($custom_order as $val){
		   	$cat[] = $category[$val];
		   }
			if ($hide_empties){
				$ntemp = array();
				foreach ($cat as $row){
					if ($row['entries'] != 0) {
						$ntemp[] = $row;
					}
				}
				$category = $ntemp;
				unset($ntemp);
			} else {
				$category = $cat;
			}
      } else {
			// Sort Ascending/Descending on field
         foreach($category as $row){ $temp[] = $row; }
			$cat = $this -> jbMDAsort($temp, $sort_field, $sort_order);
			if ($hide_empties){
				$ntemp = array();
				foreach ($cat as $row){
					if ($row['entries'] != 0) {
						$ntemp[] = $row;
					}
				}
				$category = $ntemp;
				unset($ntemp);
			} else {
				$category = $cat;
			}
      }

      // -- Section 4 -- Data Formatting and Output
		
		// HTML List Styles
		$listo = ""; $listc = "";
		if ($display_style == "NListO"){ $listo = "<ol$style_def_L>"; $listc = "</ol>"; }
		if ($display_style == "NListU"){ $listo = "<ul$style_def_L>"; $listc = "</ul>"; }
		if ($listo){ $return .= $listo; }
		$prevNest = 1;
		
		// Prepare data for output
		foreach ($category as $row) {
			$row['l_e_date'] = ($row['l_e_date'] != 0) ? date($date_format, $row['l_e_date']) : "N/A" ;
			$row['l_c_date'] = ($row['l_c_date'] != 0) ? date($date_format, $row['l_c_date']) : "N/A" ;
		   $tagdata = $TMPL->tagdata;
		   // Nested listing information added
		   if ($listo){
			   if ($prevNest != $row['nest_lvl']){
			   	if ($prevNest < $row['nest_lvl']){
			   		$return .= $listo;
			   		$prevNest++;
			   	} else {
			   		$back = $prevNest - $row['nest_lvl'];
			   		for ($x = 0; $x < $back; $x++){ $return .= $listc; }
		   			$prevNest = $row['nest_lvl'];
			   	}
			   }
			}
		   if ($listo) { $tagdata = "<li$style_def_I>".$tagdata."</li>\n"; }
		   // Interpolate data
  			foreach ($TMPL->var_single as $key => $val) { 
				$tagdata = $TMPL->swap_var_single ($key, $row[$key], $tagdata);
			}
			// Add output to string
 		   $return .= $tagdata;
		}
		
		// Close Nested Lising
      if ($listc){ for ($x = 0; $x < $row['nest_lvl']; $x++){ $return .= $listc; } }

		// Output all the data for output
		$this -> return_data = $return;

	} // Close Cat_lister Function

	// Function - jbHsort
	function jbHsort($parent, $tree, $stop, &$outOrder){
		if (count($outOrder) != $stop){
			if ((array_key_exists($parent[0], $tree)) && (count($tree[$parent[0]]) != '0')){
				$outOrder[] = $child = array_shift($tree[$parent[0]]);
				array_unshift($parent, $child);
				$this -> jbHsort($parent, $tree, $stop, $outOrder);
			} else {
				array_shift($parent);
				$this -> jbHsort($parent, $tree, $stop, $outOrder);
			}
		}
	}

	// Function - jbMDAsort
	function jbMDAsort($usa, $f, $dir){
	   // If this seems redundant, it's not, it's faster.
	   $sa = $usa;
		if ($dir == "ASC"){
	   	for ($i = 0; $i < sizeof($sa) - 1; $i++) {
	      	for ($j = 0; $j < sizeof($sa) - 1 - $i; $j++){
	            if (strtolower($sa[$j][$f]) > strtolower($sa[$j+1][$f])) {
	               $tmp = $sa[$j];
	               $sa[$j] = $sa[$j+1];
	               $sa[$j+1] = $tmp;
	            }
	      	}
	   	}
		} else {
	   	for ($i = 0; $i < sizeof($sa) - 1; $i++) {
	      	for ($j = 0; $j < sizeof($sa) - 1 - $i; $j++){
	            if (strtolower($sa[$j][$f]) < strtolower($sa[$j+1][$f])) {
	               $tmp = $sa[$j];
	               $sa[$j] = $sa[$j+1];
	               $sa[$j+1] = $tmp;
	            }
	      	}
	  		}
		}
		return $sa;
	}

	// Function - Usage
	function usage() { // Open Usage

		ob_start();

		?>
For a complete description of this plugin with complete feature lists and descriptions,
complete list of parameters and descriptions, better looking samples, additional available
functions, future enhancements, please download the associated Word Doc from:

http://www.miasmaofmusings.com/index.php/weblog/dl_cl/

------------------------------------
WHAT THIS DOES:
------------------------------------

Cat_lister replaces the basic functionality of the built-in Expression
Engine tag:

{exp:weblog:categories weblog="{master_weblog_name}"}

and offers several new features not possible with the built in tag.

Cat_lister allows for a very flexible way of displaying your categories in
the style you want to use. Categories can be displayed alphabetically,
ranked, by date, nested, and pseudo nested (see below). Categories can be
sorted ascending, descending, hierarchical, or by custom order. Category
lists can be limited in length (good for rankings), limited to depth
(see below), or without (intelligently evaluated, see below) �empty�
categories included.

Also included, and in fact the original reason for the plugin in the first
place, are entry and comment statistics. Statistic include entries per
category, comments per category, total entries per categories (include
subcategories), and total comments per categories (including subcategories).

In addition, several formatting options were not available with the original
tag. You can now specify as HTML Ordered or Unordered list, plain list (no
formatting added), or with included �Nesting Levels� (see below).

With these options you should be able to create the category list that you
want with little or no programming. It is hoped that this plugin will
reduce your time and head scratching on working out the custom queries
and sorts.

------------------------------------
New Features:
------------------------------------

Pseudo Nested List

Hierarchical Depth Limiting

Intelligently Hidden (sub)Categories

List Length Limiting

Nesting Levels

Entry and Comment Statistics

------------------------------------
Features:
------------------------------------

Custom Ordering

Custom Sorting

Display Styles

------------------------------------
BASIC USAGE:
------------------------------------

Basic usage for the Cat_lister plugin is rather straightforward. The following
example would create a simple list of categories with the number of entries and
comments within each category:

{exp:cat_lister weblog="{master_weblog_name}"}
<a href="http://www.home.com/index.php/weblog/C{cid}/" >{name}</a>
({entries}/{comments})<br />
{/exp:cat_lister}

Resulting in a list this may look something like this:

Entertainment (45/443)
News (12/203)
Personal (8/45)
Politics (19/392)

Simple and to the point, this example assumes that you do not have any subcategories,
but one simple change could take that into account and show off the power of the
�Pseudo-Nested� ability:

{exp:cat_lister weblog="{master_weblog_name}"}
<a href="http://www.home.com/index.php/weblog/C{cid}/" >{ps_name}</a>
({entries}/{comments})<br />
{/exp:cat_lister}

Now if you have subcategories, you may have a list like this:

Entertainment (45/443)
Entertainment � Movies (20/443)
Entertainment � Television (6/443)
Entertainment � Television � Comedy (2/443)
Entertainment � Television � Drama (4/443)
Entertainment � Theater (3/443)
News (12/203)
Personal (8/45)
Personal � Dating (3/45)
Personal � Career (2/45)
Politics (19/392)

The advantage of the �Pseudo Nested� list is that you will not have to provide
a huge amount of CSS definitions to have it display well. It is a fast and easy
why to display your hierarchical list and not think about it.
 
To turn the above tag into an HTML ordered list you may want to use the
following example:

{exp:cat_lister weblog="{master_weblog_name}" display_style=�NListO�}
<a href="http://www.home.com/index.php/weblog/C{cid}/" >{name}</a>
({entries}/{comments})<br />
{/exp:cat_lister}

And the resulting list would look like this:

1.	Entertainment (45/443)
   a.	Movies (20/443)
   b.	Television (6/443)
      i.	 Comedy (2/443)
      ii. Drama (4/443)
   c.	Theater (3/443)
2.	News (12/203)
3.	Personal (8/45)
   a.	Dating (3/45)
   b.	Career (2/45)
4.	Politics (19/392)

At this point you will want to add some CSS definitions to allow you to format
the list the way you want it. This is just the simplest of samples.

Advanced usage is listed below.

------------------------------------
CHEAT SHEET:
------------------------------------

Available Fields for tag creation:

{name}      = Category Name
{ps_name}   = Category Pseudo Name
{cat_desc}  = Category Description
{cat_image} = Category Image File Name
{entries}   = Total Entries Per Category
{comments}  = Total Comments Per Category
{total_e}   = Total Entries, Including Subcategories
{total_c}   = Total Comments, Including Subcategories
{l_e_date}  = Last Entry Date
{l_c_date}  = Last Comment Date
{nest_lvl}  = Current Nesting Level

These are available, but should not be needed except when debugging:

{nest_list} = Current Nesting List (Show a list of the CIDs to root
{cid}       = Category ID Number
{parent}    = Parent ID Number
{order}     = Hierarchical Ordering Number
{grp}       = Category Group Number


Available Parameters:

weblog        = "{master_weblog_name}" (REQUIRED)
custom_sort   = "1|3|6|4" (Optional)
sort_field    = �[field]� (Optional, see list above)
sort_order    = "ASC"     (Optional, DEF, CUSTOM, DESC are available but not needed)
display_style = "NListO" or "NListU" (Optional)
style_def_L   = �class=�listStyle��  (Optional)
style_def_I   = �class=�itemStyle��  (Optional)
p_seperator   = " - "   (Default is as shown)
max_depth     = "5"     (Default is 0 which shows all)
max_list      = "10"    (Default is 0 which shows all)
date_format   = "m/d/Y" (Default is as shown)
show_empty    = "yes"   (Default is as shown)

------------------------------------
ADVANCED USAGE:
------------------------------------

(These samples look MUCH better when run as the alignment will be correct. This is
examples to give you ideas.)

The CSS for Nested Level output:
(Change to suit your application of course)

<style>
.cat1 {
   font: bold 10px 'Lucida Grande', Verdana, Arial, Sans-Serif;
   width: 230px;
   padding-left: 0px;
   background: #dddddd;
}
.cat2 {
   font: 10px 'Lucida Grande', Verdana, Arial, Sans-Serif;
   text-decoration: none;
   width: 215px;
	padding-left: 15px;
   background: #cccccc;
}
.cat3 {
   font: 10px 'Lucida Grande', Verdana, Arial, Sans-Serif;
   text-decoration: none;
   width: 200px;
	padding-left: 30px;
   background: #bbbbbb;
}
</style>

This will allow for a nested list up to three levels deep. Beyond that, you will have
to add to the style definitions. Here are three samples that I think you will find useful:

Example One:
This example is a simple version of �Nesting Levels� and creates the sample below it. In
theory it should look very similar to the above examples but without the �List� tags in
the underlying code. From a modern coding standpoint this is fairly advance in that it
uses all CSS to create the effect. There are no tables and of course no lists to get in
the way of the display.

{exp:cat_lister weblog="{master_weblog_name}"}
<div class="cat{nest_lvl}">
<a href="http://www.miasmaofmusings.com/index.php/weblog/C{cid}/">
{name}</a> ({total_e}/{total_c})</div>
{/exp:cat_lister}

Entertainment (45/443)
     Movies (20/201)
     Television (6/52)
          Comedy (2/18)
          Drama (4/28)
     Theater (3/80)
News (12/203)
Personal (8/45)
     Dating (3/12)
     Career (2/19)
Politics (19/392)

Want to add a bullet in front of each, change the �{name}� to �&bull; {name}� and there
you go. Easy, instant bulleted, hierarchical list.

Example Two:
OK, so you do not like the crowded nature of the list. The numbers are too close to the
categories and you want to see them maybe all the way to the left. Why not try this:

{exp:cat_lister weblog="{master_weblog_name}"}
<div class="cat{nest_lvl}"><div style="float: left;">
<a href="http://www.miasmaofmusings.com/index.php/weblog/C{cid}/">
&bull; {name}</a></div>
<div align="right">({total_e}/{total_c})</div></div>
{/exp:cat_lister}

� Entertainment 	 (45/443)
     � Movies 	    (20/201)
     � Television 	(6/52)
          � Comedy 	(2/18)
          � Drama 	(4/28)
     � Theater 	   (3/80)
� News 	          (12/203)
� Personal 	         (8/45)
     � Dating 	      (3/12)
     � Career 	      (2/19)
� Politics 	       (19/392)

When the above is displayed in a page this will look nice and professional
and so simple to do.
 
Example Three:
How about using the nesting level with the �Pseudo Name� convention? You can get
an interesting effect with that. In this case you will not be using the �Nesting
Level� as a way of indenting, but a way of shading to denote the depth of the
selection adding a visual clue as to the location of the category.

The CSS for this example:

<style>
.cat1 {
   font: bold 10px 'Lucida Grande', Verdana, Arial, Sans-Serif;
   text-decoration: none;
   background: #dddddd;
}
.cat2 {
   font: 10px 'Lucida Grande', Verdana, Arial, Sans-Serif;
   text-decoration: none;
   background: #cccccc;
}
.cat3 {
   font: 10px 'Lucida Grande', Verdana, Arial, Sans-Serif;
   text-decoration: none;
   background: #bbbbbb;
}
</style>

{exp:cat_lister weblog="{master_weblog_name}" p_separator=� � �}
<div class="cat{nest_lvl}"><div style="float: left;">
<a href="http://www.miasmaofmusings.com/index.php/weblog/C{cid}/">
{ps_name}</a></div>
<div align="right">({entries}/{comments})</div></div>
{/exp:cat_lister}

Entertainment                        (45/443)
Entertainment � Movies               (20/201)
Entertainment � Television             (6/52)
Entertainment � Television � Comedy    (2/18)
Entertainment � Television � Drama     (4/28)
Entertainment � Theater                (3/80)
News                                 (12/203)
Personal                               (8/45)
Personal � Dating                      (3/12)
Personal � Career                      (2/19)
Politics                             (19/392)

By combining the parameter, styles and available information (nesting level) you
can create just about any output you can think of. Already I am thinking about
rollover possibilities and background images and the like making this plugin
very versatile indeed.

------------------------------------
NOTES ABOUT THIS PLUGIN:
------------------------------------

This is a rather feature rich plugin and it will only get more so as I develop it (see the
future features list above) and I am one man. I have debugged this to the best of my resources,
but since EE is so mutable there may be things I have not thought of or have not tested for.

I will say this, I have not tested for uncategorized entries. If you do not categorize entries,
I do not know what will happen. I suggest that you have some default category for all entries,
even if you make a default category of "Unfiled" or some such. This makes a lot of sense and
is proper data management.

The plugin itself comes very tersely commented, just enough to follow along what is generally
happening. I happen to be one of those people who over comment everything for my own edification
(and a very poor memory). That being the case I strip the comments and testing code from the
release software. If you would like a copy of the code with all my comments (and there are a lot)
then please let me know and I will send you a copy.

For a complete description of this plugin with complete feature lists and descriptions,
complete list of parameters and descriptions, better looking samples, additional available
functions, future enhancements, please download the associated Word Doc from:

http://www.miasmaofmusings.com/index.php/weblog/dl_cl/

------------------------------------
ADDITIONAL FUNCTIONS:
------------------------------------

There are two additional functions with this class. For more information about them, please
download the Word Doc for this plugin at:

http://www.miasmaofmusings.com/index.php/weblog/dl_cl/

The two functions are

Cat_lister::jbMDAsort which will take a multidimentional array and sort it on a provided
field and in a particular direction.

Cat_lister::jbHsort which will take a hierarchical tree array and output a hierarchical
order to be applied to an array. (This function is usable with special prep, so definately
see the doc on this one.)

------------------------------------
Revision History:
------------------------------------
1.01 - (10/13/2005) Added a security feature, thanks Paul Burdick for the catch.

1.0  - (10/10/2005) Completed all major coding, removed various bugs, cleaned 
                    up the code and the inline comments.

------------------------------------
COPYRIGHT INFORMATION:
------------------------------------

� 2005, Jeff Barsky and Miasma of Musings. You may use and distribute this plugin as you
wish, but please leave original creator information in the file. If you have questions
or suggestions, please email me at:

admin@miasmaofmusings.com

or

jbarsky@visual-impact.com

If you base another plugin on this software, please feel free to drop all information
if you change it more than 50% or so. Otherwise, I would appreciate it if this
information were to stay with the plugin.

Author: Jeff Barsky
Originating Site: http://www.miasmaofmusings.com
Version 1.01 : 10/13/2005

--END-- <?php

		  $buffer = ob_get_contents();

		  ob_end_clean();

		  return $buffer;

	 } // Close Usage

} // Close Class

?>
