diff --git a/admin/inc/class.admin_applications.inc.php b/admin/inc/class.admin_applications.inc.php
new file mode 100644
index 0000000000..04cada28b0
--- /dev/null
+++ b/admin/inc/class.admin_applications.inc.php
@@ -0,0 +1,229 @@
+
+ * @package admin
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.admin_register_hooks.inc.php 29103 2010-05-25 15:30:25Z füller $
+ */
+
+class admin_applications
+{
+ var $public_functions = array(
+ 'index' => True,
+ );
+
+ /**
+ * Our storage object
+ *
+ * @var so_sql
+ */
+ protected $so;
+
+ /**
+ * Name of our table
+ */
+ const TABLE = 'egw_applications';
+
+ /**
+ * Name of app the table is registered
+ */
+ const APP = 'phpgwapi';
+
+ /**
+ * Constructor
+ *
+ */
+ function __construct()
+ {
+ $this->so = new so_sql(self::APP,self::TABLE,null,'',true);
+ }
+
+ /**
+ * query rows for the nextmatch widget
+ *
+ * @param array $query with keys 'start', 'search', 'order', 'sort', 'col_filter'
+ * @param array &$rows returned rows/competitions
+ * @param array &$readonlys eg. to disable buttons based on acl, not use here, maybe in a derived class
+ * @return int total number of rows
+ */
+ function get_rows($query,&$rows,&$readonlys)
+ {
+ //$query['col_filter'][] = array('app_enabled' => array(1,4));
+ $total = $this->so->get_rows($query,$rows,$readonlys);
+
+ foreach($rows as $i => &$row)
+ {
+ //_debug_array($GLOBALS['egw_info']['apps'][$app]);
+ $row['image'] = (isset($row['app_icon_app'])?$row['app_icon_app']:$row['app_name']).'/'.
+ (isset($row['app_icon'])?$row['app_icon']:'navbar');
+ //_debug_array($row);
+ if($i == 0)
+ $readonlys['up['.$row['app_id'].']'] = true;
+ if($i == count($rows) - 1)
+ $readonlys['down['.$row['app_id'].']'] = true;
+ }
+
+ return $total;
+ }
+
+ /**
+ * Display the applications
+ *
+ * @param array $content
+ */
+ function index(array $content=null)
+ {
+ //_debug_array($content);
+ if(!isset($content))
+ {
+ $content['nm'] = array(
+ 'get_rows' => 'admin.admin_applications.get_rows', // I method/callback to request the data for the rows eg. 'notes.bo.get_rows'
+ 'no_filter' => True, // I disable the 1. filter
+ 'col_filter' => array('app_enabled' => array(1,4),"app_name not in ('admin','manual')"), // =All // IO filter, if not 'no_filter' => True
+ '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' => 'app_order', // IO name of the column to sort after (optional for the sortheaders)
+ 'sort' => 'ASC', // IO direction of the sort: 'ASC' or 'DESC'
+ //'default_cols' => // I columns to use if there's no user or default pref (! as first char uses all but the named columns), default all columns
+ '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)
+ );
+ }
+ elseif(isset($content['nm']['rows']['up']))
+ {
+ list($app) = each($content['nm']['rows']['up']);
+ unset($content['nm']['rows']['up']);
+ $this->move_app(-1, $app);
+ egw::invalidate_session_cache();
+ egw::redirect_link('/index.php',array('menuaction'=>'admin.admin_applications.index'));
+ exit;
+ }
+ elseif(isset($content['nm']['rows']['down']))
+ {
+ list($app) = each($content['nm']['rows']['down']);
+ unset($content['nm']['rows']['down']);
+ $this->move_app(1, $app);
+ egw::invalidate_session_cache();
+ egw::redirect_link('/index.php',array('menuaction'=>'admin.admin_applications.index'));
+ exit;
+ }
+ elseif(isset($content['number']))
+ {
+ // number apps serially
+ $order = array();
+ foreach($GLOBALS['egw_info']['apps'] as $app)
+ {
+ //_debug_array(array($app['name'],$app['status'],$app['order']));
+ if(($app['status'] == 1 || $app['status'] == 4) && $app['name']!='admin' && $app['name']!='manual')
+ {
+ $order[$app['id']] = $app['order'];
+ }
+ }
+ asort($order);
+ $app_ids = array_keys($order);
+ //_debug_array($app_ids);
+ foreach($app_ids as $pos => $app)
+ {
+ $app_name = $GLOBALS['egw']->applications->id2name($app);
+ //echo "set $app_name ($app) to ".($pos + 1)."
";
+ $newpos= ($pos+1)*5;
+ $GLOBALS['egw']->db->update(self::TABLE,array('app_order' => $newpos),array('app_id' => $app),__LINE__,__FILE__);
+ // set the egw_info->apps array as well
+ if(isset($GLOBALS['egw_info']['apps'][$app_name]))
+ {
+ $GLOBALS['egw_info']['apps'][$app_name]['order'] = $newpos;
+ }
+ }
+ egw::invalidate_session_cache();
+ egw::redirect_link('/index.php',array('menuaction'=>'admin.admin_applications.index'));
+ exit;
+ }
+
+ $tmpl = new etemplate('admin.applications');
+ $tmpl->exec('admin.admin_applications.index',$content,$sel_options,$readonlys,array(
+ 'nm' => $content['nm'],
+ ));
+ }
+
+ /**
+ * Moves an application
+ *
+ * @param int $dir the direction to move: -1 up, 1 down
+ * @param int $app id of the app to move
+ */
+ function move_app($dir,$app)
+ {
+ //echo "move_app($dir,$app,$order,$move_only)
";
+ $order = array();
+ foreach($GLOBALS['egw_info']['apps'] as $_app)
+ {
+ if(($_app['status'] == 1 || $_app['status'] == 4 ) && $app['name']!='admin' && $_app['name']!='manual')
+ {
+ $order[$_app['id']] = $_app['order'];
+ //echo '
#'.$_app['id'].': '.$_app['title'].'->'.$_app['order'].'
';
+ }
+ }
+ asort($order);
+ //_debug_array($order);
+
+ // switch positions
+ $old_pos = $order[$app];
+ $next_app = $this->find_next_key($order,$app,$dir == -1);
+ $new_pos = $order[$next_app];
+ if ($new_pos == $old_pos) $new_pos= $new_pos + $dir;
+ $GLOBALS['egw']->db->update(self::TABLE,array('app_order' => $new_pos),array('app_id' => $app),__LINE__,__FILE__);
+ $order[$app] = $new_pos;
+ $GLOBALS['egw']->db->update(self::TABLE,array('app_order' => $old_pos),array('app_id' => $next_app),__LINE__,__FILE__);
+ $order[$next_app] = $old_pos;
+ }
+
+ function find_next_key($array,$old_key,$reverse)
+ {
+ //error_log("find_next_key($old_key ".$GLOBALS['egw']->applications->id2name($old_key)." position:".$array[$old_key].",".var_dump($reverse).")");
+ $next_key = false;
+ $first = true;
+ $neworder = $array[$old_key];
+ foreach($array as $key => $value)
+ {
+ // remember the first entry
+ if ($first === true)
+ {
+ //echo $key.' first app in order ('.$value.')
';
+ $first = $key;
+ }
+ if(!$reverse)
+ {
+ //error_log( "down $value $value < $neworder && $value > ".$array[$old_key]);
+ if(((int)$value < (int)$neworder && (int)$value > (int)$array[$old_key] && $key != $old_key) || ((int)$value > (int)$array[$old_key] && $next_key === false))
+ {
+ //error_log( "matching down $value");
+ $next_key = $key;
+ $neworder = $value;
+ //echo "-match: $next_key ".$GLOBALS['egw']->applications->id2name($next_key)."
";
+ }
+ }
+ else
+ {
+ //error_log( "up $value $value > $neworder && $value < ".$array[$old_key]);
+ if(($value > $neworder && $value < $array[$old_key] && $key != $old_key) || ($value < $array[$old_key] && $next_key === false))
+ {
+ //error_log( "matching up $value");
+ $next_key = $key;
+ $neworder = $value;
+ //echo "-rmatch: $next_key ->".$GLOBALS['egw']->applications->id2name($next_key)."
";
+ }
+ }
+ }
+ //error_log($next_key."->".$GLOBALS['egw']->applications->id2name($next_key).' as next key position:'.$array[$next_key]);
+ return ($next_key === false ? $first : $next_key);
+ }
+}
+
diff --git a/admin/inc/class.admin_prefs_sidebox_hooks.inc.php b/admin/inc/class.admin_prefs_sidebox_hooks.inc.php
index ab84430d7d..1e55a7803a 100644
--- a/admin/inc/class.admin_prefs_sidebox_hooks.inc.php
+++ b/admin/inc/class.admin_prefs_sidebox_hooks.inc.php
@@ -62,6 +62,10 @@ class admin_prefs_sidebox_hooks
$file['User Groups'] = egw::link('/index.php','menuaction=admin.uiaccounts.list_groups');
}
+ if (! $GLOBALS['egw']->acl->check('applications_access',1,'admin'))
+ {
+ $file['Applications'] = egw::link('/index.php','menuaction=admin.admin_applications.index');
+ }
if (! $GLOBALS['egw']->acl->check('global_categories_access',1,'admin'))
{
$file['Global Categories'] = egw::link('/index.php','menuaction=admin.admin_categories.index&appname=phpgw');
diff --git a/admin/setup/etemplates.inc.php b/admin/setup/etemplates.inc.php
index 50379d0b2a..d43f015fde 100644
--- a/admin/setup/etemplates.inc.php
+++ b/admin/setup/etemplates.inc.php
@@ -18,6 +18,10 @@ $templ_data[] = array('name' => 'admin.accesslog.get_rows','template' => '','lan
$templ_data[] = array('name' => 'admin.accesslog.rows','template' => '','lang' => '','group' => '0','version' => '1.7.002','data' => 'a:1:{i:0;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:3:{s:2:"c1";s:2:"th";s:2:"c2";s:3:"row";s:1:"H";s:2:"1%";}i:1;a:8:{s:1:"A";a:3:{s:4:"type";s:23:"nextmatch-accountfilter";s:4:"size";s:7:"LoginID";s:4:"name";s:10:"account_id";}s:1:"B";a:3:{s:4:"type";s:16:"nextmatch-header";s:5:"label";s:12:"Login-Status";s:4:"name";s:13:"sessionstatus";}s:1:"C";a:3:{s:4:"type";s:16:"nextmatch-header";s:5:"label";s:7:"Loginid";s:4:"name";s:7:"loginid";}s:1:"D";a:3:{s:4:"type";s:16:"nextmatch-header";s:5:"label";s:2:"IP";s:4:"name";s:2:"ip";}s:1:"E";a:3:{s:4:"type";s:20:"nextmatch-sortheader";s:5:"label";s:5:"Login";s:4:"name";s:2:"li";}s:1:"F";a:3:{s:4:"type";s:20:"nextmatch-sortheader";s:5:"label";s:6:"Logout";s:4:"name";s:2:"lo";}s:1:"G";a:3:{s:4:"type";s:16:"nextmatch-header";s:5:"label";s:5:"Total";s:4:"name";s:5:"total";}s:1:"H";a:4:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"2";i:1;a:3:{s:4:"type";s:5:"label";s:5:"label";s:6:"Action";s:5:"align";s:6:"center";}i:2;a:4:{s:4:"type";s:10:"buttononly";s:4:"size";s:5:"check";s:5:"label";s:10:"Select all";s:7:"onclick";s:61:"toggle_all(this.form,form::name(\'selected[]\')); return false;";}}}i:2;a:8:{s:1:"A";a:3:{s:4:"type";s:14:"select-account";s:4:"name";s:18:"${row}[account_id]";s:8:"readonly";s:1:"1";}s:1:"B";a:2:{s:4:"type";s:5:"label";s:4:"name";s:21:"${row}[sessionstatus]";}s:1:"C";a:2:{s:4:"type";s:5:"label";s:4:"name";s:15:"${row}[loginid]";}s:1:"D";a:2:{s:4:"type";s:5:"label";s:4:"name";s:10:"${row}[ip]";}s:1:"E";a:3:{s:4:"type";s:9:"date-time";s:4:"name";s:10:"${row}[li]";s:8:"readonly";s:1:"1";}s:1:"F";a:3:{s:4:"type";s:9:"date-time";s:4:"name";s:10:"${row}[lo]";s:8:"readonly";s:1:"1";}s:1:"G";a:4:{s:4:"type";s:13:"date-duration";s:4:"name";s:13:"${row}[total]";s:8:"readonly";s:1:"1";s:4:"size";s:6:",hm,24";}s:1:"H";a:5:{s:4:"type";s:4:"hbox";s:4:"size";s:6:"2,,0,0";i:1;a:6:{s:4:"type";s:6:"button";s:4:"size";s:6:"delete";s:5:"label";s:6:"Delete";s:4:"name";s:28:"delete[$row_cont[sessionid]]";s:4:"help";s:21:"Delete this log entry";s:7:"onclick";s:40:"return confirm(\'Delete this log entry\');";}i:2;a:3:{s:4:"type";s:8:"checkbox";s:4:"size";s:20:"$row_cont[sessionid]";s:4:"name";s:10:"selected[]";}s:5:"align";s:6:"center";}}}s:4:"rows";i:2;s:4:"cols";i:8;s:4:"size";s:4:"100%";s:7:"options";a:1:{i:0;s:4:"100%";}}}','size' => '100%','style' => '','modified' => '1254816462',);
+$templ_data[] = array('name' => 'admin.applications','template' => '','lang' => '','group' => '0','version' => '1.7.001','data' => 'a:1:{i:0;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:4:{s:4:"type";s:9:"nextmatch";s:4:"size";s:4:"rows";s:4:"span";s:3:"all";s:4:"name";s:2:"nm";}}i:2;a:1:{s:1:"A";a:4:{s:4:"type";s:4:"hbox";s:4:"size";s:1:"2";i:1;a:3:{s:4:"type";s:6:"button";s:5:"label";s:28:"Number applications serially";s:4:"name";s:6:"number";}i:2;a:2:{s:4:"type";s:5:"label";s:5:"label";s:157:"Number the applications serially. If they are not numbered serially, sorting the applications could work wrong. This will not change the application\'s order.";}}}}s:4:"rows";i:2;s:4:"cols";i:1;s:4:"size";s:7:"100%,,0";s:7:"options";a:2:{i:0;s:4:"100%";i:2;s:1:"0";}}}','size' => '100%,,0','style' => '','modified' => '1276610727',);
+
+$templ_data[] = array('name' => 'admin.applications.rows','template' => '','lang' => '','group' => '0','version' => '1.7.002','data' => 'a:1:{i:0;a:6:{s:4:"type";s:4:"grid";s:4:"data";a:3:{i:0;a:7:{s:2:"c1";s:2:"th";s:2:"c2";s:3:"row";s:1:"A";s:5:"1px,1";s:1:"E";s:4:"80px";s:1:"D";s:5:"120px";s:1:"F";s:2:"80";s:1:"B";s:5:"120px";}i:1;a:6:{s:1:"A";a:1:{s:4:"type";s:5:"label";}s:1:"B";a:2:{s:4:"type";s:5:"label";s:5:"align";s:6:"center";}s:1:"C";a:3:{s:4:"type";s:16:"nextmatch-header";s:5:"label";s:4:"Name";s:4:"name";s:4:"name";}s:1:"D";a:3:{s:4:"type";s:16:"nextmatch-header";s:5:"label";s:7:"Version";s:4:"name";s:7:"version";}s:1:"E";a:3:{s:4:"type";s:16:"nextmatch-header";s:5:"label";s:5:"Order";s:4:"name";s:5:"order";}s:1:"F";a:3:{s:4:"type";s:16:"nextmatch-header";s:5:"label";s:7:"Actions";s:4:"name";s:7:"actions";}}i:2;a:6:{s:1:"A";a:3:{s:4:"type";s:5:"image";s:5:"align";s:6:"center";s:4:"name";s:14:"${row}[app_id]";}s:1:"B";a:3:{s:4:"type";s:5:"image";s:4:"name";s:13:"${row}[image]";s:5:"align";s:6:"center";}s:1:"C";a:2:{s:4:"type";s:5:"label";s:4:"name";s:16:"${row}[app_name]";}s:1:"D";a:3:{s:4:"type";s:5:"label";s:4:"name";s:19:"${row}[app_version]";s:8:"readonly";s:1:"1";}s:1:"E";a:4:{s:4:"type";s:5:"label";s:4:"name";s:17:"${row}[app_order]";s:7:"no_lang";s:1:"1";s:8:"readonly";s:1:"1";}s:1:"F";a:5:{s:4:"type";s:4:"hbox";s:8:"readonly";s:1:"1";s:4:"size";s:1:"2";i:1;a:4:{s:4:"type";s:6:"button";s:4:"size";s:3:"up2";s:5:"label";s:2:"up";s:4:"name";s:21:"up[$row_cont[app_id]]";}i:2;a:4:{s:4:"type";s:6:"button";s:4:"size";s:5:"down2";s:5:"label";s:4:"down";s:4:"name";s:23:"down[$row_cont[app_id]]";}}}}s:4:"rows";i:2;s:4:"cols";i:6;s:4:"size";s:4:"100%";s:7:"options";a:1:{i:0;s:4:"100%";}}}','size' => '100%','style' => '','modified' => '1275405742',);
+
$templ_data[] = array('name' => 'admin.categories.delete','template' => '','lang' => '','group' => '0','version' => '1.7.001','data' => 'a:2:{i:0;a:4:{s:4:"type";s:4:"grid";s:4:"data";a:2:{i:0;a:0:{}i:1;a:1:{s:1:"A";a:5:{s:4:"type";s:8:"groupbox";s:4:"size";s:1:"1";s:5:"label";s:20:"Delete this category";i:1;a:5:{s:4:"type";s:4:"grid";s:4:"data";a:4:{i:0;a:3:{s:2:"h3";s:2:"40";s:2:"h1";s:2:"30";s:2:"c2";s:11:"confirmSubs";}i:1;a:2:{s:1:"A";a:4:{s:4:"type";s:5:"label";s:4:"span";s:3:"all";s:5:"label";s:47:"Are you sure you want to delete this category ?";s:5:"align";s:6:"center";}s:1:"B";a:1:{s:4:"type";s:5:"label";}}i:2;a:2:{s:1:"A";a:5:{s:4:"type";s:8:"checkbox";s:5:"label";s:53:"Do you also want to delete all global subcategories ?";s:4:"name";s:12:"delete[subs]";s:4:"span";s:3:"all";s:5:"align";s:6:"center";}s:1:"B";a:1:{s:4:"type";s:5:"label";}}i:3;a:2:{s:1:"A";a:4:{s:4:"type";s:6:"button";s:5:"label";s:6:"Delete";s:4:"name";s:14:"delete[delete]";s:5:"align";s:6:"center";}s:1:"B";a:5:{s:4:"type";s:10:"buttononly";s:5:"label";s:6:"Cancel";s:4:"name";s:14:"delete[cancel]";s:5:"align";s:6:"center";s:7:"onclick";s:64:"set_style_by_class(\'fieldset\',\'confirmDelete\',\'display\',\'none\');";}}}s:4:"rows";i:3;s:4:"cols";i:2;s:7:"options";a:0:{}}s:4:"span";s:14:",confirmDelete";}}}s:4:"rows";i:1;s:4:"cols";i:1;}i:1;a:3:{s:4:"type";s:4:"text";s:4:"name";s:14:"delete[cat_id]";s:4:"span";s:12:",hiddenCatid";}}','size' => '','style' => '.confirmDelete {
position: absolute;
left: 120px;