From adb748d6e704b44f200d25015e8f1a9da0519d22 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 2 Apr 2016 14:16:20 +0000 Subject: [PATCH] move hooks class to api and only cache hooks in instance cache (instead of database: filesystem scan takes ~4ms, cache ~0.2ms) --- admin/inc/class.admin_hooks.inc.php | 16 +- api/setup/setup.inc.php | 124 +++++++ api/src/Hooks.php | 265 ++++++++++++++ api/src/Mail/Hooks.php | 109 ++++++ .../src/Vfs/Hooks.php | 77 ++-- phpgwapi/inc/class.hooks.inc.php | 328 ++---------------- phpgwapi/setup/setup.inc.php | 31 +- phpgwapi/setup/tables_current.inc.php | 22 -- phpgwapi/setup/tables_update.inc.php | 24 ++ setup/inc/class.setup.inc.php | 33 +- setup/inc/class.setup_cmd_ldap.inc.php | 2 +- 11 files changed, 604 insertions(+), 427 deletions(-) create mode 100755 api/setup/setup.inc.php create mode 100644 api/src/Hooks.php create mode 100644 api/src/Mail/Hooks.php rename phpgwapi/inc/class.vfs_home_hooks.inc.php => api/src/Vfs/Hooks.php (68%) diff --git a/admin/inc/class.admin_hooks.inc.php b/admin/inc/class.admin_hooks.inc.php index cf9344003d..29bddb503f 100644 --- a/admin/inc/class.admin_hooks.inc.php +++ b/admin/inc/class.admin_hooks.inc.php @@ -20,10 +20,10 @@ class admin_hooks /** * Functions callable via menuaction * - * @var unknown_type + * @var array */ var $public_functions = array( - 'ajax_register_all_hooks' => True, + 'ajax_clear_cache' => True, ); /** @@ -118,7 +118,7 @@ class admin_hooks 'id' => 'admin/clear_cache', 'no_lang' => true, 'link' => "javascript:egw.message('".lang('Clear cache and register hooks') . "
" .lang('Please wait...')."','info'); " . - "egw.json('admin.admin_hooks.ajax_register_all_hooks').sendRequest(true);" + "egw.json('admin.admin_hooks.ajax_clear_cache').sendRequest(true);" ); } @@ -159,9 +159,9 @@ class admin_hooks } /** - * Register all hooks + * Clears instance cache (and thereby also refreshes hooks) */ - function ajax_register_all_hooks() + function ajax_clear_cache() { if ($GLOBALS['egw']->acl->check('applications_acc',16,'admin')) { @@ -169,8 +169,6 @@ class admin_hooks } Api\Cache::flush(Api\Cache::INSTANCE); - $GLOBALS['egw']->hooks->register_all_hooks(); - Api\Image::invalidate(); if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) // egw object in setup is limited @@ -178,9 +176,9 @@ class admin_hooks $GLOBALS['egw']->invalidate_session_cache(); // in case with cache the egw_info array in the session } // allow apps to hook into "Admin >> Clear cache and register hooks" - $GLOBALS['egw']->hooks->process('clear_cache', array(), true); + Api\Hooks::process('clear_cache', array(), true); - egw_json_response::get()->apply('egw.message', array(lang('Done'),'success')); + Api\Json\Response::get()->apply('egw.message', array(lang('Done'), 'success')); } /** diff --git a/api/setup/setup.inc.php b/api/setup/setup.inc.php new file mode 100755 index 0000000000..5113afe610 --- /dev/null +++ b/api/setup/setup.inc.php @@ -0,0 +1,124 @@ + 'EGroupware coreteam', + 'email' => 'egroupware-developers@lists.sourceforge.net', +); + +/* The tables this app creates +$setup_info['api']['tables'][] = 'egw_config'; +$setup_info['api']['tables'][] = 'egw_applications'; +$setup_info['api']['tables'][] = 'egw_acl'; +$setup_info['api']['tables'][] = 'egw_accounts'; +$setup_info['api']['tables'][] = 'egw_preferences'; +$setup_info['api']['tables'][] = 'egw_access_log'; +$setup_info['api']['tables'][] = 'egw_languages'; +$setup_info['api']['tables'][] = 'egw_lang'; +$setup_info['api']['tables'][] = 'egw_nextid'; +$setup_info['api']['tables'][] = 'egw_categories'; +$setup_info['api']['tables'][] = 'egw_history_log'; +$setup_info['api']['tables'][] = 'egw_async'; +$setup_info['api']['tables'][] = 'egw_links'; +$setup_info['api']['tables'][] = 'egw_addressbook'; +$setup_info['api']['tables'][] = 'egw_addressbook_extra'; +$setup_info['api']['tables'][] = 'egw_addressbook_lists'; +$setup_info['api']['tables'][] = 'egw_addressbook2list'; +$setup_info['api']['tables'][] = 'egw_sqlfs'; +$setup_info['api']['tables'][] = 'egw_locks'; +$setup_info['api']['tables'][] = 'egw_sqlfs_props'; +$setup_info['api']['tables'][] = 'egw_customfields'; +$setup_info['api']['tables'][] = 'egw_sharing'; +*/ +// hooks used by vfs_home_hooks to manage user- and group-directories for the new stream based VFS +$setup_info['api']['hooks']['addaccount'] = array('EGroupware\\Api\\Vfs\\Hooks::addAccount', 'EGroupware\\Api\\Mail\\Hooks::addaccount'); +$setup_info['api']['hooks']['deleteaccount'] = array('EGroupware\\Api\\Vfs\\Hooks::deleteAccount', 'EGroupware\\Api\\Mail\\Hooks::deleteaccount'); +$setup_info['api']['hooks']['editaccount'] = array('EGroupware\\Api\\Vfs\\Hooks::editAccount', 'EGroupware\\Api\\Mail\\Hooks::addaccount'); +$setup_info['api']['hooks']['addgroup'] = 'EGroupware\\Api\\Vfs\\Hooks::addGroup'; +$setup_info['api']['hooks']['deletegroup'] = array('EGroupware\\Api\\Vfs\\Hooks::deleteGroup', 'EGroupware\\Api\\Mail\\Hooks::deletegroup'); +$setup_info['api']['hooks']['editgroup'] = 'EGroupware\\Api\\Vfs\\Hooks::editGroup'; +$setup_info['api']['hooks']['changepassword'] = 'EGroupware\\Api\\Mail\\Hooks::changepassword'; + +// installation checks +$setup_info['api']['check_install'] = array( + '' => array( + 'func' => 'pear_check', + 'from' => 'EMailAdmin', + ), + 'pear.horde.org/Horde_Imap_Client' => array( + 'func' => 'pear_check', + 'from' => 'EMailAdmin', + 'version' => '2.24.2', + ), + 'pear.horde.org/Horde_Nls' => array( + 'func' => 'pear_check', + 'from' => 'EMailAdmin', + 'version' => '2.0.3', + ), + 'pear.horde.org/Horde_Mail' => array( + 'func' => 'pear_check', + 'from' => 'EMailAdmin', + 'version' => '2.1.2', + ), + 'pear.horde.org/Horde_Smtp' => array( + 'func' => 'pear_check', + 'from' => 'EMailAdmin', + 'version' => '1.3.0', + ), + 'pear.horde.org/Horde_ManageSieve' => array( + 'func' => 'pear_check', + 'from' => 'EMailAdmin', + 'version' => '1.0.1', + ), + // next 4 are required for TNEF support + 'pear.horde.org/Horde_Compress' => array( + 'func' => 'pear_check', + 'from' => 'EMailAdmin', + 'version' => '2.0.8', + ), + 'pear.horde.org/Horde_Icalendar' => array( + 'func' => 'pear_check', + 'from' => 'EMailAdmin', + 'version' => '2.0.0', + ), + 'pear.horde.org/Horde_Mapi' => array( + 'func' => 'pear_check', + 'from' => 'EMailAdmin', + 'version' => '1.0.0', + ), + 'bcmath' => array( + 'func' => 'extension_check', + 'from' => 'EMailAdmin', + ), +); + +// CalDAV / CardDAV Sync +$setup_info['groupdav']['name'] = 'groupdav'; +$setup_info['groupdav']['version'] = '14.1'; +$setup_info['groupdav']['enable'] = 2; +$setup_info['groupdav']['app_order'] = 1; +$setup_info['groupdav']['icon'] = 'groupdav'; +$setup_info['groupdav']['icon_app'] = 'api'; +$setup_info['groupdav']['author'] = $setup_info['groupdav']['maintainer'] = array( + 'name' => 'Ralf Becker', + 'email' => 'RalfBecker@outdoor-training.de' +); +$setup_info['groupdav']['license'] = 'GPL'; +$setup_info['groupdav']['hooks']['preferences'] = 'EGroupware\\Api\\CalDAV\\Hooks::menus'; +$setup_info['groupdav']['hooks']['settings'] = 'EGroupware\\Api\\CalDAV\\Hooks::settings'; diff --git a/api/src/Hooks.php b/api/src/Hooks.php new file mode 100644 index 0000000000..b30d9618a3 --- /dev/null +++ b/api/src/Hooks.php @@ -0,0 +1,265 @@ + + * @author Ralf Becker + * Copyright (C) 2000, 2001 Dan Kuykendall + * New method hooks and docu are written by + * @license http://opensource.org/licenses/lgpl-license.php LGPL - GNU Lesser General Public License + * @package api + * @version $Id$ + */ + +namespace EGroupware\Api; + +// explicitly import old not namespaced api classes +use applications; + +/** + * Allow applications to set and use hooks to communicate with each other + * + * Hooks need to be declared in the app's setup.inc.php file and + * are cached in instance cache for 1h. + * + * Clearing instance cache or calling Api\Hooks::read(true) forces a new scan. + * + * Hooks can have one of the following formats: + * - static class method hooks are declared as: + * $setup_info['appname']['hooks']['location'] = 'class::method'; + * - method hooks, which are methods of a class. You can pass parameters to the call and + * they can return values. They get declared in setup.inc.php as: + * $setup_info['appname']['hooks']['location'] = 'app.class.method'; + * - old type, which are included files. Values can only be passed by global values and they cant return anything. + * Old declaration in setup.inc.php: + * $setup_info['appname']['hooks'][] = 'location'; + */ +class Hooks +{ + /** + * Hooks by location and appname + * + * @var array $location => $app => array($file, ...) + */ + protected static $locations; + + /** + * Executes all the hooks (the user has rights to) for a given location + * + * If no $order given, hooks are executed in the order of the applications! + * + * @param string|array $args location-name as string or array with keys location and + * further data to be passed to the hook, if its a new method-hook + * @param string|array $order appname(s as value), which should be executes first + * @param boolean $no_permission_check if True execute all hooks, not only the ones a user has rights to + * $no_permission_check should *ONLY* be used when it *HAS* to be. (jengo) + * @return array with results of each hook call (with appname as key) and value: + * - False if no hook exists (should no longer be the case), + * - True if old hook exists and + * - array of return-values, if an app implements more then one hook + * - whatever the new method-hook returns (can be True or False too!) + */ + public static function process($args, $order = array(), $no_permission_check = False) + { + //echo "

