From 0ee6d7c936b930d846919e72c70f21c7f0e1d9ac Mon Sep 17 00:00:00 2001 From: brettp Date: Tue, 13 Oct 2009 22:58:50 +0000 Subject: Standardize elgglib.php git-svn-id: http://code.elgg.org/elgg/trunk@3532 36083f99-b078-4883-b0ff-0f9b5a30f544 --- engine/lib/elgglib.php | 4353 ++++++++++++++++++++++++------------------------ 1 file changed, 2177 insertions(+), 2176 deletions(-) (limited to 'engine/lib') diff --git a/engine/lib/elgglib.php b/engine/lib/elgglib.php index fc87c1dc1..f0a0b2582 100644 --- a/engine/lib/elgglib.php +++ b/engine/lib/elgglib.php @@ -1,2312 +1,2313 @@ wwwroot . "action") ===false) - function forward($location = "") { - global $CONFIG; - if (!headers_sent()) { - - $current_page = current_page_url(); - if (strpos($current_page, $CONFIG->wwwroot . "action") ===false) - - $_SESSION['messages'] = system_messages(); - if ((substr_count($location, 'http://') == 0) && (substr_count($location, 'https://') == 0)) { - - $location = $CONFIG->url . $location; - } - - - header("Location: {$location}"); - exit; - } - return false; - + $_SESSION['messages'] = system_messages(); + if ((substr_count($location, 'http://') == 0) && (substr_count($location, 'https://') == 0)) { + $location = $CONFIG->url . $location; } - /** - * Return the current page URL. - */ - function current_page_url() - { - global $CONFIG; - - $url = parse_url($CONFIG->wwwroot); - - $page = $url['scheme'] . "://"; - - // user/pass - if ((isset($url['user'])) && ($url['user'])) $page .= $url['user']; - if ((isset($url['pass'])) && ($url['pass'])) $page .= ":".$url['pass']; - if (($url['user']) || $url['pass']) $page .="@"; - - $page .= $url['host']; - - if ((isset($url['port'])) && ($url['port'])) $page .= ":" . $url['port']; - - $page = trim($page, "/"); //$page.="/"; - - $page .= $_SERVER['REQUEST_URI']; - - return $page; - } - - /** - * Templating and visual functionality - */ - - $CURRENT_SYSTEM_VIEWTYPE = ""; - - /** - * Override the view mode detection for the elgg view system. - * - * This function will force any further views to be rendered using $viewtype. Remember to call elgg_set_viewtype() with - * no parameters to reset. - * - * @param string $viewtype The view type, e.g. 'rss', or 'default'. - * @return bool - */ - function elgg_set_viewtype($viewtype = "") - { - global $CURRENT_SYSTEM_VIEWTYPE; - - $CURRENT_SYSTEM_VIEWTYPE = $viewtype; - - return true; - } - - /** - * Return the current view type used by the elgg view system. - * - * By default, this function will return a value based on the default for your system or from the command line - * view parameter. However, you may force a given view type by calling elgg_set_viewtype() - * - * @return string The view. - */ - function elgg_get_viewtype() - { - global $CURRENT_SYSTEM_VIEWTYPE, $CONFIG; - - $viewtype = NULL; - - if ($CURRENT_SYSTEM_VIEWTYPE != "") - return $CURRENT_SYSTEM_VIEWTYPE; - - if ((empty($_SESSION['view'])) || ( (trim($CONFIG->view!="")) && ($_SESSION['view']!=$CONFIG->view) )) { - $_SESSION['view'] = "default"; - - // If we have a config default view for this site then use that instead of 'default' - if (/*(is_installed()) && */(!empty($CONFIG->view)) && (trim($CONFIG->view)!="")) - $_SESSION['view'] = $CONFIG->view; - } - - if (empty($viewtype) && is_callable('get_input')) - $viewtype = get_input('view'); - - if (empty($viewtype)) - $viewtype = $_SESSION['view']; - - return $viewtype; - } - - /** - * Return the location of a given view. - * - * @param string $view The view. - * @param string $viewtype The viewtype - */ - function elgg_get_view_location($view, $viewtype = '') - { - global $CONFIG; - - if (empty($viewtype)) - $viewtype = elgg_get_viewtype(); - - if (!isset($CONFIG->views->locations[$viewtype][$view])) { - if (!isset($CONFIG->viewpath)) { - return dirname(dirname(dirname(__FILE__))) . "/views/"; - } else { - return $CONFIG->viewpath; - } - } else { - return $CONFIG->views->locations[$viewtype][$view]; - } - - return false; - } - - /** - * Handles templating views - * - * @see set_template_handler - * - * @param string $view The name and location of the view to use - * @param array $vars Any variables that the view requires, passed as an array - * @param boolean $bypass If set to true, elgg_view will bypass any specified alternative template handler; by default, it will hand off to this if requested (see set_template_handler) - * @param boolean $debug If set to true, the viewer will complain if it can't find a view - * @param string $viewtype If set, forces the viewtype for the elgg_view call to be this value (default: standard detection) - * @return string The HTML content - */ - function elgg_view($view, $vars = "", $bypass = false, $debug = false, $viewtype = '') { - - global $CONFIG; - static $usercache; - - // basic checking for bad paths - if (strpos($view, '..') !== false) { - return false; - } - - $view_orig = $view; - - // Trigger the pagesetup event - if (!isset($CONFIG->pagesetupdone)) { - trigger_elgg_event('pagesetup','system'); - $CONFIG->pagesetupdone = true; - } - - if (!is_array($usercache)) { - $usercache = array(); - } - - if (empty($vars)) { - $vars = array(); - } - - // Load session and configuration variables into $vars - if (isset($_SESSION) /*&& is_array($_SESSION)*/ ) { // $_SESSION will always be an array if it is set - $vars += $_SESSION; //= array_merge($vars, $_SESSION); - } - $vars['config'] = array(); - if (!empty($CONFIG)) - $vars['config'] = $CONFIG; - - $vars['url'] = $CONFIG->url; - - // Load page owner variables into $vars - if (is_callable('page_owner')) { - $vars['page_owner'] = page_owner(); - } else { - $vars['page_owner'] = -1; - } - if (($vars['page_owner'] != -1) && (is_installed())) { - if (!isset($usercache[$vars['page_owner']])) { - $vars['page_owner_user'] = get_entity($vars['page_owner']); - $usercache[$vars['page_owner']] = $vars['page_owner_user']; - } else { - $vars['page_owner_user'] = $usercache[$vars['page_owner']]; - } - } - if (!isset($vars['js'])) { - $vars['js'] = ""; - } - - // If it's been requested, pass off to a template handler instead - if ($bypass == false && isset($CONFIG->template_handler) && !empty($CONFIG->template_handler)) { - $template_handler = $CONFIG->template_handler; - if (is_callable($template_handler)) - return $template_handler($view, $vars); - } - - - // Get the current viewtype - if (empty($viewtype)) - $viewtype = elgg_get_viewtype(); - - // Set up any extensions to the requested view - if (isset($CONFIG->views->extensions[$view])) { - $viewlist = $CONFIG->views->extensions[$view]; - } else { - $viewlist = array(500 => $view); - } - // Start the output buffer, find the requested view file, and execute it - ob_start(); - - foreach($viewlist as $priority => $view) { - - $view_location = elgg_get_view_location($view, $viewtype); - - if (file_exists($view_location . "{$viewtype}/{$view}.php") && !include($view_location . "{$viewtype}/{$view}.php")) { - $success = false; - - if ($viewtype != "default") { - if (include($view_location . "default/{$view}.php")) { - $success = true; - } - } - if (!$success && isset($CONFIG->debug) && $CONFIG->debug == true) { - error_log(" [This view ({$view}) does not exist] "); - } - } else if (isset($CONFIG->debug) && $CONFIG->debug == true && !file_exists($view_location . "{$viewtype}/{$view}.php")) { - - error_log($view_location . "{$viewtype}/{$view}.php"); - error_log(" [This view ({$view}) does not exist] "); - } - - } - - // Save the output buffer into the $content variable - $content = ob_get_clean(); - - // Plugin hook - $content = trigger_plugin_hook('display', 'view', array('view' => $view_orig, 'vars' => $vars), $content); - - // Return $content - return $content; - - } - - /** - * Returns whether the specified view exists - * - * @param string $view The view name - * @param string $viewtype If set, forces the viewtype - * @return true|false Depending on success - */ - function elgg_view_exists($view, $viewtype = '') { - - global $CONFIG; - - // Detect view type - if (empty($viewtype)) - $viewtype = elgg_get_viewtype(); - - if (!isset($CONFIG->views->locations[$viewtype][$view])) { - if (!isset($CONFIG->viewpath)) { - $location = dirname(dirname(dirname(__FILE__))) . "/views/"; - } else { - $location = $CONFIG->viewpath; - } - } else { - $location = $CONFIG->views->locations[$viewtype][$view]; - } - - if (file_exists($location . "{$viewtype}/{$view}.php")) { - return true; - } - - // If we got here then check whether this exists as an extension - // Note that this currently does not recursively check whether the extended view exists also - if (isset($CONFIG->views->extensions[$view])) - return true; - - return false; - - } - - /** - * Registers a view to be simply cached - * - * Views cached in this manner must take no parameters and be login agnostic - - * that is to say, they look the same no matter who is logged in (or logged out). - * - * CSS and the basic jS views are automatically cached like this. - * - * @param string $viewname View name - */ - function elgg_view_register_simplecache($viewname) { - - global $CONFIG; - - if (!isset($CONFIG->views)) - $CONFIG->views = new stdClass; - - if (!isset($CONFIG->views->simplecache)) - $CONFIG->views->simplecache = array(); - - //if (elgg_view_exists($viewname)) - $CONFIG->views->simplecache[] = $viewname; - - } - - /** - * Regenerates the simple cache. - * - * @see elgg_view_register_simplecache - * - */ - function elgg_view_regenerate_simplecache() { - - global $CONFIG; - - if (isset($CONFIG->views->simplecache)) { - - if (!file_exists($CONFIG->dataroot . 'views_simplecache')) { - @mkdir($CONFIG->dataroot . 'views_simplecache'); - } - - if (!empty($CONFIG->views->simplecache) && is_array($CONFIG->views->simplecache)) { - foreach($CONFIG->views->simplecache as $view) { - $viewcontents = elgg_view($view); - $viewname = md5(elgg_get_viewtype() . $view); - if ($handle = fopen($CONFIG->dataroot . 'views_simplecache/' . $viewname, 'w')) { - fwrite($handle, $viewcontents); - fclose($handle); - } - } - } - - datalist_set('simplecache_lastupdate',0); - - } - - } - - /** - * Enables the simple cache. - * - * @see elgg_view_register_simplecache - * - */ - - function elgg_view_enable_simplecache() { - global $CONFIG; - if(!$CONFIG->simplecache_enabled) { - datalist_set('simplecache_enabled',1); - $CONFIG->simplecache_enabled = 1; - elgg_view_regenerate_simplecache(); - } - } - - /** - * Disables the simple cache. - * - * @see elgg_view_register_simplecache - * - */ - - function elgg_view_disable_simplecache() { - global $CONFIG; - if ($CONFIG->simplecache_enabled) { - datalist_set('simplecache_enabled',0); - $CONFIG->simplecache_enabled = 0; - - // purge simple cache - if ($handle = opendir($CONFIG->dataroot.'views_simplecache')) { - while (false !== ($file = readdir($handle))) { - if ($file != "." && $file != "..") { - unlink($CONFIG->dataroot.'views_simplecache/'.$file); - } - } - closedir($handle); - } - } - } - - /** - * This is a factory function which produces an ElggCache object suitable for caching file load paths. - * - * TODO: Can this be done in a cleaner way? - * TODO: Swap to memcache etc? - */ - function elgg_get_filepath_cache() - { - global $CONFIG; - static $FILE_PATH_CACHE; - if (!$FILE_PATH_CACHE) $FILE_PATH_CACHE = new ElggFileCache($CONFIG->dataroot); - - return $FILE_PATH_CACHE; + header("Location: {$location}"); + exit; + } + + return false; +} + +/** + * Return the current page URL. + */ +function current_page_url() { + global $CONFIG; + + $url = parse_url($CONFIG->wwwroot); + + $page = $url['scheme'] . "://"; + + // user/pass + if ((isset($url['user'])) && ($url['user'])) { + $page .= $url['user']; + } + if ((isset($url['pass'])) && ($url['pass'])) { + $page .= ":".$url['pass']; + } + if (($url['user']) || $url['pass']) { + $page .="@"; + } + + $page .= $url['host']; + + if ((isset($url['port'])) && ($url['port'])) { + $page .= ":" . $url['port']; + } + + //$page.="/"; + $page = trim($page, "/"); + + $page .= $_SERVER['REQUEST_URI']; + + return $page; +} + +/** + * Templating and visual functionality + */ + +$CURRENT_SYSTEM_VIEWTYPE = ""; + +/** + * Override the view mode detection for the elgg view system. + * + * This function will force any further views to be rendered using $viewtype. Remember to call elgg_set_viewtype() with + * no parameters to reset. + * + * @param string $viewtype The view type, e.g. 'rss', or 'default'. + * @return bool + */ +function elgg_set_viewtype($viewtype = "") { + global $CURRENT_SYSTEM_VIEWTYPE; + + $CURRENT_SYSTEM_VIEWTYPE = $viewtype; + + return true; +} + +/** + * Return the current view type used by the elgg view system. + * + * By default, this function will return a value based on the default for your system or from the command line + * view parameter. However, you may force a given view type by calling elgg_set_viewtype() + * + * @return string The view. + */ +function elgg_get_viewtype() { + global $CURRENT_SYSTEM_VIEWTYPE, $CONFIG; + + $viewtype = NULL; + + if ($CURRENT_SYSTEM_VIEWTYPE != "") { + return $CURRENT_SYSTEM_VIEWTYPE; + } + + if ((empty($_SESSION['view'])) || ( (trim($CONFIG->view!="")) && ($_SESSION['view']!=$CONFIG->view) )) { + $_SESSION['view'] = "default"; + // If we have a config default view for this site then use that instead of 'default' + if (/*(is_installed()) && */(!empty($CONFIG->view)) && (trim($CONFIG->view)!="")) { + $_SESSION['view'] = $CONFIG->view; } - - /** - * Function which resets the file path cache. - * - */ - function elgg_filepath_cache_reset() - { - $cache = elgg_get_filepath_cache(); - return $cache->delete('view_paths'); + } + + if (empty($viewtype) && is_callable('get_input')) { + $viewtype = get_input('view'); + } + + if (empty($viewtype)) { + $viewtype = $_SESSION['view']; + } + + return $viewtype; +} + +/** + * Return the location of a given view. + * + * @param string $view The view. + * @param string $viewtype The viewtype + */ +function elgg_get_view_location($view, $viewtype = '') { + global $CONFIG; + + if (empty($viewtype)) { + $viewtype = elgg_get_viewtype(); + } + + if (!isset($CONFIG->views->locations[$viewtype][$view])) { + if (!isset($CONFIG->viewpath)) { + return dirname(dirname(dirname(__FILE__))) . "/views/"; + } else { + return $CONFIG->viewpath; } - - /** - * Saves a filepath cache. - * - * @param mixed $data - */ - function elgg_filepath_cache_save($data) - { - global $CONFIG; - - if ($CONFIG->viewpath_cache_enabled) { - $cache = elgg_get_filepath_cache(); - return $cache->save('view_paths', $data); - } - + } else { + return $CONFIG->views->locations[$viewtype][$view]; + } + + return false; +} + +/** + * Handles templating views + * + * @see set_template_handler + * + * @param string $view The name and location of the view to use + * @param array $vars Any variables that the view requires, passed as an array + * @param boolean $bypass If set to true, elgg_view will bypass any specified alternative template handler; by default, it will hand off to this if requested (see set_template_handler) + * @param boolean $debug If set to true, the viewer will complain if it can't find a view + * @param string $viewtype If set, forces the viewtype for the elgg_view call to be this value (default: standard detection) + * @return string The HTML content + */ +function elgg_view($view, $vars = "", $bypass = false, $debug = false, $viewtype = '') { + global $CONFIG; + static $usercache; + + // basic checking for bad paths + if (strpos($view, '..') !== false) { return false; + } + + $view_orig = $view; + + // Trigger the pagesetup event + if (!isset($CONFIG->pagesetupdone)) { + trigger_elgg_event('pagesetup','system'); + $CONFIG->pagesetupdone = true; + } + + if (!is_array($usercache)) { + $usercache = array(); + } + + if (empty($vars)) { + $vars = array(); + } + + // Load session and configuration variables into $vars + // $_SESSION will always be an array if it is set + if (isset($_SESSION) /*&& is_array($_SESSION)*/ ) { + //= array_merge($vars, $_SESSION); + $vars += $_SESSION; + } + $vars['config'] = array(); + + if (!empty($CONFIG)) { + $vars['config'] = $CONFIG; + } + + $vars['url'] = $CONFIG->url; + + // Load page owner variables into $vars + if (is_callable('page_owner')) { + $vars['page_owner'] = page_owner(); + } else { + $vars['page_owner'] = -1; + } + + if (($vars['page_owner'] != -1) && (is_installed())) { + if (!isset($usercache[$vars['page_owner']])) { + $vars['page_owner_user'] = get_entity($vars['page_owner']); + $usercache[$vars['page_owner']] = $vars['page_owner_user']; + } else { + $vars['page_owner_user'] = $usercache[$vars['page_owner']]; } - - /** - * Retrieve the contents of the filepath cache. - * - */ - function elgg_filepath_cache_load() - { - global $CONFIG; - - if ($CONFIG->viewpath_cache_enabled) { - $cache = elgg_get_filepath_cache(); - $cached_view_paths = $cache->load('view_paths'); - - if ($cached_view_paths) - return $cached_view_paths; - } - - return NULL; - } - - /** - * Enable the filepath cache. - * - */ - function elgg_enable_filepath_cache() - { - global $CONFIG; - - datalist_set('viewpath_cache_enabled',1); - $CONFIG->viewpath_cache_enabled = 1; - elgg_filepath_cache_reset(); - - } - - /** - * Disable filepath cache. - * - */ - function elgg_disable_filepath_cache() - { - global $CONFIG; - - datalist_set('viewpath_cache_enabled',0); - $CONFIG->viewpath_cache_enabled = 0; - elgg_filepath_cache_reset(); - - } - - /** - * Internal function for retrieving views used by elgg_view_tree - * - * @param unknown_type $dir - * @param unknown_type $base - * @return unknown - */ - function get_views($dir, $base) { - - $return = array(); - if (file_exists($dir) && is_dir($dir)) { - if ($handle = opendir($dir)) { - while ($view = readdir($handle)) { - if (!in_array($view, array('.','..','.svn','CVS'))) { - if (is_dir($dir . '/' . $view)) { - if ($val = get_views($dir . '/' . $view, $base . '/' . $view)) { - $return = array_merge($return, $val); - } - } else { - $view = str_replace('.php','',$view); - $return[] = $base . '/' . $view; - } - } - } - } - } - return $return; - } + } - /** - * When given a partial view root (eg 'js' or 'page_elements'), returns an array of views underneath it - * - * @param string $view_root The root view - * @param string $viewtype Optionally specify a view type other than the current one. - * @return array A list of view names underneath that root view - */ - function elgg_view_tree($view_root, $viewtype = "") { - - global $CONFIG; - static $treecache; - - // Get viewtype - if (!$viewtype) - $viewtype = elgg_get_viewtype(); - - // Has the treecache been initialised? - if (!isset($treecache)) $treecache = array(); - // A little light internal caching - if (!empty($treecache[$view_root])) return $treecache[$view_root]; - - // Examine $CONFIG->views->locations - if (isset($CONFIG->views->locations[$viewtype])) { - foreach($CONFIG->views->locations[$viewtype] as $view => $path) { - $pos = strpos($view,$view_root); - if ($pos === 0) { - $treecache[$view_root][] = $view; - } - } - } - - // Now examine core - $location = $CONFIG->viewpath; - $viewtype = elgg_get_viewtype(); - $root = $location . $viewtype . '/' . $view_root; - - if (file_exists($root) && is_dir($root)) { - $val = get_views($root, $view_root); - if (!is_array($treecache[$view_root])) $treecache[$view_root] = array(); - $treecache[$view_root] = array_merge($treecache[$view_root], $val); - } + if (!isset($vars['js'])) { + $vars['js'] = ""; + } - return $treecache[$view_root]; + // If it's been requested, pass off to a template handler instead + if ($bypass == false && isset($CONFIG->template_handler) && !empty($CONFIG->template_handler)) { + $template_handler = $CONFIG->template_handler; + if (is_callable($template_handler)) { + return $template_handler($view, $vars); } - - /** - * When given an entity, views it intelligently. - * - * Expects a view to exist called entity-type/subtype, or for the entity to have a parameter - * 'view' which lists a different view to display. In both cases, elgg_view will be called with - * array('entity' => $entity) as its parameters, and therefore this is what the view should expect - * to receive. - * - * @param ElggEntity $entity The entity to display - * @param boolean $full Determines whether or not to display the full version of an object, or a smaller version for use in aggregators etc - * @param boolean $bypass If set to true, elgg_view will bypass any specified alternative template handler; by default, it will hand off to this if requested (see set_template_handler) - * @param boolean $debug If set to true, the viewer will complain if it can't find a view - * @return string HTML (etc) to display - */ - function elgg_view_entity(ElggEntity $entity, $full = false, $bypass = true, $debug = false) { - - global $autofeed; - $autofeed = true; - - // No point continuing if entity is null. - if (!$entity) return ''; - - $view = $entity->view; - if (is_string($view)) { - return elgg_view($view,array('entity' => $entity), $bypass, $debug); - } - - $classes = array( - 'ElggUser' => 'user', - 'ElggObject' => 'object', - 'ElggSite' => 'site', - 'ElggGroup' => 'group' - ); - - $entity_class = get_class($entity); - - if (isset($classes[$entity_class])) { - $entity_type = $classes[$entity_class]; - } else { - foreach($classes as $class => $type) { - if ($entity instanceof $class) { - $entity_type = $type; - break; - } + } + + + // Get the current viewtype + if (empty($viewtype)) { + $viewtype = elgg_get_viewtype(); + } + + // Set up any extensions to the requested view + if (isset($CONFIG->views->extensions[$view])) { + $viewlist = $CONFIG->views->extensions[$view]; + } else { + $viewlist = array(500 => $view); + } + // Start the output buffer, find the requested view file, and execute it + ob_start(); + + foreach($viewlist as $priority => $view) { + $view_location = elgg_get_view_location($view, $viewtype); + + if (file_exists($view_location . "{$viewtype}/{$view}.php") + && !include($view_location . "{$viewtype}/{$view}.php")) { + $success = false; + + if ($viewtype != "default") { + if (include($view_location . "default/{$view}.php")) { + $success = true; } } - if (!isset($entity_class)) return false; - - $subtype = $entity->getSubtype(); - if (empty($subtype)) { $subtype = $entity_type; } - - $contents = ''; - if (elgg_view_exists("{$entity_type}/{$subtype}")) { - $contents = elgg_view("{$entity_type}/{$subtype}",array( - 'entity' => $entity, - 'full' => $full - ), $bypass, $debug); - } - if (empty($contents)) { - $contents = elgg_view("{$entity_type}/default",array( - 'entity' => $entity, - 'full' => $full - ), $bypass, $debug); + if (!$success && isset($CONFIG->debug) && $CONFIG->debug == true) { + error_log(" [This view ({$view}) does not exist] "); } - if ($full) // Marcus Povey 20090616 : Speculative and low impact approach for fixing #964 - { - $annotations = elgg_view_entity_annotations($entity, $full); - - if ($annotations) - $contents .= $annotations; - } - return $contents; + } else if (isset($CONFIG->debug) && $CONFIG->debug == true + && !file_exists($view_location . "{$viewtype}/{$view}.php")) { + error_log($view_location . "{$viewtype}/{$view}.php"); + error_log(" [This view ({$view}) does not exist] "); } - /** - * When given an annotation, views it intelligently. - * - * This function expects annotation views to be of the form annotation/name, where name - * is the type of annotation. - * - * @param ElggAnnotation $annotation The annotation to display - * @param boolean $full Determines whether or not to display the full version of an object, or a smaller version for use in aggregators etc - * @param boolean $bypass If set to true, elgg_view will bypass any specified alternative template handler; by default, it will hand off to this if requested (see set_template_handler) - * @param boolean $debug If set to true, the viewer will complain if it can't find a view - * @return string HTML (etc) to display - */ - function elgg_view_annotation(ElggAnnotation $annotation, $bypass = true, $debug = false) { - - global $autofeed; - $autofeed = true; - - $view = $annotation->view; - if (is_string($view)) { - return elgg_view($view,array('annotation' => $annotation), $bypass, $debug); - } - - $name = $annotation->name; - $intname = (int) $name; - if ("{$intname}" == "{$name}") { - $name = get_metastring($intname); - } - if (empty($name)) { return ""; } - - if (elgg_view_exists("annotation/{$name}")) { - return elgg_view("annotation/{$name}",array( - 'annotation' => $annotation, - ), $bypass, $debug); - } else { - return elgg_view("annotation/default",array( - 'annotation' => $annotation, - ), $bypass, $debug); - } + } + + // Save the output buffer into the $content variable + $content = ob_get_clean(); + + // Plugin hook + $content = trigger_plugin_hook('display', 'view', + array('view' => $view_orig, 'vars' => $vars), $content); + + // Return $content + return $content; +} + +/** + * Returns whether the specified view exists + * + * @param string $view The view name + * @param string $viewtype If set, forces the viewtype + * @return true|false Depending on success + */ +function elgg_view_exists($view, $viewtype = '') { + global $CONFIG; + + // Detect view type + if (empty($viewtype)) { + $viewtype = elgg_get_viewtype(); + } + + if (!isset($CONFIG->views->locations[$viewtype][$view])) { + if (!isset($CONFIG->viewpath)) { + $location = dirname(dirname(dirname(__FILE__))) . "/views/"; + } else { + $location = $CONFIG->viewpath; } - - - /** - * Returns a view of a list of entities, plus navigation. It is intended that this function - * be called from other wrapper functions. - * - * @see list_entities - * @see list_user_objects - * @see list_user_friends_objects - * @see list_entities_from_metadata - * @see list_entities_from_metadata_multi - * @see list_entities_from_relationships - * @see list_site_members - * - * @param array $entities List of entities - * @param int $count The total number of entities across all pages - * @param int $offset The current indexing offset - * @param int $limit The number of entities to display per page - * @param true|false $fullview Whether or not to display the full view (default: true) - * @param true|false $viewtypetoggle Whether or not to allow users to toggle to gallery view - * @param bool $pagination Whether pagination is offered. - * @return string The list of entities - */ - function elgg_view_entity_list($entities, $count, $offset, $limit, $fullview = true, $viewtypetoggle = true, $pagination = true) { - - $count = (int) $count; - $offset = (int) $offset; - $limit = (int) $limit; - - $context = get_context(); - - $html = elgg_view('search/entity_list',array( - 'entities' => $entities, - 'count' => $count, - 'offset' => $offset, - 'limit' => $limit, - 'baseurl' => $_SERVER['REQUEST_URI'], - 'fullview' => $fullview, - 'context' => $context, - 'viewtypetoggle' => $viewtypetoggle, - 'viewtype' => get_input('search_viewtype','list'), - 'pagination' => $pagination - )); - - return $html; - + } else { + $location = $CONFIG->views->locations[$viewtype][$view]; + } + + if (file_exists($location . "{$viewtype}/{$view}.php")) { + return true; + } + + // If we got here then check whether this exists as an extension + // Note that this currently does not recursively check whether the extended view exists also + if (isset($CONFIG->views->extensions[$view])) { + return true; + } + + return false; +} + +/** + * Registers a view to be simply cached + * + * Views cached in this manner must take no parameters and be login agnostic - + * that is to say, they look the same no matter who is logged in (or logged out). + * + * CSS and the basic jS views are automatically cached like this. + * + * @param string $viewname View name + */ +function elgg_view_register_simplecache($viewname) { + global $CONFIG; + + if (!isset($CONFIG->views)) { + $CONFIG->views = new stdClass; + } + + if (!isset($CONFIG->views->simplecache)) { + $CONFIG->views->simplecache = array(); + } + + //if (elgg_view_exists($viewname)) + $CONFIG->views->simplecache[] = $viewname; +} + +/** + * Regenerates the simple cache. + * + * @see elgg_view_register_simplecache + * + */ +function elgg_view_regenerate_simplecache() { + global $CONFIG; + + if (isset($CONFIG->views->simplecache)) { + if (!file_exists($CONFIG->dataroot . 'views_simplecache')) { + @mkdir($CONFIG->dataroot . 'views_simplecache'); } - /** - * Returns a view of a list of annotations, plus navigation. It is intended that this function - * be called from other wrapper functions. - * - * @param array $annotations List of annotations - * @param int $count The total number of annotations across all pages - * @param int $offset The current indexing offset - * @param int $limit The number of annotations to display per page - * @return string The list of annotations - */ - - function elgg_view_annotation_list($annotations, $count, $offset, $limit) { - - $count = (int) $count; - $offset = (int) $offset; - $limit = (int) $limit; - - $html = ""; - - $nav = elgg_view('navigation/pagination',array( - - 'baseurl' => $_SERVER['REQUEST_URI'], - 'offset' => $offset, - 'count' => $count, - 'limit' => $limit, - 'word' => 'annoff', - 'nonefound' => false, - - )); - - $html .= $nav; - - if (is_array($annotations) && sizeof($annotations) > 0) { - foreach($annotations as $annotation) { - $html .= elgg_view_annotation($annotation, "", false); + if (!empty($CONFIG->views->simplecache) && is_array($CONFIG->views->simplecache)) { + foreach($CONFIG->views->simplecache as $view) { + $viewcontents = elgg_view($view); + $viewname = md5(elgg_get_viewtype() . $view); + if ($handle = fopen($CONFIG->dataroot . 'views_simplecache/' . $viewname, 'w')) { + fwrite($handle, $viewcontents); + fclose($handle); } } - - if ($count) - $html .= $nav; - - return $html; - - } - - /** - * Display a selective rendered list of annotations for a given entity. - * - * The list is produced as the result of the entity:annotate plugin hook and is designed to provide a - * more generic framework to allow plugins to extend the generic display of entities with their own annotation - * renderings. - * - * This is called automatically by the framework. - * - * @param ElggEntity $entity - * @param bool $full - */ - function elgg_view_entity_annotations(ElggEntity $entity, $full = true) - { - $classes = array( - 'ElggUser' => 'user', - 'ElggObject' => 'object', - 'ElggSite' => 'site', - 'ElggGroup' => 'group' - ); - - $entity_class = get_class($entity); - - $annotations = trigger_plugin_hook('entity:annotate', $classes[$entity_class], - array( - 'entity' => $entity, - 'full' => $full, - ) - ); - - return $annotations; } - - /** - * Displays an internal layout for the use of a plugin canvas. - * Takes a variable number of parameters, which are made available - * in the views as $vars['area1'] .. $vars['areaN']. - * - * @param string $layout The name of the views in canvas/layouts/. - * @return string The layout - */ - function elgg_view_layout($layout) { - - $arg = 1; - $param_array = array(); - while ($arg < func_num_args()) { - $param_array['area' . $arg] = func_get_arg($arg); - $arg++; - } - if (elgg_view_exists("canvas/layouts/{$layout}")) { - return elgg_view("canvas/layouts/{$layout}",$param_array); - } else { - return elgg_view("canvas/default",$param_array); + + datalist_set('simplecache_lastupdate', 0); + } +} + +/** + * Enables the simple cache. + * + * @see elgg_view_register_simplecache + * + */ + +function elgg_view_enable_simplecache() { + global $CONFIG; + if(!$CONFIG->simplecache_enabled) { + datalist_set('simplecache_enabled',1); + $CONFIG->simplecache_enabled = 1; + elgg_view_regenerate_simplecache(); + } +} + +/** + * Disables the simple cache. + * + * @see elgg_view_register_simplecache + * + */ +function elgg_view_disable_simplecache() { + global $CONFIG; + if ($CONFIG->simplecache_enabled) { + datalist_set('simplecache_enabled',0); + $CONFIG->simplecache_enabled = 0; + + // purge simple cache + if ($handle = opendir($CONFIG->dataroot.'views_simplecache')) { + while (false !== ($file = readdir($handle))) { + if ($file != "." && $file != "..") { + unlink($CONFIG->dataroot.'views_simplecache/'.$file); + } } - - } - - /** - * Returns a view for the page title - * - * @param string $title The page title - * @param string $submenu Should a submenu be displayed? (default false, use not recommended) - * @return string The HTML (etc) - */ - function elgg_view_title($title, $submenu = false) { - - $title = elgg_view('page_elements/title', array('title' => $title, 'submenu' => $submenu)); - - return $title; - + closedir($handle); } - - /** - * Adds an item to the submenu - * - * @param string $label The human-readable label - * @param string $link The URL of the submenu item - * @param boolean $onclick Used to provide a JS popup to confirm delete - */ - function add_submenu_item($label, $link, $group = 'a', $onclick = false) { - - global $CONFIG; - if (!isset($CONFIG->submenu)) $CONFIG->submenu = array(); - if (!isset($CONFIG->submenu[$group])) $CONFIG->submenu[$group] = array(); - $item = new stdClass; - $item->value = $link; - $item->name = $label; - $item->onclick = $onclick; - $CONFIG->submenu[$group][] = $item; - + } +} + +/** + * This is a factory function which produces an ElggCache object suitable for caching file load paths. + * + * TODO: Can this be done in a cleaner way? + * TODO: Swap to memcache etc? + */ +function elgg_get_filepath_cache() { + global $CONFIG; + static $FILE_PATH_CACHE; + if (!$FILE_PATH_CACHE) $FILE_PATH_CACHE = new ElggFileCache($CONFIG->dataroot); + + return $FILE_PATH_CACHE; +} + +/** + * Function which resets the file path cache. + * + */ +function elgg_filepath_cache_reset() { + $cache = elgg_get_filepath_cache(); + return $cache->delete('view_paths'); +} + +/** + * Saves a filepath cache. + * + * @param mixed $data + */ +function elgg_filepath_cache_save($data) { + global $CONFIG; + + if ($CONFIG->viewpath_cache_enabled) { + $cache = elgg_get_filepath_cache(); + return $cache->save('view_paths', $data); + } + + return false; +} + +/** + * Retrieve the contents of the filepath cache. + * + */ +function elgg_filepath_cache_load() { + global $CONFIG; + + if ($CONFIG->viewpath_cache_enabled) { + $cache = elgg_get_filepath_cache(); + $cached_view_paths = $cache->load('view_paths'); + + if ($cached_view_paths) { + return $cached_view_paths; } - - /** - * Gets a formatted list of submenu items - * - * @return string List of items - */ - function get_submenu() { - - $submenu_total = ""; - global $CONFIG; - - if (isset($CONFIG->submenu) && $submenu_register = $CONFIG->submenu) { - - $preselected = false; - $comparevals = array(); - $maxcompareval = 999999; - - //asort($submenu_register); - ksort($submenu_register); - - foreach($submenu_register as $groupname => $submenu_register_group) { - foreach($submenu_register_group as $key => $item) { - - if (substr_count($item->value, $_SERVER['REQUEST_URI'])) { - $comparevals[$key] = levenshtein($item->value, $_SERVER['REQUEST_URI']); - if ($comparevals[$key] < $maxcompareval) { - $maxcompareval = $comparevals[$key]; - $preselected = $key; - $preselectedgroup = $groupname; - } - } - - } - } - - foreach($submenu_register as $groupname => $submenu_register_group) { - - $submenu = ""; - - foreach($submenu_register_group as $key => $item) { - - if ($preselected === false) { - if (substr_count($item->value, $_SERVER['REQUEST_URI'])) { - $preselected = $key; - $preselectedgroup = $groupname; - $selected = true; - } else { - $selected = false; - } - } else { - if ($key == $preselected && $groupname == $preselectedgroup) { - $selected = true; - } else { - $selected = false; - } + } + + return NULL; +} + +/** + * Enable the filepath cache. + * + */ +function elgg_enable_filepath_cache() { + global $CONFIG; + + datalist_set('viewpath_cache_enabled',1); + $CONFIG->viewpath_cache_enabled = 1; + elgg_filepath_cache_reset(); +} + +/** + * Disable filepath cache. + * + */ +function elgg_disable_filepath_cache() { + global $CONFIG; + + datalist_set('viewpath_cache_enabled',0); + $CONFIG->viewpath_cache_enabled = 0; + elgg_filepath_cache_reset(); +} + +/** + * Internal function for retrieving views used by elgg_view_tree + * + * @param unknown_type $dir + * @param unknown_type $base + * @return unknown + */ +function get_views($dir, $base) { + $return = array(); + if (file_exists($dir) && is_dir($dir)) { + if ($handle = opendir($dir)) { + while ($view = readdir($handle)) { + if (!in_array($view, array('.','..','.svn','CVS'))) { + if (is_dir($dir . '/' . $view)) { + if ($val = get_views($dir . '/' . $view, $base . '/' . $view)) { + $return = array_merge($return, $val); } - - $submenu .= elgg_view('canvas_header/submenu_template', - array( - 'href' => $item->value, - 'label' => $item->name, - 'onclick' => $item->onclick, - 'selected' => $selected, - )); - + } else { + $view = str_replace('.php','',$view); + $return[] = $base . '/' . $view; } - - $submenu_total .= elgg_view('canvas_header/submenu_group', array( - 'submenu' => $submenu, - 'group_name' => $groupname - )); - } - } - - return $submenu_total; - } - - - /** - * Automatically views comments and a comment form relating to the given entity - * - * @param ElggEntity $entity The entity to comment on - * @return string|false The HTML (etc) for the comments, or false on failure - */ - function elgg_view_comments($entity){ + } + return $return; +} - if (!($entity instanceof ElggEntity)) return false; - - if ($comments = trigger_plugin_hook('comments',$entity->getType(),array('entity' => $entity),false)) { +/** + * When given a partial view root (eg 'js' or 'page_elements'), returns an array of views underneath it + * + * @param string $view_root The root view + * @param string $viewtype Optionally specify a view type other than the current one. + * @return array A list of view names underneath that root view + */ +function elgg_view_tree($view_root, $viewtype = "") { + global $CONFIG; + static $treecache; - return $comments; + // Get viewtype + if (!$viewtype) { + $viewtype = elgg_get_viewtype(); + } - } else { - $comments = list_annotations($entity->getGUID(),'generic_comment'); - - //display the comment form - $comments .= elgg_view('comments/forms/edit',array('entity' => $entity)); - - return $comments; + // Has the treecache been initialised? + if (!isset($treecache)) { + $treecache = array(); + } + // A little light internal caching + if (!empty($treecache[$view_root])) { + return $treecache[$view_root]; + } + + // Examine $CONFIG->views->locations + if (isset($CONFIG->views->locations[$viewtype])) { + foreach($CONFIG->views->locations[$viewtype] as $view => $path) { + $pos = strpos($view,$view_root); + if ($pos === 0) { + $treecache[$view_root][] = $view; } - - } + } + } - /** - * Count the number of comments attached to an entity - * - * @param ElggEntity $entity - * @return int Number of comments - */ - function elgg_count_comments($entity) { - - if ($commentno = trigger_plugin_hook('comments:count',$entity->getType(),array('entity' => $entity),false)) { - return $commentno; - } else - return count_annotations($entity->getGUID(), "", "", "generic_comment"); - } - - /** - * Wrapper function to display search listings. - * - * @param string $icon The icon for the listing - * @param string $info Any information that needs to be displayed. - * @return string The HTML (etc) representing the listing - */ - function elgg_view_listing($icon, $info) { - return elgg_view('search/listing',array('icon' => $icon, 'info' => $info)); + // Now examine core + $location = $CONFIG->viewpath; + $viewtype = elgg_get_viewtype(); + $root = $location . $viewtype . '/' . $view_root; + + if (file_exists($root) && is_dir($root)) { + $val = get_views($root, $view_root); + if (!is_array($treecache[$view_root])) { + $treecache[$view_root] = array(); } - - /** - * Sets an alternative function to handle templates, which will be passed to by elgg_view. - * This function must take the $view and $vars parameters from elgg_view: - * - * function my_template_function(string $view, array $vars = array()) - * - * @see elgg_view - * - * @param string $function_name The name of the function to pass to. - * @return true|false - */ - function set_template_handler($function_name) { - global $CONFIG; - if (!empty($function_name) && is_callable($function_name)) { - $CONFIG->template_handler = $function_name; - return true; + $treecache[$view_root] = array_merge($treecache[$view_root], $val); + } + + return $treecache[$view_root]; +} + +/** + * When given an entity, views it intelligently. + * + * Expects a view to exist called entity-type/subtype, or for the entity to have a parameter + * 'view' which lists a different view to display. In both cases, elgg_view will be called with + * array('entity' => $entity) as its parameters, and therefore this is what the view should expect + * to receive. + * + * @param ElggEntity $entity The entity to display + * @param boolean $full Determines whether or not to display the full version of an object, or a smaller version for use in aggregators etc + * @param boolean $bypass If set to true, elgg_view will bypass any specified alternative template handler; by default, it will hand off to this if requested (see set_template_handler) + * @param boolean $debug If set to true, the viewer will complain if it can't find a view + * @return string HTML (etc) to display + */ +function elgg_view_entity(ElggEntity $entity, $full = false, $bypass = true, $debug = false) { + global $autofeed; + $autofeed = true; + + // No point continuing if entity is null. + if (!$entity) { + return ''; + } + + $view = $entity->view; + if (is_string($view)) { + return elgg_view($view,array('entity' => $entity), $bypass, $debug); + } + + $classes = array( + 'ElggUser' => 'user', + 'ElggObject' => 'object', + 'ElggSite' => 'site', + 'ElggGroup' => 'group' + ); + + $entity_class = get_class($entity); + + if (isset($classes[$entity_class])) { + $entity_type = $classes[$entity_class]; + } else { + foreach($classes as $class => $type) { + if ($entity instanceof $class) { + $entity_type = $type; + break; } - return false; } - - /** - * Extends a view by adding other views to be displayed at the same time. - * - * @param string $view The view to add to. - * @param string $view_name The name of the view to extend - * @param int $priority The priority, from 0 to 1000, to add at (lowest numbers will be displayed first) - */ - function extend_view($view, $view_name, $priority = 501, $viewtype = '') { - - global $CONFIG; - - if (!isset($CONFIG->views)) { - $CONFIG->views = new stdClass; - } - if (!isset($CONFIG->views->extensions)) { - $CONFIG->views->extensions = array(); - } - if (!isset($CONFIG->views->extensions[$view])) { - $CONFIG->views->extensions[$view][500] = "{$view}"; - } - while(isset($CONFIG->views->extensions[$view][$priority])) { - $priority++; - } - $CONFIG->views->extensions[$view][$priority] = "{$view_name}"; - ksort($CONFIG->views->extensions[$view]); - + } + if (!isset($entity_class)) { + return false; + } + + $subtype = $entity->getSubtype(); + if (empty($subtype)) { + $subtype = $entity_type; + } + + $contents = ''; + if (elgg_view_exists("{$entity_type}/{$subtype}")) { + $contents = elgg_view("{$entity_type}/{$subtype}", array( + 'entity' => $entity, + 'full' => $full + ), $bypass, $debug); + } + if (empty($contents)) { + $contents = elgg_view("{$entity_type}/default",array( + 'entity' => $entity, + 'full' => $full + ), $bypass, $debug); + } + // Marcus Povey 20090616 : Speculative and low impact approach for fixing #964 + if ($full) { + $annotations = elgg_view_entity_annotations($entity, $full); + + if ($annotations) { + $contents .= $annotations; } - - /** - * Set an alternative base location for a view (as opposed to the default of $CONFIG->viewpath) - * - * @param string $view The name of the view - * @param string $location The base location path - */ - function set_view_location($view, $location, $viewtype = '') { - - global $CONFIG; - - if (empty($viewtype)) - $viewtype = 'default'; - - if (!isset($CONFIG->views)) { - $CONFIG->views = new stdClass; - } - if (!isset($CONFIG->views->locations)) { - $CONFIG->views->locations = array($viewtype => array( - $view => $location - )); - } else if (!isset($CONFIG->views->locations[$viewtype])) { - $CONFIG->views->locations[$viewtype] = array( - $view => $location - ); - } else { - $CONFIG->views->locations[$viewtype][$view] = $location; - } - + } + return $contents; +} + +/** + * When given an annotation, views it intelligently. + * + * This function expects annotation views to be of the form annotation/name, where name + * is the type of annotation. + * + * @param ElggAnnotation $annotation The annotation to display + * @param boolean $full Determines whether or not to display the full version of an object, or a smaller version for use in aggregators etc + * @param boolean $bypass If set to true, elgg_view will bypass any specified alternative template handler; by default, it will hand off to this if requested (see set_template_handler) + * @param boolean $debug If set to true, the viewer will complain if it can't find a view + * @return string HTML (etc) to display + */ +function elgg_view_annotation(ElggAnnotation $annotation, $bypass = true, $debug = false) { + global $autofeed; + $autofeed = true; + + $view = $annotation->view; + if (is_string($view)) { + return elgg_view($view,array('annotation' => $annotation), $bypass, $debug); + } + + $name = $annotation->name; + $intname = (int) $name; + if ("{$intname}" == "{$name}") { + $name = get_metastring($intname); + } + if (empty($name)) { + return ""; + } + + if (elgg_view_exists("annotation/{$name}")) { + return elgg_view("annotation/{$name}",array('annotation' => $annotation), $bypass, $debug); + } else { + return elgg_view("annotation/default",array('annotation' => $annotation), $bypass, $debug); + } +} + + +/** + * Returns a view of a list of entities, plus navigation. It is intended that this function + * be called from other wrapper functions. + * + * @see list_entities + * @see list_user_objects + * @see list_user_friends_objects + * @see list_entities_from_metadata + * @see list_entities_from_metadata_multi + * @see list_entities_from_relationships + * @see list_site_members + * + * @param array $entities List of entities + * @param int $count The total number of entities across all pages + * @param int $offset The current indexing offset + * @param int $limit The number of entities to display per page + * @param true|false $fullview Whether or not to display the full view (default: true) + * @param true|false $viewtypetoggle Whether or not to allow users to toggle to gallery view + * @param bool $pagination Whether pagination is offered. + * @return string The list of entities + */ +function elgg_view_entity_list($entities, $count, $offset, $limit, $fullview = true, $viewtypetoggle = true, $pagination = true) { + $count = (int) $count; + $offset = (int) $offset; + $limit = (int) $limit; + + $context = get_context(); + + $html = elgg_view('search/entity_list',array( + 'entities' => $entities, + 'count' => $count, + 'offset' => $offset, + 'limit' => $limit, + 'baseurl' => $_SERVER['REQUEST_URI'], + 'fullview' => $fullview, + 'context' => $context, + 'viewtypetoggle' => $viewtypetoggle, + 'viewtype' => get_input('search_viewtype','list'), + 'pagination' => $pagination + )); + + return $html; +} + +/** + * Returns a view of a list of annotations, plus navigation. It is intended that this function + * be called from other wrapper functions. + * + * @param array $annotations List of annotations + * @param int $count The total number of annotations across all pages + * @param int $offset The current indexing offset + * @param int $limit The number of annotations to display per page + * @return string The list of annotations + */ +function elgg_view_annotation_list($annotations, $count, $offset, $limit) { + $count = (int) $count; + $offset = (int) $offset; + $limit = (int) $limit; + + $html = ""; + + $nav = elgg_view('navigation/pagination',array( + 'baseurl' => $_SERVER['REQUEST_URI'], + 'offset' => $offset, + 'count' => $count, + 'limit' => $limit, + 'word' => 'annoff', + 'nonefound' => false, + )); + + $html .= $nav; + + if (is_array($annotations) && sizeof($annotations) > 0) { + foreach($annotations as $annotation) { + $html .= elgg_view_annotation($annotation, "", false); } - - /** - * Auto-registers views from a particular starting location - * - * @param string $view_base The base of the view name - * @param string $folder The folder to begin looking in - * @param string $base_location_path The base views directory to use with set_view_location - * @param string $viewtype The type of view we're looking at (default, rss, etc) - */ - function autoregister_views($view_base, $folder, $base_location_path, $viewtype) { - - if (!isset($i)) $i = 0; - if ($handle = opendir($folder)) { - while ($view = readdir($handle)) { - if (!in_array($view,array('.','..','.svn','CVS')) && !is_dir($folder . "/" . $view)) { - if ((substr_count($view,".php") > 0) || (substr_count($view,".png") > 0)) { - if (!empty($view_base)) { $view_base_new = $view_base . "/"; } else { $view_base_new = ""; } - set_view_location($view_base_new . str_replace(".php","",$view), $base_location_path, $viewtype); - } - } else if (!in_array($view,array('.','..','.svn','CVS')) && is_dir($folder . "/" . $view)) { - if (!empty($view_base)) { $view_base_new = $view_base . "/"; } else { $view_base_new = ""; } - autoregister_views($view_base_new . $view, $folder . "/" . $view, $base_location_path, $viewtype); + } + + if ($count) { + $html .= $nav; + } + + return $html; +} + +/** + * Display a selective rendered list of annotations for a given entity. + * + * The list is produced as the result of the entity:annotate plugin hook and is designed to provide a + * more generic framework to allow plugins to extend the generic display of entities with their own annotation + * renderings. + * + * This is called automatically by the framework. + * + * @param ElggEntity $entity + * @param bool $full + */ +function elgg_view_entity_annotations(ElggEntity $entity, $full = true) { + $classes = array( + 'ElggUser' => 'user', + 'ElggObject' => 'object', + 'ElggSite' => 'site', + 'ElggGroup' => 'group' + ); + + $entity_class = get_class($entity); + + $annotations = trigger_plugin_hook('entity:annotate', $classes[$entity_class], + array( + 'entity' => $entity, + 'full' => $full, + ) + ); + + return $annotations; +} + +/** + * Displays an internal layout for the use of a plugin canvas. + * Takes a variable number of parameters, which are made available + * in the views as $vars['area1'] .. $vars['areaN']. + * + * @param string $layout The name of the views in canvas/layouts/. + * @return string The layout + */ +function elgg_view_layout($layout) { + $arg = 1; + $param_array = array(); + while ($arg < func_num_args()) { + $param_array['area' . $arg] = func_get_arg($arg); + $arg++; + } + + if (elgg_view_exists("canvas/layouts/{$layout}")) { + return elgg_view("canvas/layouts/{$layout}",$param_array); + } else { + return elgg_view("canvas/default",$param_array); + } +} + +/** + * Returns a view for the page title + * + * @param string $title The page title + * @param string $submenu Should a submenu be displayed? (default false, use not recommended) + * @return string The HTML (etc) + */ +function elgg_view_title($title, $submenu = false) { + $title = elgg_view('page_elements/title', array('title' => $title, 'submenu' => $submenu)); + + return $title; +} + +/** + * Adds an item to the submenu + * + * @param string $label The human-readable label + * @param string $link The URL of the submenu item + * @param boolean $onclick Used to provide a JS popup to confirm delete + */ +function add_submenu_item($label, $link, $group = 'a', $onclick = false) { + global $CONFIG; + + if (!isset($CONFIG->submenu)) { + $CONFIG->submenu = array(); + } + if (!isset($CONFIG->submenu[$group])) { + $CONFIG->submenu[$group] = array(); + } + + $item = new stdClass; + $item->value = $link; + $item->name = $label; + $item->onclick = $onclick; + $CONFIG->submenu[$group][] = $item; +} + +/** + * Gets a formatted list of submenu items + * + * @return string List of items + */ +function get_submenu() { + $submenu_total = ""; + global $CONFIG; + + if (isset($CONFIG->submenu) && $submenu_register = $CONFIG->submenu) { + $preselected = false; + $comparevals = array(); + $maxcompareval = 999999; + + //asort($submenu_register); + ksort($submenu_register); + + foreach($submenu_register as $groupname => $submenu_register_group) { + foreach($submenu_register_group as $key => $item) { + if (substr_count($item->value, $_SERVER['REQUEST_URI'])) { + $comparevals[$key] = levenshtein($item->value, $_SERVER['REQUEST_URI']); + if ($comparevals[$key] < $maxcompareval) { + $maxcompareval = $comparevals[$key]; + $preselected = $key; + $preselectedgroup = $groupname; } } } - - - } - - /** - * Returns a representation of a full 'page' (which might be an HTML page, RSS file, etc, depending on the current view) - * - * @param unknown_type $title - * @param unknown_type $body - * @return unknown - */ - function page_draw($title, $body, $sidebar = "") { - - // Draw the page - $output = elgg_view('pageshells/pageshell', array( - 'title' => $title, - 'body' => $body, - 'sidebar' => $sidebar, - 'sysmessages' => system_messages(null,"") - ) - ); - $split_output = str_split($output, 1024); - - foreach($split_output as $chunk) - echo $chunk; - } - - /** - * Displays a UNIX timestamp in a friendly way (eg "less than a minute ago") - * - * @param int $time A UNIX epoch timestamp - * @return string The friendly time - */ - function friendly_time($time) { - - $diff = time() - ((int) $time); - if ($diff < 60) { - return elgg_echo("friendlytime:justnow"); - } else if ($diff < 3600) { - $diff = round($diff / 60); - if ($diff == 0) $diff = 1; - if ($diff > 1) - return sprintf(elgg_echo("friendlytime:minutes"),$diff); - return sprintf(elgg_echo("friendlytime:minutes:singular"),$diff); - } else if ($diff < 86400) { - $diff = round($diff / 3600); - if ($diff == 0) $diff = 1; - if ($diff > 1) - return sprintf(elgg_echo("friendlytime:hours"),$diff); - return sprintf(elgg_echo("friendlytime:hours:singular"),$diff); - } else { - $diff = round($diff / 86400); - if ($diff == 0) $diff = 1; - if ($diff > 1) - return sprintf(elgg_echo("friendlytime:days"),$diff); - return sprintf(elgg_echo("friendlytime:days:singular"),$diff); - } - } - /** - * When given a title, returns a version suitable for inclusion in a URL - * - * @param string $title The title - * @return string The optimised title - */ - function friendly_title($title) { - $title = trim($title); - $title = strtolower($title); - $title = preg_replace("/[^\w ]/","",$title); - $title = str_replace(" ","-",$title); - $title = str_replace("--","-",$title); - return $title; - } + foreach($submenu_register as $groupname => $submenu_register_group) { + $submenu = ""; - /** - * Library loading and handling - */ - - /** - * Recursive function designed to load library files on start - * (NB: this does not include plugins.) - * - * @param string $directory Full path to the directory to start with - * @param string $file_exceptions A list of filenames (with no paths) you don't ever want to include - * @param string $file_list A list of files that you know already you want to include - * @return array Array of full filenames - */ - function get_library_files($directory, $file_exceptions = array(), $file_list = array()) { - $extensions_allowed = array('.php'); - /*if (is_file($directory) && !in_array($directory,$file_exceptions)) { - $file_list[] = $directory; - } else */ - if ($handle = opendir($directory)) { - while ($file = readdir($handle)) { - if (in_array(strrchr($file, '.'), $extensions_allowed) && !in_array($file,$file_exceptions)) { - $file_list[] = $directory . "/" . $file; - //$file_list = get_library_files($directory . "/" . $file, $file_exceptions, $file_list); + foreach($submenu_register_group as $key => $item) { + if ($preselected === false) { + if (substr_count($item->value, $_SERVER['REQUEST_URI'])) { + $preselected = $key; + $preselectedgroup = $groupname; + $selected = true; + } else { + $selected = false; + } + } else { + if ($key == $preselected && $groupname == $preselectedgroup) { + $selected = true; + } else { + $selected = false; } } + + $submenu .= elgg_view('canvas_header/submenu_template', + array( + 'href' => $item->value, + 'label' => $item->name, + 'onclick' => $item->onclick, + 'selected' => $selected, + )); + } - - return $file_list; - + + $submenu_total .= elgg_view('canvas_header/submenu_group', array( + 'submenu' => $submenu, + 'group_name' => $groupname + )); + } - - /** - * Ensures that the installation has all the correct files, that PHP is configured correctly, and so on. - * Leaves appropriate messages in the error register if not. - * - * @return true|false True if everything is ok (or Elgg is fit enough to run); false if not. - */ - function sanitised() { - - $sanitised = true; - - if (!file_exists(dirname(dirname(__FILE__)) . "/settings.php")) { - // See if we are being asked to save the file - $save_vars = get_input('db_install_vars'); - $result = ""; - if ($save_vars) - { - $result = create_settings($save_vars, dirname(dirname(__FILE__)) . "/settings.example.php"); - - if (file_put_contents(dirname(dirname(__FILE__)) . "/settings.php", $result)) - $result = ""; // blank result to stop it being displayed in textarea - + } + + return $submenu_total; +} + + +/** + * Automatically views comments and a comment form relating to the given entity + * + * @param ElggEntity $entity The entity to comment on + * @return string|false The HTML (etc) for the comments, or false on failure + */ +function elgg_view_comments($entity){ + + if (!($entity instanceof ElggEntity)) { + return false; + } + + if ($comments = trigger_plugin_hook('comments',$entity->getType(),array('entity' => $entity),false)) { + return $comments; + } else { + $comments = list_annotations($entity->getGUID(),'generic_comment'); + + //display the comment form + $comments .= elgg_view('comments/forms/edit',array('entity' => $entity)); + + return $comments; + } +} + +/** + * Count the number of comments attached to an entity + * + * @param ElggEntity $entity + * @return int Number of comments + */ +function elgg_count_comments($entity) { + if ($commentno = trigger_plugin_hook('comments:count', $entity->getType(), + array('entity' => $entity), false)) { + return $commentno; + } else { + return count_annotations($entity->getGUID(), "", "", "generic_comment"); + } +} + +/** + * Wrapper function to display search listings. + * + * @param string $icon The icon for the listing + * @param string $info Any information that needs to be displayed. + * @return string The HTML (etc) representing the listing + */ +function elgg_view_listing($icon, $info) { + return elgg_view('search/listing',array('icon' => $icon, 'info' => $info)); +} + +/** + * Sets an alternative function to handle templates, which will be passed to by elgg_view. + * This function must take the $view and $vars parameters from elgg_view: + * + * function my_template_function(string $view, array $vars = array()) + * + * @see elgg_view + * + * @param string $function_name The name of the function to pass to. + * @return true|false + */ +function set_template_handler($function_name) { + global $CONFIG; + if (!empty($function_name) && is_callable($function_name)) { + $CONFIG->template_handler = $function_name; + return true; + } + return false; +} + +/** + * Extends a view by adding other views to be displayed at the same time. + * + * @param string $view The view to add to. + * @param string $view_name The name of the view to extend + * @param int $priority The priority, from 0 to 1000, to add at (lowest numbers will be displayed first) + */ +function extend_view($view, $view_name, $priority = 501, $viewtype = '') { + global $CONFIG; + + if (!isset($CONFIG->views)) { + $CONFIG->views = new stdClass; + } + + if (!isset($CONFIG->views->extensions)) { + $CONFIG->views->extensions = array(); + } + + if (!isset($CONFIG->views->extensions[$view])) { + $CONFIG->views->extensions[$view][500] = "{$view}"; + } + + while(isset($CONFIG->views->extensions[$view][$priority])) { + $priority++; + } + + $CONFIG->views->extensions[$view][$priority] = "{$view_name}"; + ksort($CONFIG->views->extensions[$view]); +} + +/** + * Set an alternative base location for a view (as opposed to the default of $CONFIG->viewpath) + * + * @param string $view The name of the view + * @param string $location The base location path + */ +function set_view_location($view, $location, $viewtype = '') { + global $CONFIG; + + if (empty($viewtype)) { + $viewtype = 'default'; + } + + if (!isset($CONFIG->views)) { + $CONFIG->views = new stdClass; + } + + if (!isset($CONFIG->views->locations)) { + $CONFIG->views->locations = array($viewtype => array($view => $location)); + + } else if (!isset($CONFIG->views->locations[$viewtype])) { + $CONFIG->views->locations[$viewtype] = array($view => $location); + + } else { + $CONFIG->views->locations[$viewtype][$view] = $location; + } +} + +/** + * Auto-registers views from a particular starting location + * + * @param string $view_base The base of the view name + * @param string $folder The folder to begin looking in + * @param string $base_location_path The base views directory to use with set_view_location + * @param string $viewtype The type of view we're looking at (default, rss, etc) + */ +function autoregister_views($view_base, $folder, $base_location_path, $viewtype) { + if (!isset($i)) { + $i = 0; + } + + if ($handle = opendir($folder)) { + while ($view = readdir($handle)) { + if (!in_array($view,array('.','..','.svn','CVS')) && !is_dir($folder . "/" . $view)) { + if ((substr_count($view,".php") > 0) || (substr_count($view,".png") > 0)) { + if (!empty($view_base)) { + $view_base_new = $view_base . "/"; + } else { + $view_base_new = ""; + } + + set_view_location($view_base_new . str_replace(".php","",$view), $base_location_path, $viewtype); } - - // Recheck to see if the file is still missing - if (!file_exists(dirname(dirname(__FILE__)) . "/settings.php")) { - register_error(elgg_view("messages/sanitisation/settings", array('settings.php' => $result))); - $sanitised = false; + } else if (!in_array($view,array('.','..','.svn','CVS')) && is_dir($folder . "/" . $view)) { + if (!empty($view_base)) { + $view_base_new = $view_base . "/"; + } else { + $view_base_new = ""; } + autoregister_views($view_base_new . $view, $folder . "/" . $view, $base_location_path, $viewtype); } + } + } +} - if (!file_exists(dirname(dirname(dirname(__FILE__))) . "/.htaccess")) { - if (!@copy(dirname(dirname(dirname(__FILE__))) . "/htaccess_dist", dirname(dirname(dirname(__FILE__))) . "/.htaccess")) { - register_error(elgg_view("messages/sanitisation/htaccess", array('.htaccess' => file_get_contents(dirname(dirname(dirname(__FILE__))) . "/htaccess_dist")))); - $sanitised = false; - } - } - - return $sanitised; - +/** + * Returns a representation of a full 'page' (which might be an HTML page, RSS file, etc, depending on the current view) + * + * @param unknown_type $title + * @param unknown_type $body + * @return unknown + */ +function page_draw($title, $body, $sidebar = "") { + + // Draw the page + $output = elgg_view('pageshells/pageshell', array( + 'title' => $title, + 'body' => $body, + 'sidebar' => $sidebar, + 'sysmessages' => system_messages(null,"") + ) + ); + $split_output = str_split($output, 1024); + + foreach($split_output as $chunk) { + echo $chunk; + } +} + +/** + * Displays a UNIX timestamp in a friendly way (eg "less than a minute ago") + * + * @param int $time A UNIX epoch timestamp + * @return string The friendly time + */ +function friendly_time($time) { + $diff = time() - ((int) $time); + + if ($diff < 60) { + return elgg_echo("friendlytime:justnow"); + } else if ($diff < 3600) { + $diff = round($diff / 60); + if ($diff == 0) { + $diff = 1; } - - /** - * Registers - */ - - /** - * Adds an array with a name to a given generic array register. - * For example, these are used for menus. - * - * @param string $register_name The name of the top-level register - * @param string $subregister_name The name of the subregister - * @param mixed $subregister_value The value of the subregister - * @param array $children_array Optionally, an array of children - * @return true|false Depending on success - */ - function add_to_register($register_name, $subregister_name, $subregister_value, $children_array = array()) { - - global $CONFIG; - - if (empty($register_name) || empty($subregister_name)) - return false; - - if (!isset($CONFIG->registers)) - $CONFIG->registers = array(); - - if (!isset($CONFIG->registers[$register_name])) - $CONFIG->registers[$register_name] = array(); - - $subregister = new stdClass; - $subregister->name = $subregister_name; - $subregister->value = $subregister_value; - - if (is_array($children_array)) { - $subregister->children = $children_array; - } - - $CONFIG->registers[$register_name][$subregister_name] = $subregister; - return true; - + if ($diff > 1) { + return sprintf(elgg_echo("friendlytime:minutes"),$diff); } - - /** - * Returns a register object - * - * @param string $register_name The name of the register - * @param mixed $register_value The value of the register - * @param array $children_array Optionally, an array of children - * @return false|stdClass Depending on success - */ - function make_register_object($register_name, $register_value, $children_array = array()) { - - if (empty($register_name) || empty($register_value)) - return false; - - $register = new stdClass; - $register->name = $register_name; - $register->value = $register_value; - $register->children = $children_array; - - return $register; - + return sprintf(elgg_echo("friendlytime:minutes:singular"),$diff); + } else if ($diff < 86400) { + $diff = round($diff / 3600); + if ($diff == 0) { + $diff = 1; } - - /** - * If it exists, returns a particular register as an array - * - * @param string $register_name The name of the register - * @return array|false Depending on success - */ - function get_register($register_name) { - - global $CONFIG; - - if (isset($CONFIG->registers[$register_name])) - return $CONFIG->registers[$register_name]; - - return false; - + if ($diff > 1) { + return sprintf(elgg_echo("friendlytime:hours"),$diff); } - - /** - * Adds an item to the menu register - * - * @param string $menu_name The name of the top-level menu - * @param string $menu_url The URL of the page - * @param array $menu_children Optionally, an array of submenu items - * @return true|false Depending on success - */ - function add_menu($menu_name, $menu_url, $menu_children = array(), $context = "") { - global $CONFIG; - if (!isset($CONFIG->menucontexts)) { - $CONFIG->menucontexts = array(); - } - - if (empty($context)) { - $context = get_plugin_name(); - } - $CONFIG->menucontexts[] = $context; - return add_to_register('menu',$menu_name,$menu_url, $menu_children); + return sprintf(elgg_echo("friendlytime:hours:singular"),$diff); + } else { + $diff = round($diff / 86400); + if ($diff == 0) { + $diff = 1; } - - /** - * Returns a menu item for use in the children section of add_menu() - * - * @param string $menu_name The name of the menu item - * @param string $menu_url Its URL - * @return stdClass|false Depending on success - */ - function menu_item($menu_name, $menu_url) { - return make_register_object($menu_name, $menu_url); + if ($diff > 1) { + return sprintf(elgg_echo("friendlytime:days"),$diff); } - - - /** - * Message register handling - * If no parameter is given, the function returns the array of messages so far and empties it. - * Otherwise, any message or array of messages is added. - * - * @param string|array $message Optionally, a single message or array of messages to add - * @param string $register By default, "errors". This allows for different types of messages, eg errors. - * @return true|false|array Either the array of messages, or a response regarding whether the message addition was successful - */ - - function system_messages($message = "", $register = "messages", $count = false) { - - if (!isset($_SESSION['msg'])) { - $_SESSION['msg'] = array(); - } - if (!isset($_SESSION['msg'][$register]) && !empty($register)) { - $_SESSION['msg'][$register] = array(); - } - if (!$count) { - if (!empty($message) && is_array($message)) { - $_SESSION['msg'][$register] = array_merge($_SESSION['msg'][$register], $message); - var_export($_SESSION['msg']); exit; - return true; - } else if (!empty($message) && is_string($message)) { - $_SESSION['msg'][$register][] = $message; - return true; - } else if (is_null($message)) { - if ($register != "") { - $returnarray = $_SESSION['msg'][$register]; - $_SESSION['msg'][$register] = array(); - } else { - $returnarray = $_SESSION['msg']; - $_SESSION['msg'] = array(); - } - return $returnarray; - } - } else { - if (!empty($register)) { - return sizeof($_SESSION['msg'][$register]); - } else { - $count = 0; - foreach($_SESSION['msg'] as $register => $submessages) { - $count += sizeof($submessages); - } - return $count; - } + + return sprintf(elgg_echo("friendlytime:days:singular"),$diff); + } +} + +/** + * When given a title, returns a version suitable for inclusion in a URL + * + * @param string $title The title + * @return string The optimised title + */ +function friendly_title($title) { + $title = trim($title); + $title = strtolower($title); + $title = preg_replace("/[^\w ]/","",$title); + $title = str_replace(" ","-",$title); + $title = str_replace("--","-",$title); + return $title; +} + +/** + * Library loading and handling + */ + +/** + * Recursive function designed to load library files on start + * (NB: this does not include plugins.) + * + * @param string $directory Full path to the directory to start with + * @param string $file_exceptions A list of filenames (with no paths) you don't ever want to include + * @param string $file_list A list of files that you know already you want to include + * @return array Array of full filenames + */ +function get_library_files($directory, $file_exceptions = array(), $file_list = array()) { + $extensions_allowed = array('.php'); + /*if (is_file($directory) && !in_array($directory,$file_exceptions)) { + $file_list[] = $directory; + } else */ + if ($handle = opendir($directory)) { + while ($file = readdir($handle)) { + if (in_array(strrchr($file, '.'), $extensions_allowed) && !in_array($file,$file_exceptions)) { + $file_list[] = $directory . "/" . $file; + //$file_list = get_library_files($directory . "/" . $file, $file_exceptions, $file_list); } - return false; - } - - /** - * Counts the number of messages, either globally or in a particular register - * - * @param string $register Optionally, the register - * @return integer The number of messages - */ - function count_messages($register = "") { - return system_messages(null,$register,true); + } + + return $file_list; +} + +/** + * Ensures that the installation has all the correct files, that PHP is configured correctly, and so on. + * Leaves appropriate messages in the error register if not. + * + * @return true|false True if everything is ok (or Elgg is fit enough to run); false if not. + */ +function sanitised() { + $sanitised = true; + + if (!file_exists(dirname(dirname(__FILE__)) . "/settings.php")) { + // See if we are being asked to save the file + $save_vars = get_input('db_install_vars'); + $result = ""; + if ($save_vars) { + $result = create_settings($save_vars, dirname(dirname(__FILE__)) . "/settings.example.php"); + + if (file_put_contents(dirname(dirname(__FILE__)) . "/settings.php", $result)) { + // blank result to stop it being displayed in textarea + $result = ""; + } } - - /** - * An alias for system_messages($message) to handle standard user information messages - * - * @param string|array $message Message or messages to add - * @return true|false Success response - */ - function system_message($message) { - return system_messages($message, "messages"); + + // Recheck to see if the file is still missing + if (!file_exists(dirname(dirname(__FILE__)) . "/settings.php")) { + register_error(elgg_view("messages/sanitisation/settings", array('settings.php' => $result))); + $sanitised = false; } - - /** - * An alias for system_messages($message) to handle error messages - * - * @param string|array $message Error or errors to add - * @return true|false Success response - */ - function register_error($error) { - return system_messages($error, "errors"); + } + + if (!file_exists(dirname(dirname(dirname(__FILE__))) . "/.htaccess")) { + if (!@copy(dirname(dirname(dirname(__FILE__))) . "/htaccess_dist", dirname(dirname(dirname(__FILE__))) . "/.htaccess")) { + register_error(elgg_view("messages/sanitisation/htaccess", array('.htaccess' => file_get_contents(dirname(dirname(dirname(__FILE__))) . "/htaccess_dist")))); + $sanitised = false; } + } - /** - * Event register - * Adds functions to the register for a particular event, but also calls all functions registered to an event when required - * - * Event handler functions must be of the form: - * - * event_handler_function($event, $object_type, $object); - * - * And must return true or false depending on success. A false will halt the event in its tracks and no more functions will be called. - * - * You can then simply register them using the following function. Optionally, this can be called with a priority nominally from 0 to 1000, where functions with lower priority values are called first (note that priorities CANNOT be negative): - * - * register_elgg_event_handler($event, $object_type, $function_name [, $priority = 500]); - * - * Note that you can also use 'all' in place of both the event and object type. - * - * To trigger an event properly, you should always use: - * - * trigger_elgg_event($event, $object_type [, $object]); - * - * Where $object is optional, and represents the $object_type the event concerns. This will return true if successful, or false if it fails. - * - * @param string $event The type of event (eg 'init', 'update', 'delete') - * @param string $object_type The type of object (eg 'system', 'blog', 'user') - * @param string $function The name of the function that will handle the event - * @param int $priority A priority to add new event handlers at. Lower numbers will be called first (default 500) - * @param boolean $call Set to true to call the event rather than add to it (default false) - * @param mixed $object Optionally, the object the event is being performed on (eg a user) - * @return true|false Depending on success - */ - - function events($event = "", $object_type = "", $function = "", $priority = 500, $call = false, $object = null) { - - global $CONFIG; - - if (!isset($CONFIG->events)) { - $CONFIG->events = array(); - } else if (!isset($CONFIG->events[$event]) && !empty($event)) { - $CONFIG->events[$event] = array(); - } else if (!isset($CONFIG->events[$event][$object_type]) && !empty($event) && !empty($object_type)) { - $CONFIG->events[$event][$object_type] = array(); - } - - if (!$call) { - - if (!empty($event) && !empty($object_type) && is_callable($function)) { - $priority = (int) $priority; - if ($priority < 0) $priority = 0; - while (isset($CONFIG->events[$event][$object_type][$priority])) { - $priority++; - } - $CONFIG->events[$event][$object_type][$priority] = $function; - ksort($CONFIG->events[$event][$object_type]); - return true; - } else { - return false; - } - + return $sanitised; +} + +/** + * Registers + */ + +/** + * Adds an array with a name to a given generic array register. + * For example, these are used for menus. + * + * @param string $register_name The name of the top-level register + * @param string $subregister_name The name of the subregister + * @param mixed $subregister_value The value of the subregister + * @param array $children_array Optionally, an array of children + * @return true|false Depending on success + */ +function add_to_register($register_name, $subregister_name, $subregister_value, $children_array = array()) { + global $CONFIG; + + if (empty($register_name) || empty($subregister_name)) { + return false; + } + + if (!isset($CONFIG->registers)) { + $CONFIG->registers = array(); + } + + if (!isset($CONFIG->registers[$register_name])) { + $CONFIG->registers[$register_name] = array(); + } + + $subregister = new stdClass; + $subregister->name = $subregister_name; + $subregister->value = $subregister_value; + + if (is_array($children_array)) { + $subregister->children = $children_array; + } + + $CONFIG->registers[$register_name][$subregister_name] = $subregister; + return true; +} + +/** + * Returns a register object + * + * @param string $register_name The name of the register + * @param mixed $register_value The value of the register + * @param array $children_array Optionally, an array of children + * @return false|stdClass Depending on success + */ +function make_register_object($register_name, $register_value, $children_array = array()) { + if (empty($register_name) || empty($register_value)) { + return false; + } + + $register = new stdClass; + $register->name = $register_name; + $register->value = $register_value; + $register->children = $children_array; + + return $register; +} + +/** + * If it exists, returns a particular register as an array + * + * @param string $register_name The name of the register + * @return array|false Depending on success + */ +function get_register($register_name) { + global $CONFIG; + + if (isset($CONFIG->registers[$register_name])) { + return $CONFIG->registers[$register_name]; + } + + return false; +} + +/** + * Adds an item to the menu register + * + * @param string $menu_name The name of the top-level menu + * @param string $menu_url The URL of the page + * @param array $menu_children Optionally, an array of submenu items + * @return true|false Depending on success + */ +function add_menu($menu_name, $menu_url, $menu_children = array(), $context = "") { + global $CONFIG; + if (!isset($CONFIG->menucontexts)) { + $CONFIG->menucontexts = array(); + } + + if (empty($context)) { + $context = get_plugin_name(); + } + + $CONFIG->menucontexts[] = $context; + return add_to_register('menu', $menu_name, $menu_url, $menu_children); +} + +/** + * Returns a menu item for use in the children section of add_menu() + * + * @param string $menu_name The name of the menu item + * @param string $menu_url Its URL + * @return stdClass|false Depending on success + */ +function menu_item($menu_name, $menu_url) { + return make_register_object($menu_name, $menu_url); +} + + +/** + * Message register handling + * If no parameter is given, the function returns the array of messages so far and empties it. + * Otherwise, any message or array of messages is added. + * + * @param string|array $message Optionally, a single message or array of messages to add + * @param string $register By default, "errors". This allows for different types of messages, eg errors. + * @return true|false|array Either the array of messages, or a response regarding whether the message addition was successful + */ + +function system_messages($message = "", $register = "messages", $count = false) { + if (!isset($_SESSION['msg'])) { + $_SESSION['msg'] = array(); + } + if (!isset($_SESSION['msg'][$register]) && !empty($register)) { + $_SESSION['msg'][$register] = array(); + } + if (!$count) { + if (!empty($message) && is_array($message)) { + $_SESSION['msg'][$register] = array_merge($_SESSION['msg'][$register], $message); + var_export($_SESSION['msg']); exit; + return true; + } else if (!empty($message) && is_string($message)) { + $_SESSION['msg'][$register][] = $message; + return true; + } else if (is_null($message)) { + if ($register != "") { + $returnarray = $_SESSION['msg'][$register]; + $_SESSION['msg'][$register] = array(); } else { - - $return = true; - if (!empty($CONFIG->events[$event][$object_type]) && is_array($CONFIG->events[$event][$object_type])) { - foreach($CONFIG->events[$event][$object_type] as $eventfunction) { - if ($eventfunction($event, $object_type, $object) === false) { - return false; - //$return = false; - //break; - } - } - } - - if (!empty($CONFIG->events['all'][$object_type]) && is_array($CONFIG->events['all'][$object_type])) { - foreach($CONFIG->events['all'][$object_type] as $eventfunction) { - if ($eventfunction($event, $object_type, $object) === false) { - return false; - //$return = false; - //break; - } - } - } - - if (!empty($CONFIG->events[$event]['all']) && is_array($CONFIG->events[$event]['all'])) { - foreach($CONFIG->events[$event]['all'] as $eventfunction) { - if ($eventfunction($event, $object_type, $object) === false) { - return false; - //$return = false; - //break; - } - } - } - - if (!empty($CONFIG->events['all']['all']) && is_array($CONFIG->events['all']['all'])) { - foreach($CONFIG->events['all']['all'] as $eventfunction) { - if ($eventfunction($event, $object_type, $object) === false) { - return false; - //$return = false; - //break; - } - } - } - return $return; - + $returnarray = $_SESSION['msg']; + $_SESSION['msg'] = array(); } - - return false; - - } - - /** - * Alias function for events, that registers a function to a particular kind of event - * - * @param string $event The event type - * @param string $object_type The object type - * @param string $function The function name - * @return true|false Depending on success - */ - function register_elgg_event_handler($event, $object_type, $function, $priority = 500) { - return events($event, $object_type, $function, $priority); + return $returnarray; } - - /** - * Alias function for events, that triggers a particular kind of event - * - * @param string $event The event type - * @param string $object_type The object type - * @param string $function The function name - * @return true|false Depending on success - */ - function trigger_elgg_event($event, $object_type, $object = null) { - $return = true; - $return1 = events($event, $object_type, "", null, true, $object); - if (!is_null($return1)) $return = $return1; - return $return; + } else { + if (!empty($register)) { + return sizeof($_SESSION['msg'][$register]); + } else { + $count = 0; + foreach($_SESSION['msg'] as $register => $submessages) { + $count += sizeof($submessages); + } + return $count; } - - /** - * Register a function to a plugin hook for a particular entity type, with a given priority. - * - * eg if you want the function "export_user" to be called when the hook "export" for "user" entities - * is run, use: - * - * register_plugin_hook("export", "user", "export_user"); - * - * "all" is a valid value for both $hook and $entity_type. "none" is a valid value for $entity_type. - * - * The export_user function would then be defined as: - * - * function export_user($hook, $entity_type, $returnvalue, $params); - * - * Where $returnvalue is the return value returned by the last function returned by the hook, and - * $params is an array containing a set of parameters (or nothing). - * - * @param string $hook The name of the hook - * @param string $entity_type The name of the type of entity (eg "user", "object" etc) - * @param string $function The name of a valid function to be run - * @param string $priority The priority - 0 is first, 1000 last, default is 500 - * @return true|false Depending on success - */ - function register_plugin_hook($hook, $entity_type, $function, $priority = 500) { - global $CONFIG; - - if (!isset($CONFIG->hooks)) { - $CONFIG->hooks = array(); - } else if (!isset($CONFIG->hooks[$hook]) && !empty($hook)) { - $CONFIG->hooks[$hook] = array(); - } else if (!isset($CONFIG->hooks[$hook][$entity_type]) && !empty($entity_type)) { - $CONFIG->hooks[$hook][$entity_type] = array(); + } + return false; +} + +/** + * Counts the number of messages, either globally or in a particular register + * + * @param string $register Optionally, the register + * @return integer The number of messages + */ +function count_messages($register = "") { + return system_messages(null,$register,true); +} + +/** + * An alias for system_messages($message) to handle standard user information messages + * + * @param string|array $message Message or messages to add + * @return true|false Success response + */ +function system_message($message) { + return system_messages($message, "messages"); +} + +/** + * An alias for system_messages($message) to handle error messages + * + * @param string|array $message Error or errors to add + * @return true|false Success response + */ +function register_error($error) { + return system_messages($error, "errors"); +} + +/** + * Event register + * Adds functions to the register for a particular event, but also calls all functions registered to an event when required + * + * Event handler functions must be of the form: + * + * event_handler_function($event, $object_type, $object); + * + * And must return true or false depending on success. A false will halt the event in its tracks and no more functions will be called. + * + * You can then simply register them using the following function. Optionally, this can be called with a priority nominally from 0 to 1000, where functions with lower priority values are called first (note that priorities CANNOT be negative): + * + * register_elgg_event_handler($event, $object_type, $function_name [, $priority = 500]); + * + * Note that you can also use 'all' in place of both the event and object type. + * + * To trigger an event properly, you should always use: + * + * trigger_elgg_event($event, $object_type [, $object]); + * + * Where $object is optional, and represents the $object_type the event concerns. This will return true if successful, or false if it fails. + * + * @param string $event The type of event (eg 'init', 'update', 'delete') + * @param string $object_type The type of object (eg 'system', 'blog', 'user') + * @param string $function The name of the function that will handle the event + * @param int $priority A priority to add new event handlers at. Lower numbers will be called first (default 500) + * @param boolean $call Set to true to call the event rather than add to it (default false) + * @param mixed $object Optionally, the object the event is being performed on (eg a user) + * @return true|false Depending on success + */ +function events($event = "", $object_type = "", $function = "", $priority = 500, $call = false, $object = null) { + global $CONFIG; + + if (!isset($CONFIG->events)) { + $CONFIG->events = array(); + } else if (!isset($CONFIG->events[$event]) && !empty($event)) { + $CONFIG->events[$event] = array(); + } else if (!isset($CONFIG->events[$event][$object_type]) && !empty($event) && !empty($object_type)) { + $CONFIG->events[$event][$object_type] = array(); + } + + if (!$call) { + if (!empty($event) && !empty($object_type) && is_callable($function)) { + $priority = (int) $priority; + if ($priority < 0) { + $priority = 0; } - - if (!empty($hook) && !empty($entity_type) && is_callable($function)) { - $priority = (int) $priority; - if ($priority < 0) $priority = 0; - while (isset($CONFIG->hooks[$hook][$entity_type][$priority])) { - $priority++; - } - $CONFIG->hooks[$hook][$entity_type][$priority] = $function; - ksort($CONFIG->hooks[$hook][$entity_type]); - return true; - } else { - return false; + while (isset($CONFIG->events[$event][$object_type][$priority])) { + $priority++; } - + $CONFIG->events[$event][$object_type][$priority] = $function; + ksort($CONFIG->events[$event][$object_type]); + return true; + } else { + return false; } - - /** - * Triggers a plugin hook, with various parameters as an array. For example, to provide - * a 'foo' hook that concerns an entity of type 'bar', with a parameter called 'param1' - * with value 'value1', that by default returns true, you'd call: - * - * trigger_plugin_hook('foo', 'bar', array('param1' => 'value1'), true); - * - * @see register_plugin_hook - * @param string $hook The name of the hook to trigger - * @param string $entity_type The name of the entity type to trigger it for (or "all", or "none") - * @param array $params Any parameters. It's good practice to name the keys, i.e. by using array('name' => 'value', 'name2' => 'value2') - * @param mixed $returnvalue An initial return value - * @return mixed|null The cumulative return value for the plugin hook functions - */ - function trigger_plugin_hook($hook, $entity_type, $params = null, $returnvalue = null) { - global $CONFIG; - - //if (!isset($CONFIG->hooks) || !isset($CONFIG->hooks[$hook]) || !isset($CONFIG->hooks[$hook][$entity_type])) - // return $returnvalue; - - if (!empty($CONFIG->hooks[$hook][$entity_type]) && is_array($CONFIG->hooks[$hook][$entity_type])) { - foreach($CONFIG->hooks[$hook][$entity_type] as $hookfunction) { - - $temp_return_value = $hookfunction($hook, $entity_type, $returnvalue, $params); - if (!is_null($temp_return_value)) $returnvalue = $temp_return_value; + } else { + $return = true; + if (!empty($CONFIG->events[$event][$object_type]) && is_array($CONFIG->events[$event][$object_type])) { + foreach($CONFIG->events[$event][$object_type] as $eventfunction) { + if ($eventfunction($event, $object_type, $object) === false) { + return false; } } - //else - //if (!isset($CONFIG->hooks['all'][$entity_type])) - // return $returnvalue; - - if (!empty($CONFIG->hooks['all'][$entity_type]) && is_array($CONFIG->hooks['all'][$entity_type])) { - foreach($CONFIG->hooks['all'][$entity_type] as $hookfunction) { - - $temp_return_value = $hookfunction($hook, $entity_type, $returnvalue, $params); - if (!is_null($temp_return_value)) $returnvalue = $temp_return_value; + } + + if (!empty($CONFIG->events['all'][$object_type]) && is_array($CONFIG->events['all'][$object_type])) { + foreach($CONFIG->events['all'][$object_type] as $eventfunction) { + if ($eventfunction($event, $object_type, $object) === false) { + return false; } } - //else - //if (!isset($CONFIG->hooks[$hook]['all'])) - // return $returnvalue; - - if (!empty($CONFIG->hooks[$hook]['all']) && is_array($CONFIG->hooks[$hook]['all'])) { - foreach($CONFIG->hooks[$hook]['all'] as $hookfunction) { - - $temp_return_value = $hookfunction($hook, $entity_type, $returnvalue, $params); - if (!is_null($temp_return_value)) $returnvalue = $temp_return_value; + } + + if (!empty($CONFIG->events[$event]['all']) && is_array($CONFIG->events[$event]['all'])) { + foreach($CONFIG->events[$event]['all'] as $eventfunction) { + if ($eventfunction($event, $object_type, $object) === false) { + return false; } } - //else - //if (!isset($CONFIG->hooks['all']['all'])) - // return $returnvalue; - - if (!empty($CONFIG->hooks['all']['all']) && is_array($CONFIG->hooks['all']['all'])) { - foreach($CONFIG->hooks['all']['all'] as $hookfunction) { - - $temp_return_value = $hookfunction($hook, $entity_type, $returnvalue, $params); - if (!is_null($temp_return_value)) $returnvalue = $temp_return_value; + } + + if (!empty($CONFIG->events['all']['all']) && is_array($CONFIG->events['all']['all'])) { + foreach($CONFIG->events['all']['all'] as $eventfunction) { + if ($eventfunction($event, $object_type, $object) === false) { + return false; } } - - return $returnvalue; } - - /** - * Error handling - */ - - /** - * PHP Error handler function. - * This function acts as a wrapper to catch and report PHP error messages. - * - * @see http://www.php.net/set-error-handler - * @param int $errno The level of the error raised - * @param string $errmsg The error message - * @param string $filename The filename the error was raised in - * @param int $linenum The line number the error was raised at - * @param array $vars An array that points to the active symbol table at the point that the error occurred - */ - function __elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) - { - $error = date("Y-m-d H:i:s (T)") . ": \"" . $errmsg . "\" in file " . $filename . " (line " . $linenum . ")"; - - switch ($errno) { - case E_USER_ERROR: - error_log("ERROR: " . $error); - register_error("ERROR: " . $error); - - // Since this is a fatal error, we want to stop any further execution but do so gracefully. - throw new Exception($error); - break; - - case E_WARNING : - case E_USER_WARNING : - error_log("WARNING: " . $error); - // register_error("WARNING: " . $error); - break; - - default: - global $CONFIG; - if (isset($CONFIG->debug)) { - error_log("DEBUG: " . $error); - } - // register_error("DEBUG: " . $error); + + return $return; + + } + + return false; +} + +/** + * Alias function for events, that registers a function to a particular kind of event + * + * @param string $event The event type + * @param string $object_type The object type + * @param string $function The function name + * @return true|false Depending on success + */ +function register_elgg_event_handler($event, $object_type, $function, $priority = 500) { + return events($event, $object_type, $function, $priority); +} + +/** + * Alias function for events, that triggers a particular kind of event + * + * @param string $event The event type + * @param string $object_type The object type + * @param string $function The function name + * @return true|false Depending on success + */ +function trigger_elgg_event($event, $object_type, $object = null) { + $return = true; + $return1 = events($event, $object_type, "", null, true, $object); + if (!is_null($return1)) { + $return = $return1; + } + return $return; +} + +/** + * Register a function to a plugin hook for a particular entity type, with a given priority. + * + * eg if you want the function "export_user" to be called when the hook "export" for "user" entities + * is run, use: + * + * register_plugin_hook("export", "user", "export_user"); + * + * "all" is a valid value for both $hook and $entity_type. "none" is a valid value for $entity_type. + * + * The export_user function would then be defined as: + * + * function export_user($hook, $entity_type, $returnvalue, $params); + * + * Where $returnvalue is the return value returned by the last function returned by the hook, and + * $params is an array containing a set of parameters (or nothing). + * + * @param string $hook The name of the hook + * @param string $entity_type The name of the type of entity (eg "user", "object" etc) + * @param string $function The name of a valid function to be run + * @param string $priority The priority - 0 is first, 1000 last, default is 500 + * @return true|false Depending on success + */ +function register_plugin_hook($hook, $entity_type, $function, $priority = 500) { + global $CONFIG; + + if (!isset($CONFIG->hooks)) { + $CONFIG->hooks = array(); + } else if (!isset($CONFIG->hooks[$hook]) && !empty($hook)) { + $CONFIG->hooks[$hook] = array(); + } else if (!isset($CONFIG->hooks[$hook][$entity_type]) && !empty($entity_type)) { + $CONFIG->hooks[$hook][$entity_type] = array(); + } + + if (!empty($hook) && !empty($entity_type) && is_callable($function)) { + $priority = (int) $priority; + if ($priority < 0) { + $priority = 0; + } + while (isset($CONFIG->hooks[$hook][$entity_type][$priority])) { + $priority++; + } + $CONFIG->hooks[$hook][$entity_type][$priority] = $function; + ksort($CONFIG->hooks[$hook][$entity_type]); + return true; + } else { + return false; + } +} + +/** + * Triggers a plugin hook, with various parameters as an array. For example, to provide + * a 'foo' hook that concerns an entity of type 'bar', with a parameter called 'param1' + * with value 'value1', that by default returns true, you'd call: + * + * trigger_plugin_hook('foo', 'bar', array('param1' => 'value1'), true); + * + * @see register_plugin_hook + * @param string $hook The name of the hook to trigger + * @param string $entity_type The name of the entity type to trigger it for (or "all", or "none") + * @param array $params Any parameters. It's good practice to name the keys, i.e. by using array('name' => 'value', 'name2' => 'value2') + * @param mixed $returnvalue An initial return value + * @return mixed|null The cumulative return value for the plugin hook functions + */ +function trigger_plugin_hook($hook, $entity_type, $params = null, $returnvalue = null) { + global $CONFIG; + + //if (!isset($CONFIG->hooks) || !isset($CONFIG->hooks[$hook]) || !isset($CONFIG->hooks[$hook][$entity_type])) + // return $returnvalue; + + if (!empty($CONFIG->hooks[$hook][$entity_type]) && is_array($CONFIG->hooks[$hook][$entity_type])) { + foreach($CONFIG->hooks[$hook][$entity_type] as $hookfunction) { + $temp_return_value = $hookfunction($hook, $entity_type, $returnvalue, $params); + if (!is_null($temp_return_value)) { + $returnvalue = $temp_return_value; } - - return true; } - - /** - * Custom exception handler. - * This function catches any thrown exceptions and handles them appropriately. - * - * @see http://www.php.net/set-exception-handler - * @param Exception $exception The exception being handled - */ - - function __elgg_php_exception_handler($exception) { - - error_log("*** FATAL EXCEPTION *** : " . $exception); - - ob_end_clean(); // Wipe any existing output buffer - - $body = elgg_view("messages/exceptions/exception",array('object' => $exception)); - page_draw(elgg_echo('exception:title'), $body); - + } + //else + //if (!isset($CONFIG->hooks['all'][$entity_type])) + // return $returnvalue; + + if (!empty($CONFIG->hooks['all'][$entity_type]) && is_array($CONFIG->hooks['all'][$entity_type])) { + foreach($CONFIG->hooks['all'][$entity_type] as $hookfunction) { + $temp_return_value = $hookfunction($hook, $entity_type, $returnvalue, $params); + if (!is_null($temp_return_value)) $returnvalue = $temp_return_value; } - - /** - * Data lists - */ - - $DATALIST_CACHE = array(); - - /** - * Get the value of a particular piece of data in the datalist - * - * @param string $name The name of the datalist - * @return string|false Depending on success - */ - function datalist_get($name) { - - global $CONFIG, $DATALIST_CACHE; - - // We need this, because sometimes datalists are received before the database is created - if (!is_db_installed()) return false; - - $name = sanitise_string($name); - if (isset($DATALIST_CACHE[$name])) - return $DATALIST_CACHE[$name]; - - // If memcache enabled then cache value in memcache - $value = null; - static $datalist_memcache; - if ((!$datalist_memcache) && (is_memcache_available())) - $datalist_memcache = new ElggMemcache('datalist_memcache'); - if ($datalist_memcache) $value = $datalist_memcache->load($name); - if ($value) return $value; - - // [Marcus Povey 20090217 : Now retrieving all datalist values on first load as this saves about 9 queries per page] - $result = get_data("SELECT * from {$CONFIG->dbprefix}datalists"); - if ($result) - { - foreach ($result as $row) - { - $DATALIST_CACHE[$row->name] = $row->value; - - // Cache it if memcache is available - if ($datalist_memcache) $datalist_memcache->save($row->name, $row->value); - } - - if (isset($DATALIST_CACHE[$name])) - return $DATALIST_CACHE[$name]; + } + //else + //if (!isset($CONFIG->hooks[$hook]['all'])) + // return $returnvalue; + + if (!empty($CONFIG->hooks[$hook]['all']) && is_array($CONFIG->hooks[$hook]['all'])) { + foreach($CONFIG->hooks[$hook]['all'] as $hookfunction) { + $temp_return_value = $hookfunction($hook, $entity_type, $returnvalue, $params); + if (!is_null($temp_return_value)) { + $returnvalue = $temp_return_value; } - - - /*if ($row = get_data_row("SELECT value from {$CONFIG->dbprefix}datalists where name = '{$name}' limit 1")) { - $DATALIST_CACHE[$name] = $row->value; - - // Cache it if memcache is available - if ($datalist_memcache) $datalist_memcache->save($name, $row->value); - - return $row->value; - }*/ - - return false; - } - - /** - * Sets the value for a system-wide piece of data (overwriting a previous value if it exists) - * - * @param string $name The name of the datalist - * @param string $value The new value - * @return true - */ - function datalist_set($name, $value) { - - global $CONFIG, $DATALIST_CACHE; - - $name = sanitise_string($name); - $value = sanitise_string($value); - - // If memcache is available then invalidate the cached copy - static $datalist_memcache; - if ((!$datalist_memcache) && (is_memcache_available())) - $datalist_memcache = new ElggMemcache('datalist_memcache'); - if ($datalist_memcache) $datalist_memcache->delete($name); - - //delete_data("delete from {$CONFIG->dbprefix}datalists where name = '{$name}'"); - insert_data("INSERT into {$CONFIG->dbprefix}datalists set name = '{$name}', value = '{$value}' ON DUPLICATE KEY UPDATE value='{$value}'"); - - $DATALIST_CACHE[$name] = $value; - - return true; - + } + //else + //if (!isset($CONFIG->hooks['all']['all'])) + // return $returnvalue; + + if (!empty($CONFIG->hooks['all']['all']) && is_array($CONFIG->hooks['all']['all'])) { + foreach($CONFIG->hooks['all']['all'] as $hookfunction) { + $temp_return_value = $hookfunction($hook, $entity_type, $returnvalue, $params); + if (!is_null($temp_return_value)) { + $returnvalue = $temp_return_value; + } } + } - /** - * Runs a function once - not per page load, but per installation. - * If you like, you can also set the threshold for the function execution - i.e., - * if the function was executed before or on $timelastupdatedcheck, this - * function will run it again. - * - * @param string $functionname The name of the function you want to run. - * @param int $timelastupdatedcheck Optionally, the UNIX epoch timestamp of the execution threshold - * @return true|false Depending on success. - */ - function run_function_once($functionname, $timelastupdatedcheck = 0) { - if ($lastupdated = datalist_get($functionname)) { - $lastupdated = (int) $lastupdated; - } else { - $lastupdated = 0; + return $returnvalue; +} + +/** + * Error handling + */ + +/** + * PHP Error handler function. + * This function acts as a wrapper to catch and report PHP error messages. + * + * @see http://www.php.net/set-error-handler + * @param int $errno The level of the error raised + * @param string $errmsg The error message + * @param string $filename The filename the error was raised in + * @param int $linenum The line number the error was raised at + * @param array $vars An array that points to the active symbol table at the point that the error occurred + */ +function __elgg_php_error_handler($errno, $errmsg, $filename, $linenum, $vars) { + $error = date("Y-m-d H:i:s (T)") . ": \"" . $errmsg . "\" in file " . $filename . " (line " . $linenum . ")"; + + switch ($errno) { + case E_USER_ERROR: + error_log("ERROR: " . $error); + register_error("ERROR: " . $error); + + // Since this is a fatal error, we want to stop any further execution but do so gracefully. + throw new Exception($error); + break; + + case E_WARNING : + case E_USER_WARNING : + error_log("WARNING: " . $error); + // register_error("WARNING: " . $error); + break; + + default: + global $CONFIG; + if (isset($CONFIG->debug)) { + error_log("DEBUG: " . $error); } - if (is_callable($functionname) && $lastupdated <= $timelastupdatedcheck) { - $functionname(); - datalist_set($functionname,time()); - return true; - } else { - return false; + // register_error("DEBUG: " . $error); + } + + return true; +} + +/** + * Custom exception handler. + * This function catches any thrown exceptions and handles them appropriately. + * + * @see http://www.php.net/set-exception-handler + * @param Exception $exception The exception being handled + */ +function __elgg_php_exception_handler($exception) { + error_log("*** FATAL EXCEPTION *** : " . $exception); + + ob_end_clean(); // Wipe any existing output buffer + + $body = elgg_view("messages/exceptions/exception",array('object' => $exception)); + page_draw(elgg_echo('exception:title'), $body); +} + +/** + * Data lists + */ + +$DATALIST_CACHE = array(); + +/** + * Get the value of a particular piece of data in the datalist + * + * @param string $name The name of the datalist + * @return string|false Depending on success + */ +function datalist_get($name) { + global $CONFIG, $DATALIST_CACHE; + + // We need this, because sometimes datalists are received before the database is created + if (!is_db_installed()) { + return false; + } + + $name = sanitise_string($name); + if (isset($DATALIST_CACHE[$name])) { + return $DATALIST_CACHE[$name]; + } + + // If memcache enabled then cache value in memcache + $value = null; + static $datalist_memcache; + if ((!$datalist_memcache) && (is_memcache_available())) { + $datalist_memcache = new ElggMemcache('datalist_memcache'); + } + if ($datalist_memcache) { + $value = $datalist_memcache->load($name); + } + if ($value) { + return $value; + } + + // [Marcus Povey 20090217 : Now retrieving all datalist values on first load as this saves about 9 queries per page] + $result = get_data("SELECT * from {$CONFIG->dbprefix}datalists"); + if ($result) { + foreach ($result as $row) { + $DATALIST_CACHE[$row->name] = $row->value; + + // Cache it if memcache is available + if ($datalist_memcache) { + $datalist_memcache->save($row->name, $row->value); } } - - - /** - * Privilege elevation and gatekeeper code - */ + if (isset($DATALIST_CACHE[$name])) { + return $DATALIST_CACHE[$name]; + } + } - - /** - * Gatekeeper function which ensures that a we are being executed from - * a specified location. - * - * To use, call this function with the function name (and optional file location) that it has to be called - * from, it will either return true or false. - * - * e.g. - * - * function my_secure_function() - * { - * if (!call_gatekeeper("my_call_function")) - * return false; - * - * ... do secure stuff ... - * } - * - * function my_call_function() - * { - * // will work - * my_secure_function(); - * } - * - * function bad_function() - * { - * // Will not work - * my_secure_function(); - * } - * - * @param mixed $function The function that this function must have in its call stack, - * to test against a method pass an array containing a class and method name. - * @param string $file Optional file that the function must reside in. - */ - function call_gatekeeper($function, $file = "") - { - // Sanity check - if (!$function) - return false; - - // Check against call stack to see if this is being called from the correct location - $callstack = debug_backtrace(); - $stack_element = false; - - foreach ($callstack as $call) - { - if (is_array($function)) - { - if ( - (strcmp($call['class'], $function[0]) == 0) && - (strcmp($call['function'], $function[1]) == 0) - ) - $stack_element = $call; + + /*if ($row = get_data_row("SELECT value from {$CONFIG->dbprefix}datalists where name = '{$name}' limit 1")) { + $DATALIST_CACHE[$name] = $row->value; + + // Cache it if memcache is available + if ($datalist_memcache) $datalist_memcache->save($name, $row->value); + + return $row->value; + }*/ + + return false; +} + +/** + * Sets the value for a system-wide piece of data (overwriting a previous value if it exists) + * + * @param string $name The name of the datalist + * @param string $value The new value + * @return true + */ +function datalist_set($name, $value) { + + global $CONFIG, $DATALIST_CACHE; + + $name = sanitise_string($name); + $value = sanitise_string($value); + + // If memcache is available then invalidate the cached copy + static $datalist_memcache; + if ((!$datalist_memcache) && (is_memcache_available())) { + $datalist_memcache = new ElggMemcache('datalist_memcache'); + } + + if ($datalist_memcache) { + $datalist_memcache->delete($name); + } + + //delete_data("delete from {$CONFIG->dbprefix}datalists where name = '{$name}'"); + insert_data("INSERT into {$CONFIG->dbprefix}datalists set name = '{$name}', value = '{$value}' ON DUPLICATE KEY UPDATE value='{$value}'"); + + $DATALIST_CACHE[$name] = $value; + + return true; +} + +/** + * Runs a function once - not per page load, but per installation. + * If you like, you can also set the threshold for the function execution - i.e., + * if the function was executed before or on $timelastupdatedcheck, this + * function will run it again. + * + * @param string $functionname The name of the function you want to run. + * @param int $timelastupdatedcheck Optionally, the UNIX epoch timestamp of the execution threshold + * @return true|false Depending on success. + */ +function run_function_once($functionname, $timelastupdatedcheck = 0) { + if ($lastupdated = datalist_get($functionname)) { + $lastupdated = (int) $lastupdated; + } else { + $lastupdated = 0; + } + if (is_callable($functionname) && $lastupdated <= $timelastupdatedcheck) { + $functionname(); + datalist_set($functionname,time()); + return true; + } else { + return false; + } +} + + + +/** + * Privilege elevation and gatekeeper code + */ + + +/** + * Gatekeeper function which ensures that a we are being executed from + * a specified location. + * + * To use, call this function with the function name (and optional file location) that it has to be called + * from, it will either return true or false. + * + * e.g. + * + * function my_secure_function() + * { + * if (!call_gatekeeper("my_call_function")) + * return false; + * + * ... do secure stuff ... + * } + * + * function my_call_function() + * { + * // will work + * my_secure_function(); + * } + * + * function bad_function() + * { + * // Will not work + * my_secure_function(); + * } + * + * @param mixed $function The function that this function must have in its call stack, + * to test against a method pass an array containing a class and method name. + * @param string $file Optional file that the function must reside in. + */ +function call_gatekeeper($function, $file = "") { + // Sanity check + if (!$function) { + return false; + } + + // Check against call stack to see if this is being called from the correct location + $callstack = debug_backtrace(); + $stack_element = false; + + foreach ($callstack as $call) { + if (is_array($function)) { + if ( + (strcmp($call['class'], $function[0]) == 0) && + (strcmp($call['function'], $function[1]) == 0) + ) { + $stack_element = $call; } - else - { - if (strcmp($call['function'], $function) == 0) - $stack_element = $call; + } else { + if (strcmp($call['function'], $function) == 0) { + $stack_element = $call; } } + } + + if (!$stack_element) { + return false; + } - if (!$stack_element) - return false; - - // If file then check that this it is being called from this function - if ($file) - { - $mirror = null; - - if (is_array($function)) - $mirror = new ReflectionMethod($function[0], $function[1]); - else - $mirror = new ReflectionFunction($function); - - if ((!$mirror) || (strcmp($file,$mirror->getFileName())!=0)) - return false; + // If file then check that this it is being called from this function + if ($file) { + $mirror = null; + + if (is_array($function)) { + $mirror = new ReflectionMethod($function[0], $function[1]); + } else { + $mirror = new ReflectionFunction($function); + } + + if ((!$mirror) || (strcmp($file,$mirror->getFileName())!=0)) { + return false; } - - - return true; } - - /** - * This function checks to see if it is being called at somepoint by a function defined somewhere - * on a given path (optionally including subdirectories). - * - * This function is similar to call_gatekeeper() but returns true if it is being called by a method or function which has been defined on a given path or by a specified file. - * - * @param string $path The full path and filename that this function must have in its call stack If a partial path is given and $include_subdirs is true, then the function will return true if called by any function in or below the specified path. - * @param bool $include_subdirs Are subdirectories of the path ok, or must you specify an absolute path and filename. - * @param bool $strict_mode If true then the calling method or function must be directly called by something on $path, if false the whole call stack is searched. - */ - function callpath_gatekeeper($path, $include_subdirs = true, $strict_mode = false) - { - global $CONFIG; - - $path = sanitise_string($path); - - if ($path) - { - $callstack = debug_backtrace(); - - foreach ($callstack as $call) - { - $call['file'] = str_replace("\\","/",$call['file']); - - if ($include_subdirs) - { - if (strpos($call['file'], $path) === 0) { - - if ($strict_mode) { - $callstack[1]['file'] = str_replace("\\","/",$callstack[1]['file']); - if ($callstack[1] === $call) { return true; } - } - else - { - return true; - } + + return true; +} + +/** + * This function checks to see if it is being called at somepoint by a function defined somewhere + * on a given path (optionally including subdirectories). + * + * This function is similar to call_gatekeeper() but returns true if it is being called by a method or function which has been defined on a given path or by a specified file. + * + * @param string $path The full path and filename that this function must have in its call stack If a partial path is given and $include_subdirs is true, then the function will return true if called by any function in or below the specified path. + * @param bool $include_subdirs Are subdirectories of the path ok, or must you specify an absolute path and filename. + * @param bool $strict_mode If true then the calling method or function must be directly called by something on $path, if false the whole call stack is searched. + */ +function callpath_gatekeeper($path, $include_subdirs = true, $strict_mode = false) { + global $CONFIG; + + $path = sanitise_string($path); + + if ($path) { + $callstack = debug_backtrace(); + + foreach ($callstack as $call) { + $call['file'] = str_replace("\\","/",$call['file']); + + if ($include_subdirs) { + if (strpos($call['file'], $path) === 0) { + + if ($strict_mode) { + $callstack[1]['file'] = str_replace("\\","/",$callstack[1]['file']); + if ($callstack[1] === $call) { return true; } + } else { + return true; } } - else - { - if (strcmp($path, $call['file'])==0) { - if ($strict_mode) { - if ($callstack[1] === $call) return true; - } else + } else { + if (strcmp($path, $call['file'])==0) { + if ($strict_mode) { + if ($callstack[1] === $call) { return true; + } + } else { + return true; } } - } - return false; + } - - if ($CONFIG->debug) - system_message("Gatekeeper'd function called from {$callstack[1]['file']}:{$callstack[1]['line']}\n\nStack trace:\n\n" . print_r($callstack, true)); - return false; } - + + if ($CONFIG->debug) { + system_message("Gatekeeper'd function called from {$callstack[1]['file']}:{$callstack[1]['line']}\n\nStack trace:\n\n" . print_r($callstack, true)); + } + + return false; +} + +/** + * Returns true or false depending on whether a PHP .ini setting is on or off + * + * @param string $ini_get_arg The INI setting + * @return true|false Depending on whether it's on or off + */ +function ini_get_bool($ini_get_arg) { + $temp = ini_get($ini_get_arg); + + if ($temp == '1' or strtolower($temp) == 'on') { + return true; + } + return false; +} + +/** + * Function to be used in array_filter which returns true if $string is not null. + * + * @param string $string + * @return bool + */ +function is_not_null($string) { + if (($string==='') || ($string===false) || ($string===null)) { + return false; + } + + return true; +} + +/** + * Get the full URL of the current page. + * + * @return string The URL + */ +function full_url() { + $s = empty($_SERVER["HTTPS"]) ? '' : ($_SERVER["HTTPS"] == "on") ? "s" : ""; + $protocol = substr(strtolower($_SERVER["SERVER_PROTOCOL"]), 0, strpos(strtolower($_SERVER["SERVER_PROTOCOL"]), "/")) . $s; + $port = ($_SERVER["SERVER_PORT"] == "80") ? "" : (":".$_SERVER["SERVER_PORT"]); + return $protocol . "://" . $_SERVER['SERVER_NAME'] . $port . $_SERVER['REQUEST_URI']; +} + +/** + * Useful function found in the comments on the PHP man page for ip2long. + * Returns 1 if an IP matches a given range. + * + * TODO: Check licence... assuming this is PD since it was found several places on the interwebs.. + * please check or rewrite. + * + * Matches: + * xxx.xxx.xxx.xxx (exact) + * xxx.xxx.xxx.[yyy-zzz] (range) + * xxx.xxx.xxx.xxx/nn (nn = # bits, cisco style -- i.e. /24 = class C) + * Does not match: + * xxx.xxx.xxx.xx[yyy-zzz] (range, partial octets not supported) + */ +function test_ip($range, $ip) { + $result = 1; + + # IP Pattern Matcher + # J.Adams + # + # Matches: + # + # xxx.xxx.xxx.xxx (exact) + # xxx.xxx.xxx.[yyy-zzz] (range) + # xxx.xxx.xxx.xxx/nn (nn = # bits, cisco style -- i.e. /24 = class C) + # + # Does not match: + # xxx.xxx.xxx.xx[yyy-zzz] (range, partial octets not supported) + + if (ereg("([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/([0-9]+)",$range,$regs)) { + # perform a mask match + $ipl = ip2long($ip); + $rangel = ip2long($regs[1] . "." . $regs[2] . "." . $regs[3] . "." . $regs[4]); + + $maskl = 0; + + for ($i = 0; $i< 31; $i++) { + if ($i < $regs[5]-1) { + $maskl = $maskl + pow(2,(30-$i)); + } + } + + if (($maskl & $rangel) == ($maskl & $ipl)) { + return 1; + } else { + return 0; + } + } else { + # range based + $maskocts = split("\.",$range); + $ipocts = split("\.",$ip); + + # perform a range match + for ($i=0; $i<4; $i++) { + if (ereg("\[([0-9]+)\-([0-9]+)\]",$maskocts[$i],$regs)) { + if ( ($ipocts[$i] > $regs[2]) || ($ipocts[$i] < $regs[1])) { + $result = 0; + } + } else { + if ($maskocts[$i] <> $ipocts[$i]) { + $result = 0; + } + } + } + } + + return $result; +} + +/** + * Match an IP address against a number of ip addresses or ranges, returning true if found. + * + * @param array $networks + * @param string $ip + * @return bool + */ +function is_ip_in_array(array $networks, $ip) { + global $SYSTEM_LOG; + + foreach ($networks as $network) { + if (test_ip(trim($network), $ip)) { + return true; + } + } + + return false; +} + +/** + * An interface for objects that behave as elements within a social network that have a profile. + * + */ +interface Friendable { /** - * Returns true or false depending on whether a PHP .ini setting is on or off + * Adds a user as a friend * - * @param string $ini_get_arg The INI setting - * @return true|false Depending on whether it's on or off + * @param int $friend_guid The GUID of the user to add */ - function ini_get_bool($ini_get_arg) { - $temp = ini_get($ini_get_arg); - - if ($temp == '1' or strtolower($temp) == 'on') { - return true; - } - return false; - } - + public function addFriend($friend_guid); + /** - * Function to be used in array_filter which returns true if $string is not null. + * Removes a user as a friend * - * @param string $string - * @return bool + * @param int $friend_guid The GUID of the user to remove */ - function is_not_null($string) - { - if (($string==='') || ($string===false) || ($string===null)) - return false; + public function removeFriend($friend_guid); - return true; - } - /** - * Get the full URL of the current page. + * Determines whether or not the current user is a friend of this entity * - * @return string The URL */ - function full_url() - { - $s = empty($_SERVER["HTTPS"]) ? '' : ($_SERVER["HTTPS"] == "on") ? "s" : ""; - $protocol = substr(strtolower($_SERVER["SERVER_PROTOCOL"]), 0, strpos(strtolower($_SERVER["SERVER_PROTOCOL"]), "/")) . $s; - $port = ($_SERVER["SERVER_PORT"] == "80") ? "" : (":".$_SERVER["SERVER_PORT"]); - return $protocol . "://" . $_SERVER['SERVER_NAME'] . $port . $_SERVER['REQUEST_URI']; - } - + public function isFriend(); + /** - * Useful function found in the comments on the PHP man page for ip2long. - * Returns 1 if an IP matches a given range. - * - * TODO: Check licence... assuming this is PD since it was found several places on the interwebs.. - * please check or rewrite. - * - * Matches: - * xxx.xxx.xxx.xxx (exact) - * xxx.xxx.xxx.[yyy-zzz] (range) - * xxx.xxx.xxx.xxx/nn (nn = # bits, cisco style -- i.e. /24 = class C) - * Does not match: - * xxx.xxx.xxx.xx[yyy-zzz] (range, partial octets not supported) + * Determines whether or not this entity is friends with a particular entity + * + * @param int $user_guid The GUID of the entity this entity may or may not be friends with */ - function test_ip($range, $ip) - { - $result = 1; - - # IP Pattern Matcher - # J.Adams - # - # Matches: - # - # xxx.xxx.xxx.xxx (exact) - # xxx.xxx.xxx.[yyy-zzz] (range) - # xxx.xxx.xxx.xxx/nn (nn = # bits, cisco style -- i.e. /24 = class C) - # - # Does not match: - # xxx.xxx.xxx.xx[yyy-zzz] (range, partial octets not supported) - - - if (ereg("([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/([0-9]+)",$range,$regs)) { - - # perform a mask match - $ipl = ip2long($ip); - $rangel = ip2long($regs[1] . "." . $regs[2] . "." . $regs[3] . "." . $regs[4]); - - $maskl = 0; - - for ($i = 0; $i< 31; $i++) { - if ($i < $regs[5]-1) { - $maskl = $maskl + pow(2,(30-$i)); - } - } - - if (($maskl & $rangel) == ($maskl & $ipl)) { - return 1; - } else { - return 0; - } - } else { - - # range based - $maskocts = split("\.",$range); - $ipocts = split("\.",$ip); - - # perform a range match - for ($i=0; $i<4; $i++) { - if (ereg("\[([0-9]+)\-([0-9]+)\]",$maskocts[$i],$regs)) { - if ( ($ipocts[$i] > $regs[2]) || ($ipocts[$i] < $regs[1])) { - $result = 0; - } - } - else - { - if ($maskocts[$i] <> $ipocts[$i]) { - $result = 0; - } - } - } - } - return $result; - } - + public function isFriendsWith($user_guid); + /** - * Match an IP address against a number of ip addresses or ranges, returning true if found. + * Determines whether or not a foreign entity has made this one a friend * - * @param array $networks - * @param string $ip - * @return bool + * @param int $user_guid The GUID of the foreign entity */ - function is_ip_in_array(array $networks, $ip) - { - global $SYSTEM_LOG; - - foreach ($networks as $network) - { - if (test_ip(trim($network), $ip)) - return true; - } - - return false; - } - + public function isFriendOf($user_guid); + /** - * An interface for objects that behave as elements within a social network that have a profile. + * Returns this entity's friends * + * @param string $subtype The subtype of entity to return + * @param int $limit The number of entities to return + * @param int $offset Indexing offset */ - interface Friendable { - - /** - * Adds a user as a friend - * - * @param int $friend_guid The GUID of the user to add - */ - public function addFriend($friend_guid); - - /** - * Removes a user as a friend - * - * @param int $friend_guid The GUID of the user to remove - */ - public function removeFriend($friend_guid); - - /** - * Determines whether or not the current user is a friend of this entity - * - */ - public function isFriend(); - - /** - * Determines whether or not this entity is friends with a particular entity - * - * @param int $user_guid The GUID of the entity this entity may or may not be friends with - */ - public function isFriendsWith($user_guid); - - /** - * Determines whether or not a foreign entity has made this one a friend - * - * @param int $user_guid The GUID of the foreign entity - */ - public function isFriendOf($user_guid); - - /** - * Returns this entity's friends - * - * @param string $subtype The subtype of entity to return - * @param int $limit The number of entities to return - * @param int $offset Indexing offset - */ - public function getFriends($subtype = "", $limit = 10, $offset = 0); - - /** - * Returns entities that have made this entity a friend - * - * @param string $subtype The subtype of entity to return - * @param int $limit The number of entities to return - * @param int $offset Indexing offset - */ - public function getFriendsOf($subtype = "", $limit = 10, $offset = 0); - - /** - * Returns objects in this entity's container - * - * @param string $subtype The subtype of entity to return - * @param int $limit The number of entities to return - * @param int $offset Indexing offset - */ - public function getObjects($subtype="", $limit = 10, $offset = 0); - - /** - * Returns objects in the containers of this entity's friends - * - * @param string $subtype The subtype of entity to return - * @param int $limit The number of entities to return - * @param int $offset Indexing offset - */ - public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0); - - /** - * Returns the number of object entities in this entity's container - * - * @param string $subtype The subtype of entity to count - */ - public function countObjects($subtype = ""); - - } - - function js_page_handler($page) { - - if (is_array($page) && sizeof($page)) { - $js = str_replace('.js','',$page[0]); - $return = elgg_view('js/' . $js); - - header('Content-type: text/javascript'); - header('Expires: ' . date('r',time() + 864000)); - header("Pragma: public"); - header("Cache-Control: public"); - header("Content-Length: " . strlen($return)); - - echo $return; - exit; - } - - } - + public function getFriends($subtype = "", $limit = 10, $offset = 0); + /** - * This function is a shutdown hook registered on startup which does nothing more than trigger a - * shutdown event when the script is shutting down, but before database connections have been dropped etc. + * Returns entities that have made this entity a friend * + * @param string $subtype The subtype of entity to return + * @param int $limit The number of entities to return + * @param int $offset Indexing offset */ - function __elgg_shutdown_hook() - { - global $CONFIG, $START_MICROTIME; - - trigger_elgg_event('shutdown', 'system'); - - if ($CONFIG->debug) - error_log("Page {$_SERVER['REQUEST_URI']} generated in ".(float)(microtime(true)-$START_MICROTIME)." seconds"); - } - - function elgg_init() { - - // Menu - global $CONFIG; - //add_menu(elgg_echo('content:latest'), $CONFIG->wwwroot . 'dashboard/latest.php'); - // Page handler for JS - register_page_handler('js','js_page_handler'); - extend_view('js/initialise_elgg','embed/js'); - // Register an event triggered at system shutdown - register_shutdown_function('__elgg_shutdown_hook'); - - } - - function elgg_boot() { - - // Actions - register_action('comments/add'); - register_action('comments/delete'); - - elgg_view_register_simplecache('css'); - elgg_view_register_simplecache('js/friendsPickerv1'); - elgg_view_register_simplecache('js/initialise_elgg'); - } - + public function getFriendsOf($subtype = "", $limit = 10, $offset = 0); + + /** + * Returns objects in this entity's container + * + * @param string $subtype The subtype of entity to return + * @param int $limit The number of entities to return + * @param int $offset Indexing offset + */ + public function getObjects($subtype="", $limit = 10, $offset = 0); + + /** + * Returns objects in the containers of this entity's friends + * + * @param string $subtype The subtype of entity to return + * @param int $limit The number of entities to return + * @param int $offset Indexing offset + */ + public function getFriendsObjects($subtype = "", $limit = 10, $offset = 0); + /** - * Some useful constant definitions + * Returns the number of object entities in this entity's container + * + * @param string $subtype The subtype of entity to count */ - define('ACCESS_DEFAULT',-1); - define('ACCESS_PRIVATE',0); - define('ACCESS_LOGGED_IN',1); - define('ACCESS_PUBLIC',2); - define('ACCESS_FRIENDS',-2); - - register_elgg_event_handler('init','system','elgg_init'); - register_elgg_event_handler('boot','system','elgg_boot',1000); - -?> + public function countObjects($subtype = ""); +} + + +/** + * Server javascript pages. + * + * @param $page + * @return unknown_type + */ +function js_page_handler($page) { + if (is_array($page) && sizeof($page)) { + $js = str_replace('.js','',$page[0]); + $return = elgg_view('js/' . $js); + + header('Content-type: text/javascript'); + header('Expires: ' . date('r',time() + 864000)); + header("Pragma: public"); + header("Cache-Control: public"); + header("Content-Length: " . strlen($return)); + + echo $return; + exit; + } +} + +/** + * This function is a shutdown hook registered on startup which does nothing more than trigger a + * shutdown event when the script is shutting down, but before database connections have been dropped etc. + * + */ +function __elgg_shutdown_hook() { + global $CONFIG, $START_MICROTIME; + + trigger_elgg_event('shutdown', 'system'); + + if ($CONFIG->debug) { + error_log("Page {$_SERVER['REQUEST_URI']} generated in ".(float)(microtime(true)-$START_MICROTIME)." seconds"); + } +} + +/** + * Register functions for Elgg core + * + * @return unknown_type + */ +function elgg_init() { + // Page handler for JS + register_page_handler('js','js_page_handler'); + extend_view('js/initialise_elgg','embed/js'); + + // Register an event triggered at system shutdown + register_shutdown_function('__elgg_shutdown_hook'); +} + +/** + * Boot Elgg + * @return unknown_type + */ +function elgg_boot() { + // Actions + register_action('comments/add'); + register_action('comments/delete'); + + elgg_view_register_simplecache('css'); + elgg_view_register_simplecache('js/friendsPickerv1'); + elgg_view_register_simplecache('js/initialise_elgg'); +} + +/** + * Some useful constant definitions + */ +define('ACCESS_DEFAULT',-1); +define('ACCESS_PRIVATE',0); +define('ACCESS_LOGGED_IN',1); +define('ACCESS_PUBLIC',2); +define('ACCESS_FRIENDS',-2); + +register_elgg_event_handler('init','system','elgg_init'); +register_elgg_event_handler('boot','system','elgg_boot',1000); \ No newline at end of file -- cgit v1.2.3