diff options
Diffstat (limited to 'modules')
64 files changed, 2844 insertions, 985 deletions
diff --git a/modules/aggregator/aggregator-rtl.css b/modules/aggregator/aggregator-rtl.css index ea59ca3a1..057d01565 100644 --- a/modules/aggregator/aggregator-rtl.css +++ b/modules/aggregator/aggregator-rtl.css @@ -1,3 +1,6 @@ +/** + * Right-to-Left styles for theme in the Aggregator module. + */ #aggregator .feed-source .feed-icon { float: left; diff --git a/modules/aggregator/aggregator.admin.inc b/modules/aggregator/aggregator.admin.inc index 8b817c0fa..443facb12 100644 --- a/modules/aggregator/aggregator.admin.inc +++ b/modules/aggregator/aggregator.admin.inc @@ -2,11 +2,11 @@ /** * @file - * Admin page callbacks for the aggregator module. + * Administration page callbacks for the Aggregator module. */ /** - * Menu callback; displays the aggregator administration page. + * Page callback: Displays the Aggregator module administration page. */ function aggregator_admin_overview() { return aggregator_view(); @@ -16,7 +16,7 @@ function aggregator_admin_overview() { * Displays the aggregator administration page. * * @return - * The page HTML. + * A HTML-formatted string with administration page content. */ function aggregator_view() { $result = db_query('SELECT f.fid, f.title, f.url, f.refresh, f.checked, f.link, f.description, f.hash, f.etag, f.modified, f.image, f.block, COUNT(i.iid) AS items FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.url, f.refresh, f.checked, f.link, f.description, f.hash, f.etag, f.modified, f.image, f.block ORDER BY f.title'); @@ -56,8 +56,8 @@ function aggregator_view() { * Form constructor for adding and editing feed sources. * * @param $feed - * If editing a feed, the feed to edit as a PHP stdClass value; if adding a - * new feed, NULL. + * (optional) If editing a feed, the feed to edit as a PHP stdClass value; if + * adding a new feed, NULL. Defaults to NULL. * * @ingroup forms * @see aggregator_form_feed_validate() @@ -165,6 +165,7 @@ function aggregator_form_feed_validate($form, &$form_state) { * Form submission handler for aggregator_form_feed(). * * @see aggregator_form_feed_validate() + * * @todo Add delete confirmation dialog. */ function aggregator_form_feed_submit($form, &$form_state) { @@ -398,7 +399,7 @@ function _aggregator_parse_opml($opml) { } /** - * Menu callback; refreshes a feed, then redirects to the overview page. + * Page callback: Refreshes a feed, then redirects to the overview page. * * @param $feed * An object describing the feed to be refreshed. @@ -590,6 +591,7 @@ function aggregator_form_category_validate($form, &$form_state) { * Form submission handler for aggregator_form_category(). * * @see aggregator_form_category_validate() + * * @todo Add delete confirmation dialog. */ function aggregator_form_category_submit($form, &$form_state) { diff --git a/modules/aggregator/aggregator.css b/modules/aggregator/aggregator.css index 13c58ffe7..4285631e8 100644 --- a/modules/aggregator/aggregator.css +++ b/modules/aggregator/aggregator.css @@ -1,3 +1,6 @@ +/** + * Styles for theme in the Aggregator module. + */ #aggregator .feed-source .feed-title { margin-top: 0; diff --git a/modules/aggregator/aggregator.module b/modules/aggregator/aggregator.module index 93457c68d..70f8c5cd8 100644 --- a/modules/aggregator/aggregator.module +++ b/modules/aggregator/aggregator.module @@ -266,13 +266,13 @@ function aggregator_menu() { } /** - * Title callback: Returns a title for aggregatory category pages. + * Title callback: Returns a title for aggregator category pages. * * @param $category * An aggregator category. * * @return - * An aggregator category title. + * A string with the aggregator category title. */ function _aggregator_category_title($category) { return $category['title']; @@ -723,7 +723,7 @@ function theme_aggregator_block_item($variables) { } /** - * Safely renders HTML content, as allowed. + * Renders the HTML content safely, as allowed. * * @param $value * The content to be filtered. @@ -739,7 +739,7 @@ function aggregator_filter_xss($value) { * Checks and sanitizes the aggregator configuration. * * Goes through all fetchers, parsers and processors and checks whether they - * are available. If one is missing resets to standard configuration. + * are available. If one is missing, resets to standard configuration. * * @return * TRUE if this function resets the configuration; FALSE if not. @@ -775,7 +775,7 @@ function aggregator_sanitize_configuration() { * Items count. * * @return - * Plural-formatted "@count items" + * A string that is plural-formatted as "@count items". */ function _aggregator_items($count) { return format_plural($count, '1 item', '@count items'); diff --git a/modules/aggregator/aggregator.pages.inc b/modules/aggregator/aggregator.pages.inc index cd1c4cb2c..bfba3fffb 100644 --- a/modules/aggregator/aggregator.pages.inc +++ b/modules/aggregator/aggregator.pages.inc @@ -2,14 +2,14 @@ /** * @file - * User page callbacks for the aggregator module. + * User page callbacks for the Aggregator module. */ /** - * Menu callback; displays the most recent items gathered from any feed. + * Page callback: Displays the most recent items gathered from any feed. * * @return - * The items HTML. + * The rendered list of items for the feed. */ function aggregator_page_last() { drupal_add_feed('aggregator/rss', variable_get('site_name', 'Drupal') . ' ' . t('aggregator')); @@ -20,13 +20,15 @@ function aggregator_page_last() { } /** - * Menu callback; displays all the items captured from a particular feed. + * Page callback: Displays all the items captured from the particular feed. * * @param $feed * The feed for which to display all items. * * @return * The rendered list of items for a feed. + * + * @see aggregator_menu() */ function aggregator_page_source($feed) { drupal_set_title($feed->title); @@ -40,13 +42,13 @@ function aggregator_page_source($feed) { } /** - * Menu callback; displays a form with all items captured from a feed. + * Page callback: Displays a form with all items captured from a feed. * * @param $feed - * The feed for which to list all the aggregated items. + * The feed for which to list all of the aggregated items. * * @return - * The rendered list of items for a feed. + * The rendered list of items for the feed. * * @see aggregator_page_source() */ @@ -55,13 +57,13 @@ function aggregator_page_source_form($form, $form_state, $feed) { } /** - * Menu callback; displays all the items aggregated in a particular category. + * Page callback: Displays all the items aggregated in a particular category. * * @param $category - * The category for which to list all the aggregated items. + * The category for which to list all of the aggregated items. * * @return -* The rendered list of items for a category. + * The rendered list of items for the feed. */ function aggregator_page_category($category) { drupal_add_feed('aggregator/rss/' . $category['cid'], variable_get('site_name', 'Drupal') . ' ' . t('aggregator - @title', array('@title' => $category['title']))); @@ -74,13 +76,13 @@ function aggregator_page_category($category) { } /** - * Menu callback; displays a form containing items aggregated in a category. + * Page callback: Displays a form containing items aggregated in a category. * * @param $category - * The category for which to list all the aggregated items. + * The category for which to list all of the aggregated items. * * @return -* The rendered list of items for a category. + * The rendered list of items for the feed. * * @see aggregator_page_category() */ @@ -166,7 +168,7 @@ function aggregator_feed_items_load($type, $data = NULL) { * The feed source URL. * * @return - * The rendered list of items for a feed. + * The rendered list of items for the feed. */ function _aggregator_page_list($items, $op, $feed_source = '') { if (user_access('administer news feeds') && ($op == 'categorize')) { @@ -191,10 +193,14 @@ function _aggregator_page_list($items, $op, $feed_source = '') { * @param $items * An array of the feed items. * @param $feed_source - * The feed source URL. + * (optional) The feed source URL. Defaults to an empty string. + * + * @return array + * An array of FAPI elements. * - * @ingroup forms * @see aggregator_categorize_items_submit() + * @see theme_aggregator_categorize_items() + * @ingroup forms */ function aggregator_categorize_items($items, $feed_source = '') { $form['#submit'][] = 'aggregator_categorize_items_submit'; @@ -334,7 +340,12 @@ function template_preprocess_aggregator_item(&$variables) { } /** - * Menu callback; displays all the feeds used by the aggregator. + * Page callback: Displays all the feeds used by the aggregator. + * + * @return + * An HTML-formatted string. + * + * @see aggregator_menu() */ function aggregator_page_sources() { $result = db_query('SELECT f.fid, f.title, f.description, f.image, MAX(i.timestamp) AS last FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.description, f.image ORDER BY last DESC, f.title'); @@ -358,7 +369,12 @@ function aggregator_page_sources() { } /** - * Menu callback; displays all the categories used by the aggregator. + * Page callback: Displays all the categories used by the Aggregator module. + * + * @return string + * An HTML formatted string. + * + * @see aggregator_menu() */ function aggregator_page_categories() { $result = db_query('SELECT c.cid, c.title, c.description FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid LEFT JOIN {aggregator_item} i ON ci.iid = i.iid GROUP BY c.cid, c.title, c.description'); @@ -380,7 +396,10 @@ function aggregator_page_categories() { } /** - * Menu callback; generate an RSS 0.92 feed of aggregator items or categories. + * Page callback: Generates an RSS 0.92 feed of aggregator items or categories. + * + * @return string + * An HTML formatted string. */ function aggregator_page_rss() { $result = NULL; @@ -448,12 +467,14 @@ function theme_aggregator_page_rss($variables) { } /** - * Menu callback; generates an OPML representation of all feeds. + * Page callback: Generates an OPML representation of all feeds. * * @param $cid - * If set, feeds are exported only from a category with this ID. Otherwise, all feeds are exported. + * (optional) If set, feeds are exported only from a category with this ID. + * Otherwise, all feeds are exported. Defaults to NULL. + * * @return - * The output XML. + * An OPML formatted string. */ function aggregator_page_opml($cid = NULL) { if ($cid) { @@ -468,14 +489,12 @@ function aggregator_page_opml($cid = NULL) { } /** - * Prints the OPML page for a feed. + * Prints the OPML page for the feed. * * @param $variables * An associative array containing: * - feeds: An array of the feeds to theme. * - * @return void - * * @ingroup themeable */ function theme_aggregator_page_opml($variables) { diff --git a/modules/aggregator/aggregator.processor.inc b/modules/aggregator/aggregator.processor.inc index 3f1319c8c..44ed54996 100644 --- a/modules/aggregator/aggregator.processor.inc +++ b/modules/aggregator/aggregator.processor.inc @@ -131,6 +131,12 @@ function aggregator_form_aggregator_admin_form_alter(&$form, $form_state) { * * Callback for drupal_map_assoc() within * aggregator_form_aggregator_admin_form_alter(). + * + * @param $length + * The desired length of teaser text, in bytes. + * + * @return + * A translated string explaining the teaser string length. */ function _aggregator_characters($length) { return ($length == 0) ? t('Unlimited') : format_plural($length, '1 character', '@count characters'); diff --git a/modules/aggregator/aggregator.test b/modules/aggregator/aggregator.test index eff31020f..8b95d6e37 100644 --- a/modules/aggregator/aggregator.test +++ b/modules/aggregator/aggregator.test @@ -5,6 +5,9 @@ * Tests for aggregator.module. */ +/** + * Defines a base class for testing the Aggregator module. + */ class AggregatorTestCase extends DrupalWebTestCase { function setUp() { parent::setUp('aggregator', 'aggregator_test'); @@ -13,10 +16,15 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Create an aggregator feed (simulate form submission on admin/config/services/aggregator/add/feed). + * Creates an aggregator feed. + * + * This method simulates the form submission on path + * admin/config/services/aggregator/add/feed. * * @param $feed_url - * If given, feed will be created with this URL, otherwise /rss.xml will be used. + * (optional) If given, feed will be created with this URL, otherwise + * /rss.xml will be used. Defaults to NULL. + * * @return $feed * Full feed object if possible. * @@ -33,7 +41,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Delete an aggregator feed. + * Deletes an aggregator feed. * * @param $feed * Feed object representing the feed. @@ -44,10 +52,11 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Return a randomly generated feed edit array. + * Returns a randomly generated feed edit array. * * @param $feed_url - * If given, feed will be created with this URL, otherwise /rss.xml will be used. + * (optional) If given, feed will be created with this URL, otherwise + * /rss.xml will be used. Defaults to NULL. * @return * A feed array. */ @@ -68,7 +77,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Return the count of the randomly created feed array. + * Returns the count of the randomly created feed array. * * @return * Number of feed items on default feed created by createFeed(). @@ -80,10 +89,13 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Update feed items (simulate click to admin/config/services/aggregator/update/$fid). + * Updates the feed items. + * + * This method simulates a click to + * admin/config/services/aggregator/update/$fid. * * @param $feed - * Feed object representing the feed. + * Feed object representing the feed, passed by reference. * @param $expected_count * Expected number of feed items. */ @@ -112,7 +124,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Confirm item removal from a feed. + * Confirms an item removal from a feed. * * @param $feed * Feed object representing the feed. @@ -123,7 +135,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Add and remove feed items and ensure that the count is zero. + * Adds and removes feed items and ensure that the count is zero. * * @param $feed * Feed object representing the feed. @@ -140,7 +152,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Pull feed categories from aggregator_category_feed table. + * Pulls feed categories from {aggregator_category_feed} table. * * @param $feed * Feed object representing the feed. @@ -154,7 +166,11 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Pull categories from aggregator_category table. + * Pulls categories from {aggregator_category} table. + * + * @return + * An associative array keyed by category ID and values are set to the + * category names. */ function getCategories() { $categories = array(); @@ -165,14 +181,14 @@ class AggregatorTestCase extends DrupalWebTestCase { return $categories; } - /** - * Check if the feed name and URL is unique. + * Checks whether the feed name and URL are unique. * * @param $feed_name * String containing the feed name to check. * @param $feed_url * String containing the feed URL to check. + * * @return * TRUE if feed is unique. */ @@ -182,10 +198,11 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Create a valid OPML file from an array of feeds. + * Creates a valid OPML file from an array of feeds. * * @param $feeds * An array of feeds. + * * @return * Path to valid OPML file. */ @@ -223,7 +240,7 @@ EOF; } /** - * Create an invalid OPML file. + * Creates an invalid OPML file. * * @return * Path to invalid OPML file. @@ -240,7 +257,7 @@ EOF; } /** - * Create a valid but empty OPML file. + * Creates a valid but empty OPML file. * * @return * Path to empty OPML file. @@ -275,7 +292,7 @@ EOF; * Creates sample article nodes. * * @param $count - * (optional) The number of nodes to generate. + * (optional) The number of nodes to generate. Defaults to five. */ function createSampleNodes($count = 5) { $langcode = LANGUAGE_NONE; @@ -290,7 +307,7 @@ EOF; } /** - * Tests aggregator configuration settings. + * Tests functionality of the configuration settings in the Aggregator module. */ class AggregatorConfigurationTestCase extends AggregatorTestCase { public static function getInfo() { @@ -321,6 +338,9 @@ class AggregatorConfigurationTestCase extends AggregatorTestCase { } } +/** + * Tests adding aggregator feeds. + */ class AddFeedTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -331,7 +351,7 @@ class AddFeedTestCase extends AggregatorTestCase { } /** - * Create a feed, ensure that it is unique, check the source, and delete the feed. + * Creates and ensures that a feed is unique, checks source, and deletes feed. */ function testAddFeed() { $feed = $this->createFeed(); @@ -381,6 +401,9 @@ class AddFeedTestCase extends AggregatorTestCase { } } +/** + * Tests the categorize feed functionality in the Aggregator module. + */ class CategorizeFeedTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -391,7 +414,7 @@ class CategorizeFeedTestCase extends AggregatorTestCase { } /** - * Create a feed and make sure you can add more than one category to it. + * Creates a feed and makes sure you can add more than one category to it. */ function testCategorizeFeed() { @@ -424,6 +447,9 @@ class CategorizeFeedTestCase extends AggregatorTestCase { } } +/** + * Tests functionality of updating the feed in the Aggregator module. + */ class UpdateFeedTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -434,7 +460,7 @@ class UpdateFeedTestCase extends AggregatorTestCase { } /** - * Create a feed and attempt to update it. + * Creates a feed and attempts to update it. */ function testUpdateFeed() { $remamining_fields = array('title', 'url', ''); @@ -466,6 +492,9 @@ class UpdateFeedTestCase extends AggregatorTestCase { } } +/** + * Tests functionality for removing feeds in the Aggregator module. + */ class RemoveFeedTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -476,7 +505,7 @@ class RemoveFeedTestCase extends AggregatorTestCase { } /** - * Remove a feed and ensure that all it services are removed. + * Removes a feed and ensures that all of its services are removed. */ function testRemoveFeed() { $feed = $this->createFeed(); @@ -494,6 +523,9 @@ class RemoveFeedTestCase extends AggregatorTestCase { } } +/** + * Tests functionality of updating a feed item in the Aggregator module. + */ class UpdateFeedItemTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -504,7 +536,7 @@ class UpdateFeedItemTestCase extends AggregatorTestCase { } /** - * Test running "update items" from the 'admin/config/services/aggregator' page. + * Tests running "update items" from 'admin/config/services/aggregator' page. */ function testUpdateFeedItem() { $this->createSampleNodes(); @@ -564,7 +596,7 @@ class RemoveFeedItemTestCase extends AggregatorTestCase { } /** - * Test running "remove items" from the 'admin/config/services/aggregator' page. + * Tests running "remove items" from 'admin/config/services/aggregator' page. */ function testRemoveFeedItem() { // Create a bunch of test feeds. @@ -592,6 +624,9 @@ class RemoveFeedItemTestCase extends AggregatorTestCase { } } +/** + * Tests categorization functionality in the Aggregator module. + */ class CategorizeFeedItemTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -602,6 +637,8 @@ class CategorizeFeedItemTestCase extends AggregatorTestCase { } /** + * Checks that children of a feed inherit a defined category. + * * If a feed has a category, make sure that the children inherit that * categorization. */ @@ -649,6 +686,9 @@ class CategorizeFeedItemTestCase extends AggregatorTestCase { } } +/** + * Tests importing feeds from OPML functionality for the Aggregator module. + */ class ImportOPMLTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -659,7 +699,7 @@ class ImportOPMLTestCase extends AggregatorTestCase { } /** - * Open OPML import form. + * Opens OPML import form. */ function openImportForm() { db_delete('aggregator_category')->execute(); @@ -681,7 +721,7 @@ class ImportOPMLTestCase extends AggregatorTestCase { } /** - * Submit form filled with invalid fields. + * Submits form filled with invalid fields. */ function validateImportFormFields() { $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); @@ -707,7 +747,7 @@ class ImportOPMLTestCase extends AggregatorTestCase { } /** - * Submit form with invalid, empty and valid OPML files. + * Submits form with invalid, empty, and valid OPML files. */ function submitImportForm() { $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); @@ -766,6 +806,9 @@ class ImportOPMLTestCase extends AggregatorTestCase { $this->assertTrue($category, 'Categories are correct.'); } + /** + * Tests the import of an OPML file. + */ function testOPMLImport() { $this->openImportForm(); $this->validateImportFormFields(); @@ -773,6 +816,9 @@ class ImportOPMLTestCase extends AggregatorTestCase { } } +/** + * Tests functionality of the cron process in the Aggregator module. + */ class AggregatorCronTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -783,7 +829,7 @@ class AggregatorCronTestCase extends AggregatorTestCase { } /** - * Add feeds update them on cron. + * Adds feeds and updates them via cron process. */ public function testCron() { // Create feed and test basic updating on cron. @@ -819,6 +865,9 @@ class AggregatorCronTestCase extends AggregatorTestCase { } } +/** + * Tests rendering functionality in the Aggregator module. + */ class AggregatorRenderingTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -829,9 +878,9 @@ class AggregatorRenderingTestCase extends AggregatorTestCase { } /** - * Add a feed block to the page and checks its links. + * Adds a feed block to the page and checks its links. * - * TODO: Test the category block as well. + * @todo Test the category block as well. */ public function testBlockLinks() { // Create feed. @@ -890,7 +939,7 @@ class AggregatorRenderingTestCase extends AggregatorTestCase { } /** - * Create a feed and check that feed's page. + * Creates a feed and checks that feed's page. */ public function testFeedPage() { // Increase the number of items published in the rss.xml feed so we have @@ -913,7 +962,7 @@ class AggregatorRenderingTestCase extends AggregatorTestCase { } /** - * Tests for feed parsing. + * Tests feed parsing in the Aggregator module. */ class FeedParserTestCase extends AggregatorTestCase { public static function getInfo() { @@ -933,7 +982,7 @@ class FeedParserTestCase extends AggregatorTestCase { } /** - * Test a feed that uses the RSS 0.91 format. + * Tests a feed that uses the RSS 0.91 format. */ function testRSS091Sample() { $feed = $this->createFeed($this->getRSS091Sample()); @@ -955,7 +1004,7 @@ class FeedParserTestCase extends AggregatorTestCase { } /** - * Test a feed that uses the Atom format. + * Tests a feed that uses the Atom format. */ function testAtomSample() { $feed = $this->createFeed($this->getAtomSample()); diff --git a/modules/book/book-rtl.css b/modules/book/book-rtl.css index f3a84c20e..40dff0e53 100644 --- a/modules/book/book-rtl.css +++ b/modules/book/book-rtl.css @@ -1,3 +1,7 @@ +/** + * @file + * Right-to-Left styling for the Book module. + */ .book-navigation .menu { padding: 1em 3em 0 0; diff --git a/modules/book/book.admin.inc b/modules/book/book.admin.inc index 62c6e841a..cc3f08fc8 100644 --- a/modules/book/book.admin.inc +++ b/modules/book/book.admin.inc @@ -2,11 +2,16 @@ /** * @file - * Admin page callbacks for the book module. + * Administration page callbacks for the Book module. */ /** * Returns an administrative overview of all books. + * + * @return string + * A HTML-formatted string with the administrative page content. + * + * @see book_menu() */ function book_admin_overview() { $rows = array(); @@ -53,6 +58,8 @@ function book_admin_settings() { /** * Form validation handler for book_admin_settings(). + * + * @see book_admin_settings_submit() */ function book_admin_settings_validate($form, &$form_state) { $child_type = $form_state['values']['book_child_type']; @@ -149,7 +156,7 @@ function book_admin_edit_submit($form, &$form_state) { * @param $node * The node of the top-level page in the book. * @param $form - * The form that is being modified. + * The form that is being modified, passed by reference. * * @see book_admin_edit() */ @@ -184,10 +191,10 @@ function _book_admin_table($node, &$form) { * @param $tree * A subtree of the book menu hierarchy. * @param $form - * The form that is being modified. + * The form that is being modified, passed by reference. * * @return - * The form that is being modified. + * The modified form array. * * @see book_admin_edit() */ diff --git a/modules/book/book.css b/modules/book/book.css index a8d2136df..00e379ee0 100644 --- a/modules/book/book.css +++ b/modules/book/book.css @@ -1,3 +1,7 @@ + /** + * @file + * Styling for the Book module. + */ .book-navigation .menu { border-top: 1px solid #888; diff --git a/modules/book/book.js b/modules/book/book.js index 0853e8ecb..64f4aee68 100644 --- a/modules/book/book.js +++ b/modules/book/book.js @@ -3,7 +3,6 @@ * Javascript behaviors for the Book module. */ - (function ($) { Drupal.behaviors.bookFieldsetSummaries = { diff --git a/modules/book/book.module b/modules/book/book.module index 1fb0c0b11..71b89945e 100644 --- a/modules/book/book.module +++ b/modules/book/book.module @@ -221,6 +221,9 @@ function _book_outline_remove_access($node) { * * A node can be removed from a book if it is actually in a book and it either * is not a top-level page or is a top-level page with no children. + * + * @param $node + * The node to remove from the outline. */ function _book_node_is_removable($node) { return (!empty($node->book['bid']) && (($node->book['bid'] != $node->nid) || !$node->book['has_children'])); @@ -734,7 +737,7 @@ function book_get_flat_menu($book_link) { * @param $tree * A tree of menu links in an array. * @param $flat - * A flat array of the menu links from $tree. + * A flat array of the menu links from $tree, passed by reference. * * @see book_get_flat_menu(). */ @@ -1062,8 +1065,9 @@ function _book_link_defaults($nid) { * to the structured data but can also simply iterate over all elements and * render them (as in the default template). * - * The $variables array contains the following elements: - * - book_menus + * @param $variables + * An associative array containing the following key: + * - book_menus * * @see book-all-books-block.tpl.php */ @@ -1079,8 +1083,9 @@ function template_preprocess_book_all_books_block(&$variables) { /** * Processes variables for book-navigation.tpl.php. * - * The $variables array contains the following elements: - * - book_link + * @param $variables + * An associative array containing the following key: + * - book_link * * @see book-navigation.tpl.php */ @@ -1151,8 +1156,9 @@ function template_preprocess_book_navigation(&$variables) { * Reference to the table of contents array. This is modified in place, so the * function does not have a return value. * @param $exclude - * Optional array of menu link ID values. Any link whose menu link ID is in - * this array will be excluded (along with its children). + * (optional) An array of menu link ID values. Any link whose menu link ID is + * in this array will be excluded (along with its children). Defaults to an + * empty array. * @param $depth_limit * Any link deeper than this value will be excluded (along with its children). */ @@ -1198,10 +1204,11 @@ function book_toc($bid, $depth_limit, $exclude = array()) { /** * Processes variables for book-export-html.tpl.php. * - * The $variables array contains the following elements: - * - title - * - contents - * - depth + * @param $variables + * An associative array containing the following keys: + * - title + * - contents + * - depth * * @see book-export-html.tpl.php */ @@ -1261,7 +1268,8 @@ function book_export_traverse($tree, $visit_func) { * @param $node * The node that will be output. * @param $children - * All the rendered child nodes within the current node. + * (optional) All the rendered child nodes within the current node. Defaults + * to an empty string. * * @return * The HTML generated for the given node. @@ -1280,9 +1288,10 @@ function book_node_export($node, $children = '') { /** * Processes variables for book-node-export-html.tpl.php. * - * The $variables array contains the following elements: - * - node - * - children + * @param $variables + * An associative array containing the following keys: + * - node + * - children * * @see book-node-export-html.tpl.php */ @@ -1294,6 +1303,12 @@ function template_preprocess_book_node_export_html(&$variables) { /** * Determine if a given node type is in the list of types allowed for books. + * + * @param $type + * A node type. + * + * @return + * A Boolean TRUE if the node type can be included in books; otherwise, FALSE. */ function book_type_is_allowed($type) { return in_array($type, variable_get('book_allowed_types', array('book'))); @@ -1336,7 +1351,7 @@ function book_node_type_update($type) { * * @return * A menu link, with the link translated for rendering and data added from the - * {book} table. + * {book} table. FALSE if there is an error. */ function book_link_load($mlid) { if ($item = db_query("SELECT * FROM {menu_links} ml INNER JOIN {book} b ON b.mlid = ml.mlid LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = :mlid", array( diff --git a/modules/book/book.pages.inc b/modules/book/book.pages.inc index 63a1d15a4..e5a04c5a2 100644 --- a/modules/book/book.pages.inc +++ b/modules/book/book.pages.inc @@ -7,6 +7,11 @@ /** * Menu callback: Prints a listing of all books. + * + * @return string + * A HTML-formatted string with the listing of all books content. + * + * @see book_menu() */ function book_render() { $book_list = array(); @@ -36,6 +41,8 @@ function book_render() { * @return * A string representing the node and its children in the book hierarchy in a * format determined by the $type parameter. + * + * @see book_menu() */ function book_export($type, $nid) { // Check that the node exists and that the current user has access to it. @@ -100,6 +107,11 @@ function book_export_html($nid) { * * @param $node * The book node for which to show the outline. + * + * @return string + * A HTML-formatted string with the outline form for a single node. + * + * @see book_menu() */ function book_outline($node) { drupal_set_title($node->title); diff --git a/modules/book/book.test b/modules/book/book.test index 2708e3674..81f4524ac 100644 --- a/modules/book/book.test +++ b/modules/book/book.test @@ -5,14 +5,37 @@ * Tests for book.module. */ +/** + * Tests the functionality of the Book module. + */ class BookTestCase extends DrupalWebTestCase { + + /** + * A book node. + * + * @var object + */ protected $book; - // $book_author is a user with permission to create and edit books. + + /** + * A user with permission to create and edit books. + * + * @var object + */ protected $book_author; - // $web_user is a user with permission to view a book - // and access the printer-friendly version. + + /** + * A user with permission to view a book and access printer-friendly version. + * + * @var object + */ protected $web_user; - // $admin_user is a user with permission to create and edit books and to administer blocks. + + /** + * A user with permission to create and edit books and to administer blocks. + * + * @var object + */ protected $admin_user; public static function getInfo() { @@ -36,7 +59,7 @@ class BookTestCase extends DrupalWebTestCase { } /** - * Create a new book with a page hierarchy. + * Creates a new book with a page hierarchy. */ function createBook() { // Create new book. @@ -67,7 +90,7 @@ class BookTestCase extends DrupalWebTestCase { } /** - * Test book functionality through node interfaces. + * Tests book functionality through node interfaces. */ function testBook() { // Create new book. @@ -106,18 +129,20 @@ class BookTestCase extends DrupalWebTestCase { } /** - * Check the outline of sub-pages; previous, up, and next; and printer friendly version. + * Checks the outline of sub-pages; previous, up, and next. + * + * Also checks the printer friendly version of the outline. * * @param $node * Node to check. * @param $nodes * Nodes that should be in outline. * @param $previous - * Previous link node. + * (optional) Previous link node. Defaults to FALSE. * @param $up - * Up link node. + * (optional) Up link node. Defaults to FALSE. * @param $next - * Next link node. + * (optional) Next link node. Defaults to FALSE. * @param $breadcrumb * The nodes that should be displayed in the breadcrumb. */ @@ -129,23 +154,23 @@ class BookTestCase extends DrupalWebTestCase { // Check outline structure. if ($nodes !== NULL) { - $this->assertPattern($this->generateOutlinePattern($nodes), t('Node ' . $number . ' outline confirmed.')); + $this->assertPattern($this->generateOutlinePattern($nodes), format_string('Node %number outline confirmed.', array('%number' => $number))); } else { - $this->pass(t('Node ' . $number . ' doesn\'t have outline.')); + $this->pass(format_string('Node %number does not have outline.', array('%number' => $number))); } // Check previous, up, and next links. if ($previous) { - $this->assertRaw(l('‹ ' . $previous->title, 'node/' . $previous->nid, array('attributes' => array('class' => array('page-previous'), 'title' => t('Go to previous page')))), t('Previous page link found.')); + $this->assertRaw(l('‹ ' . $previous->title, 'node/' . $previous->nid, array('attributes' => array('class' => array('page-previous'), 'title' => t('Go to previous page')))), 'Previous page link found.'); } if ($up) { - $this->assertRaw(l('up', 'node/' . $up->nid, array('attributes' => array('class' => array('page-up'), 'title' => t('Go to parent page')))), t('Up page link found.')); + $this->assertRaw(l('up', 'node/' . $up->nid, array('attributes' => array('class' => array('page-up'), 'title' => t('Go to parent page')))), 'Up page link found.'); } if ($next) { - $this->assertRaw(l($next->title . ' ›', 'node/' . $next->nid, array('attributes' => array('class' => array('page-next'), 'title' => t('Go to next page')))), t('Next page link found.')); + $this->assertRaw(l($next->title . ' ›', 'node/' . $next->nid, array('attributes' => array('class' => array('page-next'), 'title' => t('Go to next page')))), 'Next page link found.'); } // Compute the expected breadcrumb. @@ -163,20 +188,24 @@ class BookTestCase extends DrupalWebTestCase { } // Compare expected and got breadcrumbs. - $this->assertIdentical($expected_breadcrumb, $got_breadcrumb, t('The breadcrumb is correctly displayed on the page.')); + $this->assertIdentical($expected_breadcrumb, $got_breadcrumb, 'The breadcrumb is correctly displayed on the page.'); // Check printer friendly version. $this->drupalGet('book/export/html/' . $node->nid); - $this->assertText($node->title, t('Printer friendly title found.')); - $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), t('Printer friendly body found.')); + $this->assertText($node->title, 'Printer friendly title found.'); + $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), 'Printer friendly body found.'); $number++; } /** - * Create a regular expression to check for the sub-nodes in the outline. + * Creates a regular expression to check for the sub-nodes in the outline. + * + * @param array $nodes + * An array of nodes to check in outline. * - * @param array $nodes Nodes to check in outline. + * @return + * A regular expression that locates sub-nodes of the outline. */ function generateOutlinePattern($nodes) { $outline = ''; @@ -188,10 +217,12 @@ class BookTestCase extends DrupalWebTestCase { } /** - * Create book node. + * Creates a book node. * - * @param integer $book_nid Book node id or set to 'new' to create new book. - * @param integer $parent Parent book reference id. + * @param $book_nid + * A book node ID or set to 'new' to create a new book. + * @param $parent + * (optional) Parent book reference ID. Defaults to NULL. */ function createBookNode($book_nid, $parent = NULL) { // $number does not use drupal_static as it should not be reset @@ -216,7 +247,7 @@ class BookTestCase extends DrupalWebTestCase { // Check to make sure the book node was created. $node = $this->drupalGetNodeByTitle($edit['title']); - $this->assertNotNull(($node === FALSE ? NULL : $node), t('Book node found in database.')); + $this->assertNotNull(($node === FALSE ? NULL : $node), 'Book node found in database.'); $number++; return $node; @@ -236,28 +267,28 @@ class BookTestCase extends DrupalWebTestCase { // Make sure each part of the book is there. foreach ($nodes as $node) { - $this->assertText($node->title, t('Node title found in printer friendly version.')); - $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), t('Node body found in printer friendly version.')); + $this->assertText($node->title, 'Node title found in printer friendly version.'); + $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), 'Node body found in printer friendly version.'); } // Make sure we can't export an unsupported format. $this->drupalGet('book/export/foobar/' . $this->book->nid); - $this->assertResponse('404', t('Unsupported export format returned "not found".')); + $this->assertResponse('404', 'Unsupported export format returned "not found".'); // Make sure we get a 404 on a not existing book node. $this->drupalGet('book/export/html/123'); - $this->assertResponse('404', t('Not existing book node returned "not found".')); + $this->assertResponse('404', 'Not existing book node returned "not found".'); // Make sure an anonymous user cannot view printer-friendly version. $this->drupalLogout(); // Load the book and verify there is no printer-friendly version link. $this->drupalGet('node/' . $this->book->nid); - $this->assertNoLink(t('Printer-friendly version'), t('Anonymous user is not shown link to printer-friendly version.')); + $this->assertNoLink(t('Printer-friendly version'), 'Anonymous user is not shown link to printer-friendly version.'); // Try getting the URL directly, and verify it fails. $this->drupalGet('book/export/html/' . $this->book->nid); - $this->assertResponse('403', t('Anonymous user properly forbidden.')); + $this->assertResponse('403', 'Anonymous user properly forbidden.'); // Now grant anonymous users permission to view the printer-friendly // version and verify that node access restrictions still prevent them from @@ -276,30 +307,30 @@ class BookTestCase extends DrupalWebTestCase { // Set block title to confirm that the interface is available. $block_title = $this->randomName(16); $this->drupalPost('admin/structure/block/manage/book/navigation/configure', array('title' => $block_title), t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); + $this->assertText(t('The block configuration has been saved.'), 'Block configuration set.'); // Set the block to a region to confirm block is available. $edit = array(); $edit['blocks[book_navigation][region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.')); + $this->assertText(t('The block settings have been updated.'), 'Block successfully move to footer region.'); // Give anonymous users the permission 'node test view'. $edit = array(); $edit[DRUPAL_ANONYMOUS_RID . '[node test view]'] = TRUE; $this->drupalPost('admin/people/permissions/' . DRUPAL_ANONYMOUS_RID, $edit, t('Save permissions')); - $this->assertText(t('The changes have been saved.'), t("Permission 'node test view' successfully assigned to anonymous users.")); + $this->assertText(t('The changes have been saved.'), "Permission 'node test view' successfully assigned to anonymous users."); // Test correct display of the block. $nodes = $this->createBook(); $this->drupalGet('<front>'); - $this->assertText($block_title, t('Book navigation block is displayed.')); - $this->assertText($this->book->title, t('Link to book root (@title) is displayed.', array('@title' => $nodes[0]->title))); - $this->assertNoText($nodes[0]->title, t('No links to individual book pages are displayed.')); + $this->assertText($block_title, 'Book navigation block is displayed.'); + $this->assertText($this->book->title, format_string('Link to book root (@title) is displayed.', array('@title' => $nodes[0]->title))); + $this->assertNoText($nodes[0]->title, 'No links to individual book pages are displayed.'); } /** - * Test the book navigation block when an access module is enabled. + * Tests the book navigation block when an access module is enabled. */ function testNavigationBlockOnAccessModuleEnabled() { $this->drupalLogin($this->admin_user); @@ -312,19 +343,19 @@ class BookTestCase extends DrupalWebTestCase { // Set block display to 'Show block only on book pages'. $edit['book_block_mode'] = 'book pages'; $this->drupalPost('admin/structure/block/manage/book/navigation/configure', $edit, t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); + $this->assertText(t('The block configuration has been saved.'), 'Block configuration set.'); // Set the block to a region to confirm block is available. $edit = array(); $edit['blocks[book_navigation][region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.')); + $this->assertText(t('The block settings have been updated.'), 'Block successfully move to footer region.'); // Give anonymous users the permission 'node test view'. $edit = array(); $edit[DRUPAL_ANONYMOUS_RID . '[node test view]'] = TRUE; $this->drupalPost('admin/people/permissions/' . DRUPAL_ANONYMOUS_RID, $edit, t('Save permissions')); - $this->assertText(t('The changes have been saved.'), t('Permission \'node test view\' successfully assigned to anonymous users.')); + $this->assertText(t('The changes have been saved.'), "Permission 'node test view' successfully assigned to anonymous users."); // Create a book. $this->createBook(); @@ -332,12 +363,12 @@ class BookTestCase extends DrupalWebTestCase { // Test correct display of the block to registered users. $this->drupalLogin($this->web_user); $this->drupalGet('node/' . $this->book->nid); - $this->assertText($block_title, t('Book navigation block is displayed to registered users.')); + $this->assertText($block_title, 'Book navigation block is displayed to registered users.'); $this->drupalLogout(); // Test correct display of the block to anonymous users. $this->drupalGet('node/' . $this->book->nid); - $this->assertText($block_title, t('Book navigation block is displayed to anonymous users.')); + $this->assertText($block_title, 'Book navigation block is displayed to anonymous users.'); } /** @@ -350,10 +381,10 @@ class BookTestCase extends DrupalWebTestCase { // Test access to delete top-level and child book nodes. $this->drupalGet('node/' . $this->book->nid . '/outline/remove'); - $this->assertResponse('403', t('Deleting top-level book node properly forbidden.')); + $this->assertResponse('403', 'Deleting top-level book node properly forbidden.'); $this->drupalPost('node/' . $nodes[4]->nid . '/outline/remove', $edit, t('Remove')); $node4 = node_load($nodes[4]->nid, NULL, TRUE); - $this->assertTrue(empty($node4->book), t('Deleting child book node properly allowed.')); + $this->assertTrue(empty($node4->book), 'Deleting child book node properly allowed.'); // Delete all child book nodes and retest top-level node deletion. foreach ($nodes as $node) { @@ -362,6 +393,6 @@ class BookTestCase extends DrupalWebTestCase { node_delete_multiple($nids); $this->drupalPost('node/' . $this->book->nid . '/outline/remove', $edit, t('Remove')); $node = node_load($this->book->nid, NULL, TRUE); - $this->assertTrue(empty($node->book), t('Deleting childless top-level book node properly allowed.')); + $this->assertTrue(empty($node->book), 'Deleting childless top-level book node properly allowed.'); } } diff --git a/modules/comment/comment.admin.inc b/modules/comment/comment.admin.inc index 4f3d35071..43b53e27a 100644 --- a/modules/comment/comment.admin.inc +++ b/modules/comment/comment.admin.inc @@ -98,13 +98,14 @@ function comment_admin_overview($form, &$form_state, $arg) { // Remove the first node title from the node_titles array and attach to // the comment. $comment->node_title = array_shift($node_titles); + $comment_body = field_get_items('comment', $comment, 'comment_body'); $options[$comment->cid] = array( 'subject' => array( 'data' => array( '#type' => 'link', '#title' => $comment->subject, '#href' => 'comment/' . $comment->cid, - '#options' => array('attributes' => array('title' => truncate_utf8($comment->comment_body[LANGUAGE_NONE][0]['value'], 128)), 'fragment' => 'comment-' . $comment->cid), + '#options' => array('attributes' => array('title' => truncate_utf8($comment_body[0]['value'], 128)), 'fragment' => 'comment-' . $comment->cid), ), ), 'author' => theme('username', array('account' => $comment)), diff --git a/modules/comment/comment.module b/modules/comment/comment.module index 4241538a0..46115be04 100644 --- a/modules/comment/comment.module +++ b/modules/comment/comment.module @@ -2041,7 +2041,8 @@ function comment_form($form, &$form_state, $comment) { // Attach fields. $comment->node_type = 'comment_node_' . $node->type; - field_attach_form('comment', $comment, $form, $form_state); + $langcode = entity_language('comment', $comment); + field_attach_form('comment', $comment, $form, $form_state, $langcode); return $form; } @@ -2066,7 +2067,8 @@ function comment_preview($comment) { $node = node_load($comment->nid); if (!form_get_errors()) { - $comment->format = $comment->comment_body[LANGUAGE_NONE][0]['format']; + $comment_body = field_get_items('comment', $comment, 'comment_body'); + $comment->format = $comment_body[0]['format']; // Attach the user and time information. if (!empty($comment->name)) { $account = user_load_by_name($comment->name); @@ -2190,7 +2192,9 @@ function comment_submit($comment) { // 1) Filter it into HTML // 2) Strip out all HTML tags // 3) Convert entities back to plain-text. - $comment_body = $comment->comment_body[LANGUAGE_NONE][0]; + $field = field_info_field('comment_body'); + $langcode = field_is_translatable('comment', $field) ? entity_language('comment', $comment) : LANGUAGE_NONE; + $comment_body = $comment->comment_body[$langcode][0]; if (isset($comment_body['format'])) { $comment_text = check_markup($comment_body['value'], $comment_body['format']); } @@ -2284,8 +2288,16 @@ function template_preprocess_comment(&$variables) { $variables['comment'] = $comment; $variables['node'] = $node; $variables['author'] = theme('username', array('account' => $comment)); + $variables['created'] = format_date($comment->created); - $variables['changed'] = format_date($comment->changed); + + // Avoid calling format_date() twice on the same timestamp. + if ($comment->changed == $comment->created) { + $variables['changed'] = $variables['created']; + } + else { + $variables['changed'] = format_date($comment->changed); + } $variables['new'] = !empty($comment->new) ? t('new') : ''; $variables['picture'] = theme_get_setting('toggle_comment_user_picture') ? theme('user_picture', array('account' => $comment)) : ''; diff --git a/modules/dblog/dblog-rtl.css b/modules/dblog/dblog-rtl.css index 282fe971d..0fab8d065 100644 --- a/modules/dblog/dblog-rtl.css +++ b/modules/dblog/dblog-rtl.css @@ -1,3 +1,7 @@ +/** + * @file + * Right-to-Left styling for the Database Logging module. + */ .form-item-type, .form-item-severity { diff --git a/modules/dblog/dblog.admin.inc b/modules/dblog/dblog.admin.inc index 0655e7564..7c1c0e20f 100644 --- a/modules/dblog/dblog.admin.inc +++ b/modules/dblog/dblog.admin.inc @@ -2,14 +2,19 @@ /** * @file - * Administrative page callbacks for the dblog module. + * Administrative page callbacks for the Database Logging module. */ /** - * Menu callback; displays a listing of log messages. + * Page callback: Displays a listing of database log messages. * - * Messages are truncated at 56 chars. Full-length message could be viewed at - * the message details page. + * Messages are truncated at 56 chars. Full-length messages can be viewed on the + * message details page. + * + * @see dblog_clear_log_form() + * @see dblog_event() + * @see dblog_filter_form() + * @see dblog_menu() * * @ingroup logging_severity_levels */ @@ -81,12 +86,18 @@ function dblog_overview() { } /** - * Menu callback; generic function to display a page of the most frequent events. + * Page callback: Shows the most frequent log messages of a given event type. + * + * Messages are not truncated on this page because events detailed herein do not + * have links to a detailed view. + * + * @param string $type + * Type of database log events to display (e.g., 'search'). * - * Messages are not truncated because events from this page have no detail view. + * @return array + * A build array in the format expected by drupal_render(). * - * @param $type - * type of dblog events to display. + * @see dblog_menu() */ function dblog_top($type) { @@ -127,7 +138,16 @@ function dblog_top($type) { } /** - * Menu callback; displays details about a log message. + * Page callback: Displays details about a specific database log message. + * + * @param int $id + * Unique ID of the database log message. + * + * @return array|string + * If the ID is located in the Database Logging table, a build array in the + * format expected by drupal_render(); otherwise, an empty string. + * + * @see dblog_menu() */ function dblog_event($id) { $severity = watchdog_severity_levels(); @@ -184,7 +204,10 @@ function dblog_event($id) { } /** - * Build query for dblog administration filters based on session. + * Builds a query for database log administration filters based on session. + * + * @return array + * An associative array with keys 'where' and 'args'. */ function dblog_build_filter_query() { if (empty($_SESSION['dblog_overview_filter'])) { @@ -213,9 +236,16 @@ function dblog_build_filter_query() { ); } - /** - * List dblog administration filters that can be applied. + * Creates a list of database log administration filters that can be applied. + * + * @return array + * Associative array of filters. The top-level keys are used as the form + * element names for the filters, and the values are arrays with the following + * elements: + * - title: Title of the filter. + * - where: The filter condition. + * - options: Array of options for the select list for the filter. */ function dblog_filters() { $filters = array(); @@ -244,7 +274,7 @@ function dblog_filters() { /** * Returns HTML for a log message. * - * @param $variables + * @param array $variables * An associative array containing: * - event: An object with at least the message and variables properties. * - link: (optional) Format message as link, event->wid is required. @@ -274,11 +304,13 @@ function theme_dblog_message($variables) { } /** - * Return form for dblog administration filters. + * Form constructor for the database logging filter form. * - * @ingroup forms - * @see dblog_filter_form_submit() * @see dblog_filter_form_validate() + * @see dblog_filter_form_submit() + * @see dblog_overview() + * + * @ingroup forms */ function dblog_filter_form($form) { $filters = dblog_filters(); @@ -316,12 +348,13 @@ function dblog_filter_form($form) { '#value' => t('Reset') ); } - return $form; } /** - * Validate result from dblog administration filter form. + * Form validation handler for dblog_filter_form(). + * + * @see dblog_filter_form_submit() */ function dblog_filter_form_validate($form, &$form_state) { if ($form_state['values']['op'] == t('Filter') && empty($form_state['values']['type']) && empty($form_state['values']['severity'])) { @@ -330,7 +363,9 @@ function dblog_filter_form_validate($form, &$form_state) { } /** - * Process result from dblog administration filter form. + * Form submission handler for dblog_filter_form(). + * + * @see dblog_filter_form_validate() */ function dblog_filter_form_submit($form, &$form_state) { $op = $form_state['values']['op']; @@ -351,10 +386,10 @@ function dblog_filter_form_submit($form, &$form_state) { } /** - * Return form for dblog clear button. + * Form constructor for the form that clears out the log. * - * @ingroup forms * @see dblog_clear_log_submit() + * @ingroup forms */ function dblog_clear_log_form($form) { $form['dblog_clear'] = array( @@ -374,7 +409,7 @@ function dblog_clear_log_form($form) { } /** - * Submit callback: clear database with log messages. + * Form submission handler for dblog_clear_log_form(). */ function dblog_clear_log_submit() { $_SESSION['dblog_overview_filter'] = array(); diff --git a/modules/dblog/dblog.css b/modules/dblog/dblog.css index 88f4ba01b..b1278862a 100644 --- a/modules/dblog/dblog.css +++ b/modules/dblog/dblog.css @@ -1,3 +1,8 @@ +/** + * @file + * Admin styles for the Database Logging module. + */ + .form-item-type, .form-item-severity { float: left; /* LTR */ diff --git a/modules/dblog/dblog.module b/modules/dblog/dblog.module index d831548c9..9183eed69 100644 --- a/modules/dblog/dblog.module +++ b/modules/dblog/dblog.module @@ -4,9 +4,9 @@ * @file * System monitoring and logging for administrators. * - * The dblog module monitors your site and keeps a list of - * recorded events containing usage and performance data, errors, - * warnings, and similar operational information. + * The Database Logging module monitors your site and keeps a list of recorded + * events containing usage and performance data, errors, warnings, and similar + * operational information. * * @see watchdog() */ @@ -96,7 +96,7 @@ function dblog_init() { /** * Implements hook_cron(). * - * Remove expired log messages. + * Controls the size of the log table, paring it to 'dblog_row_limit' messages. */ function dblog_cron() { // Cleanup the watchdog table. @@ -121,6 +121,12 @@ function dblog_cron() { } } +/** + * Gathers a list of uniquely defined database log message types. + * + * @return array + * List of uniquely defined database log message types. + */ function _dblog_get_message_types() { $types = array(); @@ -135,7 +141,7 @@ function _dblog_get_message_types() { /** * Implements hook_watchdog(). * - * Note some values may be truncated for database column size restrictions. + * Note: Some values may be truncated to meet database column size restrictions. */ function dblog_watchdog(array $log_entry) { Database::getConnection('default', 'default')->insert('watchdog') @@ -155,7 +161,7 @@ function dblog_watchdog(array $log_entry) { } /** - * Implements hook_form_FORM_ID_alter(). + * Implements hook_form_FORM_ID_alter() for system_logging_settings(). */ function dblog_form_system_logging_settings_alter(&$form, $form_state) { $form['dblog_row_limit'] = array( diff --git a/modules/dblog/dblog.test b/modules/dblog/dblog.test index ad01e97f5..cd101930d 100644 --- a/modules/dblog/dblog.test +++ b/modules/dblog/dblog.test @@ -5,8 +5,23 @@ * Tests for dblog.module. */ +/** + * Tests logging messages to the database. + */ class DBLogTestCase extends DrupalWebTestCase { + + /** + * A user with some relevent administrative permissions. + * + * @var object + */ protected $big_user; + + /** + * A user without any permissions. + * + * @var object + */ protected $any_user; public static function getInfo() { @@ -28,7 +43,11 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Login users, create dblog events, and test dblog functionality through the admin and user interfaces. + * Tests Database Logging module functionality through interfaces. + * + * First logs in users, then creates database log events, and finally tests + * Database Logging module functionality through both the admin and user + * interfaces. */ function testDBLog() { // Login the admin user. @@ -46,12 +65,13 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Verify setting of the dblog row limit. + * Verifies setting of the database log row limit. * - * @param integer $count Log row limit. + * @param int $row_limit + * The row limit. */ private function verifyRowLimit($row_limit) { - // Change the dblog row limit. + // Change the database log row limit. $edit = array(); $edit['dblog_row_limit'] = $row_limit; $this->drupalPost('admin/config/development/logging', $edit, t('Save configuration')); @@ -66,33 +86,35 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Verify cron applies the dblog row limit. + * Verifies that cron correctly applies the database log row limit. * - * @param integer $count Log row limit. + * @param int $row_limit + * The row limit. */ private function verifyCron($row_limit) { // Generate additional log entries. $this->generateLogEntries($row_limit + 10); - // Verify dblog row count exceeds row limit. + // Verify that the database log row count exceeds the row limit. $count = db_query('SELECT COUNT(wid) FROM {watchdog}')->fetchField(); $this->assertTrue($count > $row_limit, t('Dblog row count of @count exceeds row limit of @limit', array('@count' => $count, '@limit' => $row_limit))); - // Run cron job. + // Run a cron job. $this->cronRun(); - // Verify dblog row count equals row limit plus one because cron adds a record after it runs. + // Verify that the database log row count equals the row limit plus one + // because cron adds a record after it runs. $count = db_query('SELECT COUNT(wid) FROM {watchdog}')->fetchField(); $this->assertTrue($count == $row_limit + 1, t('Dblog row count of @count equals row limit of @limit plus one', array('@count' => $count, '@limit' => $row_limit))); } /** - * Generate dblog entries. + * Generates a number of random database log events. * - * @param integer $count - * Number of log entries to generate. - * @param $type - * The type of watchdog entry. - * @param $severity - * The severity of the watchdog entry. + * @param int $count + * Number of watchdog entries to generate. + * @param string $type + * (optional) The type of watchdog entry. Defaults to 'custom'. + * @param int $severity + * (optional) The severity of the watchdog entry. Defaults to WATCHDOG_NOTICE. */ private function generateLogEntries($count, $type = 'custom', $severity = WATCHDOG_NOTICE) { global $base_root; @@ -119,42 +141,43 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Verify the logged in user has the desired access to the various dblog nodes. + * Confirms that database log reports are displayed at the correct paths. * - * @param integer $response HTTP response code. + * @param int $response + * (optional) HTTP response code. Defaults to 200. */ private function verifyReports($response = 200) { $quote = '''; - // View dblog help node. + // View the database log help page. $this->drupalGet('admin/help/dblog'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Database logging'), t('DBLog help was displayed')); } - // View dblog report node. + // View the database log report page. $this->drupalGet('admin/reports/dblog'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Recent log messages'), t('DBLog report was displayed')); } - // View dblog page-not-found report node. + // View the database log page-not-found report page. $this->drupalGet('admin/reports/page-not-found'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Top ' . $quote . 'page not found' . $quote . ' errors'), t('DBLog page-not-found report was displayed')); } - // View dblog access-denied report node. + // View the database log access-denied report page. $this->drupalGet('admin/reports/access-denied'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Top ' . $quote . 'access denied' . $quote . ' errors'), t('DBLog access-denied report was displayed')); } - // View dblog event node. + // View the database log event page. $this->drupalGet('admin/reports/event/1'); $this->assertResponse($response); if ($response == 200) { @@ -163,7 +186,7 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Verify events. + * Generates and then verifies various types of events. */ private function verifyEvents() { // Invoke events. @@ -179,14 +202,14 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Generate and verify user events. - * + * Generates and then verifies some user events. */ private function doUser() { // Set user variables. $name = $this->randomName(); $pass = user_password(); - // Add user using form to generate add user event (which is not triggered by drupalCreateUser). + // Add a user using the form to generate an add user event (which is not + // triggered by drupalCreateUser). $edit = array(); $edit['name'] = $name; $edit['mail'] = $name . '@example.com'; @@ -195,15 +218,16 @@ class DBLogTestCase extends DrupalWebTestCase { $edit['status'] = 1; $this->drupalPost('admin/people/create', $edit, t('Create new account')); $this->assertResponse(200); - // Retrieve user object. + // Retrieve the user object. $user = user_load_by_name($name); $this->assertTrue($user != NULL, t('User @name was loaded', array('@name' => $name))); - $user->pass_raw = $pass; // Needed by drupalLogin. + // pass_raw property is needed by drupalLogin. + $user->pass_raw = $pass; // Login user. $this->drupalLogin($user); // Logout user. $this->drupalLogout(); - // Fetch row ids in watchdog that relate to the user. + // Fetch the row IDs in watchdog that relate to the user. $result = db_query('SELECT wid FROM {watchdog} WHERE uid = :uid', array(':uid' => $user->uid)); foreach ($result as $row) { $ids[] = $row->wid; @@ -213,17 +237,18 @@ class DBLogTestCase extends DrupalWebTestCase { // Login the admin user. $this->drupalLogin($this->big_user); - // Delete user. + // Delete the user created at the start of this test. // We need to POST here to invoke batch_process() in the internal browser. $this->drupalPost('user/' . $user->uid . '/cancel', array('user_cancel_method' => 'user_cancel_reassign'), t('Cancel account')); - // View the dblog report. + // View the database log report. $this->drupalGet('admin/reports/dblog'); $this->assertResponse(200); - // Verify events were recorded. + // Verify that the expected events were recorded. // Add user. - // Default display includes name and email address; if too long then email is replaced by three periods. + // Default display includes name and email address; if too long, the email + // address is replaced by three periods. $this->assertLogMessage(t('New user: %name (%email).', array('%name' => $name, '%email' => $user->mail)), t('DBLog event was recorded: [add user]')); // Login user. $this->assertLogMessage(t('Session opened for %name.', array('%name' => $name)), t('DBLog event was recorded: [login user]')); @@ -232,7 +257,7 @@ class DBLogTestCase extends DrupalWebTestCase { // Delete user. $message = t('Deleted user: %name %email.', array('%name' => $name, '%email' => '<' . $user->mail . '>')); $message_text = truncate_utf8(filter_xss($message, array()), 56, TRUE, TRUE); - // Verify full message on details page. + // Verify that the full message displays on the details page. $link = FALSE; if ($links = $this->xpath('//a[text()="' . html_entity_decode($message_text) . '"]')) { // Found link with the message text. @@ -253,7 +278,7 @@ class DBLogTestCase extends DrupalWebTestCase { $not_found_url = $this->randomName(60); $this->drupalGet($not_found_url); $this->assertResponse(404); - // View dblog page-not-found report page. + // View the database log page-not-found report page. $this->drupalGet('admin/reports/page-not-found'); $this->assertResponse(200); // Check that full-length URL displayed. @@ -261,9 +286,10 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Generate and verify node events. + * Generates and then verifies some node events. * - * @param string $type Content type. + * @param string $type + * A node type (e.g., 'article', 'page' or 'poll'). */ private function doNode($type) { // Create user. @@ -272,61 +298,65 @@ class DBLogTestCase extends DrupalWebTestCase { // Login user. $this->drupalLogin($user); - // Create node using form to generate add content event (which is not triggered by drupalCreateNode). + // Create a node using the form in order to generate an add content event + // (which is not triggered by drupalCreateNode). $edit = $this->getContent($type); $langcode = LANGUAGE_NONE; $title = $edit["title"]; $this->drupalPost('node/add/' . $type, $edit, t('Save')); $this->assertResponse(200); - // Retrieve node object. + // Retrieve the node object. $node = $this->drupalGetNodeByTitle($title); $this->assertTrue($node != NULL, t('Node @title was loaded', array('@title' => $title))); - // Edit node. + // Edit the node. $edit = $this->getContentUpdate($type); $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); $this->assertResponse(200); - // Delete node. + // Delete the node. $this->drupalPost('node/' . $node->nid . '/delete', array(), t('Delete')); $this->assertResponse(200); - // View node (to generate page not found event). + // View the node (to generate page not found event). $this->drupalGet('node/' . $node->nid); $this->assertResponse(404); - // View the dblog report (to generate access denied event). + // View the database log report (to generate access denied event). $this->drupalGet('admin/reports/dblog'); $this->assertResponse(403); // Login the admin user. $this->drupalLogin($this->big_user); - // View the dblog report. + // View the database log report. $this->drupalGet('admin/reports/dblog'); $this->assertResponse(200); - // Verify events were recorded. - // Content added. + // Verify that node events were recorded. + // Was node content added? $this->assertLogMessage(t('@type: added %title.', array('@type' => $type, '%title' => $title)), t('DBLog event was recorded: [content added]')); - // Content updated. + // Was node content updated? $this->assertLogMessage(t('@type: updated %title.', array('@type' => $type, '%title' => $title)), t('DBLog event was recorded: [content updated]')); - // Content deleted. + // Was node content deleted? $this->assertLogMessage(t('@type: deleted %title.', array('@type' => $type, '%title' => $title)), t('DBLog event was recorded: [content deleted]')); - // View dblog access-denied report node. + // View the database log access-denied report page. $this->drupalGet('admin/reports/access-denied'); $this->assertResponse(200); - // Access denied. + // Verify that the 'access denied' event was recorded. $this->assertText(t('admin/reports/dblog'), t('DBLog event was recorded: [access denied]')); - // View dblog page-not-found report node. + // View the database log page-not-found report page. $this->drupalGet('admin/reports/page-not-found'); $this->assertResponse(200); - // Page not found. + // Verify that the 'page not found' event was recorded. $this->assertText(t('node/@nid', array('@nid' => $node->nid)), t('DBLog event was recorded: [page not found]')); } /** - * Create content based on content type. + * Creates random content based on node content type. * - * @param string $type Content type. - * @return array Content. + * @param string $type + * Node content type (e.g., 'article'). + * + * @return array + * Random content needed by various node types. */ private function getContent($type) { $langcode = LANGUAGE_NONE; @@ -350,10 +380,13 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Create content update based on content type. + * Creates random content as an update based on node content type. + * + * @param string $type + * Node content type (e.g., 'article'). * - * @param string $type Content type. - * @return array Content. + * @return array + * Random content needed by various node types. */ private function getContentUpdate($type) { switch ($type) { @@ -375,11 +408,14 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Login an admin user, create dblog event, and test clearing dblog functionality through the admin interface. + * Tests the addition and clearing of log events through the admin interface. + * + * Logs in the admin user, creates a database log event, and tests the + * functionality of clearing the database log through the admin interface. */ protected function testDBLogAddAndClear() { global $base_root; - // Get a count of how many watchdog entries there are. + // Get a count of how many watchdog entries already exist. $count = db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(); $log = array( 'type' => 'custom', @@ -396,27 +432,27 @@ class DBLogTestCase extends DrupalWebTestCase { ); // Add a watchdog entry. dblog_watchdog($log); - // Make sure the table count has actually incremented. + // Make sure the table count has actually been incremented. $this->assertEqual($count + 1, db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(), t('dblog_watchdog() added an entry to the dblog :count', array(':count' => $count))); // Login the admin user. $this->drupalLogin($this->big_user); - // Now post to clear the db table. + // Post in order to clear the database table. $this->drupalPost('admin/reports/dblog', array(), t('Clear log messages')); - // Count rows in watchdog that previously related to the deleted user. + // Count the rows in watchdog that previously related to the deleted user. $count = db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(); $this->assertEqual($count, 0, t('DBLog contains :count records after a clear.', array(':count' => $count))); } /** - * Test the dblog filter on admin/reports/dblog. + * Tests the database log filter functionality at admin/reports/dblog. */ protected function testFilter() { $this->drupalLogin($this->big_user); - // Clear log to ensure that only generated entries are found. + // Clear the log to ensure that only generated entries will be found. db_delete('watchdog')->execute(); - // Generate watchdog entries. + // Generate 9 random watchdog entries. $type_names = array(); $types = array(); for ($i = 0; $i < 3; $i++) { @@ -432,10 +468,10 @@ class DBLogTestCase extends DrupalWebTestCase { } } - // View the dblog. + // View the database log page. $this->drupalGet('admin/reports/dblog'); - // Confirm all the entries are displayed. + // Confirm that all the entries are displayed. $count = $this->getTypeCount($types); foreach ($types as $key => $type) { $this->assertEqual($count[$key], $type['count'], 'Count matched'); @@ -461,8 +497,8 @@ class DBLogTestCase extends DrupalWebTestCase { $this->assertEqual(array_sum($count), $type_count, 'Count matched'); } - // Set filter to match each of the three type attributes and confirm the - // number of entries displayed. + // Set the filter to match each of the two filter-type attributes and + // confirm the correct number of entries are displayed. foreach ($types as $key => $type) { $edit = array( 'type[]' => array($type['type']), @@ -480,10 +516,14 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Get the log entry information form the page. + * Gets the database log event information from the browser page. * - * @return - * List of entries and their information. + * @return array + * List of log events where each event is an array with following keys: + * - severity: (int) A database log severity constant. + * - type: (string) The type of database log event. + * - message: (string) The message for this database log event. + * - user: (string) The user associated with this database log event. */ protected function getLogEntries() { $entries = array(); @@ -502,11 +542,12 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Get the count of entries per type. + * Gets the count of database log entries by database log event type. * - * @param $types + * @param array $types * The type information to compare against. - * @return + * + * @return array * The count of each type keyed by the key of the $types array. */ protected function getTypeCount(array $types) { @@ -524,11 +565,12 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Get the watchdog severity constant corresponding to the CSS class. + * Gets the watchdog severity constant corresponding to the CSS class. * - * @param $class + * @param string $class * CSS class attribute. - * @return + * + * @return int|null * The watchdog severity constant or NULL if not found. * * @ingroup logging_severity_levels @@ -557,11 +599,12 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Extract the text contained by the element. + * Extracts the text contained by the XHTML element. * - * @param $element + * @param SimpleXMLElement $element * Element to extract text from. - * @return + * + * @return string * Extracted text. */ protected function asText(SimpleXMLElement $element) { @@ -572,21 +615,22 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Assert messages appear on the log overview screen. + * Confirms that a log message appears on the database log overview screen. * - * This function should be used only for admin/reports/dblog page, because it - * check for the message link text truncated to 56 characters. Other dblog - * pages have no detail links so contains a full message text. + * This function should only be used for the admin/reports/dblog page, because + * it checks for the message link text truncated to 56 characters. Other log + * pages have no detail links so they contain the full message text. * - * @param $log_message - * The message to check. - * @param $message + * @param string $log_message + * The database log message to check. + * @param string $message * The message to pass to simpletest. */ protected function assertLogMessage($log_message, $message) { $message_text = truncate_utf8(filter_xss($log_message, array()), 56, TRUE, TRUE); - // After filter_xss() HTML entities should be converted to their characters - // because assertLink() uses this string in xpath() to query DOM. + // After filter_xss(), HTML entities should be converted to their character + // equivalents because assertLink() uses this string in xpath() to query the + // Document Object Model (DOM). $this->assertLink(html_entity_decode($message_text), 0, $message); } } diff --git a/modules/field/field.api.php b/modules/field/field.api.php index 5f641173e..822f537bc 100644 --- a/modules/field/field.api.php +++ b/modules/field/field.api.php @@ -873,7 +873,7 @@ function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langco '#type' => $instance['widget']['type'], '#default_value' => isset($items[$delta]) ? $items[$delta] : '', ); - return $element; + return array('value' => $element); } /** @@ -1735,11 +1735,14 @@ function hook_field_storage_details_alter(&$details, $field) { * loaded. */ function hook_field_storage_load($entity_type, $entities, $age, $fields, $options) { - $field_info = field_info_field_by_ids(); $load_current = $age == FIELD_LOAD_CURRENT; foreach ($fields as $field_id => $ids) { - $field = $field_info[$field_id]; + // By the time this hook runs, the relevant field definitions have been + // populated and cached in FieldInfo, so calling field_info_field_by_id() + // on each field individually is more efficient than loading all fields in + // memory upfront with field_info_field_by_ids(). + $field = field_info_field_by_id($field_id); $field_name = $field['field_name']; $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc index 868d7bd75..dec5c20aa 100644 --- a/modules/field/field.attach.inc +++ b/modules/field/field.attach.inc @@ -283,7 +283,6 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b = 'language' => NULL, ); $options += $default_options; - $field_info = field_info_field_by_ids(); $fields = array(); $grouped_instances = array(); @@ -307,7 +306,7 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b = foreach ($instances as $instance) { $field_id = $instance['field_id']; $field_name = $instance['field_name']; - $field = $field_info[$field_id]; + $field = field_info_field_by_id($field_id); $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; if (function_exists($function)) { // Add the field to the list of fields to invoke the hook on. @@ -614,7 +613,6 @@ function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcod * non-deleted fields are operated on. */ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $options = array()) { - $field_info = field_info_field_by_ids(); $load_current = $age == FIELD_LOAD_CURRENT; // Merge default options. @@ -692,7 +690,7 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $ } // Collect the storage backend if the field has not been loaded yet. if (!isset($skip_fields[$field_id])) { - $field = $field_info[$field_id]; + $field = field_info_field_by_id($field_id); $storages[$field['storage']['type']][$field_id][] = $load_current ? $id : $vid; } } @@ -709,7 +707,7 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $ _field_invoke_multiple('load', $entity_type, $queried_entities, $age, $null, $options); // Invoke hook_field_attach_load(): let other modules act on loading the - // entitiy. + // entity. module_invoke_all('field_attach_load', $entity_type, $queried_entities, $age, $options); // Build cache data. diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc index f9c96c92b..e1acdd53b 100644 --- a/modules/field/field.crud.inc +++ b/modules/field/field.crud.inc @@ -319,7 +319,11 @@ function field_read_field($field_name, $include_additional = array()) { * Reads in fields that match an array of conditions. * * @param array $params - * An array of conditions to match against. + * An array of conditions to match against. Keys are columns from the + * 'field_config' table, values are conditions to match. Additionally, + * conditions on the 'entity_type' and 'bundle' columns from the + * 'field_config_instance' table are supported (select fields having an + * instance on a given bundle). * @param array $include_additional * The default behavior of this function is to not return fields that * are inactive or have been deleted. Setting @@ -337,8 +341,21 @@ function field_read_fields($params = array(), $include_additional = array()) { // Turn the conditions into a query. foreach ($params as $key => $value) { + // Allow filtering on the 'entity_type' and 'bundle' columns of the + // field_config_instance table. + if ($key == 'entity_type' || $key == 'bundle') { + if (empty($fci_join)) { + $fci_join = $query->join('field_config_instance', 'fci', 'fc.id = fci.field_id'); + } + $key = 'fci.' . $key; + } + else { + $key = 'fc.' . $key; + } + $query->condition($key, $value); } + if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) { $query ->condition('fc.active', 1) diff --git a/modules/field/field.info b/modules/field/field.info index f8a331a00..fcdca88e6 100644 --- a/modules/field/field.info +++ b/modules/field/field.info @@ -5,6 +5,7 @@ version = VERSION core = 7.x files[] = field.module files[] = field.attach.inc +files[] = field.info.class.inc files[] = tests/field.test dependencies[] = field_sql_storage required = TRUE diff --git a/modules/field/field.info.class.inc b/modules/field/field.info.class.inc new file mode 100644 index 000000000..de50a1bf0 --- /dev/null +++ b/modules/field/field.info.class.inc @@ -0,0 +1,668 @@ +<?php + +/* + * @file + * Definition of the FieldInfo class. + */ + +/** + * Provides field and instance definitions for the current runtime environment. + * + * A FieldInfo object is created and statically persisted through the request + * by the _field_info_field_cache() function. The object properties act as a + * "static cache" of fields and instances definitions. + * + * The preferred way to access definitions is through the getBundleInstances() + * method, which keeps cache entries per bundle, storing both fields and + * instances for a given bundle. Fields used in multiple bundles are duplicated + * in several cache entries, and are merged into a single list in the memory + * cache. Cache entries are loaded for bundles as a whole, optimizing memory + * and CPU usage for the most common pattern of iterating over all instances of + * a bundle rather than accessing a single instance. + * + * The getFields() and getInstances() methods, which return all existing field + * and instance definitions, are kept mainly for backwards compatibility, and + * should be avoided when possible, since they load and persist in memory a + * potentially large array of information. In many cases, the lightweight + * getFieldMap() method should be preferred. + */ +class FieldInfo { + + /** + * Lightweight map of fields across entity types and bundles. + * + * @var array + */ + protected $fieldMap; + + /** + * List of $field structures keyed by ID. Includes deleted fields. + * + * @var array + */ + protected $fieldsById = array(); + + /** + * Mapping of field names to the ID of the corresponding non-deleted field. + * + * @var array + */ + protected $fieldIdsByName = array(); + + /** + * Whether $fieldsById contains all field definitions or a subset. + * + * @var bool + */ + protected $loadedAllFields = FALSE; + + /** + * Separately tracks requested field names or IDs that do not exist. + * + * @var array + */ + protected $unknownFields = array(); + + /** + * Instance definitions by bundle. + * + * @var array + */ + protected $bundleInstances = array(); + + /** + * Whether $bundleInstances contains all instances definitions or a subset. + * + * @var bool + */ + protected $loadedAllInstances = FALSE; + + /** + * Separately tracks requested bundles that are empty (or do not exist). + * + * @var array + */ + protected $emptyBundles = array(); + + /** + * Extra fields by bundle. + * + * @var array + */ + protected $bundleExtraFields = array(); + + /** + * Clears the "static" and persistent caches. + */ + public function flush() { + $this->fieldMap = NULL; + + $this->fieldsById = array(); + $this->fieldIdsByName = array(); + $this->loadedAllFields = FALSE; + $this->unknownFields = array(); + + $this->bundleInstances = array(); + $this->loadedAllInstances = FALSE; + $this->emptyBundles = array(); + + $this->bundleExtraFields = array(); + + cache_clear_all('field_info:', 'cache_field', TRUE); + } + + /** + * Collects a lightweight map of fields across bundles. + * + * @return + * An array keyed by field name. Each value is an array with two entries: + * - type: The field type. + * - bundles: The bundles in which the field appears, as an array with + * entity types as keys and the array of bundle names as values. + */ + public function getFieldMap() { + // Read from the "static" cache. + if ($this->fieldMap !== NULL) { + return $this->fieldMap; + } + + // Read from persistent cache. + if ($cached = cache_get('field_info:field_map', 'cache_field')) { + $map = $cached->data; + + // Save in "static" cache. + $this->fieldMap = $map; + + return $map; + } + + $map = array(); + + $query = db_query('SELECT fc.type, fci.field_name, fci.entity_type, fci.bundle FROM {field_config_instance} fci INNER JOIN {field_config} fc ON fc.id = fci.field_id WHERE fc.active = 1 AND fc.storage_active = 1 AND fc.deleted = 0 AND fci.deleted = 0'); + foreach ($query as $row) { + $map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle; + $map[$row->field_name]['type'] = $row->type; + } + + // Save in "static" and persistent caches. + $this->fieldMap = $map; + cache_set('field_info:field_map', $map, 'cache_field'); + + return $map; + } + + /** + * Returns all active fields, including deleted ones. + * + * @return + * An array of field definitions, keyed by field ID. + */ + public function getFields() { + // Read from the "static" cache. + if ($this->loadedAllFields) { + return $this->fieldsById; + } + + // Read from persistent cache. + if ($cached = cache_get('field_info:fields', 'cache_field')) { + $this->fieldsById = $cached->data; + } + else { + // Collect and prepare fields. + foreach (field_read_fields(array(), array('include_deleted' => TRUE)) as $field) { + $this->fieldsById[$field['id']] = $this->prepareField($field); + } + + // Store in persistent cache. + cache_set('field_info:fields', $this->fieldsById, 'cache_field'); + } + + // Fill the name/ID map. + foreach ($this->fieldsById as $field) { + if (!$field['deleted']) { + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + } + + $this->loadedAllFields = TRUE; + + return $this->fieldsById; + } + + /** + * Retrieves all active, non-deleted instances definitions. + * + * @param $entity_type + * (optional) The entity type. + * + * @return + * If $entity_type is not set, all instances keyed by entity type and bundle + * name. If $entity_type is set, all instances for that entity type, keyed + * by bundle name. + */ + public function getInstances($entity_type = NULL) { + // If the full list is not present in "static" cache yet. + if (!$this->loadedAllInstances) { + + // Read from persistent cache. + if ($cached = cache_get('field_info:instances', 'cache_field')) { + $this->bundleInstances = $cached->data; + } + else { + // Collect and prepare instances. + + // We also need to populate the static field cache, since it will not + // be set by subsequent getBundleInstances() calls. + $this->getFields(); + + // Initialize empty arrays for all existing entity types and bundles. + // This is not strictly needed, but is done to preserve the behavior of + // field_info_instances() before http://drupal.org/node/1880666. + foreach (field_info_bundles() as $existing_entity_type => $bundles) { + foreach ($bundles as $bundle => $bundle_info) { + $this->bundleInstances[$existing_entity_type][$bundle] = array(); + } + } + + foreach (field_read_instances() as $instance) { + $field = $this->getField($instance['field_name']); + $instance = $this->prepareInstance($instance, $field['type']); + $this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance; + } + + // Store in persistent cache. + cache_set('field_info:instances', $this->bundleInstances, 'cache_field'); + } + + $this->loadedAllInstances = TRUE; + } + + if (isset($entity_type)) { + return isset($this->bundleInstances[$entity_type]) ? $this->bundleInstances[$entity_type] : array(); + } + else { + return $this->bundleInstances; + } + } + + /** + * Returns a field definition from a field name. + * + * This method only retrieves active, non-deleted fields. + * + * @param $field_name + * The field name. + * + * @return + * The field definition, or NULL if no field was found. + */ + public function getField($field_name) { + // Read from the "static" cache. + if (isset($this->fieldIdsByName[$field_name])) { + $field_id = $this->fieldIdsByName[$field_name]; + return $this->fieldsById[$field_id]; + } + if (isset($this->unknownFields[$field_name])) { + return; + } + + // Do not check the (large) persistent cache, but read the definition. + + // Cache miss: read from definition. + if ($field = field_read_field($field_name)) { + $field = $this->prepareField($field); + + // Save in the "static" cache. + $this->fieldsById[$field['id']] = $field; + $this->fieldIdsByName[$field['field_name']] = $field['id']; + + return $field; + } + else { + $this->unknownFields[$field_name] = TRUE; + } + } + + /** + * Returns a field definition from a field ID. + * + * This method only retrieves active fields, deleted or not. + * + * @param $field_id + * The field ID. + * + * @return + * The field definition, or NULL if no field was found. + */ + public function getFieldById($field_id) { + // Read from the "static" cache. + if (isset($this->fieldsById[$field_id])) { + return $this->fieldsById[$field_id]; + } + if (isset($this->unknownFields[$field_id])) { + return; + } + + // No persistent cache, fields are only persistently cached as part of a + // bundle. + + // Cache miss: read from definition. + if ($fields = field_read_fields(array('id' => $field_id), array('include_deleted' => TRUE))) { + $field = current($fields); + $field = $this->prepareField($field); + + // Store in the static cache. + $this->fieldsById[$field['id']] = $field; + if (!$field['deleted']) { + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + + return $field; + } + else { + $this->unknownFields[$field_id] = TRUE; + } + } + + /** + * Retrieves the instances for a bundle. + * + * The function also populates the corresponding field definitions in the + * "static" cache. + * + * @param $entity_type + * The entity type. + * @param $bundle + * The bundle name. + * + * @return + * The array of instance definitions, keyed by field name. + */ + public function getBundleInstances($entity_type, $bundle) { + // Read from the "static" cache. + if (isset($this->bundleInstances[$entity_type][$bundle])) { + return $this->bundleInstances[$entity_type][$bundle]; + } + if (isset($this->emptyBundles[$entity_type][$bundle])) { + return array(); + } + + // Read from the persistent cache. + if ($cached = cache_get("field_info:bundle:$entity_type:$bundle", 'cache_field')) { + $info = $cached->data; + + // Extract the field definitions and save them in the "static" cache. + foreach ($info['fields'] as $field) { + if (!isset($this->fieldsById[$field['id']])) { + $this->fieldsById[$field['id']] = $field; + if (!$field['deleted']) { + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + } + } + unset($info['fields']); + + // Store the instance definitions in the "static" cache'. Empty (or + // non-existent) bundles are stored separately, so that they do not + // pollute the global list returned by getInstances(). + if ($info['instances']) { + $this->bundleInstances[$entity_type][$bundle] = $info['instances']; + } + else { + $this->emptyBundles[$entity_type][$bundle] = TRUE; + } + + return $info['instances']; + } + + // Cache miss: collect from the definitions. + + $instances = array(); + + // Collect the fields in the bundle. + $params = array('entity_type' => $entity_type, 'bundle' => $bundle); + $fields = field_read_fields($params); + + // This iterates on non-deleted instances, so deleted fields are kept out of + // the persistent caches. + foreach (field_read_instances($params) as $instance) { + $field = $fields[$instance['field_name']]; + + $instance = $this->prepareInstance($instance, $field['type']); + $instances[$field['field_name']] = $instance; + + // If the field is not in our global "static" list yet, add it. + if (!isset($this->fieldsById[$field['id']])) { + $field = $this->prepareField($field); + + $this->fieldsById[$field['id']] = $field; + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + } + + // Store in the 'static' cache'. Empty (or non-existent) bundles are stored + // separately, so that they do not pollute the global list returned by + // getInstances(). + if ($instances) { + $this->bundleInstances[$entity_type][$bundle] = $instances; + } + else { + $this->emptyBundles[$entity_type][$bundle] = TRUE; + } + + // The persistent cache additionally contains the definitions of the fields + // involved in the bundle. + $cache = array( + 'instances' => $instances, + 'fields' => array() + ); + foreach ($instances as $instance) { + $cache['fields'][] = $this->fieldsById[$instance['field_id']]; + } + cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field'); + + return $instances; + } + + /** + * Retrieves the "extra fields" for a bundle. + * + * @param $entity_type + * The entity type. + * @param $bundle + * The bundle name. + * + * @return + * The array of extra fields. + */ + public function getBundleExtraFields($entity_type, $bundle) { + // Read from the "static" cache. + if (isset($this->bundleExtraFields[$entity_type][$bundle])) { + return $this->bundleExtraFields[$entity_type][$bundle]; + } + + // Read from the persistent cache. + if ($cached = cache_get("field_info:bundle_extra:$entity_type:$bundle", 'cache_field')) { + $this->bundleExtraFields[$entity_type][$bundle] = $cached->data; + return $this->bundleExtraFields[$entity_type][$bundle]; + } + + // Cache miss: read from hook_field_extra_fields(). Note: given the current + // shape of the hook, we have no other way than collecting extra fields on + // all bundles. + $info = array(); + $extra = module_invoke_all('field_extra_fields'); + drupal_alter('field_extra_fields', $extra); + // Merge in saved settings. + if (isset($extra[$entity_type][$bundle])) { + $info = $this->prepareExtraFields($extra[$entity_type][$bundle], $entity_type, $bundle); + } + + // Store in the 'static' and persistent caches. + $this->bundleExtraFields[$entity_type][$bundle] = $info; + cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field'); + + return $this->bundleExtraFields[$entity_type][$bundle]; + } + + /** + * Prepares a field definition for the current run-time context. + * + * @param $field + * The raw field structure as read from the database. + * + * @return + * The field definition completed for the current runtime context. + */ + public function prepareField($field) { + // Make sure all expected field settings are present. + $field['settings'] += field_info_field_settings($field['type']); + $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); + + // Add storage details. + $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field); + drupal_alter('field_storage_details', $details, $field); + $field['storage']['details'] = $details; + + // Populate the list of bundles using the field. + $field['bundles'] = array(); + if (!$field['deleted']) { + $map = $this->getFieldMap(); + if (isset($map[$field['field_name']])) { + $field['bundles'] = $map[$field['field_name']]['bundles']; + } + } + + return $field; + } + + /** + * Prepares an instance definition for the current run-time context. + * + * @param $instance + * The raw instance structure as read from the database. + * @param $field_type + * The field type. + * + * @return + * The field instance array completed for the current runtime context. + */ + public function prepareInstance($instance, $field_type) { + // Make sure all expected instance settings are present. + $instance['settings'] += field_info_instance_settings($field_type); + + // Set a default value for the instance. + if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) { + $instance['default_value'] = NULL; + } + + // Prepare widget settings. + $instance['widget'] = $this->prepareInstanceWidget($instance['widget'], $field_type); + + // Prepare display settings. + foreach ($instance['display'] as $view_mode => $display) { + $instance['display'][$view_mode] = $this->prepareInstanceDisplay($display, $field_type); + } + + // Fall back to 'hidden' for view modes configured to use custom display + // settings, and for which the instance has no explicit settings. + $entity_info = entity_get_info($instance['entity_type']); + $view_modes = array_merge(array('default'), array_keys($entity_info['view modes'])); + $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']); + foreach ($view_modes as $view_mode) { + if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) { + if (!isset($instance['display'][$view_mode])) { + $instance['display'][$view_mode] = array( + 'type' => 'hidden', + 'label' => 'above', + 'settings' => array(), + 'weight' => 0, + ); + } + } + } + + return $instance; + } + + /** + * Prepares widget properties for the current run-time context. + * + * @param $widget + * Widget specifications as found in $instance['widget']. + * @param $field_type + * The field type. + * + * @return + * The widget properties completed for the current runtime context. + */ + public function prepareInstanceWidget($widget, $field_type) { + $field_type_info = field_info_field_types($field_type); + + // Fill in default values. + $widget += array( + 'type' => $field_type_info['default_widget'], + 'settings' => array(), + 'weight' => 0, + ); + + $widget_type_info = field_info_widget_types($widget['type']); + // Fall back to default formatter if formatter type is not available. + if (!$widget_type_info) { + $widget['type'] = $field_type_info['default_widget']; + $widget_type_info = field_info_widget_types($widget['type']); + } + $widget['module'] = $widget_type_info['module']; + // Fill in default settings for the widget. + $widget['settings'] += field_info_widget_settings($widget['type']); + + return $widget; + } + + /** + * Adapts display specifications to the current run-time context. + * + * @param $display + * Display specifications as found in $instance['display']['a_view_mode']. + * @param $field_type + * The field type. + * + * @return + * The display properties completed for the current runtime context. + */ + public function prepareInstanceDisplay($display, $field_type) { + $field_type_info = field_info_field_types($field_type); + + // Fill in default values. + $display += array( + 'label' => 'above', + 'type' => $field_type_info['default_formatter'], + 'settings' => array(), + 'weight' => 0, + ); + if ($display['type'] != 'hidden') { + $formatter_type_info = field_info_formatter_types($display['type']); + // Fall back to default formatter if formatter type is not available. + if (!$formatter_type_info) { + $display['type'] = $field_type_info['default_formatter']; + $formatter_type_info = field_info_formatter_types($display['type']); + } + $display['module'] = $formatter_type_info['module']; + // Fill in default settings for the formatter. + $display['settings'] += field_info_formatter_settings($display['type']); + } + + return $display; + } + + /** + * Prepares 'extra fields' for the current run-time context. + * + * @param $extra_fields + * The array of extra fields, as collected in hook_field_extra_fields(). + * @param $entity_type + * The entity type. + * @param $bundle + * The bundle name. + * + * @return + * The list of extra fields completed for the current runtime context. + */ + public function prepareExtraFields($extra_fields, $entity_type, $bundle) { + $entity_type_info = entity_get_info($entity_type); + $bundle_settings = field_bundle_settings($entity_type, $bundle); + $extra_fields += array('form' => array(), 'display' => array()); + + $result = array(); + // Extra fields in forms. + foreach ($extra_fields['form'] as $name => $field_data) { + $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array(); + if (isset($settings['weight'])) { + $field_data['weight'] = $settings['weight']; + } + $result['form'][$name] = $field_data; + } + + // Extra fields in displayed entities. + $data = $extra_fields['display']; + foreach ($extra_fields['display'] as $name => $field_data) { + $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array(); + $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes'])); + foreach ($view_modes as $view_mode) { + if (isset($settings[$view_mode])) { + $field_data['display'][$view_mode] = $settings[$view_mode]; + } + else { + $field_data['display'][$view_mode] = array( + 'weight' => $field_data['weight'], + 'visible' => TRUE, + ); + } + } + unset($field_data['weight']); + $result['display'][$name] = $field_data; + } + + return $result; + } +} diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc index 9e7ab938d..77113957d 100644 --- a/modules/field/field.info.inc +++ b/modules/field/field.info.inc @@ -6,6 +6,32 @@ */ /** + * Retrieves the FieldInfo object for the current request. + * + * @return FieldInfo + * An instance of the FieldInfo class. + */ +function _field_info_field_cache() { + // Use the advanced drupal_static() pattern, since this is called very often. + static $drupal_static_fast; + + if (!isset($drupal_static_fast)) { + $drupal_static_fast['field_info_field_cache'] = &drupal_static(__FUNCTION__); + } + $field_info = &$drupal_static_fast['field_info_field_cache']; + + if (!isset($field_info)) { + // @todo The registry should save the need for an explicit include, but not + // a couple upgrade tests (DisabledNodeTypeTestCase, + // FilterFormatUpgradePathTestCase...) break in a strange way without it. + include_once dirname(__FILE__) . '/field.info.class.inc'; + $field_info = new FieldInfo(); + } + + return $field_info; +} + +/** * @defgroup field_info Field Info API * @{ * Obtain information about Field API configuration. @@ -34,7 +60,50 @@ function field_info_cache_clear() { entity_info_cache_clear(); _field_info_collate_types(TRUE); - _field_info_collate_fields(TRUE); + _field_info_field_cache()->flush(); +} + +/** + * Collates all information on existing fields and instances. + * + * Deprecated. This function is kept to ensure backwards compatibility, but has + * a serious performance impact, and should be absolutely avoided. + * See http://drupal.org/node/1880666. + * + * Use the regular field_info_*() API functions to access the information, or + * field_info_cache_clear() to clear the cached data. + */ +function _field_info_collate_fields($reset = FALSE) { + if ($reset) { + _field_info_field_cache()->flush(); + return; + } + + $cache = _field_info_field_cache(); + + // Collect fields, and build the array of IDs keyed by field_name. + $fields = $cache->getFields(); + $field_ids = array(); + foreach ($fields as $id => $field) { + if (!$field['deleted']) { + $field_ids[$field['field_name']] = $id; + } + } + + // Collect extra fields for all entity types. + $extra_fields = array(); + foreach (field_info_bundles() as $entity_type => $bundles) { + foreach ($bundles as $bundle => $info) { + $extra_fields[$entity_type][$bundle] = $cache->getBundleExtraFields($entity_type, $bundle); + } + } + + return array( + 'fields' => $fields, + 'field_ids' => $field_ids, + 'instances' => $cache->getInstances(), + 'extra_fields' => $extra_fields, + ); } /** @@ -162,281 +231,68 @@ function _field_info_collate_types($reset = FALSE) { } /** - * Collates all information on existing fields and instances. - * - * @param $reset - * If TRUE, clear the cache. The information will be rebuilt from the - * database next time it is needed. Defaults to FALSE. - * - * @return - * If $reset is TRUE, nothing. - * If $reset is FALSE, an array containing the following elements: - * - fields: Array of existing fields, keyed by field ID. This element - * lists deleted and non-deleted fields, but not inactive ones. - * Each field has an additional element, 'bundles', which is an array - * of all non-deleted instances of that field. - * - field_ids: Array of field IDs, keyed by field name. This element - * only lists non-deleted, active fields. - * - instances: Array of existing instances, keyed by entity type, bundle - * name and field name. This element only lists non-deleted instances - * whose field is active. - */ -function _field_info_collate_fields($reset = FALSE) { - static $info; - - if ($reset) { - $info = NULL; - cache_clear_all('field_info_fields', 'cache_field'); - return; - } - - if (!isset($info)) { - if ($cached = cache_get('field_info_fields', 'cache_field')) { - $info = $cached->data; - } - else { - $definitions = array( - 'field_ids' => field_read_fields(array(), array('include_deleted' => 1)), - 'instances' => field_read_instances(), - ); - - // Populate 'fields' with all fields, keyed by ID. - $info['fields'] = array(); - foreach ($definitions['field_ids'] as $key => $field) { - $info['fields'][$key] = $definitions['field_ids'][$key] = _field_info_prepare_field($field); - } - - // Build an array of field IDs for non-deleted fields, keyed by name. - $info['field_ids'] = array(); - foreach ($info['fields'] as $key => $field) { - if (!$field['deleted']) { - $info['field_ids'][$field['field_name']] = $key; - } - } - - // Populate 'instances'. Only non-deleted instances are considered. - $info['instances'] = array(); - foreach (field_info_bundles() as $entity_type => $bundles) { - foreach ($bundles as $bundle => $bundle_info) { - $info['instances'][$entity_type][$bundle] = array(); - } - } - foreach ($definitions['instances'] as $instance) { - $field = $info['fields'][$instance['field_id']]; - $instance = _field_info_prepare_instance($instance, $field); - $info['instances'][$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance; - // Enrich field definitions with the list of bundles where they have - // instances. NOTE: Deleted fields in $info['field_ids'] are not - // enriched because all of their instances are deleted, too, and - // are thus not in $definitions['instances']. - $info['fields'][$instance['field_id']]['bundles'][$instance['entity_type']][] = $instance['bundle']; - } - - // Populate 'extra_fields'. - $extra = module_invoke_all('field_extra_fields'); - drupal_alter('field_extra_fields', $extra); - // Merge in saved settings. - foreach ($extra as $entity_type => $bundles) { - foreach ($bundles as $bundle => $extra_fields) { - $extra_fields = _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle); - $info['extra_fields'][$entity_type][$bundle] = $extra_fields; - } - } - - cache_set('field_info_fields', $info, 'cache_field'); - } - } - - return $info; -} - -/** * Prepares a field definition for the current run-time context. * - * Since the field was last saved or updated, new field settings can be - * expected. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1880666. * - * @param $field - * The raw field structure as read from the database. + * @see FieldInfo::prepareField() */ function _field_info_prepare_field($field) { - // Make sure all expected field settings are present. - $field['settings'] += field_info_field_settings($field['type']); - $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); - - // Add storage details. - $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field); - drupal_alter('field_storage_details', $details, $field, $instance); - $field['storage']['details'] = $details; - - // Initialize the 'bundles' list. - $field['bundles'] = array(); - - return $field; + $cache = _field_info_field_cache(); + return $cache->prepareField($field); } /** * Prepares an instance definition for the current run-time context. * - * Since the instance was last saved or updated, a number of things might have - * changed: widgets or formatters disabled, new settings expected, new view - * modes added... - * - * @param $instance - * The raw instance structure as read from the database. - * @param $field - * The field structure for the instance. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1880666. * - * @return - * Field instance array. + * @see FieldInfo::prepareInstance() */ function _field_info_prepare_instance($instance, $field) { - // Make sure all expected instance settings are present. - $instance['settings'] += field_info_instance_settings($field['type']); - - // Set a default value for the instance. - if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) { - $instance['default_value'] = NULL; - } - - $instance['widget'] = _field_info_prepare_instance_widget($field, $instance['widget']); - - foreach ($instance['display'] as $view_mode => $display) { - $instance['display'][$view_mode] = _field_info_prepare_instance_display($field, $display); - } - - // Fallback to 'hidden' for view modes configured to use custom display - // settings, and for which the instance has no explicit settings. - $entity_info = entity_get_info($instance['entity_type']); - $view_modes = array_merge(array('default'), array_keys($entity_info['view modes'])); - $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']); - foreach ($view_modes as $view_mode) { - if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) { - if (!isset($instance['display'][$view_mode])) { - $instance['display'][$view_mode] = array( - 'type' => 'hidden', - 'label' => 'above', - 'settings' => array(), - 'weight' => 0, - ); - } - } - } - - return $instance; + $cache = _field_info_field_cache(); + return $cache->prepareInstance($instance, $field['type']); } /** * Adapts display specifications to the current run-time context. * - * @param $field - * The field structure for the instance. - * @param $display - * Display specifications as found in - * $instance['display']['some_view_mode']. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1880666. + * + * @see FieldInfo::prepareInstanceDisplay() */ function _field_info_prepare_instance_display($field, $display) { - $field_type = field_info_field_types($field['type']); - - // Fill in default values. - $display += array( - 'label' => 'above', - 'type' => $field_type['default_formatter'], - 'settings' => array(), - 'weight' => 0, - ); - if ($display['type'] != 'hidden') { - $formatter_type = field_info_formatter_types($display['type']); - // Fallback to default formatter if formatter type is not available. - if (!$formatter_type) { - $display['type'] = $field_type['default_formatter']; - $formatter_type = field_info_formatter_types($display['type']); - } - $display['module'] = $formatter_type['module']; - // Fill in default settings for the formatter. - $display['settings'] += field_info_formatter_settings($display['type']); - } - - return $display; + $cache = _field_info_field_cache(); + return $cache->prepareInstanceDisplay($display, $field['type']); } /** * Prepares widget specifications for the current run-time context. * - * @param $field - * The field structure for the instance. - * @param $widget - * Widget specifications as found in $instance['widget']. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1880666. + * + * @see FieldInfo::prepareInstanceWidget() */ function _field_info_prepare_instance_widget($field, $widget) { - $field_type = field_info_field_types($field['type']); - - // Fill in default values. - $widget += array( - 'type' => $field_type['default_widget'], - 'settings' => array(), - 'weight' => 0, - ); - - $widget_type = field_info_widget_types($widget['type']); - // Fallback to default formatter if formatter type is not available. - if (!$widget_type) { - $widget['type'] = $field_type['default_widget']; - $widget_type = field_info_widget_types($widget['type']); - } - $widget['module'] = $widget_type['module']; - // Fill in default settings for the widget. - $widget['settings'] += field_info_widget_settings($widget['type']); - - return $widget; + $cache = _field_info_field_cache(); + return $cache->prepareInstanceWidget($widget, $field['type']); } /** * Prepares 'extra fields' for the current run-time context. * - * @param $extra_fields - * The array of extra fields, as collected in hook_field_extra_fields(). - * @param $entity_type - * The entity type. - * @param $bundle - * The bundle name. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1880666. + * + * @see FieldInfo::prepareExtraFields() */ function _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle) { - $entity_type_info = entity_get_info($entity_type); - $bundle_settings = field_bundle_settings($entity_type, $bundle); - $extra_fields += array('form' => array(), 'display' => array()); - - $result = array(); - // Extra fields in forms. - foreach ($extra_fields['form'] as $name => $field_data) { - $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array(); - if (isset($settings['weight'])) { - $field_data['weight'] = $settings['weight']; - } - $result['form'][$name] = $field_data; - } - - // Extra fields in displayed entities. - $data = $extra_fields['display']; - foreach ($extra_fields['display'] as $name => $field_data) { - $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array(); - $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes'])); - foreach ($view_modes as $view_mode) { - if (isset($settings[$view_mode])) { - $field_data['display'][$view_mode] = $settings[$view_mode]; - } - else { - $field_data['display'][$view_mode] = array( - 'weight' => $field_data['weight'], - 'visible' => TRUE, - ); - } - } - unset($field_data['weight']); - $result['display'][$name] = $field_data; - } - - return $result; + $cache = _field_info_field_cache(); + return $cache->prepareExtraFields($extra_fields, $entity_type, $bundle); } /** @@ -584,21 +440,50 @@ function field_info_bundles($entity_type = NULL) { } /** + * Returns a lightweight map of fields across bundles. + * + * The function only returns active, non deleted fields. + * + * @return + * An array keyed by field name. Each value is an array with two entries: + * - type: The field type. + * - bundles: The bundles in which the field appears, as an array with entity + * types as keys and the array of bundle names as values. + */ +function field_info_field_map() { + $cache = _field_info_field_cache(); + return $cache->getFieldMap(); +} + +/** * Returns all field definitions. * + * Use of this function should be avoided when possible, since it loads and + * statically caches a potentially large array of information. Use + * field_info_field_map() instead. + * + * When iterating over the fields present in a given bundle after a call to + * field_info_instances($entity_type, $bundle), it is recommended to use + * field_info_field() on each individual field instead. + * * @return * An array of field definitions, keyed by field name. Each field has an * additional property, 'bundles', which is an array of all the bundles to * which this field belongs keyed by entity type. + * + * @see field_info_field_map() */ function field_info_fields() { + $cache = _field_info_field_cache(); + $info = $cache->getFields(); + $fields = array(); - $info = _field_info_collate_fields(); - foreach ($info['fields'] as $key => $field) { + foreach ($info as $key => $field) { if (!$field['deleted']) { $fields[$field['field_name']] = $field; } } + return $fields; } @@ -620,10 +505,8 @@ function field_info_fields() { * @see field_info_field_by_id() */ function field_info_field($field_name) { - $info = _field_info_collate_fields(); - if (isset($info['field_ids'][$field_name])) { - return $info['fields'][$info['field_ids'][$field_name]]; - } + $cache = _field_info_field_cache(); + return $cache->getField($field_name); } /** @@ -641,17 +524,19 @@ function field_info_field($field_name) { * @see field_info_field() */ function field_info_field_by_id($field_id) { - $info = _field_info_collate_fields(); - if (isset($info['fields'][$field_id])) { - return $info['fields'][$field_id]; - } + $cache = _field_info_field_cache(); + return $cache->getFieldById($field_id); } /** * Returns the same data as field_info_field_by_id() for every field. * - * This function is typically used when handling all fields of some entities - * to avoid thousands of calls to field_info_field_by_id(). + * Use of this function should be avoided when possible, since it loads and + * statically caches a potentially large array of information. + * + * When iterating over the fields present in a given bundle after a call to + * field_info_instances($entity_type, $bundle), it is recommended to use + * field_info_field() on each individual field instead. * * @return * An array, each key is a field ID and the values are field arrays as @@ -662,41 +547,57 @@ function field_info_field_by_id($field_id) { * @see field_info_field_by_id() */ function field_info_field_by_ids() { - $info = _field_info_collate_fields(); - return $info['fields']; + $cache = _field_info_field_cache(); + return $cache->getFields(); } /** * Retrieves information about field instances. * + * Use of this function to retrieve instances across separate bundles (i.e. + * when the $bundle parameter is NULL) should be avoided when possible, since + * it loads and statically caches a potentially large array of information. Use + * field_info_field_map() instead. + * + * When retrieving the instances of a specific bundle (i.e. when both + * $entity_type and $bundle_name are provided), the function also populates a + * static cache with the corresponding field definitions, allowing fast + * retrieval of field_info_field() later in the request. + * * @param $entity_type - * The entity type for which to return instances. + * (optional) The entity type for which to return instances. * @param $bundle_name - * The bundle name for which to return instances. + * (optional) The bundle name for which to return instances. If $entity_type + * is NULL, the $bundle_name parameter is ignored. * * @return * If $entity_type is not set, return all instances keyed by entity type and * bundle name. If $entity_type is set, return all instances for that entity * type, keyed by bundle name. If $entity_type and $bundle_name are set, return * all instances for that bundle. + * + * @see field_info_field_map() */ function field_info_instances($entity_type = NULL, $bundle_name = NULL) { - $info = _field_info_collate_fields(); + $cache = _field_info_field_cache(); - if (isset($entity_type) && isset($bundle_name)) { - return isset($info['instances'][$entity_type][$bundle_name]) ? $info['instances'][$entity_type][$bundle_name] : array(); + if (!isset($entity_type)) { + return $cache->getInstances(); } - elseif (isset($entity_type)) { - return isset($info['instances'][$entity_type]) ? $info['instances'][$entity_type] : array(); - } - else { - return $info['instances']; + if (!isset($bundle_name)) { + return $cache->getInstances($entity_type); } + + return $cache->getBundleInstances($entity_type, $bundle_name); } /** * Returns an array of instance data for a specific field and bundle. * + * The function populates a static cache with all fields and instances used in + * the bundle, allowing fast retrieval of field_info_field() or + * field_info_instance() later in the request. + * * @param $entity_type * The entity type for the instance. * @param $field_name @@ -709,9 +610,10 @@ function field_info_instances($entity_type = NULL, $bundle_name = NULL) { * NULL if the instance does not exist. */ function field_info_instance($entity_type, $field_name, $bundle_name) { - $info = _field_info_collate_fields(); - if (isset($info['instances'][$entity_type][$bundle_name][$field_name])) { - return $info['instances'][$entity_type][$bundle_name][$field_name]; + $cache = _field_info_field_cache(); + $info = $cache->getBundleInstances($entity_type, $bundle_name); + if (isset($info[$field_name])) { + return $info[$field_name]; } } @@ -769,11 +671,10 @@ function field_info_instance($entity_type, $field_name, $bundle_name) { * The array of pseudo-field elements in the bundle. */ function field_info_extra_fields($entity_type, $bundle, $context) { - $info = _field_info_collate_fields(); - if (isset($info['extra_fields'][$entity_type][$bundle][$context])) { - return $info['extra_fields'][$entity_type][$bundle][$context]; - } - return array(); + $cache = _field_info_field_cache(); + $info = $cache->getBundleExtraFields($entity_type, $bundle); + + return isset($info[$context]) ? $info[$context] : array(); } /** diff --git a/modules/field/field.install b/modules/field/field.install index 34d28073d..a4b153481 100644 --- a/modules/field/field.install +++ b/modules/field/field.install @@ -460,5 +460,12 @@ function field_update_7002() { } /** + * Add the FieldInfo class to the class registry. + */ +function field_update_7003() { + // Empty update to force a rebuild of the registry. +} + +/** * @} End of "addtogroup updates-7.x-extra". */ diff --git a/modules/field/field.module b/modules/field/field.module index b6cf05c9b..4331cdf42 100644 --- a/modules/field/field.module +++ b/modules/field/field.module @@ -873,7 +873,8 @@ function field_view_field($entity_type, $entity, $field_name, $display = array() if ($field = field_info_field($field_name)) { if (is_array($display)) { // When using custom display settings, fill in default values. - $display = _field_info_prepare_instance_display($field, $display); + $cache = _field_info_field_cache(); + $display = $cache->prepareInstanceDisplay($display, $field["type"]); } // Hook invocations are done through the _field_invoke() functions in @@ -1197,7 +1198,7 @@ function _element_validate_integer($element, &$form_state) { * Use element_validate_integer_positive() instead. * * @deprecated - * @see element_validate_number_positive() + * @see element_validate_integer_positive() */ function _element_validate_integer_positive($element, &$form_state) { element_validate_integer_positive($element, $form_state); diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module index a75619427..c639f38f2 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.module +++ b/modules/field/modules/field_sql_storage/field_sql_storage.module @@ -324,11 +324,14 @@ function field_sql_storage_field_storage_delete_field($field) { * Implements hook_field_storage_load(). */ function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fields, $options) { - $field_info = field_info_field_by_ids(); $load_current = $age == FIELD_LOAD_CURRENT; foreach ($fields as $field_id => $ids) { - $field = $field_info[$field_id]; + // By the time this hook runs, the relevant field definitions have been + // populated and cached in FieldInfo, so calling field_info_field_by_id() + // on each field individually is more efficient than loading all fields in + // memory upfront with field_info_field_by_ids(). + $field = field_info_field_by_id($field_id); $field_name = $field['field_name']; $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test index 8004178eb..9cd8535b5 100644 --- a/modules/field/tests/field.test +++ b/modules/field/tests/field.test @@ -1144,6 +1144,16 @@ class FieldInfoTestCase extends FieldTestCase { $this->assertIdentical($instances, $expected, "field_info_instances('user') returns " . var_export($expected, TRUE) . '.'); $instances = field_info_instances('user', 'user'); $this->assertIdentical($instances, array(), "field_info_instances('user', 'user') returns an empty array."); + + // Test that querying for invalid entity types does not add entries in the + // list returned by field_info_instances(). + field_info_cache_clear(); + field_info_instances('invalid_entity', 'invalid_bundle'); + // Simulate new request by clearing static caches. + drupal_static_reset(); + field_info_instances('invalid_entity', 'invalid_bundle'); + $instances = field_info_instances(); + $this->assertFalse(isset($instances['invalid_entity']), 'field_info_instances() does not contain entries for the invalid entity type that was queried before'); } /** @@ -1254,6 +1264,80 @@ class FieldInfoTestCase extends FieldTestCase { } /** + * Test field_info_field_map(). + */ + function testFieldMap() { + // We will overlook fields created by the 'standard' install profile. + $exclude = field_info_field_map(); + + // Create a new bundle for 'test_entity' entity type. + field_test_create_bundle('test_bundle_2'); + + // Create a couple fields. + $fields = array( + array( + 'field_name' => 'field_1', + 'type' => 'test_field', + ), + array( + 'field_name' => 'field_2', + 'type' => 'hidden_test_field', + ), + ); + foreach ($fields as $field) { + field_create_field($field); + } + + // Create a couple instances. + $instances = array( + array( + 'field_name' => 'field_1', + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ), + array( + 'field_name' => 'field_1', + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle_2', + ), + array( + 'field_name' => 'field_2', + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ), + array( + 'field_name' => 'field_2', + 'entity_type' => 'test_cacheable_entity', + 'bundle' => 'test_bundle', + ), + ); + foreach ($instances as $instance) { + field_create_instance($instance); + } + + $expected = array( + 'field_1' => array( + 'type' => 'test_field', + 'bundles' => array( + 'test_entity' => array('test_bundle', 'test_bundle_2'), + ), + ), + 'field_2' => array( + 'type' => 'hidden_test_field', + 'bundles' => array( + 'test_entity' => array('test_bundle'), + 'test_cacheable_entity' => array('test_bundle'), + ), + ), + ); + + // Check that the field map is correct. + $map = field_info_field_map(); + $map = array_diff_key($map, $exclude); + $this->assertEqual($map, $expected); + } + + /** * Test that the field_info settings convenience functions work. */ function testSettingsInfo() { @@ -1277,6 +1361,31 @@ class FieldInfoTestCase extends FieldTestCase { $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings"); } } + + /** + * Tests that the field info cache can be built correctly. + */ + function testFieldInfoCache() { + // Create a test field and ensure it's in the array returned by + // field_info_fields(). + $field_name = drupal_strtolower($this->randomName()); + $field = array( + 'field_name' => $field_name, + 'type' => 'test_field', + ); + field_create_field($field); + $fields = field_info_fields(); + $this->assertTrue(isset($fields[$field_name]), 'The test field is initially found in the array returned by field_info_fields().'); + + // Now rebuild the field info cache, and set a variable which will cause + // the cache to be cleared while it's being rebuilt; see + // field_test_entity_info(). Ensure the test field is still in the returned + // array. + field_info_cache_clear(); + variable_set('field_test_clear_info_cache_in_hook_entity_info', TRUE); + $fields = field_info_fields(); + $this->assertTrue(isset($fields[$field_name]), 'The test field is found in the array returned by field_info_fields() even if its cache is cleared while being rebuilt.'); + } } class FieldFormTestCase extends FieldTestCase { @@ -2179,6 +2288,41 @@ class FieldCrudTestCase extends FieldTestCase { } /** + * Tests reading field definitions. + */ + function testReadFields() { + $field_definition = array( + 'field_name' => 'field_1', + 'type' => 'test_field', + ); + field_create_field($field_definition); + + // Check that 'single column' criteria works. + $fields = field_read_fields(array('field_name' => $field_definition['field_name'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + + // Check that 'multi column' criteria works. + $fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => $field_definition['type'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + $fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => 'foo')); + $this->assertTrue(empty($fields), 'No field was found.'); + + // Create an instance of the field. + $instance_definition = array( + 'field_name' => $field_definition['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ); + field_create_instance($instance_definition); + + // Check that criteria spanning over the field_config_instance table work. + $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'bundle' => $instance_definition['bundle'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'field_name' => $instance_definition['field_name'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + } + + /** * Test creation of indexes on data column. */ function testFieldIndexes() { diff --git a/modules/field/tests/field_test.entity.inc b/modules/field/tests/field_test.entity.inc index 95af3eeba..c6686ebc2 100644 --- a/modules/field/tests/field_test.entity.inc +++ b/modules/field/tests/field_test.entity.inc @@ -9,6 +9,12 @@ * Implements hook_entity_info(). */ function field_test_entity_info() { + // If requested, clear the field cache while this hook is being called. See + // testFieldInfoCache(). + if (variable_get('field_test_clear_info_cache_in_hook_entity_info', FALSE)) { + field_info_cache_clear(); + } + $bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle'))); $test_entity_modes = array( 'full' => array( diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc index 44770acb9..43bb20276 100644 --- a/modules/field_ui/field_ui.admin.inc +++ b/modules/field_ui/field_ui.admin.inc @@ -1534,7 +1534,7 @@ function field_ui_existing_field_options($entity_type, $bundle) { // - locked fields, // - fields already in the current bundle, // - fields that cannot be added to the entity type, - // - fields that that shoud not be added via user interface. + // - fields that should not be added via user interface. if (empty($field['locked']) && !field_info_instance($entity_type, $field['field_name'], $bundle) @@ -1544,7 +1544,7 @@ function field_ui_existing_field_options($entity_type, $bundle) { 'type' => $field['type'], 'type_label' => $field_types[$field['type']]['label'], 'field' => $field['field_name'], - 'label' => t($instance['label']), + 'label' => $instance['label'], 'widget_type' => $instance['widget']['type'], ); } diff --git a/modules/field_ui/field_ui.module b/modules/field_ui/field_ui.module index 93cbcccc7..5f8bc45ef 100644 --- a/modules/field_ui/field_ui.module +++ b/modules/field_ui/field_ui.module @@ -332,23 +332,30 @@ function _field_ui_bundle_admin_path($entity_type, $bundle_name) { * Identifies inactive fields within a bundle. */ function field_ui_inactive_instances($entity_type, $bundle_name = NULL) { - if (!empty($bundle_name)) { - $inactive = array($bundle_name => array()); - $params = array('bundle' => $bundle_name); + $params = array('entity_type' => $entity_type); + + if (empty($bundle_name)) { + $active = field_info_instances($entity_type); + $inactive = array(); } else { - $inactive = array(); - $params = array(); + // Restrict to the specified bundle. For consistency with the case where + // $bundle_name is NULL, the $active and $inactive arrays are keyed by + // bundle name first. + $params['bundle'] = $bundle_name; + $active = array($bundle_name => field_info_instances($entity_type, $bundle_name)); + $inactive = array($bundle_name => array()); } - $params['entity_type'] = $entity_type; - $active_instances = field_info_instances($entity_type); + // Iterate on existing definitions, and spot those that do not appear in the + // $active list collected earlier. $all_instances = field_read_instances($params, array('include_inactive' => TRUE)); foreach ($all_instances as $instance) { - if (!isset($active_instances[$instance['bundle']][$instance['field_name']])) { + if (!isset($active[$instance['bundle']][$instance['field_name']])) { $inactive[$instance['bundle']][$instance['field_name']] = $instance; } } + if (!empty($bundle_name)) { return $inactive[$bundle_name]; } diff --git a/modules/field_ui/field_ui.test b/modules/field_ui/field_ui.test index d0a822a82..b67b70e2a 100644 --- a/modules/field_ui/field_ui.test +++ b/modules/field_ui/field_ui.test @@ -269,7 +269,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase { */ function assertFieldSettings($bundle, $field_name, $string = 'dummy test string', $entity_type = 'node') { // Reset the fields info. - _field_info_collate_fields(TRUE); + field_info_cache_clear(); // Assert field settings. $field = field_info_field($field_name); $this->assertTrue($field['settings']['test_field_setting'] == $string, t('Field settings were found.')); @@ -360,7 +360,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase { $this->fieldUIDeleteField($bundle_path1, $this->field_name, $this->field_label, $this->type); // Reset the fields info. - _field_info_collate_fields(TRUE); + field_info_cache_clear(); // Check that the field instance was deleted. $this->assertNull(field_info_instance('node', $this->field_name, $this->type), t('Field instance was deleted.')); // Check that the field was not deleted @@ -370,7 +370,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase { $this->fieldUIDeleteField($bundle_path2, $this->field_name, $this->field_label, $type_name2); // Reset the fields info. - _field_info_collate_fields(TRUE); + field_info_cache_clear(); // Check that the field instance was deleted. $this->assertNull(field_info_instance('node', $this->field_name, $type_name2), t('Field instance was deleted.')); // Check that the field was deleted too. diff --git a/modules/image/image.admin.inc b/modules/image/image.admin.inc index ab99a49e8..9f0fab254 100644 --- a/modules/image/image.admin.inc +++ b/modules/image/image.admin.inc @@ -326,7 +326,7 @@ function image_style_delete_form_submit($form, &$form_state) { /** * Confirmation form to revert a database style to its default. */ -function image_style_revert_form($form, $form_state, $style) { +function image_style_revert_form($form, &$form_state, $style) { $form_state['image_style'] = $style; return confirm_form( diff --git a/modules/image/image.install b/modules/image/image.install index b7aac7152..1d7bd4ef4 100644 --- a/modules/image/image.install +++ b/modules/image/image.install @@ -391,7 +391,8 @@ function image_update_7002(array &$sandbox) { } // Process the table at the top of the list. - $table = reset(array_keys($sandbox['tables'])); + $keys = array_keys($sandbox['tables']); + $table = reset($keys); $sandbox['processed'] += _image_update_7002_populate_dimensions($table, $sandbox['tables'][$table], $sandbox['last_fid']); // Has the table been fully processed? diff --git a/modules/image/image.module b/modules/image/image.module index d7178ad7d..258f12f82 100644 --- a/modules/image/image.module +++ b/modules/image/image.module @@ -254,7 +254,7 @@ function image_form_system_file_system_settings_alter(&$form, &$form_state) { } /** - * Submit handler for the file system settings form. + * Form submission handler for system_file_system_settings(). * * Adds a menu rebuild after the public file path has been changed, so that the * menu router item depending on that file path will be regenerated. @@ -312,9 +312,9 @@ function image_file_download($uri) { return -1; } - // Private file access for the original files. Note that we only - // check access for non-temporary images, since file.module will - // grant access for all temporary files. + // Private file access for the original files. Note that we only check access + // for non-temporary images, since file.module will grant access for all + // temporary files. $files = file_load_multiple(array(), array('uri' => $uri)); if (count($files)) { $file = reset($files); @@ -537,7 +537,7 @@ function image_field_update_instance($instance, $prior_instance) { } /** - * Clear cached versions of a specific file in all styles. + * Clears cached versions of a specific file in all styles. * * @param $path * The Drupal file path to the original image. @@ -553,7 +553,7 @@ function image_path_flush($path) { } /** - * Get an array of all styles and their settings. + * Gets an array of all styles and their settings. * * @return * An array of styles keyed by the image style ID (isid). @@ -614,7 +614,9 @@ function image_styles() { } /** - * Load a style by style name or ID. May be used as a loader for menu items. + * Loads a style by style name or ID. + * + * May be used as a loader for menu items. * * @param $name * The name of the style. @@ -623,6 +625,7 @@ function image_styles() { * @param $include * If set, this loader will restrict to a specific type of image style, may be * one of the defined Image style storage constants. + * * @return * An image style array containing the following keys: * - "isid": The unique image style ID. @@ -660,12 +663,20 @@ function image_style_load($name = NULL, $isid = NULL, $include = NULL) { } /** - * Save an image style. + * Saves an image style. * - * @param style - * An image style array. - * @return - * An image style array. In the case of a new style, 'isid' will be populated. + * @param array $style + * An image style array containing: + * - name: A unique name for the style. + * - isid: (optional) An image style ID. + * + * @return array + * An image style array containing: + * - name: An unique name for the style. + * - old_name: The original name for the style. + * - isid: An image style ID. + * - is_new: TRUE if this is a new style, and FALSE if it is an existing + * style. */ function image_style_save($style) { if (isset($style['isid']) && is_numeric($style['isid'])) { @@ -692,13 +703,14 @@ function image_style_save($style) { } /** - * Delete an image style. + * Deletes an image style. * * @param $style * An image style array. * @param $replacement_style_name * (optional) When deleting a style, specify a replacement style name so * that existing settings (if any) may be converted to a new style. + * * @return * TRUE on success. */ @@ -717,14 +729,17 @@ function image_style_delete($style, $replacement_style_name = '') { } /** - * Load all the effects for an image style. + * Loads all the effects for an image style. * - * @param $style - * An image style array. - * @return + * @param array $style + * An image style array containing: + * - isid: The unique image style ID that contains this image effect. + * + * @return array * An array of image effects associated with specified image style in the * format array('isid' => array()), or an empty array if the specified style * has no effects. + * @see image_effects() */ function image_style_effects($style) { $effects = image_effects(); @@ -739,10 +754,11 @@ function image_style_effects($style) { } /** - * Get an array of image styles suitable for using as select list options. + * Gets an array of image styles suitable for using as select list options. * * @param $include_empty * If TRUE a <none> option will be inserted in the options array. + * * @return * Array of image styles both key and value are set to style name. */ @@ -763,7 +779,7 @@ function image_style_options($include_empty = TRUE) { } /** - * Menu callback; Given a style and image path, generate a derivative. + * Page callback: Generates a derivative, given a style and image path. * * After generating an image, transfer it to the requesting agent. * @@ -928,7 +944,7 @@ function image_style_transform_dimensions($style_name, array &$dimensions) { } /** - * Flush cached media for a style. + * Flushes cached media for a style. * * @param $style * An image style array. @@ -960,12 +976,13 @@ function image_style_flush($style) { } /** - * Return the URL for an image derivative given a style and image path. + * Returns the URL for an image derivative given a style and image path. * * @param $style_name * The name of the style to be used with this image. * @param $path * The path to the image. + * * @return * The absolute URL where a style image can be downloaded, suitable for use * in an <img> tag. Requesting the URL will cause the image to be created. @@ -1014,7 +1031,7 @@ function image_style_path_token($style_name, $uri) { } /** - * Return the URI of an image when using a style. + * Returns the URI of an image when using a style. * * The path returned by this function may not exist. The default generation * method only creates images when they are requested by a user's browser. @@ -1023,6 +1040,7 @@ function image_style_path_token($style_name, $uri) { * The name of the style to be used with this image. * @param $uri * The URI or path to the image. + * * @return * The URI to an image style image. * @see image_style_url() @@ -1040,10 +1058,11 @@ function image_style_path($style_name, $uri) { } /** - * Save a default image style to the database. + * Saves a default image style to the database. * * @param style * An image style array provided by a module. + * * @return * An image style array. The returned style array will include the new 'isid' * assigned to the style. @@ -1061,7 +1080,7 @@ function image_default_style_save($style) { } /** - * Revert the changes made by users to a default image style. + * Reverts the changes made by users to a default image style. * * @param style * An image style array. @@ -1078,7 +1097,10 @@ function image_default_style_revert($style) { } /** - * Pull in image effects exposed by modules implementing hook_image_effect_info(). + * Returns a set of image effects. + * + * These image effects are exposed by modules implementing + * hook_image_effect_info(). * * @return * An array of image effects to be used when transforming images. @@ -1120,7 +1142,7 @@ function image_effect_definitions() { } /** - * Load the definition for an image effect. + * Loads the definition for an image effect. * * The effect definition is a set of core properties for an image effect, not * containing any user-settings. The definition defines various functions to @@ -1132,6 +1154,7 @@ function image_effect_definitions() { * The name of the effect definition to load. * @param $style * An image style array to which this effect will be added. + * * @return * An array containing the image effect definition with the following keys: * - "effect": The unique name for the effect being performed. Usually prefixed @@ -1159,7 +1182,7 @@ function image_effect_definition_load($effect, $style_name = NULL) { } /** - * Load all image effects from the database. + * Loads all image effects from the database. * * @return * An array of all image effects. @@ -1191,7 +1214,7 @@ function image_effects() { } /** - * Load a single image effect. + * Loads a single image effect. * * @param $ieid * The image effect ID. @@ -1200,6 +1223,7 @@ function image_effects() { * @param $include * If set, this loader will restrict to a specific type of image style, may be * one of the defined Image style storage constants. + * * @return * An image effect array, consisting of the following keys: * - "ieid": The unique image effect ID. @@ -1221,10 +1245,11 @@ function image_effect_load($ieid, $style_name, $include = NULL) { } /** - * Save an image effect. + * Saves an image effect. * * @param $effect * An image effect array. + * * @return * An image effect array. In the case of a new effect, 'ieid' will be set. */ @@ -1241,7 +1266,7 @@ function image_effect_save($effect) { } /** - * Delete an image effect. + * Deletes an image effect. * * @param $effect * An image effect array. @@ -1253,12 +1278,13 @@ function image_effect_delete($effect) { } /** - * Given an image object and effect, perform the effect on the file. + * Applies an image effect to the image object. * * @param $image * An image object returned by image_load(). * @param $effect * An image effect array. + * * @return * TRUE on success. FALSE if unable to perform the image effect on the image. */ @@ -1309,7 +1335,7 @@ function theme_image_style($variables) { } /** - * Accept a keyword (center, top, left, etc) and return it as a pixel offset. + * Accepts a keyword (center, top, left, etc) and returns it as a pixel offset. * * @param $value * @param $current_pixels diff --git a/modules/locale/locale.admin.inc b/modules/locale/locale.admin.inc index 253535751..f1a71ddfe 100644 --- a/modules/locale/locale.admin.inc +++ b/modules/locale/locale.admin.inc @@ -388,13 +388,13 @@ function locale_languages_edit_form_validate($form, &$form_state) { form_set_error('prefix', t('Domain and path prefix values should not be set at the same time.')); } if (!empty($form_state['values']['domain']) && $duplicate = db_query("SELECT language FROM {languages} WHERE domain = :domain AND language <> :language", array(':domain' => $form_state['values']['domain'], ':language' => $form_state['values']['langcode']))->fetchField()) { - form_set_error('domain', t('The domain (%domain) is already tied to a language (%language).', array('%domain' => $form_state['values']['domain'], '%language' => $duplicate->language))); + form_set_error('domain', t('The domain (%domain) is already tied to a language (%language).', array('%domain' => $form_state['values']['domain'], '%language' => $duplicate))); } if (empty($form_state['values']['prefix']) && language_default('language') != $form_state['values']['langcode'] && empty($form_state['values']['domain'])) { form_set_error('prefix', t('Only the default language can have both the domain and prefix empty.')); } if (!empty($form_state['values']['prefix']) && $duplicate = db_query("SELECT language FROM {languages} WHERE prefix = :prefix AND language <> :language", array(':prefix' => $form_state['values']['prefix'], ':language' => $form_state['values']['langcode']))->fetchField()) { - form_set_error('prefix', t('The prefix (%prefix) is already tied to a language (%language).', array('%prefix' => $form_state['values']['prefix'], '%language' => $duplicate->language))); + form_set_error('prefix', t('The prefix (%prefix) is already tied to a language (%language).', array('%prefix' => $form_state['values']['prefix'], '%language' => $duplicate))); } } diff --git a/modules/locale/locale.module b/modules/locale/locale.module index 94e7cd151..b60c53120 100644 --- a/modules/locale/locale.module +++ b/modules/locale/locale.module @@ -386,20 +386,53 @@ function locale_form_node_form_alter(&$form, &$form_state) { /** * Form submit handler for node_form(). * - * Checks if Locale is registered as a translation handler and handle possible - * node language changes. - * * This submit handler needs to run before entity_form_submit_build_entity() * is invoked by node_form_submit_build_node(), because it alters the values of * attached fields. Therefore, it cannot be a hook_node_submit() implementation. */ function locale_field_node_form_submit($form, &$form_state) { - if (field_has_translation_handler('node', 'locale')) { - $node = (object) $form_state['values']; - $current_language = entity_language('node', $node); - list(, , $bundle) = entity_extract_ids('node', $node); + locale_field_entity_form_submit('node', $form, $form_state); +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function locale_form_comment_form_alter(&$form, &$form_state, $form_id) { + // If a content type has multilingual support we set the content language as + // comment language. + if ($form['language']['#value'] == LANGUAGE_NONE && locale_multilingual_node_type($form['#node']->type)) { + global $language_content; + $form['language']['#value'] = $language_content->language; + $submit_callback = 'locale_field_comment_form_submit'; + array_unshift($form['actions']['preview']['#submit'], $submit_callback); + array_unshift($form['#submit'], $submit_callback); + } +} - foreach (field_info_instances('node', $bundle) as $instance) { +/** + * Form submit handler for comment_form(). + * + * This submit handler needs to run before entity_form_submit_build_entity() + * is invoked by comment_form_submit_build_comment(), because it alters the + * values of attached fields. + */ +function locale_field_comment_form_submit($form, &$form_state) { + locale_field_entity_form_submit('comment', $form, $form_state); +} + +/** + * Handles field language on submit for the given entity type. + * + * Checks if Locale is registered as a translation handler and handle possible + * language changes. + */ +function locale_field_entity_form_submit($entity_type, $form, &$form_state ) { + if (field_has_translation_handler($entity_type, 'locale')) { + $entity = (object) $form_state['values']; + $current_language = entity_language($entity_type, $entity); + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + + foreach (field_info_instances($entity_type, $bundle) as $instance) { $field_name = $instance['field_name']; $field = field_info_field($field_name); $previous_language = $form[$field_name]['#language']; @@ -407,7 +440,7 @@ function locale_field_node_form_submit($form, &$form_state) { // Handle a possible language change: new language values are inserted, // previous ones are deleted. if ($field['translatable'] && $previous_language != $current_language) { - $form_state['values'][$field_name][$current_language] = $node->{$field_name}[$previous_language]; + $form_state['values'][$field_name][$current_language] = $entity->{$field_name}[$previous_language]; $form_state['values'][$field_name][$previous_language] = array(); } } @@ -491,6 +524,9 @@ function locale_field_language_fallback(&$display_language, $entity, $langcode) */ function locale_entity_info_alter(&$entity_info) { $entity_info['node']['translation']['locale'] = TRUE; + if (isset($entity_info['comment'])) { + $entity_info['comment']['translation']['locale'] = TRUE; + } } /** @@ -1060,15 +1096,3 @@ function locale_url_outbound_alter(&$path, &$options, $original_path) { } } } - -/** - * Implements hook_form_FORM_ID_alter(). - */ -function locale_form_comment_form_alter(&$form, &$form_state, $form_id) { - // If a content type has multilingual support we set the content language as - // comment language. - if ($form['language']['#value'] == LANGUAGE_NONE && locale_multilingual_node_type($form['#node']->type)) { - global $language_content; - $form['language']['#value'] = $language_content->language; - } -} diff --git a/modules/locale/locale.test b/modules/locale/locale.test index 632506e13..d88634e19 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -2758,7 +2758,7 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { parent::setUp('locale', 'locale_test'); // Create and login user. - $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'administer content types', 'create article content')); + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'administer content types', 'administer comments', 'create article content')); $this->drupalLogin($admin_user); // Add language. @@ -2787,6 +2787,12 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { // French no matter what path prefix the URLs have. $edit = array('language' => 'fr'); $this->drupalPost("user/{$admin_user->uid}/edit", $edit, t('Save')); + + // Make comment body translatable. + $field = field_info_field('comment_body'); + $field['translatable'] = TRUE; + field_update_field($field); + $this->assertTrue(field_is_translatable('comment', $field), 'Comment body is translatable.'); } /** @@ -2817,22 +2823,46 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { foreach (language_list() as $langcode => $language) { // Post a comment with content language $langcode. $prefix = empty($language->prefix) ? '' : $language->prefix . '/'; - $edit = array("comment_body[$language_none][0][value]" => $this->randomName()); - $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Save')); + $comment_values[$node_langcode][$langcode] = $this->randomName(); + // Initially field form widgets have no language. + $edit = array( + 'subject' => $this->randomName(), + "comment_body[$language_none][0][value]" => $comment_values[$node_langcode][$langcode], + ); + $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Preview')); + // After the first submit the submitted entity language is taken into + // account. + $edit = array( + 'subject' => $edit['subject'], + "comment_body[$langcode][0][value]" => $comment_values[$node_langcode][$langcode], + ); + $this->drupalPost(NULL, $edit, t('Save')); // Check that comment language matches the current content language. - $comment = db_select('comment', 'c') - ->fields('c') + $cid = db_select('comment', 'c') + ->fields('c', array('cid')) ->condition('nid', $node->nid) ->orderBy('cid', 'DESC') + ->range(0, 1) ->execute() - ->fetchObject(); + ->fetchField(); + $comment = comment_load($cid); $comment_langcode = entity_language('comment', $comment); $args = array('%node_language' => $node_langcode, '%comment_language' => $comment_langcode, '%langcode' => $langcode); $this->assertEqual($comment_langcode, $langcode, t('The comment posted with content language %langcode and belonging to the node with language %node_language has language %comment_language', $args)); + $this->assertEqual($comment->comment_body[$langcode][0]['value'], $comment_values[$node_langcode][$langcode], 'Comment body correctly stored.'); + } + } + + // Check that comment bodies appear in the administration UI. + $this->drupalGet('admin/content/comment'); + foreach ($comment_values as $node_values) { + foreach ($node_values as $value) { + $this->assertRaw($value); } } } + } /** diff --git a/modules/menu/menu.api.php b/modules/menu/menu.api.php index 3f3818e17..22d93ef3e 100644 --- a/modules/menu/menu.api.php +++ b/modules/menu/menu.api.php @@ -11,7 +11,7 @@ */ /** - * Informs modules that a custom menu was created. + * Respond to a custom menu creation. * * This hook is used to notify modules that a custom menu has been created. * Contributed modules may use the information to perform actions based on the @@ -34,7 +34,7 @@ function hook_menu_insert($menu) { } /** - * Informs modules that a custom menu was updated. + * Respond to a custom menu update. * * This hook is used to notify modules that a custom menu has been updated. * Contributed modules may use the information to perform actions based on the @@ -59,14 +59,14 @@ function hook_menu_update($menu) { } /** - * Informs modules that a custom menu was deleted. + * Respond to a custom menu deletion. * * This hook is used to notify modules that a custom menu along with all links * contained in it (if any) has been deleted. Contributed modules may use the * information to perform actions based on the information entered into the menu * system. * - * @param $link + * @param $menu * An array representing a custom menu: * - menu_name: The unique name of the custom menu. * - title: The human readable menu title. diff --git a/modules/node/content_types.inc b/modules/node/content_types.inc index 72adc3491..4b722ee02 100644 --- a/modules/node/content_types.inc +++ b/modules/node/content_types.inc @@ -2,7 +2,7 @@ /** * @file - * Content type editing UI. + * Content type editing user interface. */ /** @@ -388,8 +388,7 @@ function node_node_type_update($info) { } /** - * Resets all of the relevant fields of a module-defined node type to their - * default values. + * Resets relevant fields of a module-defined node type to their default values. * * @param $type * The node type to reset. The node type is passed back by reference with its @@ -410,6 +409,8 @@ function node_type_reset($type) { /** * Menu callback; delete a single content type. + * + * @ingroup forms */ function node_type_delete_confirm($form, &$form_state, $type) { $form['type'] = array('#type' => 'value', '#value' => $type->type); @@ -430,6 +431,8 @@ function node_type_delete_confirm($form, &$form_state, $type) { /** * Process content type delete confirm submissions. + * + * @see node_type_delete_confirm() */ function node_type_delete_confirm_submit($form, &$form_state) { node_type_delete($form_state['values']['type']); diff --git a/modules/node/node.admin.inc b/modules/node/node.admin.inc index 1508bc054..be09b37cc 100644 --- a/modules/node/node.admin.inc +++ b/modules/node/node.admin.inc @@ -7,6 +7,10 @@ /** * Menu callback: confirm rebuilding of permissions. + * + * @see node_configure_rebuild_confirm_submit() + * @see node_menu() + * @ingroup forms */ function node_configure_rebuild_confirm() { return confirm_form(array(), t('Are you sure you want to rebuild the permissions on site content?'), @@ -15,6 +19,8 @@ function node_configure_rebuild_confirm() { /** * Handler for wipe confirmation + * + * @see node_configure_rebuild_confirm() */ function node_configure_rebuild_confirm_submit($form, &$form_state) { node_access_rebuild(TRUE); @@ -66,6 +72,9 @@ function node_node_operations() { /** * List node administration filters that can be applied. + * + * @return + * An associative array of filters. */ function node_filters() { // Regular filters @@ -110,7 +119,7 @@ function node_filters() { } /** - * Apply filters for node administration filters based on session. + * Applies filters for node administration filters based on session. * * @param $query * A SelectQuery to which the filters should be applied. @@ -133,7 +142,16 @@ function node_build_filter_query(SelectQueryInterface $query) { } /** - * Return form for node administration filters. + * Returns the node administration filters form array to node_admin_content(). + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() + * + * @ingroup forms */ function node_filter_form() { $session = isset($_SESSION['node_overview_filter']) ? $_SESSION['node_overview_filter'] : array(); @@ -208,7 +226,15 @@ function node_filter_form() { } /** - * Process result from node administration filter form. + * Form submission handler for node_filter_form(). + * + * @see node_admin_content() + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() */ function node_filter_form_submit($form, &$form_state) { $filters = node_filters(); @@ -240,15 +266,15 @@ function node_filter_form_submit($form, &$form_state) { * Make mass update of nodes, changing all nodes in the $nodes array * to update them with the field values in $updates. * - * IMPORTANT NOTE: This function is intended to work when called - * from a form submit handler. Calling it outside of the form submission - * process may not work correctly. + * IMPORTANT NOTE: This function is intended to work when called from a form + * submission handler. Calling it outside of the form submission process may not + * work correctly. * * @param array $nodes * Array of node nids to update. * @param array $updates - * Array of key/value pairs with node field names and the - * value to update that field to. + * Array of key/value pairs with node field names and the value to update that + * field to. */ function node_mass_update($nodes, $updates) { // We use batch processing to prevent timeout when updating a large number @@ -279,7 +305,17 @@ function node_mass_update($nodes, $updates) { } /** - * Node Mass Update - helper function. + * Updates individual nodes when fewer than 10 are queued. + * + * @param $nid + * ID of node to update. + * @param $updates + * Associative array of updates. + * + * @return object + * An updated node object. + * + * @see node_mass_update() */ function _node_mass_update_helper($nid, $updates) { $node = node_load($nid, NULL, TRUE); @@ -293,7 +329,14 @@ function _node_mass_update_helper($nid, $updates) { } /** - * Node Mass Update Batch operation + * Executes a batch operation for node_mass_update(). + * + * @param array $nodes + * An array of node IDs. + * @param array $updates + * Associative array of updates. + * @param array $context + * An array of contextual key/values. */ function _node_mass_update_batch_process($nodes, $updates, &$context) { if (!isset($context['sandbox']['progress'])) { @@ -324,7 +367,15 @@ function _node_mass_update_batch_process($nodes, $updates, &$context) { } /** - * Node Mass Update Batch 'finished' callback. + * Menu callback: Reports the status of batch operation for node_mass_update(). + * + * @param bool $success + * A boolean indicating whether the batch mass update operation successfully + * concluded. + * @param int $results + * The number of nodes updated via the batch mode process. + * @param array $operations + * An array of function calls (not used in this function). */ function _node_mass_update_batch_finished($success, $results, $operations) { if ($success) { @@ -339,7 +390,17 @@ function _node_mass_update_batch_finished($success, $results, $operations) { } /** - * Menu callback: content administration. + * Page callback: Form constructor for the content administration form. + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_menu() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() + * @ingroup forms */ function node_admin_content($form, $form_state) { if (isset($form_state['values']['operation']) && $form_state['values']['operation'] == 'delete') { @@ -354,6 +415,15 @@ function node_admin_content($form, $form_state) { /** * Form builder: Builds the node administration overview. + * + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() + * + * @ingroup forms */ function node_admin_nodes() { $admin_access = user_access('administer nodes'); @@ -525,8 +595,15 @@ function node_admin_nodes() { /** * Validate node_admin_nodes form submissions. * - * Check if any nodes have been selected to perform the chosen - * 'Update option' on. + * Checks whether any nodes have been selected to perform the chosen 'Update + * option' on. + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() */ function node_admin_nodes_validate($form, &$form_state) { // Error if there are no items to select. @@ -538,7 +615,14 @@ function node_admin_nodes_validate($form, &$form_state) { /** * Process node_admin_nodes form submissions. * - * Execute the chosen 'Update option' on the selected nodes. + * Executes the chosen 'Update option' on the selected nodes. + * + * @see node_admin_nodes() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() */ function node_admin_nodes_submit($form, &$form_state) { $operations = module_invoke_all('node_operations'); @@ -564,6 +648,17 @@ function node_admin_nodes_submit($form, &$form_state) { } } +/** + * Multiple node deletion confirmation form for node_admin_content(). + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm_submit() + * @ingroup forms + */ function node_multiple_delete_confirm($form, &$form_state, $nodes) { $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE); // array_filter returns only elements with TRUE values @@ -587,6 +682,16 @@ function node_multiple_delete_confirm($form, &$form_state, $nodes) { t('Delete'), t('Cancel')); } +/** + * Form submission handler for node_multiple_delete_confirm(). + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + */ function node_multiple_delete_confirm_submit($form, &$form_state) { if ($form_state['values']['confirm']) { node_delete_multiple(array_keys($form_state['values']['nodes'])); diff --git a/modules/node/node.api.php b/modules/node/node.api.php index 052effc9b..f8dcfdeff 100644 --- a/modules/node/node.api.php +++ b/modules/node/node.api.php @@ -11,8 +11,8 @@ * Functions to define and modify content types. * * Each content type is maintained by a primary module, which is either - * node.module (for content types created in the user interface) or the - * module that implements hook_node_info() to define the content type. + * node.module (for content types created in the user interface) or the module + * that implements hook_node_info() to define the content type. * * During node operations (create, update, view, delete, etc.), there are * several sets of hooks that get invoked to allow modules to modify the base @@ -22,10 +22,10 @@ * function prefix. For example, poll.module defines the base for the Poll * content type as "poll", so during creation of a poll node, hook_insert() is * only invoked by calling poll_insert(). - * - All-module hooks: This set of hooks is invoked on all implementing - * modules, to allow other modules to modify what the primary node module is - * doing. For example, hook_node_insert() is invoked on all modules when - * creating a poll node. + * - All-module hooks: This set of hooks is invoked on all implementing modules, + * to allow other modules to modify what the primary node module is doing. For + * example, hook_node_insert() is invoked on all modules when creating a poll + * node. * - Field hooks: Hooks related to the fields attached to the node. These are * invoked from the field operations functions described below, and can be * either field-type-specific or all-module hooks. @@ -56,16 +56,15 @@ * - hook_entity_update() (all) * - hook_node_access_records() (all) * - hook_node_access_records_alter() (all) - * - Loading a node (calling node_load(), node_load_multiple(), or - * entity_load() with $entity_type of 'node'): + * - Loading a node (calling node_load(), node_load_multiple() or entity_load() + * with $entity_type of 'node'): * - Node and revision information is read from database. * - hook_load() (node-type-specific) * - field_attach_load_revision() and field_attach_load() * - hook_entity_load() (all) * - hook_node_load() (all) * - Viewing a single node (calling node_view() - note that the input to - * node_view() is a loaded node, so the Loading steps above are already - * done): + * node_view() is a loaded node, so the Loading steps above are already done): * - hook_view() (node-type-specific) * - field_attach_prepare_view() * - hook_entity_prepare_view() (all) @@ -97,9 +96,8 @@ * - Revision information is deleted from database * - hook_node_revision_delete() (all) * - field_attach_delete_revision() - * - Preparing a node for editing (calling node_form() - note that if it's - * an existing node, it will already be loaded; see the Loading section - * above): + * - Preparing a node for editing (calling node_form() - note that if it is an + * existing node, it will already be loaded; see the Loading section above): * - hook_prepare() (node-type-specific) * - hook_node_prepare() (all) * - hook_form() (node-type-specific) @@ -137,16 +135,16 @@ * associated with permission to view, edit, and delete individual nodes. * * The realms and grant IDs can be arbitrarily defined by your node access - * module; it is common to use role IDs as grant IDs, but that is not - * required. Your module could instead maintain its own list of users, where - * each list has an ID. In that case, the return value of this hook would be - * an array of the list IDs that this user is a member of. + * module; it is common to use role IDs as grant IDs, but that is not required. + * Your module could instead maintain its own list of users, where each list has + * an ID. In that case, the return value of this hook would be an array of the + * list IDs that this 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. Note that the system - * makes no distinction between published and unpublished nodes. It is the - * module's responsibility to provide appropriate realms to limit access to - * unpublished content. + * A node access module may implement as many realms as necessary to properly + * define the access privileges for the nodes. Note that the system makes no + * distinction between published and unpublished nodes. It is the module's + * responsibility to provide appropriate realms to limit access to unpublished + * content. * * Node access records are stored in the {node_access} table and define which * grants are required to access a node. There is a special case for the view @@ -183,7 +181,7 @@ * @param $account * The user object whose grants are requested. * @param $op - * The node operation to be performed, such as "view", "update", or "delete". + * The node operation to be performed, such as 'view', 'update', or 'delete'. * * @return * An array whose keys are "realms" of grants, and whose values are arrays of @@ -264,6 +262,7 @@ function hook_node_grants($account, $op) { * @return * An array of grants as defined above. * + * @see hook_node_access_records_alter() * @ingroup node_access */ function hook_node_access_records($node) { @@ -350,12 +349,11 @@ function hook_node_access_records_alter(&$grants, $node) { * Alter user access rules when trying to view, edit or delete a node. * * Node access modules establish rules for user access to content. - * hook_node_grants() defines permissions for a user to view, edit or - * delete nodes by building a $grants array that indicates the permissions - * assigned to the user by each node access module. This hook is called to allow - * modules to modify the $grants array by reference, so the interaction of - * multiple node access modules can be altered or advanced business logic can be - * applied. + * hook_node_grants() defines permissions for a user to view, edit or delete + * nodes by building a $grants array that indicates the permissions assigned to + * the user by each node access module. This hook is called to allow modules to + * modify the $grants array by reference, so the interaction of multiple node + * access modules can be altered or advanced business logic can be applied. * * @see hook_node_grants() * @@ -374,8 +372,8 @@ function hook_node_access_records_alter(&$grants, $node) { * @param $op * The operation being performed, 'view', 'update' or 'delete'. * - * Developers may use this hook to either add additional grants to a user - * or to remove existing grants. These rules are typically based on either the + * Developers may use this hook to either add additional grants to a user or to + * remove existing grants. These rules are typically based on either the * permissions assigned to a user role, or specific attributes of a user * account. * @@ -412,10 +410,10 @@ function hook_node_grants_alter(&$grants, $account, $op) { * @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 + * - 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 + * - 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() { @@ -528,11 +526,10 @@ function hook_node_insert($node) { /** * Act on arbitrary nodes being loaded from the database. * - * This hook should be used to add information that is not in the node or - * node revisions table, not to replace information that is in these tables - * (which could interfere with the entity cache). For performance reasons, - * information for all available nodes should be loaded in a single query where - * possible. + * This hook should be used to add information that is not in the node or node + * revisions table, not to replace information that is in these tables (which + * could interfere with the entity cache). For performance reasons, information + * for all available nodes should be loaded in a single query where possible. * * This hook is invoked during node loading, which is handled by entity_load(), * via classes NodeController and DrupalDefaultEntityController. After the node @@ -572,15 +569,15 @@ function hook_node_load($nodes, $types) { * Modules may implement this hook if they want to have a say in whether or not * a given user has access to perform a given operation on a node. * - * The administrative account (user ID #1) always passes any access check, - * so this hook is not called in that case. Users with the "bypass node access" + * The administrative account (user ID #1) always passes any access check, so + * this hook is not called in that case. Users with the "bypass node access" * permission may always view and edit content through the administrative * interface. * - * Note that not all modules will want to influence access on all - * node types. If your module does not want to actively grant or - * block access, return NODE_ACCESS_IGNORE or simply return nothing. - * Blindly returning FALSE will break other node access modules. + * Note that not all modules will want to influence access on all node types. If + * your module does not want to actively grant or block access, return + * NODE_ACCESS_IGNORE or simply return nothing. Blindly returning FALSE will + * break other node access modules. * * Also note that this function isn't called for node listings (e.g., RSS feeds, * the default home page at path 'node', a recent content block, etc.) See @@ -651,17 +648,17 @@ function hook_node_prepare($node) { /** * Act on a node being displayed as a search result. * - * This hook is invoked from node_search_execute(), after node_load() - * and node_view() have been called. + * This hook is invoked from node_search_execute(), after node_load() and + * node_view() have been called. * * @param $node * The node being displayed in a search result. * * @return array * Extra information to be displayed with search result. This information - * should be presented as an associative array. It will be concatenated - * with the post information (last updated, author) in the default search - * result theming. + * should be presented as an associative array. It will be concatenated with + * the post information (last updated, author) in the default search result + * theming. * * @see template_preprocess_search_result() * @see search-result.tpl.php @@ -724,8 +721,8 @@ function hook_node_update($node) { /** * Act on a node being indexed for searching. * - * This hook is invoked during search indexing, after node_load(), and after - * the result of node_view() is added as $node->rendered to the node object. + * This hook is invoked during search indexing, after node_load(), and after the + * result of node_view() is added as $node->rendered to the node object. * * @param $node * The node being indexed. @@ -756,8 +753,8 @@ function hook_node_update_index($node) { * * Note: Changes made to the $node object within your hook implementation will * have no effect. The preferred method to change a node's content is to use - * hook_node_presave() instead. If it is really necessary to change - * the node at the validate stage, you can use form_set_value(). + * hook_node_presave() instead. If it is really necessary to change the node at + * the validate stage, you can use form_set_value(). * * @param $node * The node being validated. @@ -874,8 +871,8 @@ function hook_node_view_alter(&$build) { * * This hook allows a module to define one or more of its own node types. For * example, the blog module uses it to define a blog node-type named "Blog - * entry." The name and attributes of each desired node type are specified in - * an array returned by the hook. + * entry." The name and attributes of each desired node type are specified in an + * array returned by the hook. * * 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' @@ -887,22 +884,22 @@ function hook_node_view_alter(&$build) { * 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. - * - "base": the base string used to construct callbacks corresponding to - * this node type. - * (i.e. if base 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, but not always. Required. - * - "description": a brief description of the node type. Required. - * - "help": help information shown to the user when creating a node of - * this 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'). - * - "locked": boolean indicating whether the administrator can change the - * machine name of this type. FALSE = changeable (not locked), - * TRUE = unchangeable (locked). Optional (defaults to TRUE). + * - name: (required) The human-readable name of the node type. + * - base: (required) The base string used to construct callbacks + * corresponding to this node type (for example, if base 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, but not + * always. + * - description: (required) A brief description of the node type. + * - help: (optional) Help information shown to the user when creating a node + * of this type. + * - has_title: (optional) A Boolean indicating whether or not this node type + * has a title field. + * - title_label: (optional) The label for the title field of this content + * type. + * - locked: (optional) A Boolean indicating whether the administrator can + * change the machine name of this type. FALSE = changeable (not locked), + * TRUE = unchangeable (locked). * * The machine name of a node type should contain only letters, numbers, and * underscores. Underscores will be converted into hyphens for the purpose of @@ -950,20 +947,20 @@ function hook_node_info() { * corresponding to the internal name of the ranking mechanism, such as * 'recent', or 'comments'. The values should be arrays themselves, with the * following keys available: - * - "title": the human readable name of the ranking mechanism. Required. - * - "join": part of a query string to join to any additional necessary - * table. This is not necessary if the table required is already joined to - * by the base query, such as for the {node} table. Other tables should use - * the full table name as an alias to avoid naming collisions. Optional. - * - "score": part of a query string to calculate the score for the ranking - * mechanism based on values in the database. This does not need to be - * wrapped in parentheses, as it will be done automatically; it also does - * not need to take the weighted system into account, as it will be done - * automatically. It does, however, need to calculate a decimal between + * - title: (required) The human readable name of the ranking mechanism. + * - join: (optional) The part of a query string to join to any additional + * necessary table. This is not necessary if the table required is already + * joined to by the base query, such as for the {node} table. Other tables + * should use the full table name as an alias to avoid naming collisions. + * - score: (required) The part of a query string to calculate the score for + * the ranking mechanism based on values in the database. This does not need + * to be wrapped in parentheses, as it will be done automatically; it also + * does not need to take the weighted system into account, as it will be + * done automatically. It does, however, need to calculate a decimal between * 0 and 1; be careful not to cast the entire score to an integer by - * inadvertently introducing a variable argument. Required. - * - "arguments": if any arguments are required for the score, they can be - * specified in an array here. + * inadvertently introducing a variable argument. + * - arguments: (optional) If any arguments are required for the score, they + * can be specified in an array here. * * @ingroup node_api_hooks */ @@ -990,8 +987,8 @@ function hook_ranking() { /** * Respond to node type creation. * - * This hook is invoked from node_type_save() after the node type is added - * to the database. + * This hook is invoked from node_type_save() after the node type is added to + * the database. * * @param $info * The node type object that is being created. @@ -1003,8 +1000,8 @@ function hook_node_type_insert($info) { /** * Respond to node type updates. * - * This hook is invoked from node_type_save() after the node type is updated - * in the database. + * This hook is invoked from node_type_save() after the node type is updated in + * the database. * * @param $info * The node type object that is being updated. @@ -1258,25 +1255,24 @@ function hook_validate($node, $form, &$form_state) { * This hook is invoked only on the module that defines the node's content type * (use hook_node_view() to act on all node views). * - * This hook is invoked during node viewing after the node is fully loaded, - * so that the node type module can define a custom method for display, or - * add to the default display. + * This hook is invoked during node viewing after the node is fully loaded, so + * that the node type module can define a custom method for display, or add to + * the default display. * * @param $node * The node to be displayed, as returned by node_load(). * @param $view_mode * View mode, e.g. 'full', 'teaser', ... * @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, formatted as in the Form API, in - * $node->content. As with Form API arrays, the #weight property can be - * used to control the relative positions of added elements. After this - * hook is invoked, node_view() calls field_attach_view() to add field - * views to $node->content, and then invokes hook_node_view() and - * hook_node_view_alter(), so if you want to affect the final - * view of the node, you might consider implementing one of these hooks - * instead. + * 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, formatted as in the Form API, in $node->content. As + * with Form API arrays, the #weight property can be used to control the + * relative positions of added elements. After this hook is invoked, + * node_view() calls field_attach_view() to add field views to $node->content, + * and then invokes hook_node_view() and hook_node_view_alter(), so if you + * want to affect the final view of the node, you might consider implementing + * one of these hooks instead. * * @ingroup node_api_hooks */ diff --git a/modules/node/node.install b/modules/node/node.install index 16d3dbaa0..43bfd531f 100644 --- a/modules/node/node.install +++ b/modules/node/node.install @@ -914,6 +914,7 @@ function node_update_7012() { * Change {node}.vid default value from 0 to NULL to avoid deadlock issues on MySQL. */ function node_update_7013() { + db_drop_unique_key('node', 'vid'); db_change_field('node', 'vid', 'vid', array( 'description' => 'The current {node_revision}.vid version identifier.', 'type' => 'int', @@ -921,6 +922,7 @@ function node_update_7013() { 'not null' => FALSE, 'default' => NULL, )); + db_add_unique_key('node', 'vid', array('vid')); } /** diff --git a/modules/node/node.module b/modules/node/node.module index d86c74d2d..abcd4e0c5 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -200,8 +200,8 @@ function node_entity_info() { ), ); - // Search integration is provided by node.module, so search-related - // view modes for nodes are defined here and not in search.module. + // Search integration is provided by node.module, so search-related view modes + // for nodes are defined here and not in search.module. if (module_exists('search')) { $return['node']['view modes'] += array( 'search_index' => array( @@ -244,6 +244,12 @@ function node_field_display_node_alter(&$display, $context) { /** * Entity URI callback. + * + * @param $node + * A node entity. + * + * @return array + * An array with 'path' as the key and the path to the node as its value. */ function node_uri($node) { return array( @@ -296,7 +302,7 @@ function node_title_list($result, $title = NULL) { } /** - * Update the 'last viewed' timestamp of the specified node for current user. + * Updates the 'last viewed' timestamp of the specified node for current user. * * @param $node * A node object. @@ -315,8 +321,14 @@ function node_tag_new($node) { } /** - * Retrieves the timestamp at which the current user last viewed the - * specified node. + * Retrieves the timestamp for the current user's last view of a specified node. + * + * @param $nid + * A node ID. + * + * @return + * If a node has been previously viewed by the user, the timestamp in seconds + * of when the last view occurred; otherwise, zero. */ function node_last_viewed($nid) { global $user; @@ -330,12 +342,13 @@ function node_last_viewed($nid) { } /** - * Decide on the type of marker to be displayed for a given node. + * Determines the type of marker to be displayed for a given node. * * @param $nid * Node ID whose history supplies the "last viewed" timestamp. * @param $timestamp * Time which is compared against node's "last viewed" timestamp. + * * @return * One of the MARK constants. */ @@ -359,7 +372,7 @@ function node_mark($nid, $timestamp) { } /** - * Extract the type name. + * Extracts the type name. * * @param $node * Either a string or object, containing the node type information. @@ -461,6 +474,8 @@ function node_type_get_name($node) { * node_type_save(), and obsolete ones are deleted via a call to * node_type_delete(). See _node_types_build() for an explanation of the new * and obsolete types. + * + * @see _node_types_build() */ function node_types_rebuild() { _node_types_build(TRUE); @@ -483,11 +498,34 @@ function node_type_load($name) { /** * Saves a node type to the database. * - * @param $info - * The node type to save, as an object. - * - * @return - * Status flag indicating outcome of the operation. + * @param object $info + * The node type to save; an object with the following properties: + * - type: A string giving the machine name of the node type. + * - name: A string giving the human-readable name of the node type. + * - base: A string that indicates the base string for hook functions. For + * example, 'node_content' is the value used by the UI when creating a new + * node type. + * - description: A string that describes the node type. + * - help: A string giving the help information shown to the user when + * creating a node of this type. + * - custom: TRUE or FALSE indicating whether this type is defined by a module + * (FALSE) or by a user (TRUE) via Add Content Type. + * - modified: TRUE or FALSE indicating whether this type has been modified by + * an administrator. Currently not used in any way. + * - locked: TRUE or FALSE indicating whether the administrator can change the + * machine name of this type. + * - disabled: TRUE or FALSE indicating whether this type has been disabled. + * - has_title: TRUE or FALSE indicating whether this type uses the node title + * field. + * - title_label: A string containing the label for the title. + * - module: A string giving the module defining this type of node. + * - orig_type: A string giving the original machine-readable name of this + * node type. This may be different from the current type name if the + * 'locked' key is FALSE. + * + * @return int + * A status flag indicating the outcome of the operation, either SAVED_NEW or + * SAVED_UPDATED. */ function node_type_save($info) { $existing_type = !empty($info->old_type) ? $info->old_type : $info->type; @@ -540,7 +578,7 @@ function node_type_save($info) { } /** - * Add default body field to a node type. + * Adds default body field to a node type. * * @param $type * A node type object. @@ -655,6 +693,7 @@ function node_type_update_nodes($old_type, $type) { * * @param $rebuild * TRUE to rebuild node types. Equivalent to calling node_types_rebuild(). + * * @return * An object with two properties: * - names: Associative array of the names of node types, keyed by the type. @@ -761,8 +800,9 @@ function node_type_cache_reset() { * which prevents users from changing the machine name of the type. * * @param $info - * An object or array containing values to override the defaults. See - * hook_node_info() for details on what the array elements mean. + * (optional) An object or array containing values to override the defaults. + * See hook_node_info() for details on what the array elements mean. Defaults + * to an empty array. * * @return * A node type object, with missing values in $info set to their defaults. @@ -845,12 +885,13 @@ function node_rdf_mapping() { } /** - * Determine whether a node hook exists. + * Determines whether a node hook exists. * * @param $node * A node object or a string containing the node type. * @param $hook * A string containing the name of the hook. + * * @return * TRUE if the $hook exists in the node type of $node. */ @@ -860,7 +901,7 @@ function node_hook($node, $hook) { } /** - * Invoke a node hook. + * Invokes a node hook. * * @param $node * A node object or a string containing the node type. @@ -868,6 +909,7 @@ function node_hook($node, $hook) { * A string containing the name of the hook. * @param $a2, $a3, $a4 * Arguments to pass on to the hook, after the $node argument. + * * @return * The returned value of the invoked hook. */ @@ -880,11 +922,11 @@ function node_invoke($node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) { } /** - * Load node entities from the database. + * Loads node entities from the database. * * This function should be used whenever you need to load more than one node - * from the database. Nodes are loaded into memory and will not require - * database access if loaded again during the same page request. + * from the database. Nodes are loaded into memory and will not require database + * access if loaded again during the same page request. * * @see entity_load() * @see EntityFieldQuery @@ -910,7 +952,7 @@ function node_load_multiple($nids = array(), $conditions = array(), $reset = FAL } /** - * Load a node object from the database. + * Loads a node object from the database. * * @param $nid * The node ID. @@ -934,6 +976,9 @@ function node_load($nid = NULL, $vid = NULL, $reset = FALSE) { * * Fills in a few default values, and then invokes hook_prepare() on the node * type module, and hook_node_prepare() on all modules. + * + * @param $node + * A node object. */ function node_object_prepare($node) { // Set up default values, if required. @@ -963,7 +1008,9 @@ function node_object_prepare($node) { } /** - * Perform validation checks on the given node. + * Implements hook_validate(). + * + * Performs validation checks on the given node. */ function node_validate($node, $form, &$form_state) { $type = node_type_get_type($node); @@ -1000,7 +1047,13 @@ function node_validate($node, $form, &$form_state) { } /** - * Prepare node for saving by populating author and creation date. + * Prepares node for saving by populating author and creation date. + * + * @param $node + * A node object. + * + * @return + * An updated node object. */ function node_submit($node) { // A user might assign the node author by entering a user name in the node @@ -1021,7 +1074,7 @@ function node_submit($node) { } /** - * Save changes to a node or add a new node. + * Saves changes to a node or adds a new node. * * @param $node * The $node object to be saved. If $node->nid is @@ -1159,6 +1212,13 @@ function node_save($node) { * Helper function to save a revision with the uid of the current user. * * The resulting revision ID is available afterward in $node->vid. + * + * @param $node + * A node object. + * @param $uid + * The current user's UID. + * @param $update + * (optional) An array of primary keys' field names to update. */ function _node_save_revision($node, $uid, $update = NULL) { $temp_uid = $node->uid; @@ -1174,7 +1234,7 @@ function _node_save_revision($node, $uid, $update = NULL) { } /** - * Delete a node. + * Deletes a node. * * @param $nid * A node ID. @@ -1184,7 +1244,7 @@ function node_delete($nid) { } /** - * Delete multiple nodes. + * Deletes multiple nodes. * * @param $nids * An array of node IDs. @@ -1237,7 +1297,7 @@ function node_delete_multiple($nids) { } /** - * Delete a node revision. + * Deletes a node revision. * * @param $revision_id * The revision ID to delete. @@ -1262,7 +1322,7 @@ function node_revision_delete($revision_id) { } /** - * Generate an array for rendering the given node. + * Generates an array for rendering the given node. * * @param $node * A node object. @@ -1367,8 +1427,8 @@ function node_build_content($node, $view_mode = 'full', $langcode = NULL) { entity_prepare_view('node', array($node->nid => $node), $langcode); $node->content += field_attach_view('node', $node, $view_mode, $langcode); - // Always display a read more link on teasers because we have no way - // to know when a teaser view is different than a full view. + // Always display a read more link on teasers because we have no way to know + // when a teaser view is different than a full view. $links = array(); $node->content['links'] = array( '#theme' => 'links__node', @@ -1400,12 +1460,13 @@ function node_build_content($node, $view_mode = 'full', $langcode = NULL) { } /** - * Generate an array which displays a node detail page. + * Generates an array which displays a node detail page. * * @param $node * A node object. * @param $message * A flag which sets a page title relevant to the revision being viewed. + * * @return * A $page element suitable for use by drupal_render(). */ @@ -1428,6 +1489,9 @@ function node_show($node, $message = FALSE) { * * @param $node * A node object. + * + * @return + * The ID of the node if this is a full page view, otherwise FALSE. */ function node_is_page($node) { $page_node = menu_get_object(); @@ -1435,7 +1499,7 @@ function node_is_page($node) { } /** - * Process variables for node.tpl.php + * Processes variables for node.tpl.php * * Most themes utilize their own copy of node.tpl.php. The default is located * inside "modules/node/node.tpl.php". Look in there for the full list of @@ -1557,7 +1621,7 @@ function node_permission() { } /** - * Gather the rankings from the the hook_ranking implementations. + * Gathers the rankings from the the hook_ranking() implementations. * * @param $query * A query object that has been extended with the Search DB Extender. @@ -1804,6 +1868,7 @@ function node_user_delete($account) { * An associative array containing: * - form: A render element representing the form. * + * @see node_search_admin() * @ingroup themeable */ function theme_node_search_admin($variables) { @@ -1872,11 +1937,11 @@ function _node_revision_access($node, $op = 'view', $account = NULL) { $node_current_revision = node_load($node->nid); $is_current_revision = $node_current_revision->vid == $node->vid; - // There should be at least two revisions. If the vid of the given node - // and the vid of the current revision differ, then we already have two + // There should be at least two revisions. If the vid of the given node and + // the vid of the current revision differ, then we already have two // different revisions so there is no need for a separate database check. - // Also, if you try to revert to or delete the current revision, that's - // not good. + // Also, if you try to revert to or delete the current revision, that's not + // good. if ($is_current_revision && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() == 1 || $op == 'update' || $op == 'delete')) { $access[$cid] = FALSE; } @@ -1884,8 +1949,8 @@ function _node_revision_access($node, $op = 'view', $account = NULL) { $access[$cid] = TRUE; } else { - // First check the access to the current revision and finally, if the - // node passed in is not the current revision then access to that, too. + // First check the access to the current revision and finally, if the node + // passed in is not the current revision then access to that, too. $access[$cid] = node_access($op, $node_current_revision, $account) && ($is_current_revision || node_access($op, $node, $account)); } } @@ -1893,6 +1958,14 @@ function _node_revision_access($node, $op = 'view', $account = NULL) { return $access[$cid]; } +/** + * Access callback: Checks whether the user has permission to add a node. + * + * @return + * TRUE if the user has add permission, otherwise FALSE. + * + * @see node_menu() + */ function _node_add_access() { $types = node_type_get_types(); foreach ($types as $type) { @@ -2110,14 +2183,30 @@ function node_menu_local_tasks_alter(&$data, $router_item, $root_path) { } /** - * Title callback for a node type. + * Title callback: Returns the unsanitized title of the node type edit form. + * + * @param $type + * The node type object. + * + * @return string + * An unsanitized string that is the title of the node type edit form. + * + * @see node_menu() */ function node_type_page_title($type) { return $type->name; } /** - * Title callback. + * Title callback: Returns the title of the node. + * + * @param $node + * The node object. + * + * @return + * An unsanitized string that is the title of the node. + * + * @see node_menu() */ function node_page_title($node) { return $node->title; @@ -2137,7 +2226,13 @@ function node_last_changed($nid) { } /** - * Return a list of all the existing revision numbers. + * Returns a list of all the existing revision numbers. + * + * @param Drupal\node\Node $node + * The node entity. + * + * @return + * An associative array keyed by node revision number. */ function node_revision_list($node) { $revisions = array(); @@ -2223,16 +2318,16 @@ function node_block_save($delta = '', $edit = array()) { * (optional) The maximum number of nodes to find. Defaults to 10. * * @return - * An array of partial node objects or an empty array if there are no recent - * nodes visible to the current user. + * An array of node entities or an empty array if there are no recent nodes + * visible to the current user. */ function node_get_recent($number = 10) { $query = db_select('node', 'n'); if (!user_access('bypass node access')) { - // If the user is able to view their own unpublished nodes, allow them - // to see these in addition to published nodes. Check that they actually - // have some unpublished nodes to view before adding the condition. + // If the user is able to view their own unpublished nodes, allow them to + // see these in addition to published nodes. Check that they actually have + // some unpublished nodes to view before adding the condition. if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT nid FROM {node} WHERE uid = :uid AND status = :status', array(':uid' => $GLOBALS['user']->uid, ':status' => NODE_NOT_PUBLISHED))->fetchCol()) { $query->condition(db_or() ->condition('n.status', NODE_PUBLISHED) @@ -2362,7 +2457,7 @@ function node_form_block_admin_configure_alter(&$form, &$form_state) { } /** - * Form submit handler for block configuration form. + * Form submission handler for node_form_block_admin_configure_alter(). * * @see node_form_block_admin_configure_alter() */ @@ -2394,7 +2489,7 @@ function node_form_block_custom_block_delete_alter(&$form, &$form_state) { } /** - * Form submit handler for custom block delete form. + * Form submission handler for node_form_block_custom_block_delete_alter(). * * @see node_form_block_custom_block_delete_alter() */ @@ -2419,8 +2514,8 @@ function node_modules_uninstalled($modules) { /** * Implements hook_block_list_alter(). * - * Check the content type specific visibilty settings. - * Remove the block if the visibility conditions are not met. + * Check the content type specific visibilty settings. Remove the block if the + * visibility conditions are not met. */ function node_block_list_alter(&$blocks) { global $theme_key; @@ -2485,7 +2580,8 @@ function node_block_list_alter(&$blocks) { * @param $channel * An associative array containing title, link, description and other keys, * to be parsed by format_rss_channel() and format_xml_elements(). - * A list of channel elements can be found at the @link http://cyber.law.harvard.edu/rss/rss.html RSS 2.0 Specification. @endlink + * A list of channel elements can be found at the + * @link http://cyber.law.harvard.edu/rss/rss.html RSS 2.0 Specification. @endlink * The link should be an absolute URL. */ function node_feed($nids = FALSE, $channel = array()) { @@ -2559,7 +2655,7 @@ function node_feed($nids = FALSE, $channel = array()) { } /** - * Construct a drupal_render() style array from an array of loaded nodes. + * Constructs a drupal_render() style array from an array of loaded nodes. * * @param $nodes * An array of nodes as returned by node_load_multiple(). @@ -2568,8 +2664,8 @@ function node_feed($nids = FALSE, $channel = array()) { * @param $weight * An integer representing the weight of the first node in the list. * @param $langcode - * (optional) A language code to use for rendering. Defaults to the global - * content language of the current request. + * (optional) A language code to use for rendering. Defaults to NULL which is + * the global content language of the current request. * * @return * An array in the format expected by drupal_render(). @@ -2588,7 +2684,12 @@ function node_view_multiple($nodes, $view_mode = 'teaser', $weight = 0, $langcod } /** - * Menu callback; Generate a listing of promoted nodes. + * Menu callback: Generates a listing of promoted nodes. + * + * @return array + * An array in the format expected by drupal_render(). + * + * @see node_menu() */ function node_page_default() { $select = db_select('node', 'n') @@ -2638,7 +2739,15 @@ function node_page_default() { } /** - * Menu callback; view a single node. + * Menu callback: Displays a single node. + * + * @param $node + * The node object. + * + * @return + * A page array suitable for use by drupal_render(). + * + * @see node_menu() */ function node_page_view($node) { // If there is a menu link to this node, the link becomes the last part @@ -2667,7 +2776,7 @@ function node_update_index() { } /** - * Index a single node. + * Indexes a single node. * * @param $node * The node to index. @@ -2771,7 +2880,7 @@ function node_form_search_form_alter(&$form, $form_state) { } /** - * Form API callback for the search form. Registered in node_form_alter(). + * Form validation handler for node_form_alter(). */ function node_search_validate($form, &$form_state) { // Initialize using any existing basic search keywords. @@ -2819,8 +2928,8 @@ function node_search_validate($form, &$form_state) { * @{ * The node access system determines who can do what to which nodes. * - * In determining access rights for a node, node_access() first checks - * whether the user has the "bypass node access" permission. Such users have + * In determining access rights for a node, node_access() first checks whether + * the user has the "bypass node access" permission. Such users have * unrestricted access to all nodes. user 1 will always pass this check. * * Next, all implementations of hook_node_access() will be called. Each @@ -2858,8 +2967,7 @@ function node_search_validate($form, &$form_state) { */ /** - * Determine whether the current user may perform the given operation on the - * specified node. + * Determines whether the current user may perform the operation on the node. * * @param $op * The operation to be performed on the node. Possible values are: @@ -2873,6 +2981,7 @@ function node_search_validate($form, &$form_state) { * @param $account * Optional, a user object representing the user for whom the operation is to * be performed. Determines access for a user other than the current user. + * * @return * TRUE if the operation may be performed, FALSE otherwise. */ @@ -3005,6 +3114,7 @@ function node_node_access($node, $op, $account) { * * @param $type * The machine-readable name of the node type. + * * @return array * An array of permission names and descriptions. */ @@ -3038,11 +3148,11 @@ function node_list_permissions($type) { * * By default, this will include all node types in the system. To exclude a * specific node from getting permissions defined for it, set the - * node_permissions_$type variable to 0. Core does not provide an interface - * for doing so, however, contrib modules may exclude their own nodes in + * node_permissions_$type variable to 0. Core does not provide an interface for + * doing so. However, contrib modules may exclude their own nodes in * hook_install(). Alternatively, contrib modules may configure all node types - * at once, or decide to apply some other hook_node_access() implementation - * to some or all node types. + * at once, or decide to apply some other hook_node_access() implementation to + * some or all node types. * * @return * An array of node types managed by this module. @@ -3061,21 +3171,22 @@ function node_permissions_get_configured_types() { } /** - * Fetch an array of permission IDs granted to the given user ID. + * Fetches an array of permission IDs granted to the given user ID. * * The implementation here provides only the universal "all" grant. A node - * access module should implement hook_node_grants() to provide a grant - * list for the user. + * access module should implement hook_node_grants() to provide a grant list for + * the user. * - * After the default grants have been loaded, we allow modules to alter - * the grants array by reference. This hook allows for complex business - * logic to be applied when integrating multiple node access modules. + * After the default grants have been loaded, we allow modules to alter the + * grants array by reference. This hook allows for complex business logic to be + * applied when integrating multiple node access modules. * * @param $op * The operation that the user is trying to perform. * @param $account * The user object for the user performing the operation. If omitted, the * current user is used. + * * @return * An associative array in which the keys are realms, and the values are * arrays of grants for those realms. @@ -3163,11 +3274,10 @@ function node_access_view_all_nodes($account = NULL) { /** * Implements hook_query_TAG_alter(). * - * This is the hook_query_alter() for queries tagged with 'node_access'. - * It adds node access checks for the user account given by the 'account' - * meta-data (or global $user if not provided), for an operation given by - * the 'op' meta-data (or 'view' if not provided; other possible values are - * 'update' and 'delete'). + * This is the hook_query_alter() for queries tagged with 'node_access'. It adds + * node access checks for the user account given by the 'account' meta-data (or + * global $user if not provided), for an operation given by the 'op' meta-data + * (or 'view' if not provided; other possible values are 'update' and 'delete'). */ function node_query_node_access_alter(QueryAlterableInterface $query) { _node_query_node_access_alter($query, 'node'); @@ -3206,8 +3316,8 @@ function _node_query_node_access_alter($query, $type) { } // If $account can bypass node access, or there are no node access modules, - // or the operation is 'view' and the $acount has a global view grant (i.e., - // a view grant for node ID 0), we don't need to alter the query. + // or the operation is 'view' and the $account has a global view grant + // (such as a view grant for node ID 0), we don't need to alter the query. if (user_access('bypass node access', $account)) { return; } @@ -3394,15 +3504,14 @@ function node_access_acquire_grants($node, $delete = TRUE) { * * If a realm is provided, it will only delete grants from that realm, but it * will always delete a grant from the 'all' realm. Modules that utilize - * node_access can use this function when doing mass updates due to widespread + * node_access() can use this function when doing mass updates due to widespread * permission changes. * * Note: Don't call this function directly from a contributed module. Call * node_access_acquire_grants() instead. * * @param $node - * The $node being written to. All that is necessary is that it contains a - * nid. + * The node whose grants are being written. * @param $grants * A list of grants to write. Each grant is an array that must contain the * following keys: realm, gid, grant_view, grant_update, grant_delete. @@ -3410,10 +3519,14 @@ function node_access_acquire_grants($node, $delete = TRUE) { * is a module-defined id to define grant privileges. each grant_* field * is a boolean value. * @param $realm - * If provided, only read/write grants for that realm. + * (optional) If provided, read/write grants for that realm only. Defaults to + * NULL. * @param $delete - * If false, do not delete records. This is only for optimization purposes, - * and assumes the caller has already performed a mass delete of some form. + * (optional) If false, does not delete records. This is only for optimization + * purposes, and assumes the caller has already performed a mass delete of + * some form. Defaults to TRUE. + * + * @see node_access_acquire_grants() */ function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) { if ($delete) { @@ -3442,21 +3555,23 @@ function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) } /** - * Flag / unflag the node access grants for rebuilding, or read the current - * value of the flag. + * Flags or unflags the node access grants for rebuilding. * + * If the argument isn't specified, the current value of the flag is returned. * When the flag is set, a message is displayed to users with 'access * administration pages' permission, pointing to the 'rebuild' confirm form. * This can be used as an alternative to direct node_access_rebuild calls, * allowing administrators to decide when they want to perform the actual - * (possibly time consuming) rebuild. - * When unsure the current user is an administrator, node_access_rebuild - * should be used instead. + * (possibly time consuming) rebuild. When unsure if the current user is an + * administrator, node_access_rebuild() should be used instead. * * @param $rebuild * (Optional) The boolean value to be written. - * @return - * (If no value was provided for $rebuild) The current value of the flag. + * + * @return + * The current value of the flag if no value was provided for $rebuild. + * + * @see node_access_rebuild() */ function node_access_needs_rebuild($rebuild = NULL) { if (!isset($rebuild)) { @@ -3471,15 +3586,15 @@ function node_access_needs_rebuild($rebuild = NULL) { } /** - * Rebuild the node access database. This is occasionally needed by modules - * that make system-wide changes to access levels. + * Rebuilds the node access database. * - * When the rebuild is required by an admin-triggered action (e.g module - * settings form), calling node_access_needs_rebuild(TRUE) instead of + * This is occasionally needed by modules that make system-wide changes to + * access levels. When the rebuild is required by an admin-triggered action (e.g + * module settings form), calling node_access_needs_rebuild(TRUE) instead of * node_access_rebuild() lets the user perform his changes and actually * rebuild only once he is done. * - * Note : As of Drupal 6, node access modules are not required to (and actually + * Note: As of Drupal 6, node access modules are not required to (and actually * should not) call node_access_rebuild() in hook_enable/disable anymore. * * @see node_access_needs_rebuild() @@ -3543,11 +3658,14 @@ function node_access_rebuild($batch_mode = FALSE) { } /** - * Batch operation for node_access_rebuild_batch. + * Performs batch operation for node_access_rebuild(). + * + * This is a multistep operation: we go through all nodes by packs of 20. The + * batch processing engine interrupts processing and sends progress feedback + * after 1 second execution time. * - * This is a multistep operation : we go through all nodes by packs of 20. - * The batch processing engine interrupts processing and sends progress - * feedback after 1 second execution time. + * @param array $context + * An array of contextual key/value information for rebuild batch process. */ function _node_access_rebuild_batch_operation(&$context) { if (empty($context['sandbox'])) { @@ -3578,7 +3696,14 @@ function _node_access_rebuild_batch_operation(&$context) { } /** - * Post-processing for node_access_rebuild_batch. + * Performs post-processing for node_access_rebuild(). + * + * @param bool $success + * A boolean indicating whether the re-build process has completed. + * @param array $results + * An array of results information. + * @param array $operations + * An array of function calls (not used in this function). */ function _node_access_rebuild_batch_finished($success, $results, $operations) { if ($success) { @@ -3595,7 +3720,6 @@ function _node_access_rebuild_batch_finished($success, $results, $operations) { * @} End of "defgroup node_access". */ - /** * @defgroup node_content Hook implementations for user-created content types * @{ @@ -3631,6 +3755,7 @@ function node_content_form($node, $form_state) { /** * Implements hook_forms(). + * * All node forms share the same form handler. */ function node_forms() { @@ -3715,6 +3840,12 @@ function node_action_info() { /** * Sets the status of a node to 1 (published). * + * @param $node + * A node object. + * @param $context + * (optional) Array of additional information about what triggered the action. + * Not used for this action. + * * @ingroup actions */ function node_publish_action($node, $context = array()) { @@ -3725,6 +3856,12 @@ function node_publish_action($node, $context = array()) { /** * Sets the status of a node to 0 (unpublished). * + * @param $node + * A node object. + * @param $context + * (optional) Array of additional information about what triggered the action. + * Not used for this action. + * * @ingroup actions */ function node_unpublish_action($node, $context = array()) { @@ -3735,6 +3872,12 @@ function node_unpublish_action($node, $context = array()) { /** * Sets the sticky-at-top-of-list property of a node to 1. * + * @param $node + * A node object. + * @param $context + * (optional) Array of additional information about what triggered the action. + * Not used for this action. + * * @ingroup actions */ function node_make_sticky_action($node, $context = array()) { @@ -3745,6 +3888,12 @@ function node_make_sticky_action($node, $context = array()) { /** * Sets the sticky-at-top-of-list property of a node to 0. * + * @param $node + * A node object. + * @param $context + * (optional) Array of additional information about what triggered the action. + * Not used for this action. + * * @ingroup actions */ function node_make_unsticky_action($node, $context = array()) { @@ -3755,6 +3904,12 @@ function node_make_unsticky_action($node, $context = array()) { /** * Sets the promote property of a node to 1. * + * @param $node + * A node object. + * @param $context + * (optional) Array of additional information about what triggered the action. + * Not used for this action. + * * @ingroup actions */ function node_promote_action($node, $context = array()) { @@ -3765,6 +3920,12 @@ function node_promote_action($node, $context = array()) { /** * Sets the promote property of a node to 0. * + * @param $node + * A node object. + * @param $context + * (optional) Array of additional information about what triggered the action. + * Not used for this action. + * * @ingroup actions */ function node_unpromote_action($node, $context = array()) { @@ -3775,6 +3936,9 @@ function node_unpromote_action($node, $context = array()) { /** * Saves a node. * + * @param $node + * The node to be saved. + * * @ingroup actions */ function node_save_action($node) { @@ -3791,6 +3955,9 @@ function node_save_action($node) { * Array with the following elements: * - 'owner_uid': User ID to assign to the node. * + * @see node_assign_owner_action_form() + * @see node_assign_owner_action_validate() + * @see node_assign_owner_action_submit() * @ingroup actions */ function node_assign_owner_action($node, $context) { @@ -3801,6 +3968,16 @@ function node_assign_owner_action($node, $context) { /** * Generates the settings form for node_assign_owner_action(). + * + * @param $context + * Array of additional information about what triggered the action. Includes + * the following elements: + * - 'owner_uid': User ID to assign to the node. + * + * @see node_assign_owner_action_submit() + * @see node_assign_owner_action_validate() + * + * @ingroup forms */ function node_assign_owner_action_form($context) { $description = t('The username of the user to which you would like to assign ownership.'); @@ -3841,6 +4018,8 @@ function node_assign_owner_action_form($context) { /** * Validates settings form for node_assign_owner_action(). + * + * @see node_assign_owner_action_submit() */ function node_assign_owner_action_validate($form, $form_state) { $exists = (bool) db_query_range('SELECT 1 FROM {users} WHERE name = :name', 0, 1, array(':name' => $form_state['values']['owner_name']))->fetchField(); @@ -3851,6 +4030,8 @@ function node_assign_owner_action_validate($form, $form_state) { /** * Saves settings form for node_assign_owner_action(). + * + * @see node_assign_owner_action_validate() */ function node_assign_owner_action_submit($form, $form_state) { // Username can change, so we need to store the ID, not the username. @@ -3860,6 +4041,14 @@ function node_assign_owner_action_submit($form, $form_state) { /** * Generates settings form for node_unpublish_by_keyword_action(). + * + * @param array $context + * Array of additional information about what triggered this action. + * + * @return array + * A form array. + * + * @see node_unpublish_by_keyword_action_submit() */ function node_unpublish_by_keyword_action_form($context) { $form['keywords'] = array( diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc index c6cb1bcc2..dee92e16c 100644 --- a/modules/node/node.pages.inc +++ b/modules/node/node.pages.inc @@ -5,7 +5,6 @@ * Page callbacks for adding, editing, deleting, and revisions management for content. */ - /** * Menu callback; presents the node editing form. */ @@ -63,6 +62,12 @@ function theme_node_add_list($variables) { /** * Returns a node submission form. + * + * @param $type + * The node type for the submitted node. + * + * @return + * The themed form. */ function node_add($type) { global $user; @@ -75,6 +80,12 @@ function node_add($type) { return $output; } +/** + * Form validation handler for node_form(). + * + * @see node_form() + * @see node_form_submit() + */ function node_form_validate($form, &$form_state) { // $form_state['node'] contains the actual entity being edited, but we must // not update it with form values that have not yet been validated, so we @@ -85,7 +96,13 @@ function node_form_validate($form, &$form_state) { } /** - * Generate the node add/edit form array. + * Form constructor for the node add/edit form. + * + * @see node_form_validate() + * @see node_form_submit() + * @see node_form_build_preview() + * @see node_form_delete_submit() + * @ingroup forms */ function node_form($form, &$form_state, $node) { global $user; @@ -311,7 +328,12 @@ function node_form($form, &$form_state, $node) { } /** - * Button submit function: handle the 'Delete' button on the node form. + * Form submission handler for node_form(). + * + * Handles the 'Delete' button on the node form. + * + * @see node_form() + * @see node_form_validate() */ function node_form_delete_submit($form, &$form_state) { $destination = array(); @@ -323,7 +345,14 @@ function node_form_delete_submit($form, &$form_state) { $form_state['redirect'] = array('node/' . $node->nid . '/delete', array('query' => $destination)); } - +/** + * Form submission handler for node_form(). + * + * Handles the 'Preview' button on the node form. + * + * @see node_form() + * @see node_form_validate() + */ function node_form_build_preview($form, &$form_state) { $node = node_form_submit_build_node($form, $form_state); $form_state['node_preview'] = node_preview($node); @@ -331,7 +360,15 @@ function node_form_build_preview($form, &$form_state) { } /** - * Generate a node preview. + * Generates a node preview. + * + * @param $node + * The node to preview. + * + * @return + * An HTML-formatted string of a node preview. + * + * @see node_form_build_preview() */ function node_preview($node) { if (node_access('create', $node) || node_access('update', $node)) { @@ -377,6 +414,7 @@ function node_preview($node) { * An associative array containing: * - node: The node object which is being previewed. * + * @see node_preview() * @ingroup themeable */ function theme_node_preview($variables) { @@ -407,6 +445,12 @@ function theme_node_preview($variables) { return $output; } +/** + * Form submission handler for node_form(). + * + * @see node_form() + * @see node_form_validate() + */ function node_form_submit($form, &$form_state) { $node = node_form_submit_build_node($form, $form_state); $insert = empty($node->nid); @@ -472,7 +516,9 @@ function node_form_submit_build_node($form, &$form_state) { } /** - * Menu callback -- ask for confirmation of node deletion + * Form constructor for the node deletion confirmation form. + * + * @see node_delete_confirm_submit() */ function node_delete_confirm($form, &$form_state, $node) { $form['#node'] = $node; @@ -488,7 +534,9 @@ function node_delete_confirm($form, &$form_state, $node) { } /** - * Execute node deletion + * Executes node deletion. + * + * @see node_delete_confirm() */ function node_delete_confirm_submit($form, &$form_state) { if ($form_state['values']['confirm']) { @@ -502,7 +550,15 @@ function node_delete_confirm_submit($form, &$form_state) { } /** - * Generate an overview table of older revisions of a node. + * Generates an overview table of older revisions of a node. + * + * @param $node + * A node object. + * + * @return array + * An array as expected by drupal_render(). + * + * @see node_menu() */ function node_revision_overview($node) { drupal_set_title(t('Revisions for %title', array('%title' => $node->title)), PASS_THROUGH); @@ -553,13 +609,26 @@ function node_revision_overview($node) { } /** - * Ask for confirmation of the reversion to prevent against CSRF attacks. + * Asks for confirmation of the reversion to prevent against CSRF attacks. + * + * @param int $node_revision + * The node revision ID. + * + * @return array + * An array as expected by drupal_render(). + * + * @see node_menu() + * @see node_revision_revert_confirm_submit() + * @ingroup forms */ function node_revision_revert_confirm($form, $form_state, $node_revision) { $form['#node_revision'] = $node_revision; return confirm_form($form, t('Are you sure you want to revert to the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', '', t('Revert'), t('Cancel')); } +/** + * Form submission handler for node_revision_revert_confirm(). + */ function node_revision_revert_confirm_submit($form, &$form_state) { $node_revision = $form['#node_revision']; $node_revision->revision = 1; @@ -572,11 +641,29 @@ function node_revision_revert_confirm_submit($form, &$form_state) { $form_state['redirect'] = 'node/' . $node_revision->nid . '/revisions'; } +/** + * Form constructor for the revision deletion confirmation form. + * + * This form prevents against CSRF attacks. + * + * @param $node_revision + * The node revision ID. + * + * @return + * An array as expected by drupal_render(). + * + * @see node_menu() + * @see node_revision_delete_confirm_submit() + * @ingroup forms + */ function node_revision_delete_confirm($form, $form_state, $node_revision) { $form['#node_revision'] = $node_revision; return confirm_form($form, t('Are you sure you want to delete the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', t('This action cannot be undone.'), t('Delete'), t('Cancel')); } +/** + * Form submission handler for node_revision_delete_confirm(). + */ function node_revision_delete_confirm_submit($form, &$form_state) { $node_revision = $form['#node_revision']; node_revision_delete($node_revision->vid); diff --git a/modules/node/node.test b/modules/node/node.test index d789d3c0e..2180f5838 100644 --- a/modules/node/node.test +++ b/modules/node/node.test @@ -149,6 +149,9 @@ class NodeLoadHooksTestCase extends DrupalWebTestCase { } } +/** + * Tests the node revision functionality. + */ class NodeRevisionsTestCase extends DrupalWebTestCase { protected $nodes; protected $logs; @@ -198,7 +201,7 @@ class NodeRevisionsTestCase extends DrupalWebTestCase { } /** - * Check node revision related operations. + * Checks node revision related operations. */ function testRevisions() { $nodes = $this->nodes; @@ -282,6 +285,9 @@ class NodeRevisionsTestCase extends DrupalWebTestCase { } } +/** + * Tests the node edit functionality. + */ class PageEditTestCase extends DrupalWebTestCase { protected $web_user; protected $admin_user; @@ -302,7 +308,7 @@ class PageEditTestCase extends DrupalWebTestCase { } /** - * Check node edit functionality. + * Checks node edit functionality. */ function testPageEdit() { $this->drupalLogin($this->web_user); @@ -369,7 +375,7 @@ class PageEditTestCase extends DrupalWebTestCase { } /** - * Check changing node authored by fields. + * Tests changing a node's "authored by" field. */ function testPageAuthoredBy() { $this->drupalLogin($this->admin_user); @@ -414,6 +420,9 @@ class PageEditTestCase extends DrupalWebTestCase { } } +/** + * Tests the node entity preview functionality. + */ class PagePreviewTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -431,7 +440,7 @@ class PagePreviewTestCase extends DrupalWebTestCase { } /** - * Check the node preview functionality. + * Checks the node preview functionality. */ function testPagePreview() { $langcode = LANGUAGE_NONE; @@ -455,7 +464,7 @@ class PagePreviewTestCase extends DrupalWebTestCase { } /** - * Check the node preview functionality, when using revisions. + * Checks the node preview functionality, when using revisions. */ function testPagePreviewWithRevisions() { $langcode = LANGUAGE_NONE; @@ -485,6 +494,9 @@ class PagePreviewTestCase extends DrupalWebTestCase { } } +/** + * Tests creating and saving a node. + */ class NodeCreationTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -503,7 +515,7 @@ class NodeCreationTestCase extends DrupalWebTestCase { } /** - * Create a "Basic page" node and verify its consistency in the database. + * Creates a "Basic page" node and verifies its consistency in the database. */ function testNodeCreation() { // Create a node. @@ -522,7 +534,7 @@ class NodeCreationTestCase extends DrupalWebTestCase { } /** - * Create a page node and verify that a transaction rolls back the failed creation + * Verifies that a transaction rolls back the failed creation. */ function testFailedPageCreation() { // Create a node. @@ -563,6 +575,9 @@ class NodeCreationTestCase extends DrupalWebTestCase { } } +/** + * Tests the functionality of node entity edit permissions. + */ class PageViewTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -573,7 +588,7 @@ class PageViewTestCase extends DrupalWebTestCase { } /** - * Creates a node and then an anonymous and unpermissioned user attempt to edit the node. + * Tests an anonymous and unpermissioned user attempting to edit the node. */ function testPageView() { // Create a node to view. @@ -602,6 +617,9 @@ class PageViewTestCase extends DrupalWebTestCase { } } +/** + * Tests the summary length functionality. + */ class SummaryLengthTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -612,7 +630,7 @@ class SummaryLengthTestCase extends DrupalWebTestCase { } /** - * Creates a node and then an anonymous and unpermissioned user attempt to edit the node. + * Tests the node summary length functionality. */ function testSummaryLength() { // Create a node to view. @@ -644,6 +662,9 @@ class SummaryLengthTestCase extends DrupalWebTestCase { } } +/** + * Tests XSS functionality with a node entity. + */ class NodeTitleXSSTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -653,6 +674,9 @@ class NodeTitleXSSTestCase extends DrupalWebTestCase { ); } + /** + * Tests XSS functionality with a node entity. + */ function testNodeTitleXSS() { // Prepare a user to do the stuff. $web_user = $this->drupalCreateUser(array('create page content', 'edit any page content')); @@ -678,6 +702,9 @@ class NodeTitleXSSTestCase extends DrupalWebTestCase { } } +/** + * Tests the availability of the syndicate block. + */ class NodeBlockTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -709,7 +736,7 @@ class NodeBlockTestCase extends DrupalWebTestCase { } /** - * Check that the post information displays when enabled for a content type. + * Checks that the post information displays when enabled for a content type. */ class NodePostSettingsTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -728,7 +755,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { } /** - * Set "Basic page" content type to display post information and confirm its presence on a new node. + * Confirms "Basic page" content type and post information is on a new node. */ function testPagePostInfo() { @@ -751,7 +778,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { } /** - * Set "Basic page" content type to not display post information and confirm its absence on a new node. + * Confirms absence of post information on a new node. */ function testPageNotPostInfo() { @@ -774,7 +801,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { } /** - * Ensure that data added to nodes by other modules appears in RSS feeds. + * Ensures that data added to nodes by other modules appears in RSS feeds. * * Create a node, enable the node_test module to ensure that extra data is * added to the node->content array, then verify that the data appears on the @@ -801,8 +828,7 @@ class NodeRSSContentTestCase extends DrupalWebTestCase { } /** - * Create a new node and ensure that it includes the custom data when added - * to an RSS feed. + * Ensures that a new node includes the custom data when added to an RSS feed. */ function testNodeRSSContent() { // Create a node. @@ -841,9 +867,11 @@ class NodeRSSContentTestCase extends DrupalWebTestCase { } /** - * Test case to verify basic node_access functionality. + * Tests basic node_access functionality. + * + * Note that hook_node_access_records() is covered in another test class. + * * @todo Cover hook_node_access in a separate test class. - * hook_node_access_records is covered in another test class. */ class NodeAccessTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -855,7 +883,7 @@ class NodeAccessTestCase extends DrupalWebTestCase { } /** - * Asserts node_access correctly grants or denies access. + * Asserts node_access() correctly grants or denies access. */ function assertNodeAccess($ops, $node, $account) { foreach ($ops as $op => $result) { @@ -910,7 +938,7 @@ class NodeAccessTestCase extends DrupalWebTestCase { } /** - * Test case to verify hook_node_access_records functionality. + * Tests hook_node_access_records() functionality. */ class NodeAccessRecordsTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -929,7 +957,7 @@ class NodeAccessRecordsTestCase extends DrupalWebTestCase { } /** - * Create a node and test the creation of node access rules. + * Creates a node and tests the creation of node access rules. */ function testNodeAccessRecords() { // Create an article node. @@ -1005,9 +1033,6 @@ class NodeAccessBaseTableTestCase extends DrupalWebTestCase { ); } - /** - * Enable modules and create user with specific permissions. - */ public function setUp() { parent::setUp('node_access_test'); node_access_rebuild(); @@ -1015,7 +1040,7 @@ class NodeAccessBaseTableTestCase extends DrupalWebTestCase { } /** - * Test the "private" node access. + * Tests the "private" node access functionality. * * - Create 2 users with "access content" and "create article" permissions. * - Each user creates one private and one not private article. @@ -1152,7 +1177,7 @@ class NodeAccessBaseTableTestCase extends DrupalWebTestCase { } /** - * Test case to check node save related functionality, including import-save + * Tests node save related functionality, including import-save. */ class NodeSaveTestCase extends DrupalWebTestCase { @@ -1173,7 +1198,8 @@ class NodeSaveTestCase extends DrupalWebTestCase { } /** - * Import test, to check if custom node ids are saved properly. + * Checks whether custom node IDs are saved properly during an import operation. + * * Workflow: * - first create a piece of content * - save the content @@ -1207,8 +1233,7 @@ class NodeSaveTestCase extends DrupalWebTestCase { } /** - * Check that the "created" and "changed" timestamps are set correctly when - * saving a new node or updating an existing node. + * Verifies accuracy of the "created" and "changed" timestamp functionality. */ function testTimestamps() { // Use the default timestamps. @@ -1307,7 +1332,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Ensure that node type functions (node_type_get_*) work correctly. + * Ensures that node type functions (node_type_get_*) work correctly. * * Load available node types and validate the returned data. */ @@ -1326,7 +1351,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Test creating a content type programmatically and via a form. + * Tests creating a content type programmatically and via a form. */ function testNodeTypeCreation() { // Create a content type programmaticaly. @@ -1356,7 +1381,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Test editing a node type using the UI. + * Tests editing a node type using the UI. */ function testNodeTypeEditing() { $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types')); @@ -1409,7 +1434,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Test that node_types_rebuild() correctly handles the 'disabled' flag. + * Tests that node_types_rebuild() correctly handles the 'disabled' flag. */ function testNodeTypeStatus() { // Enable all core node modules, and all types should be active. @@ -1470,7 +1495,7 @@ class NodeTypePersistenceTestCase extends DrupalWebTestCase { } /** - * Test node type customizations persist through disable and uninstall. + * Tests that node type customizations persist through disable and uninstall. */ function testNodeTypeCustomizationPersistence() { $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types', 'administer modules')); @@ -1534,7 +1559,7 @@ class NodeTypePersistenceTestCase extends DrupalWebTestCase { } /** - * Rebuild the node_access table. + * Verifies the rebuild functionality for the node_access table. */ class NodeAccessRebuildTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -1553,6 +1578,9 @@ class NodeAccessRebuildTestCase extends DrupalWebTestCase { $this->web_user = $web_user; } + /** + * Tests rebuilding the node access permissions table. + */ function testNodeAccessRebuild() { $this->drupalGet('admin/reports/status'); $this->clickLink(t('Rebuild permissions')); @@ -1562,7 +1590,7 @@ class NodeAccessRebuildTestCase extends DrupalWebTestCase { } /** - * Test node administration page functionality. + * Tests node administration page functionality. */ class NodeAdminTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -1630,6 +1658,7 @@ class NodeAdminTestCase extends DrupalWebTestCase { * Tests content overview with different user permissions. * * Taxonomy filters are tested separately. + * * @see TaxonomyNodeFilterTestCase */ function testContentAdminPages() { @@ -1727,7 +1756,7 @@ class NodeAdminTestCase extends DrupalWebTestCase { } /** - * Test node title. + * Tests node title functionality. */ class NodeTitleTestCase extends DrupalWebTestCase { protected $admin_user; @@ -1747,7 +1776,7 @@ class NodeTitleTestCase extends DrupalWebTestCase { } /** - * Create one node and test if the node title has the correct value. + * Creates one node and tests if the node title has the correct value. */ function testNodeTitle() { // Create "Basic page" content with title. @@ -1790,7 +1819,7 @@ class NodeFeedTestCase extends DrupalWebTestCase { } /** - * Ensure that node_feed accepts and prints extra channel elements. + * Ensures that node_feed() accepts and prints extra channel elements. */ function testNodeFeedExtraChannelElements() { ob_start(); @@ -1822,7 +1851,7 @@ class NodeBlockFunctionalTest extends DrupalWebTestCase { } /** - * Test the recent comments block. + * Tests the recent comments block. */ function testRecentNodeBlock() { $this->drupalLogin($this->admin_user); @@ -1935,7 +1964,7 @@ class NodeBlockFunctionalTest extends DrupalWebTestCase { } } /** - * Test multistep node forms basic options. + * Tests basic options of multi-step node forms. */ class MultiStepNodeFormBasicOptionsTest extends DrupalWebTestCase { public static function getInfo() { @@ -1953,7 +1982,7 @@ class MultiStepNodeFormBasicOptionsTest extends DrupalWebTestCase { } /** - * Change the default values of basic options to ensure they persist. + * Tests changing the default values of basic options to ensure they persist. */ function testMultiStepNodeFormBasicOptions() { $edit = array( @@ -1985,7 +2014,7 @@ class NodeBuildContent extends DrupalWebTestCase { } /** - * Test to ensure that a node's content array is rebuilt on every call to node_build_content(). + * Ensures that content array is rebuilt on every call to node_build_content(). */ function testNodeRebuildContent() { $node = $this->drupalCreateNode(); @@ -2065,10 +2094,10 @@ class NodeQueryAlter extends DrupalWebTestCase { } /** - * Lower-level test of 'node_access' query alter, for user with access. + * Tests 'node_access' query alter, for user with access. * - * Verifies that a non-standard table alias can be used, and that a - * user with node access can view the nodes. + * Verifies that a non-standard table alias can be used, and that a user with + * node access can view the nodes. */ function testNodeQueryAlterLowLevelWithAccess() { // User with access should be able to view 4 nodes. @@ -2088,10 +2117,10 @@ class NodeQueryAlter extends DrupalWebTestCase { } /** - * Lower-level test of 'node_access' query alter, for user without access. + * Tests 'node_access' query alter, for user without access. * - * Verifies that a non-standard table alias can be used, and that a - * user without node access cannot view the nodes. + * Verifies that a non-standard table alias can be used, and that a user + * without node access cannot view the nodes. */ function testNodeQueryAlterLowLevelNoAccess() { // User without access should be able to view 0 nodes. @@ -2111,10 +2140,10 @@ class NodeQueryAlter extends DrupalWebTestCase { } /** - * Lower-level test of 'node_access' query alter, for edit access. + * Tests 'node_access' query alter, for edit access. * - * Verifies that a non-standard table alias can be used, and that a - * user with view-only node access cannot edit the nodes. + * Verifies that a non-standard table alias can be used, and that a user with + * view-only node access cannot edit the nodes. */ function testNodeQueryAlterLowLevelEditAccess() { // User with view-only access should not be able to edit nodes. @@ -2136,13 +2165,13 @@ class NodeQueryAlter extends DrupalWebTestCase { } /** - * Lower-level test of 'node_access' query alter override. + * Tests 'node_access' query alter override. * * Verifies that node_access_view_all_nodes() is called from - * node_query_node_access_alter(). We do this by checking that - * a user which normally would not have view privileges is able - * to view the nodes when we add a record to {node_access} paired - * with a corresponding privilege in hook_node_grants(). + * node_query_node_access_alter(). We do this by checking that a user who + * normally would not have view privileges is able to view the nodes when we + * add a record to {node_access} paired with a corresponding privilege in + * hook_node_grants(). */ function testNodeQueryAlterOverride() { $record = array( diff --git a/modules/node/tests/node_access_test.module b/modules/node/tests/node_access_test.module index 813bf929b..ec35c41f1 100644 --- a/modules/node/tests/node_access_test.module +++ b/modules/node/tests/node_access_test.module @@ -2,7 +2,9 @@ /** * @file - * Dummy module implementing node access related hooks to test API interaction + * A dummy module implementing node access related hooks for testing purposes. + * + * A dummy module implementing node access related hooks to test API interaction * with the Node module. This module restricts view permission to those with * a special 'node test view' permission. */ @@ -140,6 +142,8 @@ function node_access_test_page() { * database query is shown, and a list of the node IDs, for debugging purposes. * And if there is a query exception, the page says "Exception" and gives the * error. + * + * @see node_access_test_menu() */ function node_access_entity_test_page() { $output = ''; diff --git a/modules/node/tests/node_test.module b/modules/node/tests/node_test.module index a52c1fad0..fb6678521 100644 --- a/modules/node/tests/node_test.module +++ b/modules/node/tests/node_test.module @@ -2,8 +2,10 @@ /** * @file - * Dummy module implementing node related hooks to test API interaction with - * the Node module. + * A dummy module for testing node related hooks. + * + * This is a dummy module that implements node related hooks to test API + * interaction with the Node module. */ /** diff --git a/modules/node/tests/node_test_exception.module b/modules/node/tests/node_test_exception.module index 0fe9f35ea..66bc71747 100644 --- a/modules/node/tests/node_test_exception.module +++ b/modules/node/tests/node_test_exception.module @@ -2,8 +2,7 @@ /** * @file - * Dummy module implementing node related hooks to test API interaction with - * the Node module. + * A module implementing node related hooks to test API interaction. */ /** diff --git a/modules/overlay/overlay.module b/modules/overlay/overlay.module index c07cc6cfa..728198680 100644 --- a/modules/overlay/overlay.module +++ b/modules/overlay/overlay.module @@ -704,7 +704,7 @@ function overlay_overlay_child_initialize() { } /** - * Requests that the overlay overlay closes when the page is displayed. + * Requests that the overlay closes when the page is displayed. * * @param $redirect * (optional) The path that should open in the parent window after the diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index e3cab62a2..694880b91 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -3149,6 +3149,42 @@ class DrupalWebTestCase extends DrupalTestCase { } /** + * Asserts themed output. + * + * @param $callback + * The name of the theme function to invoke; e.g. 'links' for theme_links(). + * @param $variables + * An array of variables to pass to the theme function. + * @param $expected + * The expected themed output string. + * @param $message + * (optional) A message to display with the assertion. Do not translate + * messages: use format_string() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * @param $group + * (optional) The group this message is in, which is displayed in a column + * in test output. Use 'Debug' to indicate this is debugging output. Do not + * translate this string. Defaults to 'Other'; most tests do not override + * this default. + * + * @return + * TRUE on pass, FALSE on fail. + */ + protected function assertThemeOutput($callback, array $variables = array(), $expected, $message = '', $group = 'Other') { + $output = theme($callback, $variables); + $this->verbose('Variables:' . '<pre>' . check_plain(var_export($variables, TRUE)) . '</pre>' + . '<hr />' . 'Result:' . '<pre>' . check_plain(var_export($output, TRUE)) . '</pre>' + . '<hr />' . 'Expected:' . '<pre>' . check_plain(var_export($expected, TRUE)) . '</pre>' + . '<hr />' . $output + ); + if (!$message) { + $message = '%callback rendered correctly.'; + } + $message = format_string($message, array('%callback' => 'theme_' . $callback . '()')); + return $this->assertIdentical($output, $expected, $message, $group); + } + + /** * Asserts that a field exists in the current page by the given XPath. * * @param $xpath diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test index 6e1d15979..2c096fb1d 100644 --- a/modules/simpletest/tests/database_test.test +++ b/modules/simpletest/tests/database_test.test @@ -3815,3 +3815,222 @@ class DatabaseEmptyStatementTestCase extends DrupalWebTestCase { $this->assertEqual($result->fetchAll(), array(), t('Empty array returned from empty result set.')); } } + +/** + * Tests management of database connections. + */ +class ConnectionUnitTest extends DrupalUnitTestCase { + + protected $key; + protected $target; + + protected $monitor; + protected $originalCount; + + public static function getInfo() { + return array( + 'name' => 'Connection unit tests', + 'description' => 'Tests management of database connections.', + 'group' => 'Database', + ); + } + + function setUp() { + parent::setUp(); + + $this->key = 'default'; + $this->originalTarget = 'default'; + $this->target = 'DatabaseConnectionUnitTest'; + + // Determine whether the database driver is MySQL. If it is not, the test + // methods will not be executed. + // @todo Make this test driver-agnostic, or find a proper way to skip it. + // @see http://drupal.org/node/1273478 + $connection_info = Database::getConnectionInfo('default'); + $this->skipTest = (bool) $connection_info['default']['driver'] != 'mysql'; + if ($this->skipTest) { + // Insert an assertion to prevent Simpletest from interpreting the test + // as failure. + $this->pass('This test is only compatible with MySQL.'); + } + + // Create an additional connection to monitor the connections being opened + // and closed in this test. + // @see TestBase::changeDatabasePrefix() + $connection_info = Database::getConnectionInfo('default'); + Database::addConnectionInfo('default', 'monitor', $connection_info['default']); + global $databases; + $databases['default']['monitor'] = $connection_info['default']; + $this->monitor = Database::getConnection('monitor'); + } + + /** + * Adds a new database connection info to Database. + */ + protected function addConnection() { + // Add a new target to the connection, by cloning the current connection. + $connection_info = Database::getConnectionInfo($this->key); + Database::addConnectionInfo($this->key, $this->target, $connection_info[$this->originalTarget]); + + // Verify that the new target exists. + $info = Database::getConnectionInfo($this->key); + // Note: Custom assertion message to not expose database credentials. + $this->assertIdentical($info[$this->target], $connection_info[$this->key], 'New connection info found.'); + } + + /** + * Returns the connection ID of the current test connection. + * + * @return integer + */ + protected function getConnectionID() { + return (int) Database::getConnection($this->target, $this->key)->query('SELECT CONNECTION_ID()')->fetchField(); + } + + /** + * Asserts that a connection ID exists. + * + * @param integer $id + * The connection ID to verify. + */ + protected function assertConnection($id) { + $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0); + return $this->assertTrue(isset($list[$id]), format_string('Connection ID @id found.', array('@id' => $id))); + } + + /** + * Asserts that a connection ID does not exist. + * + * @param integer $id + * The connection ID to verify. + */ + protected function assertNoConnection($id) { + $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0); + return $this->assertFalse(isset($list[$id]), format_string('Connection ID @id not found.', array('@id' => $id))); + } + + /** + * Tests Database::closeConnection() without query. + * + * @todo getConnectionID() executes a query. + */ + function testOpenClose() { + if ($this->skipTest) { + return; + } + // Add and open a new connection. + $this->addConnection(); + $id = $this->getConnectionID(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertConnection($id); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertNoConnection($id); + } + + /** + * Tests Database::closeConnection() with a query. + */ + function testOpenQueryClose() { + if ($this->skipTest) { + return; + } + // Add and open a new connection. + $this->addConnection(); + $id = $this->getConnectionID(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertConnection($id); + + // Execute a query. + Database::getConnection($this->target, $this->key)->query('SHOW TABLES'); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertNoConnection($id); + } + + /** + * Tests Database::closeConnection() with a query and custom prefetch method. + */ + function testOpenQueryPrefetchClose() { + if ($this->skipTest) { + return; + } + // Add and open a new connection. + $this->addConnection(); + $id = $this->getConnectionID(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertConnection($id); + + // Execute a query. + Database::getConnection($this->target, $this->key)->query('SHOW TABLES')->fetchCol(); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertNoConnection($id); + } + + /** + * Tests Database::closeConnection() with a select query. + */ + function testOpenSelectQueryClose() { + if ($this->skipTest) { + return; + } + // Add and open a new connection. + $this->addConnection(); + $id = $this->getConnectionID(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertConnection($id); + + // Create a table. + $name = 'foo'; + Database::getConnection($this->target, $this->key)->schema()->createTable($name, array( + 'fields' => array( + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + ), + ), + )); + + // Execute a query. + Database::getConnection($this->target, $this->key)->select('foo', 'f') + ->fields('f', array('name')) + ->execute() + ->fetchAll(); + + // Drop the table. + Database::getConnection($this->target, $this->key)->schema()->dropTable($name); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertNoConnection($id); + } + +} diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test index 3df31ba5f..ebaa0c034 100644 --- a/modules/simpletest/tests/file.test +++ b/modules/simpletest/tests/file.test @@ -2579,6 +2579,15 @@ class FileNameMungingTest extends FileTestCase { } /** + * Tests munging with a null byte in the filename. + */ + function testMungeNullByte() { + $prefix = $this->randomName(); + $filename = $prefix . '.' . $this->bad_extension . "\0.txt"; + $this->assertEqual(file_munge_filename($filename, ''), $prefix . '.' . $this->bad_extension . '_.txt', 'A filename with a null byte is correctly munged to remove the null byte.'); + } + + /** * If the allow_insecure_uploads variable evaluates to true, the file should * come out untouched, no matter how evil the filename. */ diff --git a/modules/system/language.api.php b/modules/system/language.api.php index 671479309..d868b6fef 100644 --- a/modules/system/language.api.php +++ b/modules/system/language.api.php @@ -62,16 +62,22 @@ function hook_language_switch_links_alter(array &$links, $type, $path) { } /** - * Allow modules to define their own language types. + * Define language types. * * @return - * An array of language type definitions. Each language type has an identifier - * key. The language type definition is an associative array that may contain - * the following key-value pairs: - * - "name": The human-readable language type identifier. - * - "description": A description of the language type. - * - "fixed": An array of language provider identifiers. Defining this key - * makes the language type non-configurable. + * An associative array of language type definitions. The keys are the + * identifiers, which are also used as names for global variables representing + * the types in the bootstrap phase. The values are associative arrays that + * may contain the following elements: + * - name: The human-readable language type identifier. + * - description: A description of the language type. + * - fixed: A fixed array of language negotiation provider identifiers to use + * to initialize this language. Defining this key makes the language type + * non-configurable, so it will always use the specified providers in the + * given priority order. Omit to make the language type configurable. + * + * @see hook_language_types_info_alter() + * @ingroup language_negotiation */ function hook_language_types_info() { return array( @@ -90,6 +96,9 @@ function hook_language_types_info() { * * @param $language_types * Array of language type definitions. + * + * @see hook_language_types_info() + * @ingroup language_negotiation */ function hook_language_types_info_alter(array &$language_types) { if (isset($language_types['custom_language_type'])) { @@ -98,31 +107,35 @@ function hook_language_types_info_alter(array &$language_types) { } /** - * Allow modules to define their own language providers. + * Define language negotiation providers. * * @return - * An array of language provider definitions. Each language provider has an - * identifier key. The language provider definition is an associative array - * that may contain the following key-value pairs: - * - "types": An array of allowed language types. If a language provider does - * not specify which language types it should be used with, it will be - * available for all the configurable language types. - * - "callbacks": An array of functions that will be called to perform various - * tasks. Possible key-value pairs are: - * - "language": Required. The callback that will determine the language - * value. - * - "switcher": The callback that will determine the language switch links - * associated to the current language provider. - * - "url_rewrite": The callback that will provide URL rewriting. - * - "file": A file that will be included before the callback is invoked; this - * allows callback functions to be in separate files. - * - "weight": The default weight the language provider has. - * - "name": A human-readable identifier. - * - "description": A description of the language provider. - * - "config": An internal path pointing to the language provider - * configuration page. - * - "cache": The value Drupal's page cache should be set to for the current - * language provider to be invoked. + * An associative array of language negotiation provider definitions. The keys + * are provider identifiers, and the values are associative arrays definining + * each provider, with the following elements: + * - types: An array of allowed language types. If a language negotiation + * provider does not specify which language types it should be used with, it + * will be available for all the configurable language types. + * - callbacks: An associative array of functions that will be called to + * perform various tasks. Possible elements are: + * - negotiation: (required) Name of the callback function that determines + * the language value. + * - language_switch: (optional) Name of the callback function that + * determines links for a language switcher block associated with this + * provider. See language_switcher_url() for an example. + * - url_rewrite: (optional) Name of the callback function that provides URL + * rewriting, if needed by this provider. + * - file: The file where callback functions are defined (this file will be + * included before the callbacks are invoked). + * - weight: The default weight of the provider. + * - name: The translated human-readable name for the provider. + * - description: A translated longer description of the provider. + * - config: An internal path pointing to the provider's configuration page. + * - cache: The value Drupal's page cache should be set to for the current + * provider to be invoked. + * + * @see hook_language_negotiation_info_alter() + * @ingroup language_negotiation */ function hook_language_negotiation_info() { return array( @@ -135,18 +148,21 @@ function hook_language_negotiation_info() { 'file' => drupal_get_path('module', 'custom') . '/custom.module', 'weight' => -4, 'types' => array('custom_language_type'), - 'name' => t('Custom language provider'), - 'description' => t('This is a custom language provider.'), + 'name' => t('Custom language negotiation provider'), + 'description' => t('This is a custom language negotiation provider.'), 'cache' => 0, ), ); } /** - * Perform alterations on language providers. + * Perform alterations on language negoiation providers. * * @param $language_providers - * Array of language provider definitions. + * Array of language negotiation provider definitions. + * + * @see hook_language_negotiation_info() + * @ingroup language_negotiation */ function hook_language_negotiation_info_alter(array &$language_providers) { if (isset($language_providers['custom_language_provider'])) { diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index 061898c85..05543be6a 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -1594,6 +1594,7 @@ function system_site_information_settings_validate($form, &$form_state) { * @ingroup forms */ function system_cron_settings() { + global $base_url; $form['description'] = array( '#markup' => '<p>' . t('Cron takes care of running periodic tasks like checking for updates and indexing content for search.') . '</p>', ); @@ -1606,6 +1607,11 @@ function system_cron_settings() { $form['status'] = array( '#markup' => $status, ); + + $form['cron_url'] = array( + '#markup' => '<p>' . t('To run cron from outside the site, go to <a href="!cron">!cron</a>', array('!cron' => url($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => variable_get('cron_key', 'drupal')))))) . '</p>', + ); + $form['cron'] = array( '#type' => 'fieldset', ); diff --git a/modules/system/system.api.php b/modules/system/system.api.php index adef26141..195bc8354 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -154,7 +154,10 @@ function hook_hook_info_alter(&$hooks) { * the name of the bundle object. * - bundles: An array describing all bundles for this object type. Keys are * bundles machine names, as found in the objects' 'bundle' property - * (defined in the 'entity keys' entry above). Elements: + * (defined in the 'entity keys' entry above). This entry can be omitted if + * this entity type exposes a single bundle (all entities have the same + * collection of fields). The name of this single bundle will be the same as + * the entity type. Elements: * - label: The human-readable name of the bundle. * - uri callback: Same as the 'uri callback' key documented above for the * entity type, but for the bundle only. When determining the URI of an @@ -1201,6 +1204,10 @@ function hook_menu_get_item_alter(&$router_item, $path, $original_map) { * same weight are ordered alphabetically. * - "menu_name": Optional. Set this to a custom menu if you don't want your * item to be placed in Navigation. + * - "expanded": Optional. If set to TRUE, and if a menu link is provided for + * this menu item (as a result of other properties), then the menu link is + * always expanded, equivalent to its 'always expanded' checkbox being set + * in the UI. * - "context": (optional) Defines the context a tab may appear in. By * default, all tabs are only displayed as local tasks when being rendered * in a page context. All tabs that should be accessible as contextual links @@ -1412,7 +1419,7 @@ function hook_menu_link_delete($link) { * - #link: An associative array containing: * - title: The localized title of the link. * - href: The system path to link to. - * - localized_options: An array of options to pass to url(). + * - localized_options: An array of options to pass to l(). * - #active: Whether the link should be marked as 'active'. * * @param $data @@ -1929,8 +1936,9 @@ function hook_image_toolkits() { * The drupal_mail() id of the message. Look at module source code or * drupal_mail() for possible id values. * - 'to': - * The address or addresses the message will be sent to. The - * formatting of this string must comply with RFC 2822. + * The address or addresses the message will be sent to. The formatting of + * this string will be validated with the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink * - 'from': * The address the message will be marked as being from, which is * either a custom address or the site-wide default email address. @@ -2100,7 +2108,9 @@ function hook_permission() { * specify how a particular render array is to be rendered as HTML (this is * usually the case if the theme function is assigned to the render array's * #theme property), or they return the HTML that should be returned by an - * invocation of theme(). + * invocation of theme(). See + * @link http://drupal.org/node/933976 Using the theme layer Drupal 7.x @endlink + * for more information on how to implement theme hooks. * * The following parameters are all optional. * @@ -2196,6 +2206,8 @@ function hook_permission() { * '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. + * + * @see hook_theme_registry_alter() */ function hook_theme($existing, $type, $theme, $path) { return array( @@ -2290,7 +2302,8 @@ function hook_theme_registry_alter(&$theme_registry) { * @return * The machine-readable name of the theme that should be used for the current * page request. The value returned from this function will only have an - * effect if it corresponds to a currently-active theme on the site. + * effect if it corresponds to a currently-active theme on the site. Do not + * return a value if you do not wish to set a custom theme. */ function hook_custom_theme() { // Allow the user to request a particular theme via a query parameter. @@ -2476,8 +2489,9 @@ function hook_watchdog(array $log_entry) { * An array to be filled in. Elements in this array include: * - id: An ID to identify the mail sent. Look at module source code * or drupal_mail() for possible id values. - * - to: The address or addresses the message will be sent to. The - * formatting of this string must comply with RFC 2822. + * - to: The address or addresses the message will be sent to. The formatting + * of this string will be validated with the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink * - subject: Subject of the e-mail to be sent. This must not contain any * newline characters, or the mail may not be sent properly. drupal_mail() * sets this to an empty string when the hook is invoked. @@ -3092,44 +3106,48 @@ function hook_schema() { 'description' => 'The primary identifier for a node.', 'type' => 'serial', 'unsigned' => TRUE, - 'not null' => TRUE), + 'not null' => TRUE, + ), 'vid' => array( 'description' => 'The current {node_revision}.vid version identifier.', 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, - 'default' => 0), + 'default' => 0, + ), 'type' => array( 'description' => 'The {node_type} of this node.', 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, - 'default' => ''), + 'default' => '', + ), 'title' => array( 'description' => 'The title of this node, always treated as non-markup plain text.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, - 'default' => ''), + 'default' => '', ), + ), 'indexes' => array( 'node_changed' => array('changed'), 'node_created' => array('created'), - ), + ), 'unique keys' => array( 'nid_vid' => array('nid', 'vid'), - 'vid' => array('vid') - ), + 'vid' => array('vid'), + ), 'foreign keys' => array( 'node_revision' => array( 'table' => 'node_revision', 'columns' => array('vid' => 'vid'), - ), + ), 'node_author' => array( 'table' => 'users', - 'columns' => array('uid' => 'uid') - ), - ), + 'columns' => array('uid' => 'uid'), + ), + ), 'primary key' => array('nid'), ); return $schema; @@ -3236,8 +3254,7 @@ function hook_query_TAG_alter(QueryAlterableInterface $query) { * 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 - * @link http://drupal.org/node/146843 http://drupal.org/node/146843 @endlink + * See the @link http://drupal.org/node/146843 Schema API documentation @endlink * for details on hook_schema and how database tables are defined. * * Note that since this function is called from a full bootstrap, all functions @@ -3621,6 +3638,9 @@ function hook_registry_files_alter(&$files, $modules) { * inspect later. It is important to remove any temporary variables using * variable_del() before your last task has completed and control is handed * back to the installer. + * + * @param array $install_state + * An array of information about the current installation state. * * @return * A keyed array of tasks the profile will perform during the final stage of @@ -3679,7 +3699,7 @@ function hook_registry_files_alter(&$files, $modules) { * @see install_state_defaults() * @see batch_set() */ -function hook_install_tasks() { +function hook_install_tasks(&$install_state) { // Here, we define a variable to allow tasks to indicate that a particular, // processor-intensive batch process needs to be triggered later on in the // installation. diff --git a/modules/system/system.install b/modules/system/system.install index 7b667678e..338e73a21 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -3032,6 +3032,7 @@ function system_update_7073() { 'default' => '', 'binary' => TRUE, )); + db_drop_unique_key('file_managed', 'uri'); db_change_field('file_managed', 'uri', 'uri', array( 'description' => 'The URI to access the file (either local or remote).', 'type' => 'varchar', @@ -3040,6 +3041,7 @@ function system_update_7073() { 'default' => '', 'binary' => TRUE, )); + db_add_unique_key('file_managed', 'uri', array('uri')); } /** diff --git a/modules/system/system.updater.inc b/modules/system/system.updater.inc index 0df1ad955..a14d788b1 100644 --- a/modules/system/system.updater.inc +++ b/modules/system/system.updater.inc @@ -73,8 +73,12 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface { return array(); } + /** + * Returns a list of post install actions. + */ public function postInstallTasks() { return array( + l(t('Install another module'), 'admin/modules/install'), l(t('Enable newly added modules'), 'admin/modules'), l(t('Administration pages'), 'admin'), ); diff --git a/modules/update/update.module b/modules/update/update.module index 85c0968d5..d5728be3e 100644 --- a/modules/update/update.module +++ b/modules/update/update.module @@ -139,10 +139,10 @@ function update_init() { if (!empty($verbose)) { if (isset($status[$type]['severity'])) { if ($status[$type]['severity'] == REQUIREMENT_ERROR) { - drupal_set_message($status[$type]['description'], 'error'); + drupal_set_message($status[$type]['description'], 'error', FALSE); } elseif ($status[$type]['severity'] == REQUIREMENT_WARNING) { - drupal_set_message($status[$type]['description'], 'warning'); + drupal_set_message($status[$type]['description'], 'warning', FALSE); } } } @@ -152,7 +152,7 @@ function update_init() { if (isset($status[$type]) && isset($status[$type]['reason']) && $status[$type]['reason'] === UPDATE_NOT_SECURE) { - drupal_set_message($status[$type]['description'], 'error'); + drupal_set_message($status[$type]['description'], 'error', FALSE); } } } diff --git a/modules/user/user.admin.inc b/modules/user/user.admin.inc index 1cc2c4a24..932c20593 100644 --- a/modules/user/user.admin.inc +++ b/modules/user/user.admin.inc @@ -5,6 +5,21 @@ * Admin page callback file for the user module. */ +/** + * Page callback: Generates the appropriate user administration form. + * + * This function generates the user registration, multiple user cancellation, + * or filtered user list admin form, depending on the argument and the POST + * form values. + * + * @param string $callback_arg + * (optional) Indicates which form to build. Defaults to '', which will + * trigger the user filter form. If the POST value 'op' is present, this + * function uses that value as the callback argument. + * + * @return string + * A renderable form array for the respective request. + */ function user_admin($callback_arg = '') { $op = isset($_POST['op']) ? $_POST['op'] : $callback_arg; diff --git a/modules/user/user.module b/modules/user/user.module index 622fe4d25..c1c7ec218 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -1517,15 +1517,33 @@ function theme_user_list($variables) { return theme('item_list', array('items' => $items, 'title' => $title)); } +/** + * Determines if the current user is anonymous. + * + * @return bool + * TRUE if the user is anonymous, FALSE if the user is authenticated. + */ function user_is_anonymous() { // Menu administrators can see items for anonymous when administering. return !$GLOBALS['user']->uid || !empty($GLOBALS['menu_admin']); } +/** + * Determines if the current user is logged in. + * + * @return bool + * TRUE if the user is logged in, FALSE if the user is anonymous. + */ function user_is_logged_in() { return (bool) $GLOBALS['user']->uid; } +/** + * Determines if the current user has access to the user registration page. + * + * @return bool + * TRUE if the user is not already logged in and can register for an account. + */ function user_register_access() { return user_is_anonymous() && variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); } @@ -3353,7 +3371,7 @@ function user_filters() { $options = array(); foreach (module_implements('permission') as $module) { $function = $module . '_permission'; - if ($permissions = $function('permission')) { + if ($permissions = $function()) { asort($permissions); foreach ($permissions as $permission => $description) { $options[t('@module module', array('@module' => $module))][$permission] = t($permission); |