".__METHOD__.'('.array2string($args).','.array2string($order).','.array2string($no_permission_check).")

\n"; + $location = is_array($args) ? (isset($args['hook_location']) ? $args['hook_location'] : $args['location']) : $args; + + if (!isset(self::$locations)) self::read(); + $hooks = self::$locations[$location]; + if (!isset($hooks) || empty($hooks)) return array(); // not a single app implements that hook + + $apps = array_keys($hooks); + if (!$no_permission_check) + { + // on install of a new egroupware both hook-apps and user apps may be empty/not set + $apps = array_intersect((array)$apps,array_keys((array)$GLOBALS['egw_info']['user']['apps'])); + } + if ($order) + { + $apps = array_unique(array_merge((array)$order,$apps)); + } + $results = array(); + foreach((array)$apps as $appname) + { + $results[$appname] = self::single($args,$appname,$no_permission_check); + } + return $results; + } + + /** + * Executes a single hook of a given location and application + * + * @param string|array $args location-name as string or array with keys location, appname and + * further data to be passed to the hook, if its a new method-hook + * @param string $appname name of the app, which's hook to execute, if empty the current app is used + * @param boolean $no_permission_check =false if True execute all hooks, not only the ones a user has rights to + * $no_permission_check should *ONLY* be used when it *HAS* to be. (jengo) + * @param boolean $try_unregistered =false If true, try to include old file-hook anyway (for setup) + * @return mixed False if no hook exists, True if old hook exists and whatever the new method-hook returns (can be True or False too!). + */ + public static function single($args, $appname = '', $no_permission_check = False)//,$try_unregistered = False) + { + //error_log(__METHOD__."(".array2string($args).",'$appname','$no_permission_check','$try_unregistered')"); + + if (!isset(self::$locations)) self::read(); + + if (!is_array($args)) $args = array('location' => $args); + $location = isset($args['hook_location']) ? $args['hook_location'] : $args['location']; + + if (!$appname) + { + $appname = is_array($args) && isset($args['appname']) ? $args['appname'] : $GLOBALS['egw_info']['flags']['currentapp']; + } + // excute hook only if $no_permission_check or user has run-rights for app + if (!($no_permission_check || isset($GLOBALS['egw_info']['user']['apps'][$appname]))) + { + return false; + } + + $ret = array(); + foreach((array)self::$locations[$location][$appname] as $hook) + { + try { + // old style file hook + if ($hook[0] == '/') + { + if (!file_exists(EGW_SERVER_ROOT.$hook)) + { + error_log(__METHOD__."() old style hook file '$hook' not found --> ignored!"); + continue; + } + include(EGW_SERVER_ROOT.$hook); + return true; + } + + list($class, $method) = explode('::', $hook); + + // static method of an autoloadable class + if (isset($method) && class_exists($class)) + { + if (is_callable($hook)) $ret[] = call_user_func($hook, $args); + } + // app.class.method or not autoloadable class + else + { + $ret[] = ExecMethod2($hook, $args); + } + } + catch (\Exception $e) { + _egw_log_exception($e); + } + } + + if (!$ret) return false; + + return count($ret) == 1 ? $ret[0] : $ret; + } + + /** + * loop through the applications and count the apps implementing a hooks + * + * @param string $location location-name + * @return int the number of found hooks + */ + function count($location) + { + if (!isset(self::$locations)) self::read(); + + return count(self::$locations[$location]); + } + + /** + * check if a given hook for an application is registered + * + * @param string $location location-name + * @param string $app appname + * @param boolean $return_methods =false true: return hook-method(s) + * @return int|array the number of found hooks or for $return_methods array with methods + */ + public static function exists($location, $app, $return_methods=false) + { + if (!isset(self::$locations)) self::read(); + + //error_log(__METHOD__.__LINE__.array2string(self::$locations[$location])); + return $return_methods ? self::$locations[$location][$app] : count(self::$locations[$location][$app]); + } + + /** + * check which apps implement a given hook + * + * @param string $location location-name + * @return array of apps implementing given hook + */ + public static function implemented($location) + { + if (!isset(self::$locations)) self::read(); + + //error_log(__METHOD__.__LINE__.array2string(self::$locations[$location])); + return isset(self::$locations[$location]) ? array_keys(self::$locations[$location]) : array(); + } + + /** + * Read all hooks into self::$locations + * + * @param boolan $force_rescan =false true: do not use instance cache + */ + protected static function read($force_rescan=false) + { + //$starttime = microtime(true); + if ($force_rescan) Cache::unsetInstance(__CLASS__, 'locations'); + + self::$locations = Cache::getInstance(__CLASS__, 'locations', function() + { + // if we run in setup, we need to read installed apps first + if (!$GLOBALS['egw_info']['apps']) + { + $applications = new applications(); + $applications->read_installed_apps(); + } + + // read all apps using just filesystem data + $locations = array(); + foreach(array_merge(array('api'), array_keys($GLOBALS['egw_info']['apps'])) as $appname) + { + if ($appname[0] == '.' || !is_dir(EGW_SERVER_ROOT.'/'.$appname)) continue; + + $f = EGW_SERVER_ROOT . '/' . $appname . '/setup/setup.inc.php'; + $setup_info = array($appname => array()); + if(@file_exists($f)) include($f); + + // some apps have setup_info for more then themselfs (eg. api for groupdav) + foreach($setup_info as $appname => $data) + { + foreach((array)$data['hooks'] as $location => $methods) + { + if (is_int($location)) + { + $location = $methods; + $methods = '/'.$appname.'/inc/hook_'.$methods.'.inc.php'; + } + $locations[$location][$appname] = (array)$methods; + } + } + } + return $locations; + }, array(), 3600); + + //error_log(__METHOD__."() took ".number_format(1000*(microtime(true)-$starttime), 1)."ms, size=".Vfs::hsize(strlen(json_encode(self::$locations)))); + } + + /** + * Static function to build pgp encryption sidebox menu + * @param type $appname application name + */ + public static function pgp_encryption_menu($appname) + { + if (Header\UserAgent::mobile()) return; + + // PGP Encryption (Mailvelope plugin) restore/backup menu + $file = Array( + 'Backup/Restore ...' => 'javascript:app.'.$appname.'.mailvelopeCreateBackupRestoreDialog();', + ); + display_sidebox($appname, lang('PGP Encryption'), $file); + } +} diff --git a/api/src/Mail/Hooks.php b/api/src/Mail/Hooks.php new file mode 100644 index 0000000000..cb651eb5a2 --- /dev/null +++ b/api/src/Mail/Hooks.php @@ -0,0 +1,109 @@ + + * @author Ralf Becker + * @copyright (c) 2008-16 by leithoff-At-stylite.de + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @version $Id$ + */ + +namespace EGroupware\Api\Mail; + +/** + * diverse static Mail hooks + */ +class Hooks +{ + /** + * Password changed hook --> unset cached objects, as password might be used for email connection + * + * @param array $hook_data + */ + public static function changepassword($hook_data) + { + if (!empty($hook_data['old_passwd'])) + { + Credentials::changepassword($hook_data); + } + } + + /** + * Hook called before an account get deleted + * + * @param array $data + * @param int $data['account_id'] numerical id + * @param string $data['account_lid'] account-name + * @param int $data['new_owner'] account-id of new owner, or false if data should get deleted + */ + static function deleteaccount(array $data) + { + self::run_plugin_hooks('deleteAccount', $data); + + // as mail accounts contain credentials, we do NOT assign them to user users + Account::delete(0, $data['account_id']); + } + + /** + * Hook called before a group get deleted + * + * @param array $data + * @param int $data['account_id'] numerical id + * @param string $data['account_name'] account-name + */ + static function deletegroup(array $data) + { + Account::delete(0, $data['account_id']); + } + + /** + * Hook called when an account get added or edited + * + * @param array $data + * @param int $data['account_id'] numerical id + * @param string $data['account_lid'] account-name + * @param string $data['account_email'] email + */ + static function addaccount(array $data) + { + $method = $data['location'] == 'addaccount' ? 'addAccount' : 'updateAccount'; + self::run_plugin_hooks($method, $data); + } + + /** + * Run hook on plugins of all mail-accounts of given account_id + * + * @param string $method plugin method to run + * @param array $data hook-data incl. value for key account_id + */ + protected static function run_plugin_hooks($method, array $data) + { + foreach(Account::search((int)$data['account_id'], 'params') as $params) + { + if (!Account::is_multiple($params)) continue; // no need to waste time on personal accounts + + try { + $account = new Account($params); + if ($account->acc_smtp_type != 'emailadmin_smtp' && ($smtp = $account->smtpServer(true)) && + is_a($smtp, 'emailadmin_smtp') && get_class($smtp) != 'emailadmin_smtp') + { + $smtp->$method($data); + } + if ($account->acc_imap_type != 'emailadmin_imap' && $account->acc_imap_admin_username && + $account->acc_imap_admin_password && ($imap = $account->imapServer(true)) && + is_a($imap, 'emailadmin_imap') && get_class($imap) != 'emailadmin_imap') + { + $imap->$method($data); + } + } + catch(\Exception $e) { + _egw_log_exception($e); + // ignore exception, without stalling other hooks + } + } + } +} diff --git a/phpgwapi/inc/class.vfs_home_hooks.inc.php b/api/src/Vfs/Hooks.php similarity index 68% rename from phpgwapi/inc/class.vfs_home_hooks.inc.php rename to api/src/Vfs/Hooks.php index 184125376f..44406aca13 100644 --- a/phpgwapi/inc/class.vfs_home_hooks.inc.php +++ b/api/src/Vfs/Hooks.php @@ -1,18 +1,22 @@ - * @copyright (c) 2008-9 by Ralf Becker + * @copyright (c) 2008-16 by Ralf Becker * @version $Id$ */ +namespace EGroupware\Api\Vfs; + +use EGroupware\Api; + /** - * eGroupWare API: VFS - Hooks to add/rename/delete user and group home-directories + * VFS - Hooks to add/rename/delete user and group home-directories * * This class implements the creation, renaming or deletion of home-dirs via some hooks from admin: * - create the homedir if a new user gets created @@ -20,7 +24,7 @@ * - delete the homedir or copy its content to an other users homedir, if a user gets deleted * --> these hooks are registered via phpgwapi/setup/setup.inc.php and called by the admin app */ -class vfs_home_hooks +class Hooks { /** * Should we log our calls to the error_log @@ -40,14 +44,14 @@ class vfs_home_hooks { if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($data).')'); // create a user-dir - egw_vfs::$is_root = true; - if (egw_vfs::mkdir($dir='/home/'.$data['account_lid'],0700,0)) + Api\Vfs::$is_root = true; + if (Api\Vfs::mkdir($dir='/home/'.$data['account_lid'],0700,0)) { - egw_vfs::chown($dir,$data['account_id']); - egw_vfs::chgrp($dir,0); - egw_vfs::chmod($dir,0700); // only user has access + Api\Vfs::chown($dir,$data['account_id']); + Api\Vfs::chgrp($dir,0); + Api\Vfs::chmod($dir,0700); // only user has access } - egw_vfs::$is_root = false; + Api\Vfs::$is_root = false; } /** @@ -66,9 +70,9 @@ class vfs_home_hooks return; // nothing to do here } // rename the user-dir - egw_vfs::$is_root = true; - egw_vfs::rename('/home/'.$data['old_loginid'],'/home/'.$data['account_lid']); - egw_vfs::$is_root = false; + Api\Vfs::$is_root = true; + Api\Vfs::rename('/home/'.$data['old_loginid'],'/home/'.$data['account_lid']); + Api\Vfs::$is_root = false; } /** @@ -82,25 +86,28 @@ class vfs_home_hooks static function deleteAccount($data) { if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($data).')'); - egw_vfs::$is_root = true; + Api\Vfs::$is_root = true; if ($data['new_owner'] && ($new_lid = $GLOBALS['egw']->accounts->id2name($data['new_owner']))) { // copy content of user-dir to new owner's user-dir as old-home-$name - for ($i=''; file_exists(egw_vfs::PREFIX.($new_dir = '/home/'.$new_lid.'/old-home-'.$data['account_lid'].$i)); $i++); - egw_vfs::rename('/home/'.$data['account_lid'],$new_dir); + for ($i=''; file_exists(Api\Vfs::PREFIX.($new_dir = '/home/'.$new_lid.'/old-home-'.$data['account_lid'].$i)); $i++) + { + + } + Api\Vfs::rename('/home/'.$data['account_lid'],$new_dir); // make the new owner the owner of the dir and it's content - egw_vfs::find($new_dir,array(),array('egw_vfs','chown'),$data['new_owner']); + Api\Vfs::find($new_dir,array(),array('egw_vfs','chown'),$data['new_owner']); } elseif(!empty($data['account_lid']) && $data['account_lid'] != '/') { // delete the user-directory - egw_vfs::remove('/home/'.$data['account_lid']); + Api\Vfs::remove('/home/'.$data['account_lid']); } else { - throw new egw_exception_assertion_failed(__METHOD__.'('.array2string($data).') account_lid NOT set!'); + throw new Api\Exception\AssertionFailed(__METHOD__.'('.array2string($data).') account_lid NOT set!'); } - egw_vfs::$is_root = false; + Api\Vfs::$is_root = false; } /** @@ -113,17 +120,17 @@ class vfs_home_hooks static function addGroup($data) { if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($data).')'); - if (empty($data['account_lid'])) throw new egw_exception_wrong_parameter('account_lid must not be empty!'); + if (empty($data['account_lid'])) throw new Api\Exception\WrongParameter('account_lid must not be empty!'); // create a group-dir - egw_vfs::$is_root = true; - if (egw_vfs::mkdir($dir='/home/'.$data['account_lid'],0070,0)) + Api\Vfs::$is_root = true; + if (Api\Vfs::mkdir($dir='/home/'.$data['account_lid'],0070,0)) { - egw_vfs::chown($dir,0); - egw_vfs::chgrp($dir,abs($data['account_id'])); // gid in Vfs is positiv! - egw_vfs::chmod($dir,0070); // only group has access + Api\Vfs::chown($dir,0); + Api\Vfs::chgrp($dir,abs($data['account_id'])); // gid in Vfs is positiv! + Api\Vfs::chmod($dir,0070); // only group has access } - egw_vfs::$is_root = false; + Api\Vfs::$is_root = false; } /** @@ -145,7 +152,7 @@ class vfs_home_hooks if ($data['account_lid'] == $data['old_name']) { // check if group directory exists and create it if not (by calling addGroup hook) - if (!egw_vfs::stat('/home/'.$data['account_lid'])) + if (!Api\Vfs::stat('/home/'.$data['account_lid'])) { self::addGroup($data); } @@ -153,9 +160,9 @@ class vfs_home_hooks else { // rename the group-dir - egw_vfs::$is_root = true; - egw_vfs::rename('/home/'.$data['old_name'],'/home/'.$data['account_lid']); - egw_vfs::$is_root = false; + Api\Vfs::$is_root = true; + Api\Vfs::rename('/home/'.$data['old_name'],'/home/'.$data['account_lid']); + Api\Vfs::$is_root = false; } } @@ -172,11 +179,11 @@ class vfs_home_hooks if(empty($data['account_lid']) || $data['account_lid'] == '/') { - throw new egw_exception_assertion_failed(__METHOD__.'('.array2string($data).') account_lid NOT set!'); + throw new Api\Exception\AssertionFailed(__METHOD__.'('.array2string($data).') account_lid NOT set!'); } // delete the group-directory - egw_vfs::$is_root = true; - egw_vfs::remove('/home/'.$data['account_lid']); - egw_vfs::$is_root = false; + Api\Vfs::$is_root = true; + Api\Vfs::remove('/home/'.$data['account_lid']); + Api\Vfs::$is_root = false; } } \ No newline at end of file diff --git a/phpgwapi/inc/class.hooks.inc.php b/phpgwapi/inc/class.hooks.inc.php index 70d311a5d6..080fbea12d 100644 --- a/phpgwapi/inc/class.hooks.inc.php +++ b/phpgwapi/inc/class.hooks.inc.php @@ -12,199 +12,38 @@ * @version $Id$ */ +use EGroupware\Api; + /** * class which gives ability for applications to set and use hooks to communicate with each other * - * Hooks need to be declared in the app's setup.inc.php file and they have to be registered - * (copied into the database) by - * - installing or updating the app via setup or - * - running Admin >> register all hooks - * As the hooks-class can get cached in the session (session-type PHP_RESTORE), you also have to log - * out and in again, that your changes take effect. - * - * Hooks can have one of the following formats: - * - static class method hooks are declared as: - * $setup_info['appname']['hooks']['location'] = 'class::method'; - * - method hooks, which are methods of a class. You can pass parameters to the call and - * they can return values. They get declared in setup.inc.php as: - * $setup_info['appname']['hooks']['location'] = 'app.class.method'; - * - old type, which are included files. Values can only be passed by global values and they cant return anything. - * Old declaration in setup.inc.php: - * $setup_info['appname']['hooks'][] = 'location'; + * @deprecated use static methods of Api\Hooks::process() and Api\Hooks::single */ -class hooks +class hooks extends Api\Hooks { - /** - * Reference to the global db object - * - * @var egw_db - */ - var $db; - var $table = 'egw_hooks'; - /** - * Hooks by location and appname - * - * @var array $location => $app => $file - */ - var $locations; - - /** - * constructor, reads and caches the complete hooks table - * - * @param egw_db $db =null database class, if null we use $GLOBALS['egw']->db - */ - function __construct($db=null) - { - $this->db = $db ? $db : $GLOBALS['egw']->db; // this is to allow setup to set the db - - // sort hooks by app-order - foreach($this->db->select($this->table,'hook_appname,hook_location,hook_filename',false,__LINE__,__FILE__,false,'ORDER BY app_order','phpgwapi',0,'JOIN egw_applications ON hook_appname=app_name') as $row) - { - $this->locations[$row['hook_location']][$row['hook_appname']] = $row['hook_filename']; - } - //_debug_array($this->locations); - } - - /** - * Executes all the hooks (the user has rights to) for a given location - * - * If no $order given, hooks are executed in the order of the applications! - * - * @param string|array $args location-name as string or array with keys location and - * further data to be passed to the hook, if its a new method-hook - * @param string|array $order appname(s as value), which should be executes first - * @param boolean $no_permission_check if True execute all hooks, not only the ones a user has rights to - * $no_permission_check should *ONLY* be used when it *HAS* to be. (jengo) - * @return array with results of each hook call (with appname as key) and value: - * - False if no hook exists (should no longer be the case), - * - True if old hook exists and - * - whatever the new method-hook returns (can be True or False too!). - */ - function process($args, $order = array(), $no_permission_check = False) - { - //echo "

