From 66df602593230a2483d6538927fd66310c28c3f8 Mon Sep 17 00:00:00 2001 From: Angie Byron <webchick@24967.no-reply.drupal.org> Date: Tue, 25 Nov 2008 02:37:33 +0000 Subject: [PATCH] #314870 by Rob Loach, drewish, catch, and sun: Add hook API documentation to Drupal core. --- CHANGELOG.txt | 2 + modules/block/block.api.php | 128 +++ modules/comment/comment.api.php | 48 + modules/filter/filter.api.php | 163 +++ modules/help/help.api.php | 67 ++ modules/locale/locale.api.php | 29 + modules/menu/menu.api.php | 146 +++ modules/node/node.api.php | 652 ++++++++++++ modules/search/search.api.php | 284 ++++++ modules/system/system.api.php | 1525 +++++++++++++++++++++++++++++ modules/system/system.module | 11 +- modules/system/system.test | 35 + modules/taxonomy/taxonomy.api.php | 148 +++ modules/trigger/trigger.api.php | 156 +++ modules/update/update.api.php | 45 + modules/user/user.api.php | 126 +++ 16 files changed, 3558 insertions(+), 7 deletions(-) create mode 100644 modules/block/block.api.php create mode 100644 modules/comment/comment.api.php create mode 100644 modules/filter/filter.api.php create mode 100644 modules/help/help.api.php create mode 100644 modules/locale/locale.api.php create mode 100644 modules/menu/menu.api.php create mode 100644 modules/node/node.api.php create mode 100644 modules/search/search.api.php create mode 100644 modules/system/system.api.php create mode 100644 modules/taxonomy/taxonomy.api.php create mode 100644 modules/trigger/trigger.api.php create mode 100644 modules/update/update.api.php create mode 100644 modules/user/user.api.php diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 4327198a1733..711017d190e7 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -31,6 +31,8 @@ Drupal 7.0, xxxx-xx-xx (development version) * Added an edit tab to taxonomy term pages. * Redesigned password strength validator. * Redesigned the add content type screen. +- Documentation: + * Hook API documentation now included in Drupal core. - News aggregator: * Added OPML import functionality for RSS feeds. * Optionally, RSS feeds may be configured to not automatically generate feed blocks. diff --git a/modules/block/block.api.php b/modules/block/block.api.php new file mode 100644 index 000000000000..57517dbc260b --- /dev/null +++ b/modules/block/block.api.php @@ -0,0 +1,128 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by the Block module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Declare a block or set of blocks. + * + * Any module can export a block (or blocks) to be displayed by defining + * the _block hook. This hook is called by theme.inc to display a block, + * and also by block.module to procure the list of available blocks. + * + * @param $op + * What kind of information to retrieve about the block or blocks. + * Possible values: + * - 'list': A list of all blocks defined by the module. + * - 'configure': Configuration form for the block. + * - 'save': Save the configuration options. + * - 'view': Process the block when enabled in a region in order to view its contents. + * @param $delta + * Which block to return (not applicable if $op is 'list'). This is a + * descriptive string used to identify blocks within each module and also + * within the theme system. The $delta for each block is defined within + * the array that your module returns when $op is 'list' (see below). + * @param $edit + * If $op is 'save', the submitted form data from the configuration form. + * @return + * - If $op is 'list': An associative array whose keys define the $delta + * for each block and whose values contain the block descriptions. Each + * block description is itself an associative array, with the following + * key-value pairs: + * - 'info': (required) The human-readable name of the block. + * - 'cache': A bitmask of flags describing how the block should behave with + * respect to block caching. The following shortcut bitmasks are provided + * as constants in block.module: + * - BLOCK_CACHE_PER_ROLE (default): The block can change depending on the + * roles the user viewing the page belongs to. + * - BLOCK_CACHE_PER_USER: The block can change depending on the user + * viewing the page. This setting can be resource-consuming for sites + * with large number of users, and should only be used when + * BLOCK_CACHE_PER_ROLE is not sufficient. + * - BLOCK_CACHE_PER_PAGE: The block can change depending on the page + * being viewed. + * - BLOCK_CACHE_GLOBAL: The block is the same for every user on every + * page where it is visible. + * - BLOCK_NO_CACHE: The block should not get cached. + * - 'weight', 'status', 'region', 'visibility', 'pages': + * You can give your blocks an explicit weight, enable them, limit them to + * given pages, etc. These settings will be registered when the block is first + * loaded at admin/block, and from there can be changed manually via block + * administration. + * Note that if you set a region that isn't available in a given theme, the + * block will be registered instead to that theme's default region (the first + * item in the _regions array). + * - If $op is 'configure': optionally return the configuration form. + * - If $op is 'save': return nothing. + * - If $op is 'view': return an array which must define a 'subject' element + * and a 'content' element defining the block indexed by $delta. + * + * The functions mymodule_display_block_exciting and _amazing, as used in the + * example, should of course be defined somewhere in your module and return the + * content you want to display to your users. If the "content" element is empty, + * no block will be displayed even if "subject" is present. + * + * After completing your blocks, do not forget to enable them in the + * block admin menu. + * + * For a detailed usage example, see block_example.module. + */ +function hook_block($op = 'list', $delta = '', $edit = array()) { + if ($op == 'list') { + $blocks['exciting'] = array( + 'info' => t('An exciting block provided by Mymodule.'), + 'weight' => 0, + 'status' => 1, + 'region' => 'left', + // BLOCK_CACHE_PER_ROLE will be assumed for block 0. + ); + + $blocks['amazing'] = array( + 'info' => t('An amazing block provided by Mymodule.'), + 'cache' => BLOCK_CACHE_PER_ROLE | BLOCK_CACHE_PER_PAGE, + ); + + return $blocks; + } + elseif ($op == 'configure' && $delta == 'exciting') { + $form['items'] = array( + '#type' => 'select', + '#title' => t('Number of items'), + '#default_value' => variable_get('mymodule_block_items', 0), + '#options' => array('1', '2', '3'), + ); + return $form; + } + elseif ($op == 'save' && $delta == 'exciting') { + variable_set('mymodule_block_items', $edit['items']); + } + elseif ($op == 'view') { + switch ($delta) { + case 'exciting': + $block = array( + 'subject' => t('Default title of the exciting block'), + 'content' => mymodule_display_block_exciting(), + ); + break; + case 'amazing': + $block = array( + 'subject' => t('Default title of the amazing block'), + 'content' => mymodule_display_block_amazing(), + ); + break; + } + return $block; + } +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/modules/comment/comment.api.php b/modules/comment/comment.api.php new file mode 100644 index 000000000000..a486dd932043 --- /dev/null +++ b/modules/comment/comment.api.php @@ -0,0 +1,48 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by the Comment module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Act on comments. + * + * This hook allows modules to extend the comments system. + * + * @param $a1 + * Dependent on the action being performed. + * - For "validate","update","insert", passes in an array of form values submitted by the user. + * - For all other operations, passes in the comment the action is being performed on. + * @param $op + * What kind of action is being performed. Possible values: + * - "insert": The comment is being inserted. + * - "update": The comment is being updated. + * - "view": The comment is being viewed. This hook can be used to add additional data to the comment before theming. + * - "validate": The user has just finished editing the comment and is + * trying to preview or submit it. This hook can be used to check or + * even modify the node. Errors should be set with form_set_error(). + * - "publish": The comment is being published by the moderator. + * - "unpublish": The comment is being unpublished by the moderator. + * - "delete": The comment is being deleted by the moderator. + * @return + * Dependent on the action being performed. + * - For all other operations, nothing. + */ +function hook_comment(&$a1, $op) { + if ($op == 'insert' || $op == 'update') { + $nid = $a1['nid']; + } + + cache_clear_all_like(drupal_url(array('id' => $nid))); +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/modules/filter/filter.api.php b/modules/filter/filter.api.php new file mode 100644 index 000000000000..a02c5a2fb7b0 --- /dev/null +++ b/modules/filter/filter.api.php @@ -0,0 +1,163 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by the Filter module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Define content filters. + * + * Content in Drupal is passed through all enabled filters before it is + * output. This lets a module modify content to the site administrator's + * liking. + * + * This hook contains all that is needed for having a module provide filtering + * functionality. + * + * Depending on $op, different tasks are performed. + * + * A module can contain as many filters as it wants. The 'list' operation tells + * the filter system which filters are available. Every filter has a numerical + * 'delta' which is used to refer to it in every operation. + * + * Filtering is a two-step process. First, the content is 'prepared' by calling + * the 'prepare' operation for every filter. The purpose of 'prepare' is to + * escape HTML-like structures. For example, imagine a filter which allows the + * user to paste entire chunks of programming code without requiring manual + * escaping of special HTML characters like @< or @&. If the programming code + * were left untouched, then other filters could think it was HTML and change + * it. For most filters however, the prepare-step is not necessary, and they can + * just return the input without changes. + * + * Filters should not use the 'prepare' step for anything other than escaping, + * because that would short-circuits the control the user has over the order + * in which filters are applied. + * + * The second step is the actual processing step. The result from the + * prepare-step gets passed to all the filters again, this time with the + * 'process' operation. It's here that filters should perform actual changing of + * the content: transforming URLs into hyperlinks, converting smileys into + * images, etc. + * + * An important aspect of the filtering system are 'input formats'. Every input + * format is an entire filter setup: which filters to enable, in what order + * and with what settings. Filters that provide settings should usually store + * these settings per format. + * + * If the filter's behaviour depends on an extensive list and/or external data + * (e.g. a list of smileys, a list of glossary terms) then filters are allowed + * to provide a separate, global configuration page rather than provide settings + * per format. In that case, there should be a link from the format-specific + * settings to the separate settings page. + * + * For performance reasons content is only filtered once; the result is stored + * in the cache table and retrieved the next time the piece of content is + * displayed. If a filter's output is dynamic it can override the cache + * mechanism, but obviously this feature should be used with caution: having one + * 'no cache' filter in a particular input format disables caching for the + * entire format, not just for one filter. + * + * Beware of the filter cache when developing your module: it is advised to set + * your filter to 'no cache' while developing, but be sure to remove it again + * if it's not needed. You can clear the cache by running the SQL query 'DELETE + * FROM cache_filter'; + * + * @param $op + * Which filtering operation to perform. Possible values: + * - list: provide a list of available filters. + * Returns an associative array of filter names with numerical keys. + * These keys are used for subsequent operations and passed back through + * the $delta parameter. + * - no cache: Return true if caching should be disabled for this filter. + * - description: Return a short description of what this filter does. + * - prepare: Return the prepared version of the content in $text. + * - process: Return the processed version of the content in $text. + * - settings: Return HTML form controls for the filter's settings. These + * settings are stored with variable_set() when the form is submitted. + * Remember to use the $format identifier in the variable and control names + * to store settings per input format (e.g. "mymodule_setting_$format"). + * @param $delta + * Which of the module's filters to use (applies to every operation except + * 'list'). Modules that only contain one filter can ignore this parameter. + * @param $format + * Which input format the filter is being used in (applies to 'prepare', + * 'process' and 'settings'). + * @param $text + * The content to filter (applies to 'prepare' and 'process'). + * @param $langcode + * The language code associated with the content, e.g. 'en' for English. This + * enables filters to be language aware and can be used to implement language + * specific text replacements. + * @param $cache_id + * The cache id of the content. + * @return + * The return value depends on $op. The filter hook is designed so that a + * module can return $text for operations it does not use/need. + * + * For a detailed usage example, see filter_example.module. For an example of + * using multiple filters in one module, see filter_filter() and + * filter_filter_tips(). + */ +function hook_filter($op, $delta = 0, $format = -1, $text = '', $langcode = '', $cache_id = 0) { + switch ($op) { + case 'list': + return array(0 => t('Code filter')); + + case 'description': + return t('Allows users to post code verbatim using <code> and <?php ?> tags.'); + + case 'prepare': + // Note: we use the bytes 0xFE and 0xFF to replace < > during the + // filtering process. These bytes are not valid in UTF-8 data and thus + // least likely to cause problems. + $text = preg_replace('@<code>(.+?)</code>@se', "'\xFEcode\xFF' . codefilter_escape('\\1') . '\xFE/code\xFF'", $text); + $text = preg_replace('@<(\?(php)?|%)(.+?)(\?|%)>@se', "'\xFEphp\xFF' . codefilter_escape('\\3') . '\xFE/php\xFF'", $text); + return $text; + + case "process": + $text = preg_replace('@\xFEcode\xFF(.+?)\xFE/code\xFF@se', "codefilter_process_code('$1')", $text); + $text = preg_replace('@\xFEphp\xFF(.+?)\xFE/php\xFF@se', "codefilter_process_php('$1')", $text); + return $text; + + default: + return $text; + } +} + +/** + * Provide tips for using filters. + * + * A module's tips should be informative and to the point. Short tips are + * preferably one-liners. + * + * @param $delta + * Which of this module's filters to use. Modules which only implement one + * filter can ignore this parameter. + * @param $format + * Which format we are providing tips for. + * @param $long + * If set to true, long tips are requested, otherwise short tips are needed. + * @return + * The text of the filter tip. + * + * + */ +function hook_filter_tips($delta, $format, $long = false) { + if ($long) { + return t('To post pieces of code, surround them with <code>...</code> tags. For PHP code, you can use <?php ... ?>, which will also colour it based on syntax.'); + } + else { + return t('You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.'); + } +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/modules/help/help.api.php b/modules/help/help.api.php new file mode 100644 index 000000000000..387fb90cad80 --- /dev/null +++ b/modules/help/help.api.php @@ -0,0 +1,67 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by the Help module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Provide online user help. + * + * By implementing hook_help(), a module can make documentation + * available to the engine or to other modules. All user help should be + * returned using this hook; developer help should be provided with + * Doxygen/api.module comments. + * + * @param $path + * A Drupal menu router path the help is being requested for, e.g. + * admin/node or user/edit. If the router path includes a % wildcard, + * then this will appear in the path - for example all node pages will + * have the path node/% or node/%/view. + * Also recognizes special descriptors after a "#" sign. Some examples: + * - admin/help#modulename + * The module's help text, displayed on the admin/help page and through + * the module's individual help link. + * - user/help#modulename + * The help for a distributed authorization module (if applicable). + * @param $arg + * An array that corresponds to the return of the arg() function - if a module + * needs to provide help for a page with additional parameters after the + * Drupal path or help for a specific value for a wildcard in the path, then + * the values in this array can be referenced. For example you could provide + * help for user/1 by looking for the path user/% and $arg[1] == '1'. This + * array should always be used rather than directly invoking arg(). Note that + * depending on which module is invoking hook_help, $arg may contain only, + * empty strings. Regardless, $arg[0] to $arg[11] will always be set. + * @return + * A localized string containing the help text. Every web link, l(), or + * url() must be replaced with %something and put into the final t() + * call: + * $output .= 'A role defines a group of users that have certain + * privileges as defined in %permission.'; + * $output = t($output, array('%permission' => l(t('user permissions'), + * 'admin/user/permission'))); + * + * For a detailed usage example, see page_example.module. + */ +function hook_help($path, $arg) { + switch ($path) { + case 'admin/help#block': + return '<p>' . t('Blocks are boxes of content that may be rendered into certain regions of your web pages, for example, into sidebars. Blocks are usually generated automatically by modules (e.g., Recent Forum Topics), but administrators can also define custom blocks.') . '</p>'; + + case 'admin/build/block': + return t('<p>Blocks are boxes of content that may be rendered into certain regions of your web pages, for example, into sidebars. They are usually generated automatically by modules, but administrators can create blocks manually.</p> +<p>If you want certain blocks to disable themselves temporarily during high server loads, check the "Throttle" box. You can configure the auto-throttle on the <a href="@throttle">throttle configuration page</a> after having enabled the throttle module.</p> +<p>You can configure the behaviour of each block (for example, specifying on which pages and for what users it will appear) by clicking the "configure" link for each block.</p>', array('@throttle' => url('admin/settings/throttle'))); + } +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/modules/locale/locale.api.php b/modules/locale/locale.api.php new file mode 100644 index 000000000000..60ad3b7f1047 --- /dev/null +++ b/modules/locale/locale.api.php @@ -0,0 +1,29 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by the Locale module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Allows modules to define their own text groups that can be translated. + * + * @param $op + * Type of operation. Currently, only supports 'groups'. + */ +function hook_locale($op = 'groups') { + switch ($op) { + case 'groups': + return array('custom' => t('Custom')); + } +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/modules/menu/menu.api.php b/modules/menu/menu.api.php new file mode 100644 index 000000000000..7c1543a6de81 --- /dev/null +++ b/modules/menu/menu.api.php @@ -0,0 +1,146 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by the Menu module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Define menu items and page callbacks. + * + * This hook enables modules to register paths, which determines whose + * requests are to be handled. Depending on the type of registration + * requested by each path, a link is placed in the the navigation block and/or + * an item appears in the menu administration page (q=admin/menu). + * + * This hook is called rarely - for example when modules are enabled. + * + * @return + * An array of menu items. Each menu item has a key corresponding to the + * Drupal path being registered. The item is an associative array that may + * contain the following key-value pairs: + * + * - "title": Required. The untranslated title of the menu item. + * - "description": The untranslated description of the menu item. + * - "page callback": The function to call to display a web page when the user + * visits the path. If omitted, the parent menu item's callback will be used + * instead. + * - "page arguments": An array of arguments to pass to the page callback + * function. Integer values pass the corresponding URL component (see arg()). + * - "access callback": A function returning a boolean value that determines + * whether the user has access rights to this menu item. Defaults to + * user_access() unless a value is inherited from a parent menu item.. + * - "access arguments": An array of arguments to pass to the access callback + * function. Integer values pass the corresponding URL component. + * - "weight": An integer that determines relative position of items in the + * menu; higher-weighted items sink. Defaults to 0. When in doubt, leave + * this alone; the default alphabetical order is usually best. + * - "menu_name": Optional. Set this to a custom menu if you don't want your + * item to be placed in Navigation. + * - "type": A bitmask of flags describing properties of the menu item. + * Many shortcut bitmasks are provided as constants in menu.inc: + * - MENU_NORMAL_ITEM: Normal menu items show up in the menu tree and can be + * moved/hidden by the administrator. + * - MENU_CALLBACK: Callbacks simply register a path so that the correct + * function is fired when the URL is accessed. + * - MENU_SUGGESTED_ITEM: Modules may "suggest" menu items that the + * administrator may enable. + * - MENU_LOCAL_TASK: Local tasks are rendered as tabs by default. + * - MENU_DEFAULT_LOCAL_TASK: Every set of local tasks should provide one + * "default" task, that links to the same path as its parent when clicked. + * If the "type" key is omitted, MENU_NORMAL_ITEM is assumed. + * + * For a detailed usage example, see page_example.module. + * + * For comprehensive documentation on the menu system, see + * http://drupal.org/node/102338. + * + */ +function hook_menu() { + $items = array(); + + $items['blog'] = array( + 'title' => 'blogs', + 'page callback' => 'blog_page', + 'access arguments' => array('access content'), + 'type' => MENU_SUGGESTED_ITEM, + ); + $items['blog/feed'] = array( + 'title' => t('RSS feed'), + 'page callback' => 'blog_feed', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +/** + * Alter the data being saved to the {menu_router} table after hook_menu is invoked. + * + * This hook is invoked by menu_router_build(). The menu definitions are passed + * in by reference. Each element of the $callbacks array is one item returned + * by a module from hook_menu. Additional items may be added, or existing items + * altered. + * + * @param $callbacks + * Associative array of menu router definitions returned from hook_menu(). + * @return + * None. + */ +function hook_menu_alter(&$callbacks) { + // Example - disable the page at node/add + $callbacks['node/add']['access callback'] = FALSE; +} + +/** + * Alter the data being saved to the {menu_links} table by menu_link_save(). + * + * @param $item + * Associative array defining a menu link as passed into menu_link_save(). + * @param $menu + * Associative array containg the menu router returned from menu_router_build(). + * @return + * None. + */ +function hook_menu_link_alter(&$item, $menu) { + // Example 1 - make all new admin links hidden (a.k.a disabled). + if (strpos($item['link_path'], 'admin') === 0 && empty($item['mlid'])) { + $item['hidden'] = 1; + } + // Example 2 - flag a link to be altered by hook_translated_menu_link_alter() + if ($item['link_path'] == 'devel/cache/clear') { + $item['options']['alter'] = TRUE; + } +} + +/** + * Alter a menu link after it's translated, but before it's rendered. + * + * This hook may be used, for example, to add a page-specific query string. + * For performance reasons, only links that have $item['options']['alter'] == TRUE + * will be passed into this hook. The $item['options']['alter'] flag should + * generally be set using hook_menu_link_alter(). + * + * @param $item + * Associative array defining a menu link after _menu_link_translate() + * @param $map + * Associative array containing the menu $map (path parts and/or objects). + * @return + * None. + */ +function hook_translated_menu_link_alter(&$item, $map) { + if ($item['href'] == 'devel/cache/clear') { + $item['localized_options']['query'] = drupal_get_destination(); + } +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/modules/node/node.api.php b/modules/node/node.api.php new file mode 100644 index 000000000000..9bf2816edaeb --- /dev/null +++ b/modules/node/node.api.php @@ -0,0 +1,652 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by the Node module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Inform the node access system what permissions the user has. + * + * This hook is for implementation by node access modules. In addition to + * managing access rights for nodes, the node access module must tell + * the node access system what 'grant IDs' the current user has. In many + * cases, the grant IDs will simply be role IDs, but grant IDs can be + * arbitrary based upon the module. + * + * For example, modules can maintain their own lists of users, where each + * list has an ID. In that case, the module could return a list of all + * IDs of all lists that the current user is a member of. + * + * A node access module may implement as many realms as necessary to + * properly define the access privileges for the nodes. + * + * @param $user + * The user object whose grants are requested. + * @param $op + * The node operation to be performed, such as "view", "update", or "delete". + * @return + * An array whose keys are "realms" of grants such as "user" or "role", and + * whose values are linear lists of grant IDs. + * + * For a detailed example, see node_access_example.module. + * + * @ingroup node_access + */ +function hook_node_grants($account, $op) { + if (user_access('access private content', $account)) { + $grants['example'] = array(1); + } + $grants['example_owner'] = array($user->uid); + return $grants; +} + +/** + * Set permissions for a node to be written to the database. + * + * When a node is saved, a module implementing node access will be asked + * if it is interested in the access permissions to a node. If it is + * interested, it must respond with an array of array of permissions for that + * node. + * + * Each item in the array should contain: + * + * 'realm' + * This should only be realms for which the module has returned + * grant IDs in hook_node_grants. + * 'gid' + * This is a 'grant ID', which can have an arbitrary meaning per realm. + * 'grant_view' + * If set to TRUE a user with the gid in the realm can view this node. + * 'grant_edit' + * If set to TRUE a user with the gid in the realm can edit this node. + * 'grant_delete' + * If set to TRUE a user with the gid in the realm can delete this node. + * 'priority' + * If multiple modules seek to set permissions on a node, the realms + * that have the highest priority will win out, and realms with a lower + * priority will not be written. If there is any doubt, it is best to + * leave this 0. + * + * @ingroup node_access + */ +function hook_node_access_records($node) { + if (node_access_example_disabling()) { + return; + } + + // We only care about the node if it's been marked private. If not, it is + // treated just like any other node and we completely ignore it. + if ($node->private) { + $grants = array(); + $grants[] = array( + 'realm' => 'example', + 'gid' => TRUE, + 'grant_view' => TRUE, + 'grant_update' => FALSE, + 'grant_delete' => FALSE, + 'priority' => 0, + ); + + // For the example_author array, the GID is equivalent to a UID, which + // means there are many many groups of just 1 user. + $grants[] = array( + 'realm' => 'example_author', + 'gid' => $node->uid, + 'grant_view' => TRUE, + 'grant_update' => TRUE, + 'grant_delete' => TRUE, + 'priority' => 0, + ); + return $grants; + } +} + +/** + * Add mass node operations. + * + * This hook enables modules to inject custom operations into the mass operations + * dropdown found at admin/content/node, by associating a callback function with + * the operation, which is called when the form is submitted. The callback function + * receives one initial argument, which is an array of the checked nodes. + * + * @return + * An array of operations. Each operation is an associative array that may + * contain the following key-value pairs: + * - "label": Required. The label for the operation, displayed in the dropdown menu. + * - "callback": Required. The function to call for the operation. + * - "callback arguments": Optional. An array of additional arguments to pass to + * the callback function. + * + */ +function hook_node_operations() { + $operations = array( + 'approve' => array( + 'label' => t('Approve the selected posts'), + 'callback' => 'node_operations_approve', + ), + 'promote' => array( + 'label' => t('Promote the selected posts'), + 'callback' => 'node_operations_promote', + ), + 'sticky' => array( + 'label' => t('Make the selected posts sticky'), + 'callback' => 'node_operations_sticky', + ), + 'demote' => array( + 'label' => t('Demote the selected posts'), + 'callback' => 'node_operations_demote', + ), + 'unpublish' => array( + 'label' => t('Unpublish the selected posts'), + 'callback' => 'node_operations_unpublish', + ), + 'delete' => array( + 'label' => t('Delete the selected posts'), + ), + ); + return $operations; +} + +/** + * Act on nodes defined by other modules. + * + * Despite what its name might make you think, hook_nodeapi() is not + * reserved for node modules. On the contrary, it allows modules to react + * to actions affecting all kinds of nodes, regardless of whether that + * module defined the node. + * + * It is common to find hook_nodeapi() used in conjunction with + * hook_form_alter(). Modules use hook_form_alter() to place additional form + * elements onto the node edit form, and hook_nodeapi() is used to read and + * write those values to and from the database. + * + * @param &$node + * The node the action is being performed on. + * @param $op + * What kind of action is being performed. Possible values: + * - "alter": the $node->content array has been rendered, so the node body or + * teaser is filtered and now contains HTML. This op should only be used when + * text substitution, filtering, or other raw text operations are necessary. + * - "delete": The node is being deleted. + * - "delete_revision": The revision of the node is deleted. You can delete data + * associated with that revision. + * - "insert": The node is being created (inserted in the database). + * - "load": The node is about to be loaded from the database. This hook + * can be used to load additional data at this time. + * - "prepare": The node is about to be shown on the add/edit form. + * - "prepare_translation": The node is being cloned for translation. Load + * additional data or copy values from $node->translation_source. + * - "print": Prepare a node view for printing. Used for printer-friendly + * view in book_module + * - "rss_item": An RSS feed is generated. The module can return properties + * to be added to the RSS item generated for this node. See comment_nodeapi() + * and upload_nodeapi() for examples. The $node passed can also be modified + * to add or remove contents to the feed item. + * - "search_result": The node is displayed as a search result. If you + * want to display extra information with the result, return it. + * - "presave": The node passed validation and is about to be saved. Modules may + * use this to make changes to the node before it is saved to the database. + * - "update": The node is being updated. + * - "update_index": The node is being indexed. If you want additional + * information to be indexed which is not already visible through + * nodeapi "view", then you should return it here. + * - "validate": The user has just finished editing the node and is + * trying to preview or submit it. This hook can be used to check + * the node data. Errors should be set with form_set_error(). + * - "view": The node content is being assembled before rendering. The module + * may add elements $node->content prior to rendering. This hook will be + * called after hook_view(). The format of $node->content is the same as + * used by Forms API. + * @param $a3 + * - For "view", passes in the $teaser parameter from node_view(). + * - For "validate", passes in the $form parameter from node_validate(). + * @param $a4 + * - For "view", passes in the $page parameter from node_view(). + * @return + * This varies depending on the operation. + * - The "presave", "insert", "update", "delete", "print" and "view" + * operations have no return value. + * - The "load" operation should return an array containing pairs + * of fields => values to be merged into the node object. + * + * If you are writing a node module, do not use this hook to perform + * actions on your type of node alone. Instead, use the hooks set aside + * for node modules, such as hook_insert() and hook_form(). That said, for + * some operations, such as "delete_revision" or "rss_item" there is no + * corresponding hook so even the module defining the node will need to + * implement hook_nodeapi(). + */ +function hook_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { + switch ($op) { + case 'presave': + if ($node->nid && $node->moderate) { + // Reset votes when node is updated: + $node->score = 0; + $node->users = ''; + $node->votes = 0; + } + break; + case 'insert': + case 'update': + if ($node->moderate && user_access('access submission queue')) { + drupal_set_message(t('The post is queued for approval')); + } + elseif ($node->moderate) { + drupal_set_message(t('The post is queued for approval. The editors will decide whether it should be published.')); + } + break; + case 'view': + $node->content['my_additional_field'] = array( + '#value' => theme('mymodule_my_additional_field', $additional_field), + '#weight' => 10, + ); + break; + } +} + +/** + * Define module-provided node types. + * + * This is a hook used by node modules. This hook is required for modules to + * define one or more node types. It is called to determine the names and the + * attributes of a module's node types. + * + * Only module-provided node types should be defined through this hook. User- + * provided (or 'custom') node types should be defined only in the 'node_type' + * database table, and should be maintained by using the node_type_save() and + * node_type_delete() functions. + * + * @return + * An array of information on the module's node types. The array contains a + * sub-array for each node type, with the machine-readable type name as the + * key. Each sub-array has up to 10 attributes. Possible attributes: + * - "name": the human-readable name of the node type. Required. + * - "module": a string telling Drupal how a module's functions map to hooks + * (i.e. if module is defined as example_foo, then example_foo_insert will + * be called when inserting a node of that type). This string is usually + * the name of the module in question, but not always. Required. + * - "description": a brief description of the node type. Required. + * - "help": text that will be displayed at the top of the submission form for + * this content type. Optional (defaults to ''). + * - "has_title": boolean indicating whether or not this node type has a title + * field. Optional (defaults to TRUE). + * - "title_label": the label for the title field of this content type. + * Optional (defaults to 'Title'). + * - "has_body": boolean indicating whether or not this node type has a body + * field. Optional (defaults to TRUE). + * - "body_label": the label for the body field of this content type. Optional + * (defaults to 'Body'). + * - "min_word_count": the minimum number of words for the body field to be + * considered valid for this content type. Optional (defaults to 0). + * - "locked": boolean indicating whether the machine-readable name of this + * content type can (FALSE) or cannot (TRUE) be edited by a site + * administrator. Optional (defaults to TRUE). + * + * The machine-readable name of a node type should contain only letters, + * numbers, and underscores. Underscores will be converted into hyphens for the + * purpose of contructing URLs. + * + * All attributes of a node type that are defined through this hook (except for + * 'locked') can be edited by a site administrator. This includes the + * machine-readable name of a node type, if 'locked' is set to FALSE. + * + * For a detailed usage example, see node_example.module. + */ +function hook_node_info() { + return array( + 'book' => array( + 'name' => t('book page'), + 'module' => 'book', + 'description' => t("A book is a collaborative writing effort: users can collaborate writing the pages of the book, positioning the pages in the right order, and reviewing or modifying pages previously written. So when you have some information to share or when you read a page of the book and you didn't like it, or if you think a certain page could have been written better, you can do something about it."), + ) + ); +} + +/** + * Act on node type changes. + * + * This hook allows modules to take action when a node type is modified. + * + * @param $op + * What is being done to $info. Possible values: + * - "delete" + * - "insert" + * - "update" + * @param $info + * The node type object on which $op is being performed. + * @return + * None. + */ +function hook_node_type($op, $info) { + + switch ($op) { + case 'delete': + variable_del('comment_' . $info->type); + break; + case 'update': + if (!empty($info->old_type) && $info->old_type != $info->type) { + $setting = variable_get('comment_' . $info->old_type, COMMENT_NODE_READ_WRITE); + variable_del('comment_' . $info->old_type); + variable_set('comment_' . $info->type, $setting); + } + break; + } +} + +/** + * Define access restrictions. + * + * This hook allows node modules to limit access to the node types they + * define. + * + * @param $op + * The operation to be performed. Possible values: + * - "create" + * - "delete" + * - "update" + * - "view" + * @param $node + * The node on which the operation is to be performed, or, if it does + * not yet exist, the type of node to be created. + * @param $account + * A user object representing the user for whom the operation is to be + * performed. + * @return + * TRUE if the operation is to be allowed; + * FALSE if the operation is to be denied; + * NULL to not override the settings in the node_access table, or access + * control modules. + * + * The administrative account (user ID #1) always passes any access check, + * so this hook is not called in that case. If this hook is not defined for + * a node type, all access checks will fail, so only the administrator will + * be able to see content of that type. However, users with the "administer + * nodes" permission may always view and edit content through the + * administrative interface. + * @see http://api.drupal.org/api/group/node_access/7 + * + * For a detailed usage example, see node_example.module. + * + * @ingroup node_access + */ +function hook_access($op, $node, $account) { + if ($op == 'create') { + return user_access('create stories', $account); + } + + if ($op == 'update' || $op == 'delete') { + if (user_access('edit own stories', $account) && ($account->uid == $node->uid)) { + return TRUE; + } + } +} + +/** + * Respond to node deletion. + * + * This is a hook used by node modules. It is called to allow the module + * to take action when a node is being deleted from the database by, for + * example, deleting information from related tables. + * + * @param &$node + * The node being deleted. + * @return + * None. + * + * To take action when nodes of any type are deleted (not just nodes of + * the type defined by this module), use hook_nodeapi() instead. + * + * For a detailed usage example, see node_example.module. + */ +function hook_delete(&$node) { + db_query('DELETE FROM {mytable} WHERE nid = %d', $node->nid); +} + +/** + * This is a hook used by node modules. It is called after load but before the + * node is shown on the add/edit form. + * + * @param &$node + * The node being saved. + * @return + * None. + * + * For a usage example, see image.module. + */ +function hook_prepare(&$node) { + if ($file = file_check_upload($field_name)) { + $file = file_save_upload($field_name, _image_filename($file->filename, NULL, TRUE)); + if ($file) { + if (!image_get_info($file->filepath)) { + form_set_error($field_name, t('Uploaded file is not a valid image')); + return; + } + } + else { + return; + } + $node->images['_original'] = $file->filepath; + _image_build_derivatives($node, true); + $node->new_file = TRUE; + } +} + +/** + * Display a node editing form. + * + * This hook, implemented by node modules, is called to retrieve the form + * that is displayed when one attempts to "create/edit" an item. This form is + * displayed at the URI http://www.example.com/?q=node/<add|edit>/nodetype. + * + * @param &$node + * The node being added or edited. + * @param $form_state + * The form state array. Changes made to this variable will have no effect. + * @return + * An array containing the form elements to be displayed in the node + * edit form. + * + * The submit and preview buttons, taxonomy controls, and administrative + * accoutrements are displayed automatically by node.module. This hook + * needs to return the node title, the body text area, and fields + * specific to the node type. + * + * For a detailed usage example, see node_example.module. + */ +function hook_form(&$node, $form_state) { + $type = node_get_types('type', $node); + + $form['title'] = array( + '#type' => 'textfield', + '#title' => check_plain($type->title_label), + '#required' => TRUE, + ); + $form['body'] = array( + '#type' => 'textarea', + '#title' => check_plain($type->body_label), + '#rows' => 20, + '#required' => TRUE, + ); + $form['field1'] = array( + '#type' => 'textfield', + '#title' => t('Custom field'), + '#default_value' => $node->field1, + '#maxlength' => 127, + ); + $form['selectbox'] = array( + '#type' => 'select', + '#title' => t('Select box'), + '#default_value' => $node->selectbox, + '#options' => array( + 1 => 'Option A', + 2 => 'Option B', + 3 => 'Option C', + ), + '#description' => t('Please choose an option.'), + ); + + return $form; +} + +/** + * Respond to node insertion. + * + * This is a hook used by node modules. It is called to allow the module + * to take action when a new node is being inserted in the database by, + * for example, inserting information into related tables. + * + * @param $node + * The node being inserted. + * @return + * None. + * + * To take action when nodes of any type are inserted (not just nodes of + * the type(s) defined by this module), use hook_nodeapi() instead. + * + * For a detailed usage example, see node_example.module. + */ +function hook_insert($node) { + db_query("INSERT INTO {mytable} (nid, extra) + VALUES (%d, '%s')", $node->nid, $node->extra); +} + +/** + * Load node-type-specific information. + * + * This is a hook used by node modules. It is called to allow the module + * a chance to load extra information that it stores about a node, or + * possibly replace already loaded information - which can be dangerous. + * + * @param $node + * The node being loaded. At call time, node.module has already loaded + * the basic information about the node, such as its node ID (nid), + * title, and body. + * @return + * An object containing properties of the node being loaded. This will + * be merged with the passed-in $node to result in an object containing + * a set of properties resulting from adding the extra properties to + * the passed-in ones, and overwriting the passed-in ones with the + * extra properties if they have the same name as passed-in properties. + * + * For a detailed usage example, see node_example.module. + */ +function hook_load($node) { + $additions = db_fetch_object(db_query('SELECT * FROM {mytable} WHERE vid = %d', $node->vid)); + return $additions; +} + +/** + * Respond to node updating. + * + * This is a hook used by node modules. It is called to allow the module + * to take action when an edited node is being updated in the database by, + * for example, updating information in related tables. + * + * @param $node + * The node being updated. + * @return + * None. + * + * To take action when nodes of any type are updated (not just nodes of + * the type(s) defined by this module), use hook_nodeapi() instead. + * + * For a detailed usage example, see node_example.module. + */ +function hook_update($node) { + db_query("UPDATE {mytable} SET extra = '%s' WHERE nid = %d", + $node->extra, $node->nid); +} + +/** + * Verify a node editing form. + * + * This is a hook used by node modules. It is called to allow the module + * to verify that the node is in a format valid to post to the site. + * Errors should be set with form_set_error(). + * + * @param $node + * The node to be validated. + * @param $form + * The node edit form array. + * @return + * None. + * + * To validate nodes of all types (not just nodes of the type(s) defined by + * this module), use hook_nodeapi() instead. + * + * Changes made to the $node object within a hook_validate() function will + * have no effect. The preferred method to change a node's content is to use + * hook_submit() or hook_nodeapi($op='submit') instead. If it is really + * necessary to change the node at the validate stage, you can use function + * form_set_value(). + * + * For a detailed usage example, see node_example.module. + */ +function hook_validate($node, &$form) { + if (isset($node->end) && isset($node->start)) { + if ($node->start > $node->end) { + form_set_error('time', t('An event may not end before it starts.')); + } + } +} + +/** + * Display a node. + * + * This is a hook used by node modules. It allows a module to define a + * custom method of displaying its nodes, usually by displaying extra + * information particular to that node type. + * + * @param $node + * The node to be displayed. + * @param $teaser + * Whether we are to generate a "teaser" or summary of the node, rather than + * display the whole thing. + * @param $page + * Whether the node is being displayed as a standalone page. If this is + * TRUE, the node title should not be displayed, as it will be printed + * automatically by the theme system. Also, the module may choose to alter + * the default breadcrumb trail in this case. + * @return + * $node. The passed $node parameter should be modified as necessary and + * returned so it can be properly presented. Nodes are prepared for display + * by assembling a structured array in $node->content, rather than directly + * manipulating $node->body and $node->teaser. The format of this array is + * the same used by the Forms API. As with FormAPI arrays, the #weight + * property can be used to control the relative positions of added elements. + * If for some reason you need to change the body or teaser returned by + * node_prepare(), you can modify $node->content['body']['#value']. Note + * that this will be the un-rendered content. To modify the rendered output, + * see hook_nodeapi($op = 'alter'). + * + * For a detailed usage example, see node_example.module. + */ +function hook_view($node, $teaser = FALSE, $page = FALSE) { + if ($page) { + $breadcrumb = array(); + $breadcrumb[] = array('path' => 'example', 'title' => t('example')); + $breadcrumb[] = array('path' => 'example/' . $node->field1, + 'title' => t('%category', array('%category' => $node->field1))); + $breadcrumb[] = array('path' => 'node/' . $node->nid); + menu_set_location($breadcrumb); + } + + $node = node_prepare($node, $teaser); + $node->content['myfield'] = array( + '#value' => theme('mymodule_myfield', $node->myfield), + '#weight' => 1, + ); + + return $node; +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/modules/search/search.api.php b/modules/search/search.api.php new file mode 100644 index 000000000000..8d7e97bf2c2a --- /dev/null +++ b/modules/search/search.api.php @@ -0,0 +1,284 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by the Search module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Define a custom search routine. + * + * This hook allows a module to perform searches on content it defines + * (custom node types, users, or comments, for example) when a site search + * is performed. + * + * Note that you can use form API to extend the search. You will need to use + * hook_form_alter() to add any additional required form elements. You can + * process their values on submission using a custom validation function. + * You will need to merge any custom search values into the search keys + * using a key:value syntax. This allows all search queries to have a clean + * and permanent URL. See node_form_alter() for an example. + * + * @param $op + * A string defining which operation to perform: + * - 'name': the hook should return a translated name defining the type of + * items that are searched for with this module ('content', 'users', ...) + * - 'reset': the search index is going to be rebuilt. Modules which use + * hook_update_index() should update their indexing bookkeeping so that it + * starts from scratch the next time hook_update_index() is called. + * - 'search': the hook should perform a search using the keywords in $keys + * - 'status': if the module implements hook_update_index(), it should return + * an array containing the following keys: + * - remaining: the amount of items that still need to be indexed + * - total: the total amount of items (both indexed and unindexed) + * + * @param $keys + * The search keywords as entered by the user. + * + * @return + * An array of search results. + * Each item in the result set array may contain whatever information + * the module wishes to display as a search result. + * To use the default search result display, each item should be an + * array which can have the following keys: + * - link: the URL of the found item + * - type: the type of item + * - title: the name of the item + * - user: the author of the item + * - date: a timestamp when the item was last modified + * - extra: an array of optional extra information items + * - snippet: an excerpt or preview to show with the result + * (can be generated with search_excerpt()) + * Only 'link' and 'title' are required, but it is advised to fill in + * as many of these fields as possible. + * + * The example given here is for node.module, which uses the indexed search + * capabilities. To do this, node module also implements hook_update_index() + * which is used to create and maintain the index. + * + * We call do_search() with the keys, the module name and extra SQL fragments + * to use when searching. See hook_update_index() for more information. + * + * @ingroup search + */ +function hook_search($op = 'search', $keys = null) { + switch ($op) { + case 'name': + return t('Content'); + + case 'reset': + db_query("UPDATE {search_dataset} SET reindex = %d WHERE type = 'node'", REQUEST_TIME); + return; + + case 'status': + $total = db_result(db_query('SELECT COUNT(*) FROM {node} WHERE status = 1')); + $remaining = db_result(db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE n.status = 1 AND d.sid IS NULL OR d.reindex <> 0")); + return array('remaining' => $remaining, 'total' => $total); + + case 'admin': + $form = array(); + // Output form for defining rank factor weights. + $form['content_ranking'] = array( + '#type' => 'fieldset', + '#title' => t('Content ranking'), + ); + $form['content_ranking']['#theme'] = 'node_search_admin'; + $form['content_ranking']['info'] = array( + '#value' => '<em>' . t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>' + ); + + // Note: reversed to reflect that higher number = higher ranking. + $options = drupal_map_assoc(range(0, 10)); + foreach (module_invoke_all('ranking') as $var => $values) { + $form['content_ranking']['factors']['node_rank_' . $var] = array( + '#title' => $values['title'], + '#type' => 'select', + '#options' => $options, + '#default_value' => variable_get('node_rank_' . $var, 0), + ); + } + return $form; + + case 'search': + // Build matching conditions + list($join1, $where1) = _db_rewrite_sql(); + $arguments1 = array(); + $conditions1 = 'n.status = 1'; + + if ($type = search_query_extract($keys, 'type')) { + $types = array(); + foreach (explode(',', $type) as $t) { + $types[] = "n.type = '%s'"; + $arguments1[] = $t; + } + $conditions1 .= ' AND (' . implode(' OR ', $types) . ')'; + $keys = search_query_insert($keys, 'type'); + } + + if ($category = search_query_extract($keys, 'category')) { + $categories = array(); + foreach (explode(',', $category) as $c) { + $categories[] = "tn.tid = %d"; + $arguments1[] = $c; + } + $conditions1 .= ' AND (' . implode(' OR ', $categories) . ')'; + $join1 .= ' INNER JOIN {term_node} tn ON n.vid = tn.vid'; + $keys = search_query_insert($keys, 'category'); + } + + if ($languages = search_query_extract($keys, 'language')) { + $categories = array(); + foreach (explode(',', $languages) as $l) { + $categories[] = "n.language = '%s'"; + $arguments1[] = $l; + } + $conditions1 .= ' AND (' . implode(' OR ', $categories) . ')'; + $keys = search_query_insert($keys, 'language'); + } + + // Get the ranking expressions. + $rankings = _node_rankings(); + + // When all search factors are disabled (ie they have a weight of zero), + // The default score is based only on keyword relevance. + if ($rankings['total'] == 0) { + $total = 1; + $arguments2 = array(); + $join2 = ''; + $select2 = 'i.relevance AS score'; + } + else { + $total = $rankings['total']; + $arguments2 = $rankings['arguments']; + $join2 = implode(' ', $rankings['join']); + $select2 = '(' . implode(' + ', $rankings['score']) . ') AS score'; + } + + // Do search. + $find = do_search($keys, 'node', 'INNER JOIN {node} n ON n.nid = i.sid ' . $join1, $conditions1 . (empty($where1) ? '' : ' AND ' . $where1), $arguments1, $select2, $join2, $arguments2); + + // Load results. + $results = array(); + foreach ($find as $item) { + // Build the node body. + $node = node_load($item->sid); + $node->build_mode = NODE_BUILD_SEARCH_RESULT; + $node = node_build_content($node, FALSE, FALSE); + $node->body = drupal_render($node->content); + + // Fetch comments for snippet. + $node->body .= module_invoke('comment', 'nodeapi', $node, 'update_index'); + // Fetch terms for snippet. + $node->body .= module_invoke('taxonomy', 'nodeapi', $node, 'update_index'); + + $extra = node_invoke_nodeapi($node, 'search_result'); + + $results[] = array( + 'link' => url('node/' . $item->sid, array('absolute' => TRUE)), + 'type' => check_plain(node_get_types('name', $node)), + 'title' => $node->title, + 'user' => theme('username', $node), + 'date' => $node->changed, + 'node' => $node, + 'extra' => $extra, + 'score' => $total ? ($item->score / $total) : 0, + 'snippet' => search_excerpt($keys, $node->body), + ); + } + return $results; + } +} + +/** + * Preprocess text for the search index. + * + * This hook is called both for text added to the search index, as well as + * the keywords users have submitted for searching. + * + * This is required for example to allow Japanese or Chinese text to be + * searched. As these languages do not use spaces, it needs to be split into + * separate words before it can be indexed. There are various external + * libraries for this. + * + * @param $text + * The text to split. This is a single piece of plain-text that was + * extracted from between two HTML tags. Will not contain any HTML entities. + * @return + * The text after processing. + */ +function hook_search_preprocess($text) { + // Do processing on $text + return $text; +} + +/** + * Update Drupal's full-text index for this module. + * + * Modules can implement this hook if they want to use the full-text indexing + * mechanism in Drupal. + * + * This hook is called every cron run if search.module is enabled. A module + * should check which of its items were modified or added since the last + * run. It is advised that you implement a throttling mechanism which indexes + * at most 'search_cron_limit' items per run (see example below). + * + * You should also be aware that indexing may take too long and be aborted if + * there is a PHP time limit. That's why you should update your internal + * bookkeeping multiple times per run, preferably after every item that + * is indexed. + * + * Per item that needs to be indexed, you should call search_index() with + * its content as a single HTML string. The search indexer will analyse the + * HTML and use it to assign higher weights to important words (such as + * titles). It will also check for links that point to nodes, and use them to + * boost the ranking of the target nodes. + * + * @ingroup search + */ +function hook_update_index() { + $last = variable_get('node_cron_last', 0); + $limit = (int)variable_get('search_cron_limit', 100); + + $result = db_query_range('SELECT n.nid, c.last_comment_timestamp FROM {node} n LEFT JOIN {node_comment_statistics} c ON n.nid = c.nid WHERE n.status = 1 AND n.moderate = 0 AND (n.created > %d OR n.changed > %d OR c.last_comment_timestamp > %d) ORDER BY GREATEST(n.created, n.changed, c.last_comment_timestamp) ASC', $last, $last, $last, 0, $limit); + + while ($node = db_fetch_object($result)) { + $last_comment = $node->last_comment_timestamp; + $node = node_load(array('nid' => $node->nid)); + + // We update this variable per node in case cron times out, or if the node + // cannot be indexed (PHP nodes which call drupal_goto, for example). + // In rare cases this can mean a node is only partially indexed, but the + // chances of this happening are very small. + variable_set('node_cron_last', max($last_comment, $node->changed, $node->created)); + + // Get node output (filtered and with module-specific fields). + if (node_hook($node, 'view')) { + node_invoke($node, 'view', false, false); + } + else { + $node = node_prepare($node, false); + } + // Allow modules to change $node->body before viewing. + node_invoke_nodeapi($node, 'view', false, false); + + $text = '<h1>' . drupal_specialchars($node->title) . '</h1>' . $node->body; + + // Fetch extra data normally not visible + $extra = node_invoke_nodeapi($node, 'update_index'); + foreach ($extra as $t) { + $text .= $t; + } + + // Update index + search_index($node->nid, 'node', $text); + } +} +/** + * @} End of "addtogroup hooks". + */ diff --git a/modules/system/system.api.php b/modules/system/system.api.php new file mode 100644 index 000000000000..8aa10b2d01a3 --- /dev/null +++ b/modules/system/system.api.php @@ -0,0 +1,1525 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by Drupal core and the System module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Perform periodic actions. + * + * Modules that require to schedule some commands to be executed at regular + * intervals can implement hook_cron(). The engine will then call the hook + * at the appropriate intervals defined by the administrator. This interface + * is particularly handy to implement timers or to automate certain tasks. + * Database maintenance, recalculation of settings or parameters, and + * automatic mailings are good candidates for cron tasks. + * + * @return + * None. + * + * This hook will only be called if cron.php is run (e.g. by crontab). + */ +function hook_cron() { + $result = db_query('SELECT * FROM {site} WHERE checked = 0 OR checked + refresh < :time', array(':time' => REQUEST_TIME)); + + foreach ($result as $site) { + cloud_update($site); + } +} + +/** + * Rewrite database queries, usually for access control. + * + * Add JOIN and WHERE statements to queries and decide whether the primary_field + * shall be made DISTINCT. For node objects, primary field is always called nid. + * For taxonomy terms, it is tid and for vocabularies it is vid. For comments, + * it is cid. Primary table is the table where the primary object (node, file, + * term_node etc.) is. + * + * You shall return an associative array. Possible keys are 'join', 'where' and + * 'distinct'. The value of 'distinct' shall be 1 if you want that the + * primary_field made DISTINCT. + * + * @param $query + * Query to be rewritten. + * @param $primary_table + * Name or alias of the table which has the primary key field for this query. + * Typical table names would be: {blocks}, {comments}, {forum}, {node}, + * {menu}, {term_data} or {vocabulary}. However, it is more common for + * $primary_table to contain the usual table alias: b, c, f, n, m, t or v. + * @param $primary_field + * Name of the primary field. + * @param $args + * Array of additional arguments. + * @return + * An array of join statements, where statements, distinct decision. + */ +function hook_db_rewrite_sql($query, $primary_table, $primary_field, $args) { + switch ($primary_field) { + case 'nid': + // this query deals with node objects + $return = array(); + if ($primary_table != 'n') { + $return['join'] = "LEFT JOIN {node} n ON $primary_table.nid = n.nid"; + } + $return['where'] = 'created >' . mktime(0, 0, 0, 1, 1, 2005); + return $return; + break; + case 'tid': + // this query deals with taxonomy objects + break; + case 'vid': + // this query deals with vocabulary objects + break; + } +} + +/** + * Allows modules to declare their own Forms API element types and specify their + * default values. + * + * This hook allows modules to declare their own form element types and to + * specify their default values. The values returned by this hook will be + * merged with the elements returned by hook_form() implementations and so + * can return defaults for any Form APIs keys in addition to those explicitly + * mentioned below. + * + * Each of the form element types defined by this hook is assumed to have + * a matching theme function, e.g. theme_elementtype(), which should be + * registered with hook_theme() as normal. + * + * Form more information about custom element types see the explanation at + * http://drupal.org/node/169815. + * + * @return + * An associative array describing the element types being defined. The array + * contains a sub-array for each element type, with the machine-readable type + * name as the key. Each sub-array has a number of possible attributes: + * - "#input": boolean indicating whether or not this element carries a value + * (even if it's hidden). + * - "#process": array of callback functions taking $element and $form_state. + * - "#after_build": array of callback functions taking $element and $form_state. + * - "#validate": array of callback functions taking $form and $form_state. + * - "#element_validate": array of callback functions taking $element and + * $form_state. + * - "#pre_render": array of callback functions taking $element and $form_state. + * - "#post_render": array of callback functions taking $element and $form_state. + * - "#submit": array of callback functions taking $form and $form_state. + */ +function hook_elements() { + $type['filter_format'] = array('#input' => TRUE); + return $type; +} + +/** + * Perform cleanup tasks. + * + * This hook is run at the end of each page request. It is often used for + * page logging and printing out debugging information. + * + * Only use this hook if your code must run even for cached page views. + * If you have code which must run once on all non cached pages, use + * hook_init instead. Thats the usual case. If you implement this hook + * and see an error like 'Call to undefined function', it is likely that + * you are depending on the presence of a module which has not been loaded yet. + * It is not loaded because Drupal is still in bootstrap mode. + * + * @param $destination + * If this hook is invoked as part of a drupal_goto() call, then this argument + * will be a fully-qualified URL that is the destination of the redirect. + * Modules may use this to react appropriately; for example, nothing should + * be output in this case, because PHP will then throw a "headers cannot be + * modified" error when attempting the redirection. + * @return + * None. + */ +function hook_exit($destination = NULL) { + db_query('UPDATE {counter} SET hits = hits + 1 WHERE type = 1'); +} + +/** + * Insert closing HTML. + * + * This hook enables modules to insert HTML just before the \</body\> closing + * tag of web pages. This is useful for adding JavaScript code to the footer + * and for outputting debug information. It is not possible to add JavaScript + * to the header at this point, and developers wishing to do so should use + * hook_init() instead. + * + * @param $main + * Whether the current page is the front page of the site. + * @return + * The HTML to be inserted. + */ +function hook_footer($main = 0) { + if (variable_get('dev_query', 0)) { + return '<div style="clear:both;">' . devel_query_table() . '</div>'; + } +} + +/** + * Perform necessary alterations to the JavaScript before it is presented on + * the page. + * + * @param $javascript + * An array of all JavaScript being presented on the page. + * @see drupal_add_js() + * @see drupal_get_js() + * @see drupal_js_defaults() + */ +function hook_js_alter(&$javascript) { + // Swap out jQuery to use an updated version of the library. + $javascript['misc/jquery.js']['data'] = drupal_get_path('module', 'jquery_update') . '/jquery.js'; +} + +/** + * Perform alterations before a form is rendered. + * + * One popular use of this hook is to add form elements to the node form. When + * altering a node form, the node object retrieved at from $form['#node']. + * + * @param $form + * Nested array of form elements that comprise the form. + * @param $form_state + * A keyed array containing the current state of the form. + * @param $form_id + * String representing the name of the form itself. Typically this is the + * name of the function that generated the form. + * @return + * None. + */ +function hook_form_alter(&$form, $form_state, $form_id) { + if (isset($form['type']) && $form['type']['#value'] . '_node_settings' == $form_id) { + $form['workflow']['upload_' . $form['type']['#value']] = array( + '#type' => 'radios', + '#title' => t('Attachments'), + '#default_value' => variable_get('upload_' . $form['type']['#value'], 1), + '#options' => array(t('Disabled'), t('Enabled')), + ); + } +} + +/** + * Provide a form-specific alteration instead of the global hook_form_alter(). + * + * Modules can implement hook_form_FORM_ID_alter() to modify a specific form, + * rather than implementing hook_form_alter() and checking the form ID, or + * using long switch statements to alter multiple forms. + * + * Note that this hook fires before hook_form_alter(). Therefore all + * implementations of hook_form_FORM_ID_alter() will run before all implementations + * of hook_form_alter(), regardless of the module order. + * + * @param $form + * Nested array of form elements that comprise the form. + * @param $form_state + * A keyed array containing the current state of the form. + * @return + * None. + * + * @see drupal_prepare_form(). + */ +function hook_form_FORM_ID_alter(&$form, &$form_state) { + // Modification for the form with the given form ID goes here. For example, if + // FORM_ID is "user_register" this code would run only on the user + // registration form. + + // Add a checkbox to registration form about agreeing to terms of use. + $form['terms_of_use'] = array( + '#type' => 'checkbox', + '#title' => t("I agree with the website's terms and conditions."), + '#required' => TRUE, + ); +} + +/** + * Map form_ids to builder functions. + * + * This hook allows modules to build multiple forms from a single form "factory" + * function but each form will have a different form id for submission, + * validation, theming or alteration by other modules. + * + * The callback arguments will be passed as parameters to the function. Callers + * of drupal_get_form() are also able to pass in parameters. These will be + * appended after those specified by hook_forms(). + * + * See node_forms() for an actual example of how multiple forms share a common + * building function. + * + * @return + * An array keyed by form id with callbacks and optional, callback arguments. + */ +function hook_forms() { + $forms['mymodule_first_form'] = array( + 'callback' => 'mymodule_form_builder', + 'callback arguments' => array('some parameter'), + ); + $forms['mymodule_second_form'] = array( + 'callback' => 'mymodule_form_builder', + ); + return $forms; +} + +/** + * Perform setup tasks. See also, hook_init. + * + * This hook is run at the beginning of the page request. It is typically + * used to set up global parameters which are needed later in the request. + * + * Only use this hook if your code must run even for cached page views.This hook + * is called before modules or most include files are loaded into memory. + * It happens while Drupal is still in bootstrap mode. + * + * @return + * None. + */ +function hook_boot() { + // we need user_access() in the shutdown function. make sure it gets loaded + drupal_load('module', 'user'); + register_shutdown_function('devel_shutdown'); +} + +/** + * Perform setup tasks. See also, hook_boot. + * + * This hook is run at the beginning of the page request. It is typically + * used to set up global parameters which are needed later in the request. + * when this hook is called, all modules are already loaded in memory. + * + * For example, this hook is a typical place for modules to add CSS or JS + * that should be present on every page. This hook is not run on cached + * pages - though CSS or JS added this way will be present on a cached page. + * + * @return + * None. + */ +function hook_init() { + drupal_add_css(drupal_get_path('module', 'book') . '/book.css'); +} + +/** +* Define image toolkits provided by this module. +* +* The file which includes each toolkit's functions must be declared as part of +* the files array in the module .info file so that the registry will find and +* parse it. +* +* @return +* An array of image toolkit names. +*/ +function hook_image_toolkits() { + return array('gd'); +} + +/** + * Define internal Drupal links. + * + * This hook enables modules to add links to many parts of Drupal. Links + * may be added in nodes or in the navigation block, for example. + * + * The returned array should be a keyed array of link entries. Each link can + * be in one of two formats. + * + * The first format will use the l() function to render the link: + * - attributes: Optional. See l() for usage. + * - fragment: Optional. See l() for usage. + * - href: Required. The URL of the link. + * - html: Optional. See l() for usage. + * - query: Optional. See l() for usage. + * - title: Required. The name of the link. + * + * The second format can be used for non-links. Leaving out the href index will + * select this format: + * - title: Required. The text or HTML code to display. + * - attributes: Optional. An associative array of HTML attributes to apply to the span tag. + * - html: Optional. If not set to true, check_plain() will be run on the title before it is displayed. + * + * @param $type + * An identifier declaring what kind of link is being requested. + * Possible values: + * - comment: Links to be placed below a comment being viewed. + * - node: Links to be placed below a node being viewed. + * @param $object + * A node object or a comment object according to the $type. + * @param $teaser + * In case of node link: a 0/1 flag depending on whether the node is + * displayed with its teaser or its full form. + * @return + * An array of the requested links. + * + */ +function hook_link($type, $object, $teaser = FALSE) { + $links = array(); + + if ($type == 'node' && isset($object->parent)) { + if (!$teaser) { + if (book_access('create', $object)) { + $links['book_add_child'] = array( + 'title' => t('add child page'), + 'href' => "node/add/book/parent/$object->nid", + ); + } + if (user_access('see printer-friendly version')) { + $links['book_printer'] = array( + 'title' => t('printer-friendly version'), + 'href' => 'book/export/html/' . $object->nid, + 'attributes' => array('title' => t('Show a printer-friendly version of this book page and its sub-pages.')) + ); + } + } + } + + $links['sample_link'] = array( + 'title' => t('go somewhere'), + 'href' => 'node/add', + 'query' => 'foo=bar', + 'fragment' => 'anchorname', + 'attributes' => array('title' => t('go to another page')), + ); + + // Example of a link that's not an anchor + if ($type == 'video') { + if (variable_get('video_playcounter', 1) && user_access('view play counter')) { + $links['play_counter'] = array( + 'title' => format_plural($object->play_counter, '1 play', '@count plays'), + ); + } + } + + return $links; +} + +/** + * Perform alterations before links on a node are rendered. One popular use of + * this hook is to add/delete links from other modules. + * + * @param $links + * Nested array of links for the node + * @param $node + * A node object for editing links on + * @return + * None. + */ +function hook_link_alter(&$links, $node) { + foreach ($links AS $module => $link) { + if (strstr($module, 'taxonomy_term')) { + // Link back to the forum and not the taxonomy term page + $links[$module]['#href'] = str_replace('taxonomy/term', 'forum', $link['#href']); + } + } +} + +/** + * Perform alterations profile items before they are rendered. You may omit/add/re-sort/re-categorize, etc. + * + * @param $account + * A user object whose profile is being rendered. Profile items + * are stored in $account->content. + * @return + * None. + */ +function hook_profile_alter(&$account) { + foreach ($account->content AS $key => $field) { + // do something + } +} + +/** + * Alter any aspect of the emails sent by Drupal. You can use this hook + * to add a common site footer to all outgoing emails; add extra header + * fields and/or modify the mails sent out in any way. HTML-izing the + * outgoing mails is one possibility. See also drupal_mail(). + * + * @param $message + * A structured array containing the message to be altered. Keys in this + * array include: + * mail_id + * An id to identify the mail sent. Look into the module source codes + * for possible mail_id values. + * to + * The mail address or addresses where the message will be send to. The + * formatting of this string must comply with RFC 2822. + * subject + * Subject of the e-mail to be sent. This must not contain any newline + * characters, or the mail may not be sent properly. + * body + * An array of lines containing the message to be sent. Drupal will format + * the correct line endings for you. + * from + * The From, Reply-To, Return-Path and Error-To headers in $headers + * are already set to this value (if given). + * headers + * Associative array containing the headers to add. This is typically + * used to add extra headers (From, Cc, and Bcc). + * @return + * None. + */ +function hook_mail_alter(&$message) { + if ($message['mail_id'] == 'my_message') { + $message['body'] .= "\n\n--\nMail sent out from " . variable_get('sitename', t('Drupal')); + } +} + +/** + * Alter the information parsed from module and theme .info files + * + * This hook is invoked in module_rebuild_cache() and in system_theme_data(). + * A module may implement this hook in order to add to or alter the data + * generated by reading the .info file with drupal_parse_info_file(). + * + * @param &$info + * The .info file contents, passed by reference so that it can be altered. + * @param $file + * Full information about the module or theme, including $file->name, and + * $file->filename + */ +function hook_system_info_alter(&$info, $file) { + // Only fill this in if the .info file does not define a 'datestamp'. + if (empty($info['datestamp'])) { + $info['datestamp'] = filemtime($file->filename); + } +} + +/** + * Define user permissions. + * + * This hook can supply permissions that the module defines, so that they + * can be selected on the user permissions page and used to restrict + * access to actions the module performs. + * + * @return + * An array of which permission names are the keys and their corresponding value is a description of the permission + * + * The permissions in the array do not need to be wrapped with the function t(), + * since the string extractor takes care of extracting permission names defined in the perm hook for translation. + * + * Permissions are checked using user_access(). + * + * For a detailed usage example, see page_example.module. + */ +function hook_perm() { + return array( + 'administer my module' => t('Perform maintenance tasks for my module'), + ); +} + +/** + * Register a module (or theme's) theme implementations. + * + * Modules and themes implementing this return an array of arrays. The key + * to each sub-array is the internal name of the hook, and the array contains + * info about the hook. Each array may contain the following items: + * + * - arguments: (required) An array of arguments that this theme hook uses. This + * value allows the theme layer to properly utilize templates. The + * array keys represent the name of the variable, and the value will be + * used as the default value if not specified to the theme() function. + * These arguments must be in the same order that they will be given to + * the theme() function. + * - file: The file the implementation resides in. This file will be included + * prior to the theme being rendered, to make sure that the function or + * preprocess function (as needed) is actually loaded; this makes it possible + * to split theme functions out into separate files quite easily. + * - path: Override the path of the file to be used. Ordinarily the module or + * theme path will be used, but if the file will not be in the default path, + * include it here. This path should be relative to the Drupal root + * directory. + * - template: If specified, this theme implementation is a template, and this + * is the template file <b>without an extension</b>. Do not put .tpl.php + * on this file; that extension will be added automatically by the default + * rendering engine (which is PHPTemplate). If 'path', above, is specified, + * the template should also be in this path. + * - function: If specified, this will be the function name to invoke for this + * implementation. If neither file nor function is specified, a default + * function name will be assumed. For example, if a module registers + * the 'node' theme hook, 'theme_node' will be assigned to its function. + * If the chameleon theme registers the node hook, it will be assigned + * 'chameleon_node' as its function. + * - pattern: A regular expression pattern to be used to allow this theme + * implementation to have a dynamic name. The convention is to use __ to + * differentiate the dynamic portion of the theme. For example, to allow + * forums to be themed individually, the pattern might be: 'forum__'. Then, + * when the forum is themed, call: <code>theme(array('forum__' . $tid, 'forum'), + * $forum)</code>. + * - preprocess functions: A list of functions used to preprocess this data. + * Ordinarily this won't be used; it's automatically filled in. By default, + * for a module this will be filled in as template_preprocess_HOOK. For + * a theme this will be filled in as phptemplate_preprocess and + * phptemplate_preprocess_HOOK as well as themename_preprocess and + * themename_preprocess_HOOK. + * - override preprocess functions: Set to TRUE when a theme does NOT want the + * standard preprocess functions to run. This can be used to give a theme + * FULL control over how variables are set. For example, if a theme wants + * total control over how certain variables in the page.tpl.php are set, + * this can be set to true. Please keep in mind that when this is used + * by a theme, that theme becomes responsible for making sure necessary + * variables are set. + * - type: (automatically derived) Where the theme hook is defined: + * 'module', 'theme_engine', or 'theme'. + * - theme path: (automatically derived) The directory path of the theme or + * module, so that it doesn't need to be looked up. + * - theme paths: (automatically derived) An array of template suggestions where + * .tpl.php files related to this theme hook may be found. + * + * The following parameters are all optional. + * + * @param $existing + * An array of existing implementations that may be used for override + * purposes. This is primarily useful for themes that may wish to examine + * existing implementations to extract data (such as arguments) so that + * it may properly register its own, higher priority implementations. + * @param $type + * What 'type' is being processed. This is primarily useful so that themes + * tell if they are the actual theme being called or a parent theme. + * May be one of: + * - module: A module is being checked for theme implementations. + * - base_theme_engine: A theme engine is being checked for a theme which is a parent of the actual theme being used. + * - theme_engine: A theme engine is being checked for the actual theme being used. + * - base_theme: A base theme is being checked for theme implementations. + * - theme: The actual theme in use is being checked. + * @param $theme + * The actual name of theme that is being being checked (mostly only useful for + * theme engine). + * @param $path + * The directory path of the theme or module, so that it doesn't need to be + * looked up. + * + * @return + * A keyed array of theme hooks. + */ +function hook_theme($existing, $type, $theme, $path) { + return array( + 'forum_display' => array( + 'arguments' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL), + ), + 'forum_list' => array( + 'arguments' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL), + ), + 'forum_topic_list' => array( + 'arguments' => array('tid' => NULL, 'topics' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL), + ), + 'forum_icon' => array( + 'arguments' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0), + ), + 'forum_topic_navigation' => array( + 'arguments' => array('node' => NULL), + ), + ); +} + +/** + * Alter the theme registry information returned from hook_theme(). + * + * The theme registry stores information about all available theme hooks, + * including which callback functions those hooks will call when triggered, + * what template files are exposed by these hooks, and so on. + * + * Note that this hook is only executed as the theme cache is re-built. + * Changes here will not be visible until the next cache clear. + * + * The $theme_registry array is keyed by theme hook name, and contains the + * information returned from hook_theme(), as well as additional properties + * added by _theme_process_registry(). + * + * For example: + * @code + * $theme_registry['user_profile'] = array( + * 'arguments' => array( + * 'account' => NULL, + * ), + * 'template' => 'modules/user/user-profile', + * 'file' => 'modules/user/user.pages.inc', + * 'type' => 'module', + * 'theme path' => 'modules/user', + * 'theme paths' => array( + * 0 => 'modules/user', + * ), + * 'preprocess functions' => array( + * 0 => 'template_preprocess', + * 1 => 'template_preprocess_user_profile', + * ), + * ) + * ); + * @endcode + * + * @param $theme_registry + * The entire cache of theme registry information, post-processing. + * @see hook_theme() + * @see _theme_process_registry() + */ +function hook_theme_registry_alter(&$theme_registry) { + // Kill the next/previous forum topic navigation links. + foreach ($theme_registry['forum_topic_navigation']['preprocess functions'] as $key => $value) { + if ($value = 'template_preprocess_forum_topic_navigation') { + unset($theme_registry['forum_topic_navigation']['preprocess functions'][$key]); + } + } +} + +/** + * Register XML-RPC callbacks. + * + * This hook lets a module register callback functions to be called when + * particular XML-RPC methods are invoked by a client. + * + * @return + * An array which maps XML-RPC methods to Drupal functions. Each array + * element is either a pair of method => function or an array with four + * entries: + * - The XML-RPC method name (for example, module.function). + * - The Drupal callback function (for example, module_function). + * - The method signature is an array of XML-RPC types. The first element + * of this array is the type of return value and then you should write a + * list of the types of the parameters. XML-RPC types are the following + * (See the types at http://www.xmlrpc.com/spec): + * - "boolean": 0 (false) or 1 (true). + * - "double": a floating point number (for example, -12.214). + * - "int": a integer number (for example, -12). + * - "array": an array without keys (for example, array(1, 2, 3)). + * - "struct": an associative array or an object (for example, + * array('one' => 1, 'two' => 2)). + * - "date": when you return a date, then you may either return a + * timestamp (time(), mktime() etc.) or an ISO8601 timestamp. When + * date is specified as an input parameter, then you get an object, + * which is described in the function xmlrpc_date + * - "base64": a string containing binary data, automatically + * encoded/decoded automatically. + * - "string": anything else, typically a string. + * - A descriptive help string, enclosed in a t() function for translation + * purposes. + * Both forms are shown in the example. + */ +function hook_xmlrpc() { + return array( + 'drupal.login' => 'drupal_login', + array( + 'drupal.site.ping', + 'drupal_directory_ping', + array('boolean', 'string', 'string', 'string', 'string', 'string'), + t('Handling ping request')) + ); +} + +/** + * Log an event message + * + * This hook allows modules to route log events to custom destinations, such as + * SMS, Email, pager, syslog, ...etc. + * + * @param $log_entry + * The log_entry is an associative array containing the following keys: + * - type: The type of message for this entry. For contributed modules, this is + * normally the module name. Do not use 'debug', use severity WATCHDOG_DEBUG instead. + * - user: The user object for the user who was logged in when the event happened. + * - request_uri: The Request URI for the page the event happened in. + * - referer: The page that referred the use to the page where the event occurred. + * - ip: The IP address where the request for the page came from. + * - timestamp: The UNIX timetamp of the date/time the event occurred + * - severity: One of the following values as defined in RFC 3164 http://www.faqs.org/rfcs/rfc3164.html + * WATCHDOG_EMERG Emergency: system is unusable + * WATCHDOG_ALERT Alert: action must be taken immediately + * WATCHDOG_CRITICAL Critical: critical conditions + * WATCHDOG_ERROR Error: error conditions + * WATCHDOG_WARNING Warning: warning conditions + * WATCHDOG_NOTICE Notice: normal but significant condition + * WATCHDOG_INFO Informational: informational messages + * WATCHDOG_DEBUG Debug: debug-level messages + * - link: an optional link provided by the module that called the watchdog() function. + * - message: The text of the message to be logged. + * + * @return + * None. + */ +function hook_watchdog($log_msg) { + global $base_url; + + $severity_list = array( + WATCHDOG_EMERG => t('Emergency'), + WATCHDOG_ALERT => t('Alert'), + WATCHDOG_CRITICAL => t('Critical'), + WATCHDOG_ERROR => t('Error'), + WATCHDOG_WARNING => t('Warning'), + WATCHDOG_NOTICE => t('Notice'), + WATCHDOG_INFO => t('Info'), + WATCHDOG_DEBUG => t('Debug'), + ); + + $to = "someone@example.com"; + $subject = t('[@site_name] @severity_desc: Alert from your web site', array( + '@site_name' => variable_get('site_name', 'Drupal'), + '@severity_desc' => $severity_list[$log['severity']])); + + $message = "\nSite: @base_url"; + $message .= "\nSeverity: (@severity) @severity_desc"; + $message .= "\nTimestamp: @timestamp"; + $message .= "\nType: @type"; + $message .= "\nIP Address: @ip"; + $message .= "\nRequest URI: @request_uri"; + $message .= "\nReferrer URI: @referer_uri"; + $message .= "\nUser: (@uid) @name"; + $message .= "\nLink: @link"; + $message .= "\nMessage: \n\n@message"; + + $message = t($message, array( + '@base_url' => $base_url, + '@severity' => $log_msg['severity'], + '@severity_desc' => $severity_list[$log_msg['severity']], + '@timestamp' => format_date($log_msg['timestamp']), + '@type' => $log_msg['type'], + '@ip' => $log_msg['ip'], + '@request_uri' => $log_msg['request_uri'], + '@referer_uri' => $log_msg['referer'], + '@uid' => $log_msg['user']->uid, + '@name' => $log_msg['user']->name, + '@link' => strip_tags($log_msg['link']), + '@message' => strip_tags($log_msg['message']), + )); + + drupal_mail('emaillog', $to, $subject, $body, $from = NULL, $headers = array()); +} + +/** + * Prepare a message based on parameters. @see drupal_mail for more. + * + * @param $key + * An identifier of the mail. + * @param $message + * An array to be filled in. Keys in this array include: + * - 'mail_id': + * An id to identify the mail sent. Look into the module source codes + * for possible mail_id values. + * - 'to': + * The mail address or addresses where the message will be send to. The + * formatting of this string must comply with RFC 2822. + * - 'subject': + * Subject of the e-mail to be sent. This must not contain any newline + * characters, or the mail may not be sent properly. Empty string when + * the hook is invoked. + * - 'body': + * An array of lines containing the message to be sent. Drupal will format + * the correct line endings for you. Empty array when the hook is invoked. + * - 'from': + * The From, Reply-To, Return-Path and Error-To headers in $headers + * are already set to this value (if given). + * - 'headers': + * Associative array containing the headers to add. This is typically + * used to add extra headers (From, Cc, and Bcc). + * @param $params + * An arbitrary array of parameters set by the caller to drupal_mail. + */ +function hook_mail($key, &$message, $params) { + $account = $params['account']; + $context = $params['context']; + $variables = array( + '%site_name' => variable_get('site_name', 'Drupal'), + '%username' => $account->name, + ); + if ($context['hook'] == 'taxonomy') { + $object = $params['object']; + $vocabulary = taxonomy_vocabulary_load($object->vid); + $variables += array( + '%term_name' => $object->name, + '%term_description' => $object->description, + '%term_id' => $object->tid, + '%vocabulary_name' => $vocabulary->name, + '%vocabulary_description' => $vocabulary->description, + '%vocabulary_id' => $vocabulary->vid, + ); + } + + // Node-based variable translation is only available if we have a node. + if (isset($params['node'])) { + $node = $params['node']; + $variables += array( + '%uid' => $node->uid, + '%node_url' => url('node/' . $node->nid, array('absolute' => TRUE)), + '%node_type' => node_get_types('name', $node), + '%title' => $node->title, + '%teaser' => $node->teaser, + '%body' => $node->body, + ); + } + $subject = strtr($context['subject'], $variables); + $body = strtr($context['message'], $variables); + $message['subject'] .= str_replace(array("\r", "\n"), '', $subject); + $message['body'][] = drupal_html_to_text($body); +} + +/** + * Add a list of cache tables to be cleared. + * + * This hook allows your module to add cache table names to the list of cache + * tables that will be cleared by the Clear button on the Performance page or + * whenever drupal_flush_all_caches is invoked. + * + * @see drupal_flush_all_caches() + * + * @param None. + * + * @return + * An array of cache table names. + */ +function hook_flush_caches() { + return array('cache_example'); +} + +/** + * Perform necessary actions after modules are installed. + * + * This function differs from hook_install() as it gives all other + * modules a chance to perform actions when a module is installed, + * whereas hook_install() will only be called on the module actually + * being installed. + * + * @see hook_install() + * + * @param $modules + * An array of the installed modules. + */ +function hook_modules_installed($modules) { + if (in_array('lousy_module', $modules)) { + variable_set('lousy_module_conflicting_variable', FALSE); + } +} + +/** + * Perform necessary actions after modules are enabled. + * + * This function differs from hook_enable() as it gives all other + * modules a chance to perform actions when modules are enabled, + * whereas hook_enable() will only be called on the module actually + * being enabled. + * + * @see hook_enable() + * + * @param $modules + * An array of the enabled modules. + */ +function hook_modules_enabled($modules) { + if (in_array('lousy_module', $modules)) { + drupal_set_message(t('mymodule is not compatible with lousy_module'), 'error'); + mymodule_disable_functionality(); + } +} + +/** + * Perform necessary actions after modules are disabled. + * + * This function differs from hook_disable() as it gives all other + * modules a chance to perform actions when modules are disabled, + * whereas hook_disable() will only be called on the module actually + * being disabled. + * + * @see hook_disable() + * + * @param $modules + * An array of the disabled modules. + */ +function hook_modules_disabled($modules) { + if (in_array('lousy_module', $modules)) { + mymodule_enable_functionality(); + } +} + +/** + * Perform necessary actions after modules are uninstalled. + * + * This function differs from hook_uninstall() as it gives all other + * modules a chance to perform actions when a module is uninstalled, + * whereas hook_uninstall() will only be called on the module actually + * being uninstalled. + * + * It is recommended that you implement this module if your module + * stores data that may have been set by other modules. + * + * @see hook_uninstall() + * + * @param $modules + * The name of the uninstalled module. + */ +function hook_modules_uninstalled($modules) { + foreach ($modules as $module) { + db_delete('mymodule_table') + ->condition('module', $module) + ->execute(); + } + mymodule_cache_rebuild(); +} + +/** + * custom_url_rewrite_outbound is not a hook, it's a function you can add to + * settings.php to alter all links generated by Drupal. This function is called from url(). + * This function is called very frequently (100+ times per page) so performance is + * critical. + * + * This function should change the value of $path and $options by reference. + * + * @param $path + * The alias of the $priginal_path as defined in the database. + * If there is no match in the database it'll be the same as $original_path + * @param $options + * An array of link attributes such as querystring and fragment. See url(). + * @param $orignal_path + * The unaliased Drupal path that is being linked to. + */ +function custom_url_rewrite_outbound(&$path, &$options, $original_path) { + global $user; + + // Change all 'node' to 'article'. + if (preg_match('|^node(/.*)|', $path, $matches)) { + $path = 'article' . $matches[1]; + } + // Create a path called 'e' which lands the user on her profile edit page. + if ($path == 'user/' . $user->uid . '/edit') { + $path = 'e'; + } + +} + +/** + * custom_url_rewrite_inbound is not a hook, it's a function you can add to + * settings.php to alter incoming requests so they map to a Drupal path. + * This function is called before modules are loaded and + * the menu system is initialized and it changes $_GET['q']. + * + * This function should change the value of $result by reference. + * + * @param $result + * The Drupal path based on the database. If there is no match in the database it'll be the same as $path. + * @param $path + * The path to be rewritten. + * @param $path_language + * An optional language code to rewrite the path into. + */ +function custom_url_rewrite_inbound(&$result, $path, $path_language) { + global $user; + + // Change all article/x requests to node/x + if (preg_match('|^article(/.*)|', $path, $matches)) { + $result = 'node' . $matches[1]; + } + // Redirect a path called 'e' to the user's profile edit page. + if ($path == 'e') { + $result = 'user/' . $user->uid . '/edit'; + } +} + +/** + * Load additional information into a file object. + * + * file_load() calls this hook to allow modules to load additional information + * into the $file. + * + * @param $file + * The file object being loaded. + * @return + * None. + * + * @see file_load() + */ +function hook_file_load(&$file) { + // Add the upload specific data into the file object. + $values = db_query('SELECT * FROM {upload} u WHERE u.fid = :fid', array(':fid' => $file->fid))->fetch(PDO::FETCH_ASSOC); + foreach ((array)$values as $key => $value) { + $file->{$key} = $value; + } +} + +/** + * Check that files meet a given criteria. + * + * This hook lets modules perform additional validation on files. They're able + * to report a failure by returning one or more error messages. + * + * @param $file + * The file object being validated. + * @return + * An array of error messages. If there are no problems with the file return + * an empty array. + * + * @see file_validate() + */ +function hook_file_validate(&$file) { + $errors = array(); + + if (empty($file->filename)) { + $errors[] = t("The file's name is empty. Please give a name to the file."); + } + if (strlen($file->filename) > 255) { + $errors[] = t("The file's name exceeds the 255 characters limit. Please rename the file and try again."); + } + + return $errors; +} + +/** + * Respond to a file being added. + * + * This hook is called when a file has been added to the database. The hook + * doesn't distinguish between files created as a result of a copy or those + * created by an upload. + * + * @param $file + * The file that has just been created. + * @return + * None. + * + * @see file_save() + */ +function hook_file_insert(&$file) { + +} + +/** + * Respond to a file being updated. + * + * This hook is called when file_save() is called on an existing file. + * + * @param $file + * The file that has just been updated. + * @return + * None. + * + * @see file_save() + */ +function hook_file_update(&$file) { + +} + +/** + * Respond to a file that has been copied. + * + * @param $file + * The newly copied file object. + * @param $source + * The original file before the copy. + * @return + * None. + * + * @see file_copy() + */ +function hook_file_copy($file, $source) { + +} + +/** + * Respond to a file that has been moved. + * + * @param $file + * The updated file object after the move. + * @param $source + * The original file object before the move. + * @return + * None. + * + * @see file_move() + */ +function hook_file_move($file, $source) { + +} + +/** + * Report the number of times a file is referenced by a module. + * + * This hook is called to determine if a files is in use. Multiple modules may + * be referencing the same file and to prevent one from deleting a file used by + * another this hook is called. + * + * @param $file + * The file object being checked for references. + * @return + * If the module uses this file return an array with the module name as the + * key and the value the number of times the file is used. + * + * @see file_delete() + * @see upload_file_references() + */ +function hook_file_references($file) { + // If upload.module is still using a file, do not let other modules delete it. + $count = db_query('SELECT COUNT(*) FROM {upload} WHERE fid = :fid', array(':fid' => $file->fid))->fetchField(); + if ($count) { + // Return the name of the module and how many references it has to the file. + return array('upload' => $count); + } +} + +/** + * Respond to a file being deleted. + * + * @param $file + * The file that has just been deleted. + * @return + * None. + * + * @see file_delete() + * @see upload_file_delete() + */ +function hook_file_delete($file) { + // Delete all information associated with the file. + db_delete('upload')->condition('fid', $file->fid)->execute(); +} + +/** + * Respond to a file that has changed status. + * + * The typical change in status is from temporary to permanent. + * + * @param $file + * The file being changed. + * @return + * None. + * + * @see hook_file_status() + */ +function hook_file_status($file) { +} + +/** + * Control access to private file downloads and specify HTTP headers. + * + * This hook allows modules enforce permissions on file downloads when the + * private file download method is selected. Modules can also provide headers + * to specify information like the file's name or MIME type. + * + * @param $filepath + * String of the file's path. + * @return + * If the user does not have permission to access the file, return -1. If the + * user has permission, return an array with the appropriate headers. If the + * file is not controlled by the current module, the return value should be + * NULL. + * + * @see file_download() + * @see upload_file_download() + */ +function hook_file_download($filepath) { + // Check if the file is controlled by the current module. + $filepath = file_create_path($filepath); + $result = db_query("SELECT f.* FROM {files} f INNER JOIN {upload} u ON f.fid = u.fid WHERE filepath = '%s'", $filepath); + if ($file = db_fetch_object($result)) { + if (!user_access('view uploaded files')) { + return -1; + } + return array( + 'Content-Type: ' . $file->filemime, + 'Content-Length: ' . $file->filesize, + ); + } +} + +/** + * Check installation requirements and do status reporting. + * + * This hook has two closely related uses, determined by the $phase argument: + * checking installation requirements ($phase == 'install') + * and status reporting ($phase == 'runtime'). + * + * Note that this hook, like all others dealing with installation and updates, + * must reside in a module_name.install file, or it will not properly abort + * the installation of the module if a critical requirement is missing. + * + * During the 'install' phase, modules can for example assert that + * library or server versions are available or sufficient. + * Note that the installation of a module can happen during installation of + * Drupal itself (by install.php) with an installation profile or later by hand. + * As a consequence, install-time requirements must be checked without access + * to the full Drupal API, because it is not available during install.php. + * For localisation you should for example use $t = get_t() to + * retrieve the appropriate localisation function name (t() or st()). + * If a requirement has a severity of REQUIREMENT_ERROR, install.php will abort + * or at least the module will not install. + * Other severity levels have no effect on the installation. + * Module dependencies do not belong to these installation requirements, + * but should be defined in the module's .info file. + * + * The 'runtime' phase is not limited to pure installation requirements + * but can also be used for more general status information like maintenance + * tasks and security issues. + * The returned 'requirements' will be listed on the status report in the + * administration section, with indication of the severity level. + * Moreover, any requirement with a severity of REQUIREMENT_ERROR severity will + * result in a notice on the the administration overview page. + * + * @param $phase + * The phase in which hook_requirements is run: + * - 'install': the module is being installed. + * - 'runtime': the runtime requirements are being checked and shown on the + * status report page. + * + * @return + * A keyed array of requirements. Each requirement is itself an array with + * the following items: + * - 'title': the name of the requirement. + * - 'value': the current value (e.g. version, time, level, ...). During + * install phase, this should only be used for version numbers, do not set + * it if not applicable. + * - 'description': description of the requirement/status. + * - 'severity': the requirement's result/severity level, one of: + * - REQUIREMENT_INFO: For info only. + * - REQUIREMENT_OK: The requirement is satisfied. + * - REQUIREMENT_WARNING: The requirement failed with a warning. + * - REQUIREMENT_ERROR: The requirement failed with an error. + */ +function hook_requirements($phase) { + $requirements = array(); + // Ensure translations don't break at install time + $t = get_t(); + + // Report Drupal version + if ($phase == 'runtime') { + $requirements['drupal'] = array( + 'title' => $t('Drupal'), + 'value' => VERSION, + 'severity' => REQUIREMENT_INFO + ); + } + + // Test PHP version + $requirements['php'] = array( + 'title' => $t('PHP'), + 'value' => ($phase == 'runtime') ? l(phpversion(), 'admin/logs/status/php') : phpversion(), + ); + if (version_compare(phpversion(), DRUPAL_MINIMUM_PHP) < 0) { + $requirements['php']['description'] = $t('Your PHP installation is too old. Drupal requires at least PHP %version.', array('%version' => DRUPAL_MINIMUM_PHP)); + $requirements['php']['severity'] = REQUIREMENT_ERROR; + } + + // Report cron status + if ($phase == 'runtime') { + $cron_last = variable_get('cron_last', NULL); + + if (is_numeric($cron_last)) { + $requirements['cron']['value'] = $t('Last run !time ago', array('!time' => format_interval(REQUEST_TIME - $cron_last))); + } + else { + $requirements['cron'] = array( + 'description' => $t('Cron has not run. It appears cron jobs have not been setup on your system. Please check the help pages for <a href="@url">configuring cron jobs</a>.', array('@url' => 'http://drupal.org/cron')), + 'severity' => REQUIREMENT_ERROR, + 'value' => $t('Never run'), + ); + } + + $requirements['cron']['description'] .= ' ' . t('You can <a href="@cron">run cron manually</a>.', array('@cron' => url('admin/logs/status/run-cron'))); + + $requirements['cron']['title'] = $t('Cron maintenance tasks'); + } + + return $requirements; +} + +/** + * Define the current version of the database schema. + * + * A Drupal schema definition is an array structure representing one or + * more tables and their related keys and indexes. A schema is defined by + * hook_schema() which must live in your module's .install file. + * + * By implementing hook_schema() and specifying the tables your module + * declares, you can easily create and drop these tables on all + * supported database engines. You don't have to deal with the + * different SQL dialects for table creation and alteration of the + * supported database engines. + * + * See the Schema API Handbook at http://drupal.org/node/146843 for + * details on schema definition structures. + * + * @return + * A schema definition structure array. For each element of the + * array, the key is a table name and the value is a table structure + * definition. + */ +function hook_schema() { + $schema['node'] = array( + // example (partial) specification for table "node" + 'description' => t('The base table for nodes.'), + 'fields' => array( + 'nid' => array( + 'description' => t('The primary identifier for a node.'), + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE), + 'vid' => array( + 'description' => t('The current {node_revisions}.vid version identifier.'), + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0), + 'type' => array( + 'description' => t('The {node_type} of this node.'), + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => ''), + 'title' => array( + 'description' => t('The title of this node, always treated a non-markup plain text.'), + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => ''), + ), + 'indexes' => array( + 'node_changed' => array('changed'), + 'node_created' => array('created'), + ), + 'unique keys' => array( + 'nid_vid' => array('nid', 'vid'), + 'vid' => array('vid') + ), + 'primary key' => array('nid'), + ); + return $schema; +} + +/** + * Perform alterations to existing database schemas. + * + * When a module modifies the database structure of another module (by + * changing, adding or removing fields, keys or indexes), it should + * implement hook_schema_alter() to update the default $schema to take + * it's changes into account. + * + * See hook_schema() for details on the schema definition structure. + * + * @param $schema + * Nested array describing the schemas for all modules. + * @return + * None. + */ +function hook_schema_alter(&$schema) { + // Add field to existing schema. + $schema['users']['fields']['timezone_id'] = array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => t('Per-user timezone configuration.'), + ); +} + +/** + * Install the current version of the database schema, and any other setup tasks. + * + * The hook will be called the first time a module is installed, and the + * module's schema version will be set to the module's greatest numbered update + * hook. Because of this, anytime a hook_update_N() is added to the module, this + * function needs to be updated to reflect the current version of the database + * schema. + * + * See the Schema API documentation at http://drupal.org/node/146843 + * for details on hook_schema, where a database tables are defined. + * + * Note that since this function is called from a full bootstrap, all functions + * (including those in modules enabled by the current page request) are + * available when this hook is called. Use cases could be displaying a user + * message, or calling a module function necessary for initial setup, etc. + * + * Please be sure that anything added or modified in this function that can + * be removed during uninstall should be removed with hook_uninstall(). + * + * @see hook_uninstall() + */ +function hook_install() { + drupal_install_schema('upload'); +} + +/** + * Perform a single update. For each patch which requires a database change add + * a new hook_update_N() which will be called by update.php. + * + * The database updates are numbered sequentially according to the version of Drupal you are compatible with. + * + * Schema updates should adhere to the Schema API: http://drupal.org/node/150215 + * + * Database updates consist of 3 parts: + * - 1 digit for Drupal core compatibility + * - 1 digit for your module's major release version (e.g. is this the 5.x-1.* (1) or 5.x-2.* (2) series of your module?) + * - 2 digits for sequential counting starting with 00 + * + * The 2nd digit should be 0 for initial porting of your module to a new Drupal + * core API. + * + * Examples: + * - mymodule_update_5200() + * - This is the first update to get the database ready to run mymodule 5.x-2.*. + * - mymodule_update_6000() + * - This is the required update for mymodule to run with Drupal core API 6.x. + * - mymodule_update_6100() + * - This is the first update to get the database ready to run mymodule 6.x-1.*. + * - mymodule_update_6200() + * - This is the first update to get the database ready to run mymodule 6.x-2.*. + * Users can directly update from 5.x-2.* to 6.x-2.* and they get all 60XX + * and 62XX updates, but not 61XX updates, because those reside in the + * 6.x-1.x branch only. + * + * A good rule of thumb is to remove updates older than two major releases of + * Drupal. + * + * Never renumber update functions. + * + * Further information about releases and release numbers: + * - http://drupal.org/handbook/version-info + * - http://drupal.org/node/93999 (Overview of contributions branches and tags) + * - http://drupal.org/handbook/cvs/releases + * + * Implementations of this hook should be placed in a mymodule.install file in + * the same directory at mymodule.module. Drupal core's updates are implemented + * using the system module as a name and stored in database/updates.inc. + * + * @return An array with the results of the calls to update_sql(). An upate + * function can force the current and all later updates for this + * module to abort by returning a $ret array with an element like: + * $ret['#abort'] = array('success' => FALSE, 'query' => 'What went wrong'); + * The schema version will not be updated in this case, and all the + * aborted updates will continue to appear on update.php as updates that + * have not yet been run. + */ +function hook_update_N() { + $ret = array(); + db_add_field($ret, 'mytable1', 'newcol', array('type' => 'int', 'not null' => TRUE)); + return $ret; +} + +/** + * Remove any information that the module sets. + * + * The information that the module should remove includes: + * - variables that the module has set using variable_set() or system_settings_form() + * - tables the module has created, using drupal_uninstall_schema() + * - modifications to existing tables + * + * The module should not remove its entry from the {system} table. + * + * The uninstall hook will fire when the module gets uninstalled. + */ +function hook_uninstall() { + drupal_uninstall_schema('upload'); + variable_del('upload_file_types'); +} + +/** + * Perform necessary actions after module is enabled. + * + * The hook is called everytime module is enabled. + */ +function hook_enable() { + mymodule_cache_rebuild(); +} + +/** + * Perform necessary actions before module is disabled. + * + * The hook is called everytime module is disabled. + */ +function hook_disable() { + mymodule_cache_rebuild(); +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/modules/system/system.module b/modules/system/system.module index a2af7f54d597..dcc4d44ba19f 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -1508,18 +1508,15 @@ function system_cron() { db_query('DELETE FROM {batch} WHERE timestamp < %d', REQUEST_TIME - 864000); // Remove temporary files that are older than DRUPAL_MAXIMUM_TEMP_FILE_AGE. - $result = db_query('SELECT * FROM {files} WHERE status = %d and timestamp < %d', FILE_STATUS_TEMPORARY, REQUEST_TIME - DRUPAL_MAXIMUM_TEMP_FILE_AGE); - while ($file = db_fetch_object($result)) { - if (file_exists($file->filepath)) { - // If files that exist cannot be deleted, continue so the database remains - // consistent. + $result = db_query('SELECT fid FROM {files} WHERE status & :permanent != :permanent AND timestamp < :timestamp', array(':permanent' => FILE_STATUS_PERMANENT, ':timestamp' => REQUEST_TIME - DRUPAL_MAXIMUM_TEMP_FILE_AGE)); + foreach ($result as $row) { + if ($file = file_load($row->fid)) { if (!file_delete($file)) { watchdog('file system', 'Could not delete temporary file "%path" during garbage collection', array('%path' => $file->filepath), WATCHDOG_ERROR); - continue; } } - db_query('DELETE FROM {files} WHERE fid = %d', $file->fid); } + $core = array('cache', 'cache_block', 'cache_filter', 'cache_page', 'cache_form', 'cache_menu'); $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); foreach ($cache_tables as $table) { diff --git a/modules/system/system.test b/modules/system/system.test index a88cef45713c..1c6be0701786 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -252,6 +252,41 @@ class CronRunTestCase extends DrupalWebTestCase { // Execute cron directly. $this->assertTrue(drupal_cron_run(), t('Cron ran successfully.')); } + + /** + * Ensure that temporary files are removed. + */ + function testTempFileCleanup() { + // Create files for all the possible combinations of age and status. We're + // using UPDATE statments rather than file_save() because it would set the + // timestamp. + + // Temporary file that is older than DRUPAL_MAXIMUM_TEMP_FILE_AGE. + $temp_old = file_save_data(''); + db_query('UPDATE {files} SET status = :status, timestamp = :timestamp WHERE fid = :fid', array(':status' => FILE_STATUS_TEMPORARY, ':timestamp' => 1, ':fid' => $temp_old->fid)); + $this->assertTrue(file_exists($temp_old->filepath), t('Old temp file was created correctly.')); + + // Temporary file that is less than DRUPAL_MAXIMUM_TEMP_FILE_AGE. + $temp_new = file_save_data(''); + db_query('UPDATE {files} SET status = :status WHERE fid = :fid', array(':status' => FILE_STATUS_TEMPORARY, ':fid' => $temp_new->fid)); + $this->assertTrue(file_exists($temp_new->filepath), t('New temp file was created correctly.')); + + // Permanent file that is older than DRUPAL_MAXIMUM_TEMP_FILE_AGE. + $perm_old = file_save_data(''); + db_query('UPDATE {files} SET timestamp = :timestamp WHERE fid = :fid', array(':timestamp' => 1, ':fid' => $perm_old->fid)); + $this->assertTrue(file_exists($perm_old->filepath), t('Old permanent file was created correctly.')); + + // Permanent file that is newer than DRUPAL_MAXIMUM_TEMP_FILE_AGE. + $perm_new = file_save_data(''); + $this->assertTrue(file_exists($perm_new->filepath), t('New permanent file was created correctly.')); + + // Run cron and then ensure that only the old, temp file was deleted. + $this->assertTrue(drupal_cron_run(), t('Cron ran successfully.')); + $this->assertFalse(file_exists($temp_old->filepath), t('Old temp file was correctly removed.')); + $this->assertTrue(file_exists($temp_new->filepath), t('New temp file was correctly ignored.')); + $this->assertTrue(file_exists($perm_old->filepath), t('Old permanent file was correctly ignored.')); + $this->assertTrue(file_exists($perm_new->filepath), t('New permanent file was correctly ignored.')); + } } class AdminOverviewTestCase extends DrupalWebTestCase { diff --git a/modules/taxonomy/taxonomy.api.php b/modules/taxonomy/taxonomy.api.php new file mode 100644 index 000000000000..553386000de4 --- /dev/null +++ b/modules/taxonomy/taxonomy.api.php @@ -0,0 +1,148 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by the Taxonomy module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Act on taxonomy vocabularies when loaded. + * + * Modules implementing this hook can act on the vocabulary object returned by + * taxonomy_vocabulary_load(). + * + * @param $vocabulary + * A taxonomy vocabulary object. + */ +function hook_taxonomy_vocabulary_load($vocabulary) { + $vocabulary->synonyms = variable_get('taxonomy_' . $vocabulary->vid . '_synonyms', FALSE); +} + +/** + * Act on taxonomy vocabularies when inserted. + * + * Modules implementing this hook can act on the vocabulary object when saved + * to the database. + * + * @param $vocabulary + * A taxonomy vocabulary object. + */ +function hook_taxonomy_vocabulary_insert($vocabulary) { + if ($vocabulary->synonyms) { + variable_set('taxonomy_' . $vocabulary->vid . '_synonyms', TRUE); + } +} + +/** + * Act on taxonomy vocabularies when updated. + * + * Modules implementing this hook can act on the term object when updated. + * + * @param $term + * A taxonomy term object, passed by reference. + */ +function hook_taxonomy_vocabulary_update($term) { + $status = $vocabulary->synonyms ? TRUE : FALSE; + if ($vocabulary->synonyms) { + variable_set('taxonomy_' . $vocabulary->vid . '_synonyms', $status); + } +} + +/** + * Respond to the deletion of taxonomy vocabularies. + * + * Modules implementing this hook can respond to the deletion of taxonomy + * vocabularies from the database. + * + * @param $vocabulary + * A taxonomy vocabulary object. + */ +function hook_taxonomy_vocabulary_delete($vocabulary) { + if (variable_get('taxonomy_' . $vocabulary->vid . '_synonyms', FALSE)) { + variable_del('taxonomy_' . $vocabulary->vid . '_synonyms'); + } +} + +/** + * Act on taxonomy terms when loaded. + * + * Modules implementing this hook can act on the term object returned by + * taxonomy_term_load(). + * + * @param $term + * A taxonomy term object. + */ +function hook_taxonomy_term_load($term) { + $term->synonyms = taxonomy_get_synonyms($term->tid); +} + +/** + * Act on taxonomy terms when inserted. + * + * Modules implementing this hook can act on the term object when saved to + * the database. + * + * @param $term + * A taxonomy term object. + */ +function hook_taxonomy_term_insert($term) { + if (!empty($term->synonyms)) { + foreach (explode ("\n", str_replace("\r", '', $term->synonyms)) as $synonym) { + if ($synonym) { + db_insert('term_synonym') + ->fields(array( + 'tid' => $term->tid, + 'name' => rtrim($synonym), + )) + ->execute(); + } + } + } +} + +/** + * Act on taxonomy terms when updated. + * + * Modules implementing this hook can act on the term object when updated. + * + * @param $term + * A taxonomy term object. + */ +function hook_taxonomy_term_update($term) { + hook_taxonomy_term_delete($term); + if (!empty($term->synonyms)) { + foreach (explode ("\n", str_replace("\r", '', $term->synonyms)) as $synonym) { + if ($synonym) { + db_insert('term_synonym') + ->fields(array( + 'tid' => $term->tid, + 'name' => rtrim($synonym), + )) + ->execute(); + } + } + } +} + +/** + * Respond to the deletion of taxonomy terms. + * + * Modules implementing this hook can respond to the deletion of taxonomy + * terms from the database. + * + * @param $term + * A taxonomy term object. + */ +function hook_taxonomy_term_delete($term) { + db_delete('term_synoynm')->condition('tid', $term->tid)->execute(); +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/modules/trigger/trigger.api.php b/modules/trigger/trigger.api.php new file mode 100644 index 000000000000..e33ce2205aa5 --- /dev/null +++ b/modules/trigger/trigger.api.php @@ -0,0 +1,156 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by the Trigger module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Declare information about one or more Drupal actions. + * + * Any module can define any number of Drupal actions. The trigger module is an + * example of a module that uses actions. An action consists of two or three + * parts: (1) an action definition (returned by this hook), (2) a function which + * does the action (which by convention is named module + '_' + description of + * what the function does + '_action'), and an optional form definition + * function that defines a configuration form (which has the name of the action + * with '_form' appended to it.) + * + * @return + * - An array of action descriptions. Each action description is an associative + * array, where the key of the item is the action's function, and the + * following key-value pairs: + * - 'type': (required) the type is determined by what object the action + * acts on. Possible choices are node, user, comment, and system. Or + * whatever your own custom type is. So, for the nodequeue module, the + * type might be set to 'nodequeue' if the action would be performed on a + * nodequeue. + * - 'description': (required) The human-readable name of the action. + * - 'configurable': (required) If FALSE, then the action doesn't require + * any extra configuration. If TRUE, then you should define a form + * function with the same name as the key, but with '_form' appended to + * it (i.e., the form for 'node_assign_owner_action' is + * 'node_assign_owner_action_form'.) + * This function will take the $context as the only parameter, and is + * paired with the usual _submit function, and possibly a _validate + * function. + * - 'hooks': (required) An array of all of the operations this action is + * appropriate for, keyed by hook name. The trigger module uses this to + * filter out inappropriate actions when presenting the interface for + * assigning actions to events. If you are writing actions in your own + * modules and you simply want to declare support for all possible hooks, + * you can set 'hooks' => array('any' => TRUE). Common hooks are 'user', + * 'nodeapi', 'comment', or 'taxonomy'. Any hook that has been described + * to Drupal in hook_hook_info() will work is a possiblity. + * - 'behavior': (optional) Human-readable array of behavior descriptions. + * The only one we have now is 'changes node property'. You will almost + * certainly never have to return this in your own implementations of this + * hook. + * + * The function that is called when the action is triggered is passed two + * parameters - an object of the same type as the 'type' value of the + * hook_action_info array, and a context variable that contains the context + * under which the action is currently running, sent as an array. For example, + * the actions module sets the 'hook' and 'op' keys of the context array (so, + * 'hook' may be 'nodeapi' and 'op' may be 'insert'). + */ +function hook_action_info() { + return array( + 'comment_unpublish_action' => array( + 'description' => t('Unpublish comment'), + 'type' => 'comment', + 'configurable' => FALSE, + 'hooks' => array( + 'comment' => array('insert', 'update'), + ) + ), + 'comment_unpublish_by_keyword_action' => array( + 'description' => t('Unpublish comment containing keyword(s)'), + 'type' => 'comment', + 'configurable' => TRUE, + 'hooks' => array( + 'comment' => array('insert', 'update'), + ) + ) + ); +} + +/** + * Execute code after an action is deleted. + * + * @param $aid + * The action ID. + */ +function hook_actions_delete($aid) { + db_query("DELETE FROM {actions_assignments} WHERE aid = '%s'", $aid); +} + +/** + * Alter the actions declared by another module. + * + * Called by actions_list() to allow modules to alter the return + * values from implementations of hook_action_info(). + * + * @see trigger_example_action_info_alter(). + */ +function hook_action_info_alter(&$actions) { + $actions['node_unpublish_action']['description'] = t('Unpublish and remove from public view.'); +} + +/** + * Expose a list of triggers (events) that your module is allowing users to + * assign actions to. + * + * This hook is used by the Triggers API to present information about triggers + * (or events) that your module allows users to assign actions to. + * + * See also hook_action_info(). + * + * @return + * - A nested array. The outermost key defines the module that the triggers + * are from. The menu system will use the key to look at the .info file of + * the module and make a local task (a tab) in the trigger UI. + * - The next key defines the hook being described. + * - Inside of that array are a list of arrays keyed by hook operation. + * - Each of those arrays have a key of 'runs when' and a value which is + * an English description of the hook. + * + * For example, the node_hook_info implementation has 'node' as the outermost + * key, as that's the module it's in. Next it has 'nodeapi' as the next key, + * as hook_nodeapi() is what applies to changes in nodes. Finally the keys + * after that are the various operations for hook_nodeapi() that the node module + * is exposing as triggers. + */ +function hook_hook_info() { + return array( + 'node' => array( + 'nodeapi' => array( + 'presave' => array( + 'runs when' => t('When either saving a new post or updating an existing post'), + ), + 'insert' => array( + 'runs when' => t('After saving a new post'), + ), + 'update' => array( + 'runs when' => t('After saving an updated post'), + ), + 'delete' => array( + 'runs when' => t('After deleting a post') + ), + 'view' => array( + 'runs when' => t('When content is viewed by an authenticated user') + ), + ), + ), + ); +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/modules/update/update.api.php b/modules/update/update.api.php new file mode 100644 index 000000000000..e9fc111ec308 --- /dev/null +++ b/modules/update/update.api.php @@ -0,0 +1,45 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by the Update Status module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Alter the information about available updates for projects. + * + * @param $projects + * Reference to an array of information about available updates to each + * project installed on the system. + * + * @see update_calculate_project_data() + */ +function hook_update_status_alter(&$projects) { + $settings = variable_get('update_advanced_project_settings', array()); + foreach ($projects as $project => $project_info) { + if (isset($settings[$project]) && isset($settings[$project]['check']) && + ($settings[$project]['check'] == 'never' || + (isset($project_info['recommended']) && + $settings[$project]['check'] === $project_info['recommended']))) { + $projects[$project]['status'] = UPDATE_NOT_CHECKED; + $projects[$project]['reason'] = t('Ignored from settings'); + if (!empty($settings[$project]['notes'])) { + $projects[$project]['extra'][] = array( + 'class' => 'admin-note', + 'label' => t('Administrator note'), + 'data' => $settings[$project]['notes'], + ); + } + } + } +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/modules/user/user.api.php b/modules/user/user.api.php new file mode 100644 index 000000000000..3b5855cf5db9 --- /dev/null +++ b/modules/user/user.api.php @@ -0,0 +1,126 @@ +<?php +// $Id$ + +/** + * @file + * Hooks provided by the User module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Act on user account actions. + * + * This hook allows modules to react when operations are performed on user + * accounts. + * + * @param $op + * What kind of action is being performed. Possible values (in alphabetical order): + * - "after_update": The user object has been updated and changed. Use this if + * (probably along with 'insert') if you want to reuse some information from + * the user object. + * - "categories": A set of user information categories is requested. + * - "delete": The user account is being deleted. The module should remove its + * custom additions to the user object from the database. + * - "form": The user account edit form is about to be displayed. The module + * should present the form elements it wishes to inject into the form. + * - "insert": The user account is being added. The module should save its + * custom additions to the user object into the database and set the saved + * fields to NULL in $edit. + * - "load": The user account is being loaded. The module may respond to this + * - "login": The user just logged in. + * - "logout": The user just logged out. + * and insert additional information into the user object. + * - "register": The user account registration form is about to be displayed. + * The module should present the form elements it wishes to inject into the + * form. + * - "submit": Modify the account before it gets saved. + * - "update": The user account is being changed. The module should save its + * custom additions to the user object into the database and set the saved + * fields to NULL in $edit. + * - "validate": The user account is about to be modified. The module should + * validate its custom additions to the user object, registering errors as + * necessary. + * - "view": The user's account information is being displayed. The module + * should format its custom additions for display and add them to the + * $account->content array. + * @param &$edit + * The array of form values submitted by the user. + * @param &$account + * The user object on which the operation is being performed. + * @param $category + * The active category of user information being edited. + * @return + * This varies depending on the operation. + * - "categories": A linear array of associative arrays. These arrays have + * keys: + * - "name": The internal name of the category. + * - "title": The human-readable, localized name of the category. + * - "weight": An integer specifying the category's sort ordering. + * - "delete": None. + * - "form", "register": A $form array containing the form elements to display. + * - "insert": None. + * - "load": None. + * - "login": None. + * - "logout": None. + * - "submit": None: + * - "update": None. + * - "validate": None. + * - "view": None. For an example see: user_user(). + */ +function hook_user($op, &$edit, &$account, $category = NULL) { + if ($op == 'form' && $category == 'account') { + $form['comment_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Comment settings'), + '#collapsible' => TRUE, + '#weight' => 4); + $form['comment_settings']['signature'] = array( + '#type' => 'textarea', + '#title' => t('Signature'), + '#default_value' => $edit['signature'], + '#description' => t('Your signature will be publicly displayed at the end of your comments.')); + return $form; + } +} + +/** + * Add mass user operations. + * + * This hook enables modules to inject custom operations into the mass operations + * dropdown found at admin/user/user, by associating a callback function with + * the operation, which is called when the form is submitted. The callback function + * receives one initial argument, which is an array of the checked users. + * + * @return + * An array of operations. Each operation is an associative array that may + * contain the following key-value pairs: + * - "label": Required. The label for the operation, displayed in the dropdown menu. + * - "callback": Required. The function to call for the operation. + * - "callback arguments": Optional. An array of additional arguments to pass to + * the callback function. + * + */ +function hook_user_operations() { + $operations = array( + 'unblock' => array( + 'label' => t('Unblock the selected users'), + 'callback' => 'user_user_operations_unblock', + ), + 'block' => array( + 'label' => t('Block the selected users'), + 'callback' => 'user_user_operations_block', + ), + 'delete' => array( + 'label' => t('Delete the selected users'), + ), + ); + return $operations; +} + +/** + * @} End of "addtogroup hooks". + */ -- GitLab