category manager
mailling@bigfoot.com
mailling@bigfoot.com
Wed, 18 Jul 2001 15:17:10 -0500
I finished it and tested it, it should work even if we could optimize it a
lot. Maybe, we could add later on a cache, ...
----
- lib/Category.php
<?php
// $Horde: horde/lib/Category.php,v 1.13 2001/07/15 22:48:24 shuther Exp $
/** Required values for $params:
* groupid define each group of categories we want to build
*/
// Return codes
/** @constant CATEGORY_OK Operation succeeded. */
define('CATEGORY_OK', 0);
/** @constant CATEGORY_ERROR Operation failed. */
define('CATEGORY_ERROR', -1);
/** @constant CATEGORY_ERROR_PARAMS Bad or missing parameters. */
define('CATEGORY_ERROR_PARAMS', -2);
/** @constant CATEGORY_ERROR_CONNECT Connection failure. */
define('CATEGORY_ERROR_CONNECT', -3);
/** @constant CATEGORY_ERROR_AUTH Authentication failure. */
define('CATEGORY_ERROR_AUTH', -4);
/** @constant CATEGORY_ERROR_EMPTY Empty retrieval result. */
define('CATEGORY_ERROR_EMPTY', -5);
/** @constant CATEGORY_ERROR_UNSUPPORTED Method not supported by driver. */
define('CATEGORY_ERROR_UNSUPPORTED', -6);
/** @constant CATEGORY_ERROR_ALREADYEXISTS The category already exists. */
define('CATEGORY_ERROR_ALREADYEXISTS', -7);
/** @constant CATEGORY_ERROR_NOTEXIST The category/parent doesn't exist. */
define('CATEGORY_ERROR_NOTEXIST', -8);
/** @constant CATEGORY_ERROR_REMOVE You must remove before every childs!. */
define('CATEGORY_ERROR_REMOVE', -9);
//Kind of category
/** @constant CATEGORY_GROUPID_GROUP Group of groups (for users) or users*/
define('CATEGORY_GROUPID_GROUP', 1);
/** @constant CATEGORY_GROUPID_MENU Define a menu, like PEAR/HTML/menu.php */
define('CATEGORY_GROUPID_MENU', 2);
/** @constant CATEGORY_GROUPID_BOOKMARK Define categories for boommarks */
define('CATEGORY_GROUPID_BOOKMARK', 3);
//Kind of format
/** @constant CATEGORY_FORMAT_LIST List every category in an array */
define('CATEGORY_FORMAT_LIST', 1);
/** @constant CATEGORY_FORMAT_TREE List every category in an array,
similar to PEAR/html/menu.php*/
define('CATEGORY_FORMAT_TREE', 2);
/**
* The Category:: class provides a common abstracted interface into the
* various backends for the Horde authentication system.
*
* @author Stephane Huther <shuther@bigfoot.com>
* @version $Revision: 1. $
* @since Horde 1.3
* @package horde.category
*/
class Category {
/**
* Array of all categories: indexed by name=parent
*/
var $categories = Array();
/**
* Hash containing connection parameters.
* @var array $params
*/
var $params = Array();
/**
* Constructor
* @param array $params A hash containing any additional
* configuration or connection parameters a
subclass
* might need.
* here, we need 'groupid' = a constant that
defines
* in each group we will work
*/
function Category($params)
{
$this->params = $params;
}
/**
* Attempts to return a concrete Category instance based on $driver.
*
* @param string $driver The type of concrete Category subclass to return.
* This is based on the storage driver
($driver). The
* code is dynamically included.
* @param array $params A hash containing any additional
* configuration or connection parameters a subclass
* might need.
* here, we need 'groupid' = a constant that
defines
* in each group we will work
*
* @return object Category The newly created concrete Category instance,
* or false on an error.
* @see constants CATEGORY_GROUPID_*
*/
function factory($driver, $params)
{
$driver = strtolower($driver);
if (empty($driver) || (strcmp($driver, 'none') == 0)) {
return new Category($params);
}
if (include_once dirname(__FILE__) . '/Category/' . $driver .
'.php') {
$class = 'Category_' . $driver;
return new $class($params);
} else {
return false;
}
}
/**
* Attempts to return a reference to a concrete Category instance based on
* $driver. It will only create a new instance if no Category instance
* with the same parameters currently exists.
*
* This should be used if multiple permissions sources (and, thus,
* multiple Category instances) are required.
*
* This method must be invoked as: $var = &Category::singleton()
*
* @param string $driver The type of concrete Category subclass to return.
* This is based on the storage driver
($driver). The
* code is dynamically included.
* @param array $params (optional) A hash containing any additional
* configuration or connection parameters a subclass
* might need.
*
* @return object Category The concrete Auth reference, or false on an
* error.
*/
function &singleton($driver, $params = array())
{
static $instances;
if (!isset($instances)) $instances = array();
$signature = md5(strtolower($driver) . '][' . implode('][', $params));
if (!isset($instances[$signature])) {
$instances[$signature] = Category::factory($driver, $params);
}
return $instances[$signature];
}
/**
* Add a category
*
* note: there is no check against circular reference!!!
* @param string $name The name of the category.
* @param optional string $parent the name of the parent category
*/
function add ($name, $parent=-1)
{
return $this->protected_add($name, $parent);
}
/**
* Add a category
*
* note: there is no check against circular reference!!!
* @param string $name The name of the category.
* @param optional string $parent the name of the parent category
*
* @access protected
*/
function protected_add ($name, $parent=-1)
{
if (isset($this->categories[$name]))
return CATEGORY_ERROR_ALREADYEXISTS;
if ($parent!=-1 && !isset($this->categories[$parent]))
return CATEGORY_ERROR_NOTEXIST;
$this->categories[$name]=$parent;
return CATEGORY_OK;
}
/**
* Remove a group
*
* @param string $name The name of the category.
*/
function remove ($name)
{
return $this->protected_remove($name);
}
/**
* Remove a group
*
* @param string $name The name of the category.
* @access protected
*/
function protected_remove ($name)
{
if (!isset($this->categories[$name]))
return CATEGORY_ERROR_NOTEXIST;
if (in_array($name, array_values($this->categories)))
return CATEGORY_ERROR_REMOVE;
$this->categories[$name]=null;
unset($this->categories[$name]);
return CATEGORY_OK;
}
/**
* Move a category
*
* note: there is no check against circular reference!!!
* @param string $name The name of the category.
* @param string $new_parent The name of the new parent.
*/
function move ($name,$new_parent)
{
return protected_move($name,$new_parent);
}
/**
* Move a category
*
* note: there is no check against circular reference!!!
* @param string $name The name of the category.
* @param string $new_parent The name of the new parent.
*
* @access protected
*/
function protected_move ($name,$new_parent)
{
if (!isset($this->categories[$name]))
return CATEGORY_ERROR_NOTEXIST;
if ($new_parent!=-1 && !isset($this->categories[$new_parent]))
return CATEGORY_ERROR_NOTEXIST;
$this->categories[$name]=$new_parent;
return CATEGORY_OK;
}
/**
* Export a list of categories
*
* @param integer $format Format of the export
* @param string optional $parent The name of the parent from
* where we export.
*
* @return mixed
*/
function &export ($format,$parent=-1)
{
$out=array();
switch ($format)
{
case CATEGORY_FORMAT_LIST:
$this->extractAllLevelList($out, $parent);
break;
case CATEGORY_FORMAT_TREE:
$this->extractAllLevelTree($out, $parent);
break;
default:
return CATEGORY_ERROR_PARAMS;
}
return $out;
}
/**
* Extract one level of categories, based on a parent, get the childs
*
* @param string optional $parent The name of the parent from
* where we begin.
*
* @return array
*/
function extract1Level($parent=-1)
{
$out=array();
foreach ($this->categories as $name=>$vparent)
{
if ($vparent==$parent)
{
if (!isset($out[$parent])) $out[$parent]=array();
$out[$parent][$name]=true;
}
}
return $out;
}
/**
* Extract all level of categories, based on a parent
* Tree format
*
* @param array $out Contain the result
* @param string optional $parent The name of the parent from
* where we begin.
*
*/
function extractAllLevelTree(&$out, $parent=-1)
{
$k=$this->extract1Level($parent);
if (!isset($k[$parent]))
return;
$k=$k[$parent];
foreach ($k as $name=>$v)
{
if (!isset($out[$parent]) || !is_array($out[$parent]))
$out[$parent]=array();
$out[$parent][$name]=true;
$this->extractAllLevelTree($out[$parent], $name);
}
}
/**
* Extract all level of categories, based on a parent
* List format
*
* @param string optional $parent The name of the parent from
* where we begin.
* @param array $out Contain the result
*
*/
function extractAllLevelList(&$out, $parent=-1)
{
$k=$this->extract1Level($parent);
if (!isset($k[$parent]))
return;
$k=$k[$parent];
foreach ($k as $name=>$v)
{
printf ('%s - %s<br>',$parent, $name);
if (!isset($out[$parent])) $out[$parent]=array();
if (!isset($out[$parent][$name]))
{
$out[$parent][$name]=true;
$this->extractAllLevelList($out, $name);
}
}
}
/**
* Get a list of parents, based on a child
*
* @param string $child The name of the child
*
* @return array
*/
function getParents($child)
{
if (!isset($this->categories[$child]))
return CATEGORY_ERROR_NOTEXIST;
return $this->categories[$child];
}
}
?>
----
- lib/Category/sql.php
<?php
// $Horde: horde/lib/Category/sql.php,v 1.7 2001/07/16 21:28:48 shuther Exp $
/**
* The Categoryh_sql class provides a sql implementation of the Horde
* caqtegory system.
*
* Required values for $params:
* 'phptype' The database type (ie. 'pgsql', 'mysql, etc.).
* 'hostspec' The hostname of the database server.
* 'protocol' The communication protocol ('tcp', 'unix', etc.).
* 'username' The username with which to connect to the database.
* 'password' The password associated with 'username'.
* 'database' The name of the database.
* 'table' The name of the preferences table in 'database'.
* 'sequenceName' The name of the sequence to compute an ID
*
* The table structure for the preferences is as follows:
*
*CREATE TABLE category (
* id INT not null ,
* groupid MEDIUMINT not null ,
* name VARCHAR (250) not null ,
* parent INT not null ,
* PRIMARY KEY (id)
* )
* comment = 'Horde Cateogory Manager';
* ALTER TABLE category ADD INDEX(groupid);
* ALTER TABLE category ADD UNIQUE(name,groupid);
*
* @author Stephane Huther <shuther@bigfoot.com>
* @version $Revision: 1.0 $
* @since Horde 1.3
* @package horde.category
*
*
*/
class Category_sql extends Category {
/**
* Handle for the current database connection.
* @var resource $db
*/
var $db;
/**
* Boolean indicating whether or not we're connected to the SQL server.
* @var boolean $connected
*/
var $connected = false;
/**
* Constructs a new SQL authentication object.
*
* @param array $params A hash containing connection parameters.
*/
function Category_sql($params)
{
$this->Category($params);
}
/**
* Attempts to open a persistent connection to the SQL server.
*
* @return constant CATEGORY_OK on success, CATEGORY_ERROR_* on failure.
*/
function connect()
{
if (!$this->connected) {
if (!is_array($this->params)) return CATEGORY_ERROR_PARAMS;
if (!isset($this->params['phptype'])) return
CATEGORY_ERROR_PARAMS;
if (!isset($this->params['hostspec'])) return
CATEGORY_ERROR_PARAMS;
if (!isset($this->params['username'])) return
CATEGORY_ERROR_PARAMS;
if (!isset($this->params['password'])) return
CATEGORY_ERROR_PARAMS;
if (!isset($this->params['database'])) return
CATEGORY_ERROR_PARAMS;
if (!isset($this->params['table'])) return CATEGORY_ERROR_PARAMS;
if (!isset($this->params['sequenceName'])) return
CATEGORY_ERROR_PARAMS;
/* Connect to the SQL server using the supplied parameters. */
include_once 'DB.php';
$this->db = &DB::connect($this->params, true);
if (DB::isError($this->db) || DB::isWarning($this->db)) {
return CATEGORY_ERROR_CONNECT;
}
$this->connected = true;
$r=$this->fullyload(); //Load all the database in memory
if ($r!=CATEGORY_OK)
return $r;
}
return CATEGORY_OK;
}
/**
* Disconnect from the SQL server and clean up the connection.
*
* @return boolean true on success, false on failure.
*/
function disconnect()
{
if ($this->connected) {
$this->connected = false;
return $this->db->disconnect();
}
return true;
}
/**
* Dump the database to the array of this class
* it is a way to do a one way synchronize
*
* @return mixed may return a DB error if not an array
*
* note: there is no check against circular reference!!!
*/
function fullyload()
{
$query=sprintf('SELECT t.name name, t2.name parent FROM %s t, %s t2 WHERE
t.groupid=%s and t.parent=t2.id and t2.groupid=%s ',
$this->params['table'], $this->params['table'], $this->params['groupid'],
$this->params['groupid']);
/* Execute the query. */
$result = $this->db->getAssoc($query);
if (DB::isError($result)) {
return $result;
}
$this->categories=$result;
$query=sprintf('SELECT t.name name, -1 parent FROM %s t WHERE t.groupid=%s
and t.parent="-1"', $this->params['table'], $this->params['groupid']);
/* Execute the query. */
$result2 = $this->db->getAssoc($query);
if (DB::isError($result2)) {
return $result2;
}
$this->categories=array_merge($result,$result2);
return CATEGORY_OK;
}
/**
* Add a category
*
* note: there is no check against circular reference!!!
* @param string $name The name of the category.
* @param optional string $parent the name of the parent category
*/
function add ($name, $parent=-1)
{
if ($this->connect() != AUTH_OK) {
// PEAR error
return false;
}
if (isset($this->categories[$name]))
return CATEGORY_ERROR_ALREADYEXISTS;
if ($parent!=-1 && !isset($this->categories[$parent]))
return CATEGORY_ERROR_NOTEXIST;
$id=$this->db->nextId($this->params['sequenceName']);
if (DB::isError($id))
{
return $id; //Error
}
if (-1==$parent)
{
$query=sprintf('INSERT INTO %s (id, groupid, name, parent) VALUES (%s,
%s, "%s", -1)', $this->params['table'], $id, $this->params['groupid'],
$this->db->quoteString($name));
}
else
{
$query=sprintf('SELECT t.id id FROM %s t WHERE t.name="%s"',
$this->params['table'], $this->db->quoteString($parent));
$parentid=$this->db->getCol($query);
if (DB::isError($parentid))
{
return $parentid; //Error
}
if (0==$parentid) $parentid[0]=-1;
$query=sprintf('INSERT INTO %s (id, groupid, name, parent) VALUES (%s,
%s, "%s", %s)', $this->params['table'], $id, $this->params['groupid'],
$this->db->quoteString($name), $parentid[0]);
}
/* Execute the query. */
$result = $this->db->query($query);
if (!DB::isError($result)) {
$this->categories[$name]=$parent;
return CATEGORY_OK;
}
return CATEGORY_ERROR;
}
/**
* Remove a group
*
* @param string $name The name of the category.
*/
function remove ($name)
{
if (!isset($this->categories[$name]))
return CATEGORY_ERROR_NOTEXIST;
if (in_array($name, array_values($this->categories)))
return CATEGORY_ERROR_REMOVE;
$query=sprintf('DELETE FROM %s WHERE groupid=%s AND name="%s"',
$this->params['table'], $this->params['groupid'],
$this->db->quoteString($name));
/* Execute the query. */
$result = $this->db->query($query);
if (!DB::isError($result)) {
$this->categories[$name]=null;
unset($this->categories[$name]);
return CATEGORY_OK;
}
return CATEGORY_ERROR;
}
/**
* Move a category
*
* note: there is no check against circular reference!!!
* @param string $name The name of the category.
* @param string $new_parent The name of the new parent.
*/
function move ($name,$new_parent=-1)
{
if (!isset($this->categories[$name]))
return CATEGORY_ERROR_NOTEXIST;
if ($new_parent!=-1 && !isset($this->categories[$new_parent]))
return CATEGORY_ERROR_NOTEXIST;
if (-1==$new_parent)
{
$query=sprintf('UPDATE %s SET parent="-1" WHERE groupid=%s AND
name="%s"', $this->params['table'], $this->params['groupid'],
$this->db->quoteString($name));
}
else
{
$query=sprintf('SELECT t.id id FROM %s t WHERE t.name="%s" AND
t.groupid=%s', $this->params['table'], $this->db->quoteString($new_parent),
$this->params['groupid']);
$parentid=$this->db->getCol($query);
if (DB::isError($parentid))
{
return $parentid; //Error
}
$query=sprintf('UPDATE %s SET parent=%s WHERE groupid=%s AND name="%s"',
$this->params['table'], $parentid[0], $this->params['groupid'],
$this->db->quoteString($name));
}
/* Execute the query. */
$result = $this->db->query($query);
if (!DB::isError($result)) {
$this->categories[$name]=$new_parent;
return CATEGORY_OK;
}
return CATEGORY_ERROR;
}
}
?>
--
we have to add in lib/base.php
require_once HORDE_BASE . '/lib/Category.php';
and in horde.php.dist
/**
** Horde Category Manager
**/
// What backend should we use for category to Horde? Valid
// options are currently 'none' and 'sql'.
// In the case of none, the categories are kept in the page/memory.
// They are destroyed just after
$conf['category']['driver'] = 'sql';
// An array holding any parameters that the Category object will need to
// function correctly.
$conf['category']['params'] = array();
$conf['category']['params']['phptype'] = 'mysql';
$conf['category']['params']['hostspec'] = 'localhost';
$conf['category']['params']['username'] = 'login';
$conf['category']['params']['password'] = 'password';
$conf['category']['params']['database'] = 'horde';
$conf['category']['params']['table'] = 'category';
$conf['category']['params']['sequenceName'] = 'categorySQ';
-----------
Exemple:
$driver = $conf['category']['driver'];
$params = $conf['category']['params'];
$params['groupid']= CATEGORY_GROUPID_GROUP;
$categories = &Category::singleton($driver, $params);
$categories->add('name A');
$categories->add('name B');
$categories->add('name C','name A');
$categories->add('name D','name A');
$categories->add('name E','name C');
$categories->add('name F','name Z');
$categories->move('name C','name D');
var_dump($categories->export(CATEGORY_FORMAT_TREE));
---
Any comments are welcomed