".__METHOD__.'('.array2string($args).','.array2string($order).','.array2string($no_permission_check).")

\n"; - $location = is_array($args) ? (isset($args['hook_location']) ? $args['hook_location'] : $args['location']) : $args; - - $hooks = $this->locations[$location]; - if (!isset($hooks) || empty($hooks)) return array(); // not a single app implements that hook - - $apps = array_keys($hooks); - if (!$no_permission_check) - { - // on install of a new egroupware both hook-apps and user apps may be empty/not set - $apps = array_intersect((array)$apps,array_keys((array)$GLOBALS['egw_info']['user']['apps'])); - } - if ($order) - { - $apps = array_unique(array_merge((array)$order,$apps)); - } - $results = array(); - foreach((array)$apps as $appname) - { - $results[$appname] = $this->single($args,$appname,$no_permission_check); - } - return $results; - } - - /** - * executes a single hook of a given location and application - * - * @param string|array $args location-name as string or array with keys location, appname and - * further data to be passed to the hook, if its a new method-hook - * @param string $appname name of the app, which's hook to execute, if empty the current app is used - * @param boolean $no_permission_check =false if True execute all hooks, not only the ones a user has rights to - * $no_permission_check should *ONLY* be used when it *HAS* to be. (jengo) - * @param boolean $try_unregistered =false If true, try to include old file-hook anyway (for setup) - * @return mixed False if no hook exists, True if old hook exists and whatever the new method-hook returns (can be True or False too!). - */ - function single($args, $appname = '', $no_permission_check = False,$try_unregistered = False) - { - //echo "

