".__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".__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)) {