2010-01-31 00:57:03 +01:00
< ? php
/**
2016-04-27 21:12:20 +02:00
* EGroupware admin - Edit global categories
2010-01-31 00:57:03 +01:00
*
* @ link http :// www . egroupware . org
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
* @ package admin
2016-04-27 21:12:20 +02:00
* @ copyright ( c ) 2010 - 16 by Ralf Becker < RalfBecker - AT - outdoor - training . de >
2010-01-31 00:57:03 +01:00
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
* @ version $Id $
*/
2016-04-27 21:12:20 +02:00
use EGroupware\Api ;
use EGroupware\Api\Framework ;
use EGroupware\Api\Acl ;
use EGroupware\Api\Etemplate ;
use EGroupware\Api\Categories ;
2010-01-31 00:57:03 +01:00
/**
* Edit global categories
*/
class admin_categories
{
/**
* Which methods of this class can be called as menuaction
*
* @ var array
*/
public $public_functions = array (
'index' => true ,
'edit' => true ,
2019-05-03 22:39:07 +02:00
'delete' => true ,
2010-01-31 00:57:03 +01:00
);
/**
* Path where the icons are stored ( relative to webserver_url )
*/
2016-05-09 12:05:57 +02:00
const ICON_PATH = '/api/images' ;
2011-02-15 13:51:21 +01:00
2011-06-21 22:14:56 +02:00
protected $appname = 'admin' ;
protected $get_rows = 'admin.admin_categories.get_rows' ;
protected $list_link = 'admin.admin_categories.index' ;
protected $add_link = 'admin.admin_categories.edit' ;
protected $edit_link = 'admin.admin_categories.edit' ;
2011-06-16 00:27:40 +02:00
2010-01-31 00:57:03 +01:00
/**
* Stupid old admin ACL - dont think anybody uses or understands it ; - )
2011-02-15 13:51:21 +01:00
*
2010-01-31 00:57:03 +01:00
* @ var boolean
*/
private static $acl_search ;
private static $acl_add ;
private static $acl_view ;
private static $acl_edit ;
private static $acl_delete ;
private static $acl_add_sub ;
/**
* Constructor
*/
function __construct ()
{
if ( ! isset ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'admin' ]))
{
2016-04-27 21:12:20 +02:00
throw new Api\Exception\NoPermission\Admin ();
2010-01-31 00:57:03 +01:00
}
2015-08-04 11:55:29 +02:00
if ( $GLOBALS [ 'egw' ] -> acl -> check ( 'global_categorie' , 1 , 'admin' ))
2010-01-31 00:57:03 +01:00
{
$GLOBALS [ 'egw' ] -> redirect_link ( '/index.php' );
}
self :: init_static ();
}
2011-02-15 13:51:21 +01:00
2010-01-31 00:57:03 +01:00
/**
* Init static vars ( static constructor )
*/
public static function init_static ()
{
if ( is_null ( self :: $acl_search ))
{
2015-08-04 11:55:29 +02:00
self :: $acl_search = ! $GLOBALS [ 'egw' ] -> acl -> check ( 'global_categorie' , 2 , 'admin' );
self :: $acl_add = ! $GLOBALS [ 'egw' ] -> acl -> check ( 'global_categorie' , 4 , 'admin' );
self :: $acl_view = ! $GLOBALS [ 'egw' ] -> acl -> check ( 'global_categorie' , 8 , 'admin' );
self :: $acl_edit = ! $GLOBALS [ 'egw' ] -> acl -> check ( 'global_categorie' , 16 , 'admin' );
self :: $acl_delete = ! $GLOBALS [ 'egw' ] -> acl -> check ( 'global_categorie' , 32 , 'admin' );
self :: $acl_add_sub = ! $GLOBALS [ 'egw' ] -> acl -> check ( 'global_categorie' , 64 , 'admin' );
2010-01-31 00:57:03 +01:00
}
}
2011-02-15 13:51:21 +01:00
2010-01-31 00:57:03 +01:00
/**
* Edit / add a category
2011-02-15 13:51:21 +01:00
*
2014-07-14 15:30:26 +02:00
* @ param array $content = null
* @ param string $msg = ''
2010-01-31 00:57:03 +01:00
*/
public function edit ( array $content = null , $msg = '' )
{
2010-04-13 13:45:22 +02:00
// read the session, as the global_cats param is stored with it.
2016-06-27 18:34:26 +02:00
$appname = $content [ 'appname' ] ? $content [ 'appname' ] : ( $_GET [ 'appname' ] ? $_GET [ 'appname' ] : Api\Categories :: GLOBAL_APPNAME );
2016-04-27 21:12:20 +02:00
$session = Api\Cache :: getSession ( __CLASS__ . $appname , 'nm' );
2010-04-13 13:45:22 +02:00
unset ( $session );
2010-01-31 00:57:03 +01:00
if ( ! isset ( $content ))
{
2011-02-15 13:51:21 +01:00
if ( ! ( isset ( $_GET [ 'cat_id' ]) && $_GET [ 'cat_id' ] > 0 &&
2016-04-27 21:12:20 +02:00
( $content = Categories :: read ( $_GET [ 'cat_id' ]))))
2010-01-31 00:57:03 +01:00
{
$content = array ( 'data' => array ());
if ( isset ( $_GET [ 'parent' ]) && $_GET [ 'parent' ] > 0 )
{
2012-04-05 20:33:49 +02:00
// Sub-category - set some defaults from parent
2010-01-31 00:57:03 +01:00
$content [ 'parent' ] = ( int ) $_GET [ 'parent' ];
2016-04-27 21:12:20 +02:00
$parent_cat = Categories :: read ( $content [ 'parent' ]);
2012-04-05 20:33:49 +02:00
$content [ 'owner' ] = $parent_cat [ 'owner' ];
2010-01-31 00:57:03 +01:00
}
if ( isset ( $_GET [ 'appname' ]) && isset ( $GLOBALS [ 'egw_info' ][ 'apps' ][ $_GET [ 'appname' ]]))
{
2011-06-21 22:14:56 +02:00
$appname = $_GET [ 'appname' ];
2010-01-31 00:57:03 +01:00
}
else
{
2016-04-27 21:12:20 +02:00
$appname = Categories :: GLOBAL_APPNAME ;
2010-01-31 00:57:03 +01:00
}
}
2011-07-07 20:54:59 +02:00
elseif ( $content [ 'appname' ] != $appname || ! self :: $acl_edit || ( $content [ 'owner' ] != $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] && $this -> appname != 'admin' ))
2010-01-31 00:57:03 +01:00
{
// only allow to view category
$readonlys [ '__ALL__' ] = true ;
$readonlys [ 'button[cancel]' ] = false ;
}
$content [ 'base_url' ] = self :: icon_url ();
}
elseif ( $content [ 'button' ] || $content [ 'delete' ])
{
2016-04-27 21:12:20 +02:00
$cats = new Categories ( $content [ 'owner' ] ? $content [ 'owner' ] : Categories :: GLOBAL_ACCOUNT , $content [ 'appname' ]);
2010-01-31 00:57:03 +01:00
2014-07-14 12:19:32 +02:00
if ( $content [ 'delete' ][ 'delete' ] || $content [ 'delete' ][ 'subs' ])
2010-01-31 00:57:03 +01:00
{
$button = 'delete' ;
2014-07-14 12:19:32 +02:00
$delete_subs = $content [ 'delete' ][ 'subs' ] ? true : false ;
2010-01-31 00:57:03 +01:00
}
else
{
2019-02-12 22:13:45 +01:00
$button = key ( $content [ 'button' ]);
2010-01-31 00:57:03 +01:00
unset ( $content [ 'button' ]);
}
unset ( $content [ 'delete' ]);
2014-09-25 18:03:28 +02:00
$refresh_app = $this -> appname == 'preferences' ? $content [ 'appname' ] : $this -> appname ;
2010-01-31 00:57:03 +01:00
switch ( $button )
{
case 'save' :
case 'apply' :
2013-12-10 01:03:36 +01:00
if ( is_array ( $content [ 'owner' ])) $content [ 'owner' ] = implode ( ',' , $content [ 'owner' ]);
2011-06-16 17:51:42 +02:00
if ( $content [ 'owner' ] == '' ) $content [ 'owner' ] = 0 ;
2019-03-20 17:30:54 +01:00
unset ( $content [ 'msg' ]);
2010-01-31 00:57:03 +01:00
if ( $content [ 'id' ] && self :: $acl_edit )
{
2014-03-05 18:33:42 +01:00
$data = $cats -> id2name ( $content [ 'id' ], 'data' );
2018-12-10 23:52:52 +01:00
if ( ! $content [ 'parent' ])
{
$content [ 'parent' ] = '0' ;
}
try
{
2019-05-03 22:39:07 +02:00
$cmd = new admin_cmd_category ( $appname , $content , $cats -> read ( $content [ 'id' ]), $content [ 'admin_cmd' ]);
2018-12-10 23:52:52 +01:00
$msg = $cmd -> run ();
2010-10-14 17:33:10 +02:00
}
2016-04-27 21:12:20 +02:00
catch ( Api\Exception\WrongUserinput $e )
2010-10-14 17:33:10 +02:00
{
$msg = lang ( 'Unwilling to save category with current settings. Check for inconsistency:' ) . $e -> getMessage (); // display conflicts etc.
}
2010-01-31 00:57:03 +01:00
}
elseif ( ! $content [ 'id' ] && (
$content [ 'parent' ] && self :: $acl_add_sub ||
! $content [ 'parent' ] && self :: $acl_add ))
{
2018-12-10 23:52:52 +01:00
$cmd = new admin_cmd_category ( $appname , $content );
$cmd -> run ();
$content [ 'id' ] = $cmd -> cat_id ;
2013-12-10 23:38:36 +01:00
$msg = lang ( 'Category saved.' );
2010-01-31 00:57:03 +01:00
}
else
{
$msg = lang ( 'Permission denied!' );
unset ( $button );
}
2015-10-27 17:55:57 +01:00
// If color changed, we need to do an edit 'refresh' instead of 'update'
// to reload the whole nextmatch instead of just the row
$change_color = ( $data [ 'color' ] != $content [ 'data' ][ 'color' ]);
// Nicely reload just the category window / iframe
if ( $change_color )
{
2016-04-27 21:12:20 +02:00
if ( Api\Json\Response :: isJSONResponse ())
2015-10-27 17:55:57 +01:00
{
if ( $this -> appname != 'admin' )
{
2018-07-31 17:30:57 +02:00
// Need to forcably re-load everything to force the CSS to be loaded
Api\Json\Response :: get () -> redirect ( Framework :: link ( '/index.php' , array (
'menuaction' => 'preferences.preferences_categories_ui.index' ,
'ajax' => 'true' ,
'cats_app' => $appname
)), TRUE , $this -> appname );
2015-10-27 17:55:57 +01:00
}
else
{
2018-07-31 17:30:57 +02:00
// Need to forcably re-load everything to force the CSS to be loaded
Api\Json\Response :: get () -> redirect ( Framework :: link ( '/index.php' , array (
'menuaction' => 'admin.admin_ui.index' ,
'load' => $this -> list_link ,
'ajax' => 'true' ,
'appname' => $appname
)), TRUE , $this -> appname );
2015-10-27 17:55:57 +01:00
}
2018-07-31 17:30:57 +02:00
Framework :: window_close ();
return ;
2015-10-27 17:55:57 +01:00
}
else
{
2016-04-27 21:12:20 +02:00
Categories :: css ( $refresh_app == 'admin' ? Categories :: GLOBAL_APPNAME : $refresh_app );
Framework :: refresh_opener ( '' , null , null );
2015-10-27 17:55:57 +01:00
if ( $button == 'save' )
{
2016-04-27 21:12:20 +02:00
Framework :: window_close ();
2015-10-27 17:55:57 +01:00
}
return ;
}
}
2016-03-19 13:58:20 +01:00
2013-12-16 21:11:58 +01:00
if ( $button == 'save' )
{
2016-06-13 12:30:21 +02:00
Framework :: refresh_opener ( $msg , $this -> appname , $content [ 'id' ], $change_color ? null : 'update' , $refresh_app );
2016-04-27 21:12:20 +02:00
Framework :: window_close ();
2013-12-16 21:11:58 +01:00
}
2010-01-31 00:57:03 +01:00
break ;
case 'delete' :
if ( self :: $acl_delete )
{
2018-12-10 23:52:52 +01:00
$cmd = new admin_cmd_delete_category ( $content [ 'id' ], $delete_subs );
$msg = $cmd -> run ();
2015-10-05 22:02:11 +02:00
2016-04-27 21:12:20 +02:00
Framework :: refresh_opener ( $msg , $refresh_app , $content [ 'id' ], 'delete' , $this -> appname );
Framework :: window_close ();
2013-12-10 01:03:36 +01:00
return ;
2010-01-31 00:57:03 +01:00
}
else
{
$msg = lang ( 'Permission denied!' );
unset ( $button );
}
break ;
}
2015-10-27 17:55:57 +01:00
// This should probably refresh the application $this->appname in the target tab $refresh_app, but that breaks pretty much everything
2016-06-13 12:30:21 +02:00
Framework :: refresh_opener ( $msg , $this -> appname , $content [ 'id' ], $change_color ? null : 'update' , $refresh_app );
2010-01-31 00:57:03 +01:00
}
$content [ 'msg' ] = $msg ;
2011-07-07 20:54:59 +02:00
if ( ! $content [ 'appname' ]) $content [ 'appname' ] = $appname ;
2014-01-20 15:25:18 +01:00
if ( $content [ 'data' ][ 'icon' ])
{
$content [ 'icon_url' ] = $content [ 'base_url' ] . $content [ 'data' ][ 'icon' ];
}
2011-06-16 00:27:40 +02:00
2011-06-21 22:14:56 +02:00
$sel_options [ 'icon' ] = self :: get_icons ();
$sel_options [ 'owner' ] = array ();
2011-06-24 15:35:39 +02:00
2011-06-16 00:27:40 +02:00
// User's category - add current value to be able to preserve owner
2012-04-11 18:58:30 +02:00
if ( ! $content [ 'id' ])
2011-06-27 16:34:40 +02:00
{
if ( $this -> appname != 'admin' )
{
$content [ 'owner' ] = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ];
}
2012-04-11 18:58:30 +02:00
elseif ( ! $content [ 'owner' ])
2011-06-27 16:34:40 +02:00
{
$content [ 'owner' ] = 0 ;
}
}
2011-06-21 22:14:56 +02:00
2011-06-27 22:44:43 +02:00
if ( $this -> appname != 'admin' && $content [ 'owner' ] > 0 )
2011-06-21 22:14:56 +02:00
{
2016-04-27 21:12:20 +02:00
$sel_options [ 'owner' ][ $content [ 'owner' ]] = Api\Accounts :: username ( $content [ 'owner' ]);
2011-06-16 17:51:42 +02:00
}
2011-06-22 21:01:00 +02:00
// Add 'All users', in case owner is readonlys
if ( $content [ 'id' ] && $content [ 'owner' ] == 0 )
{
$sel_options [ 'owner' ][ 0 ] = lang ( 'All users' );
}
2011-06-27 16:44:27 +02:00
if ( $this -> appname == 'admin' || ( $content [ 'id' ] && ! (( int ) $content [ 'owner' ] > 0 )))
2011-06-14 20:50:35 +02:00
{
2011-07-05 18:35:32 +02:00
if ( $content [ 'owner' ] > 0 )
{
2016-04-27 21:12:20 +02:00
$content [ 'msg' ] .= " \n " . lang ( 'owner "%1" removed, please select group-owner' , Api\Accounts :: username ( $content [ 'owner' ]));
2011-07-05 18:35:32 +02:00
$content [ 'owner' ] = 0 ;
}
2011-06-21 22:14:56 +02:00
$sel_options [ 'owner' ][ 0 ] = lang ( 'All users' );
2016-05-06 09:38:23 +02:00
foreach ( $GLOBALS [ 'egw' ] -> accounts -> search ( array ( 'type' => 'groups' )) as $acc )
2011-06-14 20:50:35 +02:00
{
2011-06-16 17:51:42 +02:00
if ( $acc [ 'account_type' ] == 'g' )
{
2016-04-27 21:12:20 +02:00
$sel_options [ 'owner' ][ $acc [ 'account_id' ]] = Etemplate\Widget\Select :: accountInfo ( $acc [ 'account_id' ], $acc );
2011-06-16 17:51:42 +02:00
}
2011-06-14 20:50:35 +02:00
}
2011-06-21 22:14:56 +02:00
$content [ 'no_private' ] = true ;
2011-06-27 16:44:27 +02:00
}
2011-06-27 17:54:07 +02:00
2011-06-27 16:44:27 +02:00
if ( $this -> appname == 'admin' )
{
2011-06-22 21:03:21 +02:00
$content [ 'access' ] = 'public' ;
2012-04-16 18:37:18 +02:00
// Allow admins access to all categories as parent
$content [ 'all_cats' ] = 'all_no_acl' ;
2011-06-27 22:44:43 +02:00
$readonlys [ 'owner' ] = false ;
2011-06-22 21:01:00 +02:00
} else {
$readonlys [ 'owner' ] = true ;
$readonlys [ 'access' ] = $content [ 'owner' ] != $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ];
2011-06-14 20:50:35 +02:00
}
2016-04-27 21:12:20 +02:00
Framework :: includeJS ( '.' , 'global_categories' , 'admin' );
2014-09-25 18:03:28 +02:00
2011-06-21 22:14:56 +02:00
$readonlys [ 'button[delete]' ] = ! $content [ 'id' ] || ! self :: $acl_delete || // cant delete not yet saved category
2011-07-07 20:54:59 +02:00
$appname != $content [ 'appname' ] || // Can't edit a category from a different app
2011-06-22 21:54:41 +02:00
( $this -> appname != 'admin' && $content [ 'owner' ] != $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ]);
2014-06-30 10:59:19 +02:00
// Make sure $content['owner'] is an array otherwise it wont show up values in the multiselectbox
if ( ! is_array ( $content [ 'owner' ])) $content [ 'owner' ] = explode ( ',' , $content [ 'owner' ]);
2016-04-27 21:12:20 +02:00
$tmpl = new Etemplate ( 'admin.categories.edit' );
2011-06-21 22:14:56 +02:00
$tmpl -> exec ( $this -> edit_link , $content , $sel_options , $readonlys , $content + array (
'old_parent' => $content [ 'old_parent' ] ? $content [ 'old_parent' ] : $content [ 'parent' ], 'appname' => $appname
2010-01-31 00:57:03 +01:00
), 2 );
}
2011-02-15 13:51:21 +01:00
2010-01-31 00:57:03 +01:00
/**
* Return URL of an icon , or base url with trailing slash
2011-02-15 13:51:21 +01:00
*
2014-07-14 15:30:26 +02:00
* @ param string $icon = '' filename
2010-01-31 00:57:03 +01:00
* @ return string url
*/
static function icon_url ( $icon = '' )
{
return $GLOBALS [ 'egw_info' ][ 'server' ][ 'webserver_url' ] . self :: ICON_PATH . '/' . $icon ;
}
2011-02-15 13:51:21 +01:00
2010-01-31 00:57:03 +01:00
/**
2016-05-09 12:05:57 +02:00
* Return icons from / api / images
2011-02-15 13:51:21 +01:00
*
2010-01-31 00:57:03 +01:00
* @ return array filename => label
*/
static function get_icons ()
{
$icons = array ();
2016-05-09 12:05:57 +02:00
if ( file_exists ( $image_dir = EGW_SERVER_ROOT . self :: ICON_PATH ) && ( $dir = dir ( $image_dir )))
2010-01-31 00:57:03 +01:00
{
2016-05-09 12:05:57 +02:00
$matches = null ;
while (( $file = $dir -> read ()))
2010-01-31 00:57:03 +01:00
{
2016-05-09 12:05:57 +02:00
if ( preg_match ( '/^(.*)\\.(png|gif|jpe?g)$/i' , $file , $matches ))
{
$icons [ $file ] = ucfirst ( $matches [ 1 ]);
}
2010-01-31 00:57:03 +01:00
}
2016-05-09 12:05:57 +02:00
$dir -> close ();
asort ( $icons );
2010-01-31 00:57:03 +01:00
}
return $icons ;
}
/**
* query rows for the nextmatch widget
*
* @ param array $query with keys 'start' , 'search' , 'order' , 'sort' , 'col_filter'
* @ param array & $rows returned rows / competitions
2016-04-27 21:12:20 +02:00
* @ param array & $readonlys eg . to disable buttons based on Acl , not use here , maybe in a derived class
2010-01-31 00:57:03 +01:00
* @ return int total number of rows
*/
2011-07-06 17:10:46 +02:00
public function get_rows ( & $query , & $rows , & $readonlys )
2010-01-31 00:57:03 +01:00
{
self :: init_static ();
2011-06-21 22:14:56 +02:00
$filter = array ();
2016-04-27 21:12:20 +02:00
$globalcat = ( $query [ 'filter' ] === Categories :: GLOBAL_ACCOUNT || ! $query [ 'filter' ]);
2010-04-13 12:29:05 +02:00
if ( isset ( $query [ 'global_cats' ]) && $query [ 'global_cats' ] === false )
{
$globalcat = false ;
}
2012-02-15 10:40:05 +01:00
if ( $globalcat && $query [ 'filter' ]) $filter [ 'access' ] = 'public' ;
// new column-filter access has highest priority
if ( ! empty ( $query [ 'col_filter' ][ 'access' ])) $filter [ 'access' ] = $query [ 'col_filter' ][ 'access' ];
2016-04-27 21:12:20 +02:00
Api\Cache :: setSession ( __CLASS__ . $query [ 'appname' ], 'nm' , $query );
2010-01-31 00:57:03 +01:00
2011-06-27 17:49:42 +02:00
if ( $query [ 'filter' ] > 0 || $query [ 'col_filter' ][ 'owner' ])
{
2011-06-22 22:58:53 +02:00
$owner = $query [ 'col_filter' ][ 'owner' ] ? $query [ 'col_filter' ][ 'owner' ] : $query [ 'filter' ];
2011-06-21 22:14:56 +02:00
}
2011-07-06 16:44:23 +02:00
if ( $query [ 'col_filter' ][ 'app' ])
{
2011-07-07 20:34:33 +02:00
$filter [ 'appname' ] = $query [ 'col_filter' ][ 'app' ];
2011-07-06 16:44:23 +02:00
}
2016-04-27 21:12:20 +02:00
$GLOBALS [ 'egw' ] -> categories = $cats = new Categories ( $filter [ 'owner' ], $query [ 'appname' ]);
$globals = isset ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'admin' ]) ? 'all_no_acl' : $globalcat ; // ignore Acl only for admins
2012-11-05 18:56:03 +01:00
$parent = $query [ 'search' ] ? false : 0 ;
2014-09-25 18:03:28 +02:00
$rows = $cats -> return_sorted_array ( $query [ 'start' ], false , $query [ 'search' ], $query [ 'sort' ], $query [ 'order' ], $globals , $parent , true , $filter );
2011-06-22 22:45:42 +02:00
$count = $cats -> total_records ;
foreach ( $rows as $key => & $row )
2010-01-31 00:57:03 +01:00
{
2011-06-27 17:49:42 +02:00
$row [ 'owner' ] = explode ( ',' , $row [ 'owner' ]);
2016-06-27 18:34:26 +02:00
if (( $owner && ! in_array ( $owner , $row [ 'owner' ])) || (( string ) $query [ 'filter' ] === ( string ) Api\Categories :: GLOBAL_ACCOUNT && $row [ 'owner' ][ 0 ] > 0 ))
2011-06-22 22:45:42 +02:00
{
unset ( $rows [ $key ]);
$count -- ;
continue ;
}
2018-12-10 23:52:52 +01:00
if ( $row [ 'level' ] >= 0 )
{
$row [ 'level_spacer' ] = str_repeat ( ' ' , $row [ 'level' ]);
}
2011-02-15 13:51:21 +01:00
2010-01-31 00:57:03 +01:00
if ( $row [ 'data' ][ 'icon' ]) $row [ 'icon_url' ] = self :: icon_url ( $row [ 'data' ][ 'icon' ]);
2011-02-15 13:51:21 +01:00
2018-12-10 23:52:52 +01:00
$row [ 'subs' ] = $row [ 'children' ] ? count ( $row [ 'children' ]) : 0 ;
2011-02-15 13:51:21 +01:00
2010-01-31 00:57:03 +01:00
$row [ 'class' ] = 'level' . $row [ 'level' ];
2015-02-11 10:40:01 +01:00
if ( $row [ 'owner' ][ 0 ] > 0 && ! $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'admin' ] && $row [ 'owner' ][ 0 ] != $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ])
2011-06-21 22:14:56 +02:00
{
2011-07-04 23:16:44 +02:00
$row [ 'class' ] .= ' rowNoEdit rowNoDelete ' ;
}
2015-09-29 17:15:45 +02:00
else if ( ! $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'admin' ])
{
2016-04-27 21:12:20 +02:00
if ( ! $cats -> check_perms ( Acl :: EDIT , $row [ 'id' ]) || ! self :: $acl_edit )
2015-09-29 17:15:45 +02:00
{
$row [ 'class' ] .= ' rowNoEdit' ;
}
2016-04-27 21:12:20 +02:00
if ( ! $cats -> check_perms ( Acl :: DELETE , $row [ 'id' ]) || ! self :: $acl_delete ||
2015-09-30 18:24:08 +02:00
// Only admins can delete globals
$cats -> is_global ( $row [ 'id' ]) && ! $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'admin' ])
2015-09-29 17:15:45 +02:00
{
$row [ 'class' ] .= ' rowNoDelete' ;
}
}
2018-02-21 22:41:34 +01:00
// Can only edit or delete (via context menu) Categories for the selected app (backend restriction)
2017-02-13 18:52:31 +01:00
if ( $row [ 'appname' ] != $query [ 'appname' ])
2011-07-04 23:16:44 +02:00
{
2018-02-21 22:41:34 +01:00
$row [ 'class' ] .= ' rowNoEdit rowNoDelete ' ;
2011-06-21 22:14:56 +02:00
}
2015-10-26 19:32:40 +01:00
$readonlys [ 'nm' ][ " edit[ $row[id] ] " ] = ! self :: $acl_edit ;
$readonlys [ 'nm' ][ " add[ $row[id] ] " ] = ! self :: $acl_add_sub ;
$readonlys [ 'nm' ][ " delete[ $row[id] ] " ] = ! self :: $acl_delete ;
2010-01-31 00:57:03 +01:00
}
2014-09-25 18:03:28 +02:00
if ( true ) $rows = $count <= $query [ 'num_rows' ] ? array_values ( $rows ) : array_slice ( $rows , $query [ 'start' ], $query [ 'num_rows' ]);
2010-01-31 00:57:03 +01:00
// make appname available for actions
$rows [ 'appname' ] = $query [ 'appname' ];
2011-07-13 18:39:36 +02:00
$rows [ 'edit_link' ] = $this -> edit_link ;
2010-01-31 00:57:03 +01:00
2016-04-27 21:12:20 +02:00
// disable access column for global Categories
2012-02-15 11:25:41 +01:00
if ( $GLOBALS [ 'egw_info' ][ 'flags' ][ 'currentapp' ] == 'admin' ) $rows [ 'no_access' ] = true ;
2011-06-22 22:45:42 +02:00
$GLOBALS [ 'egw_info' ][ 'flags' ][ 'app_header' ] = lang ( $this -> appname ) . ' - ' . lang ( 'categories' ) .
2016-04-27 21:12:20 +02:00
( $query [ 'appname' ] != Categories :: GLOBAL_APPNAME ? ': ' . lang ( $query [ 'appname' ]) : '' );
2010-01-31 00:57:03 +01:00
2011-06-22 22:45:42 +02:00
return $count ;
2010-01-31 00:57:03 +01:00
}
/**
* Display the accesslog
*
2014-07-14 15:30:26 +02:00
* @ param array $content = null
* @ param string $msg = ''
2010-01-31 00:57:03 +01:00
*/
public function index ( array $content = null , $msg = '' )
{
2010-04-13 12:29:05 +02:00
//_debug_array($_GET);
2016-04-27 21:12:20 +02:00
if ( $this -> appname != 'admin' ) Api\Translation :: add_app ( 'admin' ); // need admin translations
2010-01-31 00:57:03 +01:00
if ( ! isset ( $content ))
{
if ( isset ( $_GET [ 'msg' ])) $msg = $_GET [ 'msg' ];
2011-06-22 21:23:31 +02:00
2016-04-27 21:12:20 +02:00
$appname = Categories :: GLOBAL_APPNAME ;
2011-06-22 21:23:31 +02:00
foreach ( array ( $content [ 'nm' ][ 'appname' ], $_GET [ 'cats_app' ], $_GET [ 'appname' ]) as $field )
{
2011-06-24 15:35:39 +02:00
if ( $field )
2011-06-22 21:23:31 +02:00
{
$appname = $field ;
break ;
}
}
2016-04-27 21:12:20 +02:00
$content [ 'nm' ] = Api\Cache :: getSession ( __CLASS__ . $appname , 'nm' );
2010-01-31 04:28:32 +01:00
if ( ! is_array ( $content [ 'nm' ]))
2010-01-31 00:57:03 +01:00
{
$content [ 'nm' ] = array (
2011-06-21 22:14:56 +02:00
'get_rows' => $this -> get_rows , // I method/callback to request the data for the rows eg. 'notes.bo.get_rows'
2011-06-16 00:27:40 +02:00
'options-filter' => array (
2011-06-22 21:59:22 +02:00
'' => lang ( 'All categories' ),
2016-04-27 21:12:20 +02:00
Categories :: GLOBAL_ACCOUNT => lang ( 'Global categories' ),
2011-06-16 00:27:40 +02:00
$GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] => lang ( 'Own categories' ),
),
2010-01-31 00:57:03 +01:00
'no_filter2' => True , // I disable the 2. filter (params are the same as for filter)
'no_cat' => True , // I disable the cat-selectbox
'header_left' => false , // I template to show left of the range-value, left-aligned (optional)
'header_right' => false , // I template to show right of the range-value, right-aligned (optional)
'never_hide' => True , // I never hide the nextmatch-line if less then maxmatch entries
'lettersearch' => false , // I show a lettersearch
'start' => 0 , // IO position in list
'order' => 'name' , // IO name of the column to sort after (optional for the sortheaders)
'sort' => 'ASC' , // IO direction of the sort: 'ASC' or 'DESC'
2011-06-21 22:14:56 +02:00
'default_cols' => '!color,last_mod,subs,legacy_actions' , // I columns to use if there's no user or default pref (! as first char uses all but the named columns), default all columns
2010-01-31 00:57:03 +01:00
'csv_fields' => false , // I false=disable csv export, true or unset=enable it with auto-detected fieldnames,
//or array with name=>label or name=>array('label'=>label,'type'=>type) pairs (type is a eT widget-type)
'no_search' => ! self :: $acl_search ,
2011-06-21 22:14:56 +02:00
'row_id' => 'id' ,
2013-12-10 23:23:05 +01:00
'dataStorePrefix' => 'categories' // Avoid conflict with user list when in admin
2010-01-31 00:57:03 +01:00
);
2016-06-27 18:34:26 +02:00
$content [ 'nm' ][ 'filter' ] = $this -> appname == 'admin' ? Api\Categories :: GLOBAL_ACCOUNT : $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ];
2010-01-31 00:57:03 +01:00
}
2010-04-13 13:45:22 +02:00
else
{
$content [ 'nm' ][ 'start' ] = 0 ;
}
2011-07-07 19:19:04 +02:00
$content [ 'nm' ][ 'appname' ] = $appname = $_GET [ 'appname' ] ? $_GET [ 'appname' ] : $appname ;
2011-06-21 22:14:56 +02:00
$content [ 'nm' ][ 'actions' ] = $this -> get_actions ( $appname );
2012-04-02 18:29:13 +02:00
// switch filter off for super-global categories
if ( $appname == 'phpgw' )
{
$content [ 'nm' ][ 'no_filter' ] = true ;
// Make sure filter is set properly, could be different if user was looking at something else
2016-04-27 21:12:20 +02:00
$content [ 'nm' ][ 'filter' ] = Categories :: GLOBAL_ACCOUNT ;
2012-04-02 18:29:13 +02:00
}
2011-06-21 22:14:56 +02:00
2010-04-13 13:45:22 +02:00
$content [ 'nm' ][ 'global_cats' ] = true ;
2010-04-13 12:29:05 +02:00
if ( isset ( $_GET [ 'global_cats' ]) && empty ( $_GET [ 'global_cats' ] ))
{
$content [ 'nm' ][ 'global_cats' ] = false ;
}
2010-01-31 00:57:03 +01:00
}
2011-06-21 22:14:56 +02:00
elseif ( $content [ 'nm' ][ 'action' ])
2010-01-31 00:57:03 +01:00
{
2015-01-27 23:41:13 +01:00
$appname = $content [ 'nm' ][ 'appname' ];
2012-02-15 10:40:05 +01:00
if ( ! count ( $content [ 'nm' ][ 'selected' ]) && ! $content [ 'nm' ][ 'select_all' ])
{
$msg = lang ( 'You need to select some entries first!' );
}
else
{
2011-06-21 22:14:56 +02:00
// Action has an additional action - add / delete, etc. Buttons named <multi-action>_action[action_name]
2011-07-04 23:16:44 +02:00
if ( in_array ( $content [ 'nm' ][ 'action' ], array ( 'owner' )))
{
$action = $content [ 'nm' ][ 'action' ];
2014-07-11 11:47:43 +02:00
if ( $content [ $action . '_popup' ])
{
$content = array_merge ( $content , $content [ $action . '_popup' ]);
}
2011-07-04 23:16:44 +02:00
$content [ 'nm' ][ 'action' ] .= '_' . key ( $content [ $action . '_action' ]);
if ( is_array ( $content [ $action ]))
{
$content [ $action ] = implode ( ',' , $content [ $action ]);
}
$content [ 'nm' ][ 'action' ] .= '_' . $content [ $action ];
}
2014-09-25 18:03:28 +02:00
$success = $failed = 0 ;
$action_msg = null ;
2012-02-15 10:40:05 +01:00
if ( $this -> action ( $content [ 'nm' ][ 'action' ], $content [ 'nm' ][ 'selected' ], $content [ 'nm' ][ 'select_all' ],
2014-09-25 18:03:28 +02:00
$success , $failed , $action_msg , $content [ 'nm' ]))
2012-02-15 10:40:05 +01:00
{
$msg .= lang ( '%1 category(s) %2' , $success , $action_msg );
}
elseif ( empty ( $msg ))
{
$msg .= lang ( '%1 category(s) %2, %3 failed because of insufficent rights !!!' , $success , $action_msg , $failed );
}
2016-04-27 21:12:20 +02:00
Framework :: refresh_opener ( $msg , $this -> appname );
2015-01-27 23:41:13 +01:00
$msg = '' ;
2012-02-15 10:40:05 +01:00
}
}
2010-01-31 00:57:03 +01:00
$content [ 'msg' ] = $msg ;
2016-04-27 21:12:20 +02:00
$content [ 'nm' ][ 'add_link' ] = Framework :: link ( '/index.php' , 'menuaction=' . $this -> add_link . '&cat_id=&appname=' . $appname );
2011-06-21 22:14:56 +02:00
$content [ 'edit_link' ] = $this -> edit_link . '&appname=' . $appname ;
2011-07-06 17:10:46 +02:00
$content [ 'owner' ] = '' ;
2011-06-27 17:49:42 +02:00
2011-07-07 19:19:04 +02:00
$sel_options [ 'appname' ] = $this -> get_app_list ();
$sel_options [ 'app' ] = array (
'' => lang ( 'All' ),
$appname => lang ( $appname )
);
2012-02-15 10:40:05 +01:00
$sel_options [ 'access' ] = array (
'public' => 'No' ,
'private' => 'Yes' ,
);
2014-09-25 18:03:28 +02:00
2011-07-04 23:16:44 +02:00
$sel_options [ 'owner' ][ 0 ] = lang ( 'All users' );
2016-05-06 09:38:23 +02:00
foreach ( $GLOBALS [ 'egw' ] -> accounts -> search ( array ( 'type' => 'groups' )) as $acc )
2011-07-04 23:16:44 +02:00
{
if ( $acc [ 'account_type' ] == 'g' )
{
2016-05-06 09:38:23 +02:00
$sel_options [ 'owner' ][ $acc [ 'account_id' ]] = Etemplate\Widget\Select :: accountInfo ( $acc [ 'account_id' ], $acc );
2011-07-04 23:16:44 +02:00
}
}
2011-06-27 17:49:42 +02:00
2010-01-31 04:28:32 +01:00
$readonlys [ 'add' ] = ! self :: $acl_add ;
2011-06-22 21:59:22 +02:00
if ( ! $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'admin' ])
2011-06-16 00:27:40 +02:00
{
$readonlys [ 'nm' ][ 'rows' ][ 'owner' ] = true ;
2011-06-21 22:14:56 +02:00
$readonlys [ 'nm' ][ 'col_filter' ][ 'owner' ] = true ;
2011-06-16 00:27:40 +02:00
}
2016-04-27 21:12:20 +02:00
if ( $appname == Categories :: GLOBAL_APPNAME ) {
2011-07-07 19:19:04 +02:00
$sel_options [ 'app' ] = array ( '' => '' );
$readonlys [ 'nm' ][ 'rows' ][ 'app' ] = true ;
}
2010-01-31 00:57:03 +01:00
2016-04-27 21:12:20 +02:00
$tmpl = new Etemplate ( 'admin.categories.index' );
2016-06-13 16:04:11 +02:00
// we need to set a different dom-id for each application and also global categories of that app
// otherwise eT2 objects are overwritter when a second categories template is shown
$tmpl -> set_dom_id ( $appname . '.' . $this -> appname . '.categories.index' );
2012-05-22 21:53:58 +02:00
2016-03-19 13:58:20 +01:00
// Category styles
2016-04-27 21:12:20 +02:00
Categories :: css ( $appname );
2012-05-22 21:53:58 +02:00
2011-06-21 22:14:56 +02:00
$tmpl -> exec ( $this -> list_link , $content , $sel_options , $readonlys , array (
2010-01-31 00:57:03 +01:00
'nm' => $content [ 'nm' ],
));
}
2011-06-21 22:14:56 +02:00
2019-05-03 22:39:07 +02:00
/**
* Dialog to delete a category
*
* @ param array $content = null
*/
public function delete ( array $content = null )
{
if ( ! is_array ( $content ))
{
if ( isset ( $_GET [ 'cat_id' ]))
{
$content = array (
'cat_id' => ( int ) $_GET [ 'cat_id' ],
);
}
//error_log(__METHOD__."() \$_GET[account_id]=$_GET[account_id], \$_GET[contact_id]=$_GET[contact_id] content=".array2string($content));
}
$cats = new Categories ( '' , Categories :: id2name ( $content [ 'cat_id' ], 'appname' ));
if ( ! $cats -> check_perms ( Acl :: DELETE , $content [ 'cat_id' ]) || ! self :: $acl_delete ||
// Only admins can delete globals
$cats -> is_global ( $content [ 'cat_id' ]) && ! $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'admin' ])
{
Framework :: window_close ( lang ( 'Permission denied!!!' ));
}
if ( $content [ 'button' ])
{
if ( $cats -> check_perms ( Acl :: DELETE , $content [ 'cat_id' ], ( boolean ) $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'admin' ]))
{
$cmd = new admin_cmd_delete_category (
$content [ 'cat_id' ],
key ( $content [ 'button' ]) == 'delete_sub' ,
$content [ 'admin_cmd' ]
);
$cmd -> run ();
Framework :: refresh_opener ( lang ( 'Deleted' ), 'admin' , $content [ 'cat_id' ], 'delete' );
Framework :: window_close ();
}
}
$tpl = new Etemplate ( 'admin.categories.delete' );
$tpl -> exec ( 'admin.admin_categories.delete' , $content , array (), array (), $content , 2 );
}
2016-06-27 18:34:26 +02:00
protected function get_actions ( $appname = Api\Categories :: GLOBAL_APPNAME ) {
2011-06-21 22:14:56 +02:00
$actions = array (
'open' => array ( // does edit if allowed, otherwise view
'caption' => 'Open' ,
'default' => true ,
'allowOnMultiple' => false ,
'url' => 'menuaction=' . $this -> edit_link . '&cat_id=$id&appname=' . $appname ,
'popup' => '600x380' ,
'group' => $group = 1 ,
),
2011-07-04 23:16:44 +02:00
'add' => array (
2011-06-21 22:14:56 +02:00
'caption' => 'Add' ,
'allowOnMultiple' => false ,
'icon' => 'new' ,
2011-11-03 15:37:43 +01:00
'url' => 'menuaction=' . $this -> add_link . '&appname=' . $appname ,
2011-06-21 22:14:56 +02:00
'popup' => '600x380' ,
'group' => $group ,
),
2011-07-04 23:16:44 +02:00
'sub' => array (
2011-06-21 22:14:56 +02:00
'caption' => 'Add sub' ,
'allowOnMultiple' => false ,
'icon' => 'new' ,
'url' => 'menuaction=' . $this -> add_link . '&parent=$id&appname=' . $appname ,
'popup' => '600x380' ,
'group' => $group ,
'disableClass' => 'rowNoSub' ,
),
2011-07-04 23:16:44 +02:00
'owner' => array (
'caption' => 'Change owner' ,
'icon' => 'users' ,
'nm_action' => 'open_popup' ,
'group' => $group ,
'disableClass' => 'rowNoEdit' ,
),
'delete' => array (
2011-06-21 22:14:56 +02:00
'caption' => 'Delete' ,
'allowOnMultiple' => true ,
2011-07-04 23:16:44 +02:00
'group' => ++ $group ,
2011-06-21 22:14:56 +02:00
'disableClass' => 'rowNoDelete' ,
2019-05-03 22:39:07 +02:00
'popup' => '450x400' ,
'url' => 'menuaction=admin.admin_categories.delete&cat_id=$id' ,
2011-06-21 22:14:56 +02:00
),
);
2011-07-04 23:16:44 +02:00
if ( ! $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'admin' ])
{
unset ( $actions [ 'owner' ]);
}
2011-06-21 22:14:56 +02:00
return $actions ;
}
/**
* Handles actions on multiple entries
*
2014-09-25 18:03:28 +02:00
* @ param string $_action
2011-06-21 22:14:56 +02:00
* @ param array $checked contact id ' s to use if ! $use_all
* @ param boolean $use_all if true use all entries of the current selection ( in the session )
* @ param int & $success number of succeded actions
* @ param int & $failed number of failed actions ( not enought permissions )
* @ param string & $action_msg translated verb for the actions , to be used in a message like '%1 entries deleted'
* @ param array $query get_rows parameter
* @ return boolean true if all actions succeded , false otherwise
*/
2014-09-25 18:03:28 +02:00
function action ( $_action , $checked , $use_all , & $success , & $failed , & $action_msg , array $query )
2011-06-21 22:14:56 +02:00
{
//echo '<p>'.__METHOD__."('$action',".array2string($checked).','.(int)$use_all.",...)</p>\n";
$success = $failed = 0 ;
if ( $use_all )
{
@ set_time_limit ( 0 ); // switch off the execution time limit, as it's for big selections to small
$query [ 'num_rows' ] = - 1 ; // all
2014-09-25 18:03:28 +02:00
$result = $readonlys = array ();
2011-06-21 22:14:56 +02:00
$this -> get_rows ( $query , $result , $readonlys );
$checked = array ();
foreach ( $result as $key => $info )
{
if ( is_numeric ( $key ))
{
$checked [] = $info [ 'id' ];
}
}
}
$owner = $query [ 'col_filter' ][ 'owner' ] ? $query [ 'col_filter' ][ 'owner' ] : $query [ 'filter' ];
2011-07-06 17:10:46 +02:00
$app = $query [ 'col_filter' ][ 'app' ] ? $query [ 'col_filter' ][ 'app' ] : $query [ 'appname' ];
2016-04-27 21:12:20 +02:00
$cats = new Categories ( $owner , $app );
2011-06-21 22:14:56 +02:00
2014-09-25 18:03:28 +02:00
list ( $action , $settings ) = explode ( '_' , $_action , 2 );
2011-07-04 23:16:44 +02:00
2011-07-05 18:35:32 +02:00
switch ( $action )
{
2011-07-04 23:16:44 +02:00
case 'delete' :
2015-09-29 17:15:45 +02:00
$action_msg = lang ( 'deleted' );
2011-07-05 18:35:32 +02:00
foreach ( $checked as $id )
{
2016-04-27 21:12:20 +02:00
if ( $cats -> check_perms ( Acl :: DELETE , $id , ( boolean ) $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ 'admin' ]))
2015-09-29 17:15:45 +02:00
{
2018-12-10 23:52:52 +01:00
$cmd = new admin_cmd_delete_category ( $id , $settings == 'sub' );
$cmd -> run ();
2015-09-29 17:15:45 +02:00
$success ++ ;
}
else
{
$failed ++ ;
}
2011-07-04 23:16:44 +02:00
}
break ;
case 'owner' :
$action_msg = lang ( 'updated' );
2014-09-25 18:03:28 +02:00
list ( $add_remove , $ids_csv ) = explode ( '_' , $settings , 2 );
$ids = explode ( ',' , $ids_csv );
2011-07-05 18:35:32 +02:00
// Adding 'All users' removes all the others
2016-04-27 21:12:20 +02:00
if ( $add_remove == 'add' && array_search ( Categories :: GLOBAL_ACCOUNT , $ids ) !== false ) $ids = array ( Categories :: GLOBAL_ACCOUNT );
2011-07-05 18:35:32 +02:00
foreach ( $checked as $id )
{
2011-07-04 23:16:44 +02:00
if ( ! $data = $cats -> read ( $id )) continue ;
2011-07-05 18:35:32 +02:00
$data [ 'owner' ] = explode ( ',' , $data [ 'owner' ]);
2016-04-27 21:12:20 +02:00
if ( array_search ( Categories :: GLOBAL_ACCOUNT , $data [ 'owner' ]) !== false || $data [ 'owner' ][ 0 ] > 0 )
2011-07-05 18:35:32 +02:00
{
$data [ 'owner' ] = array ();
}
2011-07-04 23:16:44 +02:00
$data [ 'owner' ] = $add_remove == 'add' ?
2016-04-27 21:12:20 +02:00
$ids == array ( Categories :: GLOBAL_ACCOUNT ) ? $ids : array_merge ( $data [ 'owner' ], $ids ) :
2011-07-04 23:16:44 +02:00
array_diff ( $data [ 'owner' ], $ids );
2011-07-05 18:35:32 +02:00
$data [ 'owner' ] = implode ( ',' , array_unique ( $data [ 'owner' ]));
2011-07-04 23:16:44 +02:00
2018-12-10 23:52:52 +01:00
$cmd = new admin_cmd_category ( $app , $data , array ());
if ( $cmd -> run ())
2011-07-04 23:16:44 +02:00
{
$success ++ ;
}
else
{
$failed ++ ;
}
}
break ;
2011-06-21 22:14:56 +02:00
}
return $failed == 0 ;
}
2011-06-27 17:49:42 +02:00
/**
* Get a list of apps for selectbox / filter
*/
2011-07-05 18:35:32 +02:00
protected function get_app_list ()
{
2011-06-27 17:49:42 +02:00
$apps = array ();
foreach ( $GLOBALS [ 'egw_info' ][ 'apps' ] as $app => $data )
{
if ( $app == 'phpgwapi' )
{
$apps [ 'phpgw' ] = lang ( 'Global' );
continue ;
}
2016-04-27 21:12:20 +02:00
// Skip apps that don't show in the app bar, they [usually] don't have Categories
2011-06-27 17:49:42 +02:00
if ( $data [ 'status' ] > 1 || in_array ( $app , array ( 'home' , 'admin' , 'felamimail' , 'sitemgr' , 'sitemgr-link' ))) continue ;
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ $app ])
{
$apps [ $app ] = $data [ 'title' ] ? $data [ 'title' ] : lang ( $app );
}
}
return $apps ;
}
2010-01-31 00:57:03 +01:00
}
admin_categories :: init_static ();