hooks::single(".array2string($args).",'$appname','$no_permission_check','$try_unregistered')

\n"; - if (!is_array($args)) $args = array('location' => $args); - $location = isset($args['hook_location']) ? $args['hook_location'] : $args['location']; - - if (!$appname) - { - $appname = is_array($args) && isset($args['appname']) ? $args['appname'] : $GLOBALS['egw_info']['flags']['currentapp']; - } - // excute hook only if $no_permission_check or user has run-rights for app - if (!($no_permission_check || isset($GLOBALS['egw_info']['user']['apps'][$appname]))) - { - return false; - } - $SEP = filesystem_separator(); - - /* First include the ordered apps hook file */ - if (isset($this->locations[$location][$appname]) || $try_unregistered) - { - $parts = explode('.',$method = $this->locations[$location][$appname]); - - if (strpos($method,'::') !== false || count($parts) == 3 && $parts[1] != 'inc' && $parts[2] != 'php') - { - // new style hook with method string or static method (eg. 'class::method') - try - { - return ExecMethod($method,$args); - } - catch(egw_exception_assertion_failed $e) - { - if (substr($e->getMessage(),-19) == '.inc.php not found!') - { - return false; // fail gracefully if hook class-file does not exists (like the old hooks do, eg. if app got removed) - } - throw $e; - } - } - // old style hook, with an include file - if ($try_unregistered && empty($method)) - { - $method = 'hook_'.$location.'.inc.php'; - } - $f = EGW_SERVER_ROOT . $SEP . $appname . $SEP . 'inc' . $SEP . $method; - if (file_exists($f) && - ( $GLOBALS['egw_info']['user']['apps'][$appname] || (($no_permission_check || $location == 'config' || $appname == 'phpgwapi') && $appname)) ) - { - include($f); - return True; - } - } - return False; - } - - /** - * loop through the applications and count the hooks - * - * @param string $location location-name - * @return int the number of found hooks - */ - function count($location) - { - return count($this->locations[$location]); - } - /** * check if a given hook for an application is registered * * @param string $location location-name * @param string $app appname + * @deprecated use exists($location, $app) * @return int the number of found hooks */ - function hook_exists($location, $app) + public static function hook_exists($location, $app) { - //error_log(__METHOD__.__LINE__.array2string($this->locations[$location])); - return count($this->locations[$location][$app]); + return self::exists($location, $app); } /** * check which apps implement a given hook * * @param string $location location-name + * @deprecated use implemented($location) * @return array of apps implementing given hook */ - function hook_implemented($location) + public static function hook_implemented($location) { - //error_log(__METHOD__.__LINE__.array2string($this->locations[$location])); - return isset($this->locations[$location]) ? array_keys($this->locations[$location]) : array(); + return self::implemented($location); } /** @@ -214,42 +53,16 @@ class hooks * * @param string $appname Application 'name' * @param array $hooks =null hooks to register, eg $setup_info[$app]['hooks'] or not used for only deregister the hooks + * @deprecated use Api\Hooks::read(true) to force rescan of hooks * @return boolean|int false on error, true if new hooks are supplied and registed or number of removed hooks */ - function register_hooks($appname,$hooks=null) + public static function register_hooks($appname,$hooks=null) { - if(!$appname) - { - return False; - } - $this->db->delete($this->table,array('hook_appname' => $appname),__LINE__,__FILE__); + unset($appname, $hooks); - if (!is_array($hooks) || !count($hooks)) // only deregister - { - return $this->db->affected_rows(); - } - //echo "

ADDING hooks for: $appname

"; - foreach($hooks as $key => $hook) - { - if (!is_numeric($key)) // new method-hook - { - $location = $key; - $filename = $hook; - } - else - { - $location = $hook; - $filename = "hook_$hook.inc.php"; - } - $this->db->insert($this->table,array( - 'hook_filename' => $filename, - ),array( - 'hook_appname' => $appname, - 'hook_location' => $location, - ),__LINE__,__FILE__); - $this->locations[$location][$appname] = $filename; - } - return True; + self::read(true); + + return true; } /** @@ -259,115 +72,24 @@ class hooks * * @param string $appname Application 'name' * @param string $location is required, the hook itself + * @deprecated use Api\Hooks::read(true) to force rescan of hooks * @return boolean|int false on error, true if new hooks are supplied and registed or number of removed hooks */ - function register_single_app_hook($appname, $location) + public static function register_single_app_hook($appname, $location) { - if(!$appname || empty($location)) - { - return False; - } - $SEP = filesystem_separator(); - // now register the rest again - $f = EGW_SERVER_ROOT . $SEP . $appname . $SEP . 'setup' . $SEP . 'setup.inc.php'; - $setup_info = array($appname => array()); - if(@file_exists($f)) include($f); - // some apps have setup_info for more then themselfs (eg. phpgwapi for groupdav) - $hdata = array(); - foreach($setup_info as $appname => $data) - { - if ($data['hooks']) - { - if ($hdata[$appname]) - { - $hdata[$appname]['hooks'] = array_merge($hdata[$appname]['hooks'],$data['hooks']); - } - else - { - $hdata[$appname]['hooks'] = $data['hooks']; - } - } - } - //error_log(__METHOD__.__LINE__.array2string($hdata)); - foreach((array)$hdata as $appname => $data) - { - if (array_key_exists($location,$data['hooks'])) $method = $data['hooks'][$location]; - } - if (!empty($method)) - { - //echo "

ADDING hooks for: $appname

"; - $this->db->insert($this->table,array( - 'hook_appname' => $appname, - 'hook_filename' => $method, - 'hook_location' => $location, - ),array( - 'hook_appname' => $appname, - 'hook_location' => $location, - ),__LINE__,__FILE__); - $this->locations[$location][$appname] = $method; - return True; - } - return false; + self::read(true); + + return !!self::exists($location, $appname); } /** * Register the hooks of all applications (used by admin) + * + * @deprecated use Api\Hooks::read(true) to force rescan of hooks */ - function register_all_hooks() + public static function register_all_hooks() { - // deleting hooks, to get ride of no longer existing apps - $this->db->delete($this->table,'1=1',__LINE__,__FILE__); - - // if we run in setup, we need to read installed apps first - if (!$GLOBALS['egw_info']['apps']) - { - $applications = new applications(); - $applications->read_installed_apps(); - } - - // now register all apps using just filesystem data - foreach(array_keys($GLOBALS['egw_info']['apps']) as $appname) - { - if ($appname[0] == '.' || !is_dir(EGW_SERVER_ROOT.'/'.$appname)) continue; - - $f = EGW_SERVER_ROOT . '/' . $appname . '/setup/setup.inc.php'; - $setup_info = array($appname => array()); - if(@file_exists($f)) include($f); - // some apps have setup_info for more then themselfs (eg. phpgwapi for groupdav) - $hdata = array(); - foreach($setup_info as $appname => $data) - { - if ($data['hooks']) - { - if ($hdata[$appname]) - { - $hdata[$appname]['hooks'] = array_merge($hdata[$appname]['hooks'],$data['hooks']); - } - else - { - $hdata[$appname]['hooks'] = $data['hooks']; - } - } - } - foreach((array)$hdata as $appname => $data) - { - if ($data['hooks']) $this->register_hooks($appname,$data['hooks']); - } - } - } - - /** - * Static function to build pgp encryption sidebox menu - * @param type $appname application name - */ - public static function pgp_encryption_menu($appname) - { - if (html::$ua_mobile) return; - // PGP Encryption (Mailvelope plugin) restore/backup menu - $file = Array( - 'Backup/Restore ...' => 'javascript:app.'.$appname.'.mailvelopeCreateBackupRestoreDialog();', - ); - display_sidebox($appname, lang('PGP Encryption'), $file); + self::read(true); } /** diff --git a/phpgwapi/setup/setup.inc.php b/phpgwapi/setup/setup.inc.php index cb108f9f94..dfbd49b60e 100755 --- a/phpgwapi/setup/setup.inc.php +++ b/phpgwapi/setup/setup.inc.php @@ -11,8 +11,8 @@ /* Basic information about this app */ $setup_info['phpgwapi']['name'] = 'phpgwapi'; -$setup_info['phpgwapi']['title'] = 'EGroupware API'; -$setup_info['phpgwapi']['version'] = '14.3.906'; +$setup_info['phpgwapi']['title'] = 'EGroupware old API'; +$setup_info['phpgwapi']['version'] = '14.3.908'; $setup_info['phpgwapi']['versions']['current_header'] = '1.29'; $setup_info['phpgwapi']['enable'] = 3; $setup_info['phpgwapi']['app_order'] = 1; @@ -29,10 +29,8 @@ $setup_info['phpgwapi']['tables'][] = 'egw_acl'; $setup_info['phpgwapi']['tables'][] = 'egw_accounts'; $setup_info['phpgwapi']['tables'][] = 'egw_preferences'; $setup_info['phpgwapi']['tables'][] = 'egw_access_log'; -$setup_info['phpgwapi']['tables'][] = 'egw_hooks'; $setup_info['phpgwapi']['tables'][] = 'egw_languages'; $setup_info['phpgwapi']['tables'][] = 'egw_lang'; -$setup_info['phpgwapi']['tables'][] = 'egw_nextid'; $setup_info['phpgwapi']['tables'][] = 'egw_categories'; $setup_info['phpgwapi']['tables'][] = 'egw_history_log'; $setup_info['phpgwapi']['tables'][] = 'egw_async'; @@ -49,28 +47,3 @@ $setup_info['phpgwapi']['tables'][] = 'egw_locks'; $setup_info['phpgwapi']['tables'][] = 'egw_sqlfs_props'; $setup_info['phpgwapi']['tables'][] = 'egw_customfields'; $setup_info['phpgwapi']['tables'][] = 'egw_sharing'; - -// hooks used by vfs_home_hooks to manage user- and group-directories for the new stream based VFS -$setup_info['phpgwapi']['hooks']['addaccount'] = 'phpgwapi.vfs_home_hooks.addAccount'; -$setup_info['phpgwapi']['hooks']['deleteaccount'] = 'phpgwapi.vfs_home_hooks.deleteAccount'; -$setup_info['phpgwapi']['hooks']['editaccount'] = 'phpgwapi.vfs_home_hooks.editAccount'; -$setup_info['phpgwapi']['hooks']['addgroup'] = 'phpgwapi.vfs_home_hooks.addGroup'; -$setup_info['phpgwapi']['hooks']['deletegroup'] = 'phpgwapi.vfs_home_hooks.deleteGroup'; -$setup_info['phpgwapi']['hooks']['editgroup'] = 'phpgwapi.vfs_home_hooks.editGroup'; - -/* CalDAV/CardDAV/GroupDAV app */ -$setup_info['groupdav']['name'] = 'groupdav'; -$setup_info['groupdav']['version'] = '14.1'; -$setup_info['groupdav']['enable'] = 2; -$setup_info['groupdav']['app_order'] = 1; -$setup_info['groupdav']['icon'] = 'groupdav'; -$setup_info['groupdav']['icon_app'] = 'phpgwapi'; -$setup_info['groupdav']['author'] = $setup_info['groupdav']['maintainer'] = array( - 'name' => 'Ralf Becker', - 'email' => 'RalfBecker@outdoor-training.de' -); -$setup_info['groupdav']['license'] = 'GPL'; -$setup_info['groupdav']['hooks']['preferences'] = 'EGroupware\\Api\\CalDAV\\Hooks::menus'; -$setup_info['groupdav']['hooks']['settings'] = 'EGroupware\\Api\\CalDAV\\Hooks::settings'; - - diff --git a/phpgwapi/setup/tables_current.inc.php b/phpgwapi/setup/tables_current.inc.php index f370f6cdb0..f6696dd366 100644 --- a/phpgwapi/setup/tables_current.inc.php +++ b/phpgwapi/setup/tables_current.inc.php @@ -103,18 +103,6 @@ $phpgw_baseline = array( 'ix' => array('li','lo','session_dla','session_php','notification_heartbeat',array('account_id','ip','li'),array('account_id','loginid','li')), 'uc' => array() ), - 'egw_hooks' => array( - 'fd' => array( - 'hook_id' => array('type' => 'auto','nullable' => False), - 'hook_appname' => array('type' => 'ascii','precision' => '16'), - 'hook_location' => array('type' => 'ascii','precision' => '32'), - 'hook_filename' => array('type' => 'ascii','precision' => '255') - ), - 'pk' => array('hook_id'), - 'fk' => array(), - 'ix' => array(), - 'uc' => array() - ), 'egw_languages' => array( 'fd' => array( 'lang_id' => array('type' => 'ascii','precision' => '5','nullable' => False), @@ -138,16 +126,6 @@ $phpgw_baseline = array( 'ix' => array(), 'uc' => array(array('lang','app_name','message_id')) ), - 'egw_nextid' => array( - 'fd' => array( - 'id' => array('type' => 'int','precision' => '4'), - 'appname' => array('type' => 'ascii','precision' => '16','nullable' => False) - ), - 'pk' => array('appname'), - 'fk' => array(), - 'ix' => array(), - 'uc' => array() - ), 'egw_categories' => array( 'fd' => array( 'cat_id' => array('type' => 'auto','meta' => 'category','precision' => '4','nullable' => False), diff --git a/phpgwapi/setup/tables_update.inc.php b/phpgwapi/setup/tables_update.inc.php index f9a9d58a8a..26cbc39adb 100644 --- a/phpgwapi/setup/tables_update.inc.php +++ b/phpgwapi/setup/tables_update.inc.php @@ -1036,3 +1036,27 @@ function phpgwapi_upgrade14_3_905() } return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '14.3.906'; } + +/** + * Move content of (usually empty or for LDAP 2 rows) egw_nextid table to egw_config table and drop it + */ +function phpgwapi_upgrade14_3_906() +{ + foreach($GLOBALS['egw_setup']->db->query('SELECT appname,id FROM egw_nextid', __LINE__, __FILE__) as $row) + { + common::next_id($row['appname'], $row['id']); // store it + } + $GLOBALS['egw_setup']->oProc->DropTable('egw_nextid'); + + return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '14.3.907'; +} + +/** + * Move content of (usually empty or for LDAP 2 rows) egw_nextid table to egw_config table and drop it + */ +function phpgwapi_upgrade14_3_907() +{ + $GLOBALS['egw_setup']->oProc->DropTable('egw_hooks'); + + return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '14.3.908'; +} diff --git a/setup/inc/class.setup.inc.php b/setup/inc/class.setup.inc.php index 6c3a871774..985338b0e5 100644 --- a/setup/inc/class.setup.inc.php +++ b/setup/inc/class.setup.inc.php @@ -13,6 +13,8 @@ * @version $Id$ */ +use EGroupware\Api; + class setup { var $db; @@ -650,23 +652,12 @@ class setup */ function register_hooks($appname) { - $setup_info = $GLOBALS['setup_info']; - if(!$appname) { return False; } - if(!$this->hooks_table) // No hooks table yet - { - return False; - } - - if (!is_object($this->hooks)) - { - $this->hooks =& CreateObject('phpgwapi.hooks',$this->db,$this->hooks_table); - } - $this->hooks->register_hooks($appname,$setup_info[$appname]['hooks']); + Api\Hooks::read(true); } /** @@ -758,22 +749,12 @@ class setup */ function deregister_hooks($appname) { - if(!$this->hooks_table) // No hooks table yet - { - return False; - } - if(!$appname) { return False; } - //echo "DELETING hooks for: " . $setup_info[$appname]['name']; - if (!is_object($this->hooks)) - { - $this->hooks =& CreateObject('phpgwapi.hooks',$this->db,$this->hooks_table); - } - return $this->hooks->register_hooks($appname); + Api\Hooks::read(true); } /** @@ -784,11 +765,7 @@ class setup */ function hook($location, $appname='') { - if (!is_object($this->hooks)) - { - $this->hooks =& CreateObject('phpgwapi.hooks',$this->db,$this->hooks_table); - } - return $this->hooks->single($location,$appname,True,True); + return Api\Hooks::single($location,$appname,True,True); } /** diff --git a/setup/inc/class.setup_cmd_ldap.inc.php b/setup/inc/class.setup_cmd_ldap.inc.php index 28f63b1fa9..d24a27ba32 100644 --- a/setup/inc/class.setup_cmd_ldap.inc.php +++ b/setup/inc/class.setup_cmd_ldap.inc.php @@ -607,7 +607,7 @@ class setup_cmd_ldap extends setup_cmd // running all addAccount hooks (currently NOT working, as not all work in setup) if ($this->add_account_hook === true) { - $GLOBALS['egw']->hooks->process($account,array(),true); + Api\Hooks::process($account, array(), true); } elseif(is_callable($this->add_account_hook)) {