From 3e552053f1fd3ebcc34c2fef4efb50721da749e5 Mon Sep 17 00:00:00 2001 From: Angie Byron Date: Mon, 11 Jan 2010 06:44:31 +0000 Subject: #337947 by codycraven, cwgordon7, yoroy, et al: Add a 'recent content block' for use on the dashboard. --- modules/dashboard/dashboard.css | 11 +++ modules/node/node.module | 145 ++++++++++++++++++++++++++++++++++++- modules/node/node.test | 108 ++++++++++++++++++++++++++- profiles/standard/standard.install | 10 +++ themes/seven/style.css | 144 +++++++++++++++++++----------------- themes/seven/vertical-tabs.css | 1 + 6 files changed, 348 insertions(+), 71 deletions(-) diff --git a/modules/dashboard/dashboard.css b/modules/dashboard/dashboard.css index 1df2867fd..32f3fc0c7 100644 --- a/modules/dashboard/dashboard.css +++ b/modules/dashboard/dashboard.css @@ -147,3 +147,14 @@ width: 30px; height: 1.6em; } + +/* Recent content block */ +#dashboard #block-node-recent table, +#dashboard #block-node-recent tr { + border: none; +} + +#dashboard .list-all { + text-align: right; + margin: 0 10px 0 0; +} diff --git a/modules/node/node.module b/modules/node/node.module index c71badf7e..e6c176c6a 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -166,6 +166,12 @@ function node_theme() { 'node_admin_overview' => array( 'variables' => array('name' => NULL, 'type' => NULL), ), + 'node_recent_block' => array( + 'variables' => array('nodes' => NULL), + ), + 'node_recent_content' => array( + 'variables' => array('node' => NULL), + ), ); } @@ -2007,6 +2013,9 @@ function node_block_info() { $blocks['syndicate']['info'] = t('Syndicate'); // Not worth caching. $blocks['syndicate']['cache'] = DRUPAL_NO_CACHE; + + $blocks['recent']['info'] = t('Recent content'); + return $blocks; } @@ -2014,12 +2023,144 @@ function node_block_info() { * Implements hook_block_view(). */ function node_block_view($delta = '') { - $block['subject'] = t('Syndicate'); - $block['content'] = theme('feed_icon', array('url' => url('rss.xml'), 'title' => t('Syndicate'))); + $block = array(); + switch ($delta) { + case 'syndicate': + $block['subject'] = t('Syndicate'); + $block['content'] = theme('feed_icon', array('url' => url('rss.xml'), 'title' => t('Syndicate'))); + break; + + case 'recent': + if (user_access('access content')) { + $block['subject'] = t('Recent content'); + if ($nodes = node_get_recent(variable_get('node_recent_block_count', 10))) { + $block['content'] = theme('node_recent_block', array( + 'nodes' => $nodes, + )); + } else { + $block['content'] = t('No content available.'); + } + } + break; + } return $block; } +/** + * Implements hook_block_configure(). + */ +function node_block_configure($delta = '') { + $form = array(); + if ($delta == 'recent') { + $form['node_recent_block_count'] = array( + '#type' => 'select', + '#title' => t('Number of recent content items to display'), + '#default_value' => variable_get('node_recent_block_count', 10), + '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 30)), + ); + } + return $form; +} + +/** + * Implements hook_block_save(). + */ +function node_block_save($delta = '', $edit = array()) { + if ($delta == 'recent') { + variable_set('node_recent_block_count', $edit['node_recent_block_count']); + } +} + +/** + * Find the most recent nodes that are available to the current user. + * + * @param $number + * (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. + */ +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 (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) + ->condition('n.nid', $own_unpublished, 'IN') + ); + } + else { + // If not, restrict the query to published nodes. + $query->condition('n.status', NODE_PUBLISHED); + } + } + $nids = $query + ->fields('n', array('nid')) + ->orderBy('changed', 'DESC') + ->range(0, $number) + ->addTag('node_access') + ->execute() + ->fetchCol(); + + $nodes = node_load_multiple($nids); + + return $nodes ? $nodes : array(); +} + +/** + * Returns a formatted list of recent nodes. + * + * @return + * The recent content table HTML. + * @ingroup themeable + */ +function theme_node_recent_block($variables) { + $rows = array(); + $output = ''; + + $l_options = array('query' => drupal_get_destination()); + foreach ($variables['nodes'] as $node) { + $row = array(); + $row[] = theme('node_recent_content', array('node' => $node)); + $row[] = node_access('update', $node) ? l(t('edit'), 'node/' . $node->nid . '/edit', $l_options) : ''; + $row[] = node_access('delete', $node) ? l(t('delete'), 'node/' . $node->nid . '/delete', $l_options) : ''; + $rows[] = $row; + } + + if ($rows) { + $output = theme('table', array('rows' => $rows)); + $output .= '
' . l(t('Show all content'), 'admin/content') . '
'; + } + + return $output; +} + +/** + * Returns a formatted recent node to be displayed in the recent content block. + * + * @return + * The recent content node's HTML. + * @ingroup themeable + */ +function theme_node_recent_content($variables) { + $node = $variables['node']; + + $output = '
'; + $output .= l($node->title, 'node/' . $node->nid); + $output .= theme('mark', array('type' => node_mark($node->nid, $node->changed))); + $output .= '
'; + $output .= theme('username', array('account' => user_load($node->uid))); + $output .= '
'; + + return $output; +} + /** * A generic function for generating RSS feeds from a set of nodes. * diff --git a/modules/node/node.test b/modules/node/node.test index df0b29bc1..4cd0a4859 100644 --- a/modules/node/node.test +++ b/modules/node/node.test @@ -378,7 +378,7 @@ class NodeCreationTestCase extends DrupalWebTestCase { // Check that the failed rollback was logged. $records = db_query("SELECT wid FROM {watchdog} WHERE message LIKE 'Explicit rollback failed%'")->fetchAll(); - $this->assertTrue(count($records) > 0, t('Transactions not supported, and rollback error logged to watchdog.')); + $this->assertTrue(count($records) > 0, t('Transactions not supported, and rollback error logged to watchdog.')); } // Check that the rollback error was logged. @@ -1170,3 +1170,109 @@ class NodeFeedTestCase extends DrupalWebTestCase { $this->assertTrue(strpos($output, 'Drupal is a registered trademark of Dries Buytaert.') !== FALSE); } } + +/** + * Functional tests for the node module blocks. + */ +class NodeBlockFunctionalTest extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Node blocks', + 'description' => 'Test node block functionality.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp('node'); + + // Create users and test node. + $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer blocks')); + $this->web_user = $this->drupalCreateUser(array('access content', 'create article content')); + } + + /** + * Test the recent comments block. + */ + function testRecentNodeBlock() { + $this->drupalLogin($this->admin_user); + + // Disallow anonymous users to view content. + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access content' => FALSE, + )); + + // Set the block to a region to confirm block is available. + $edit = array( + 'node_recent[region]' => 'sidebar_first', + ); + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + $this->assertText(t('The block settings have been updated.'), t('Block saved to first sidebar region.')); + + // Set block title and variables. + $block = array( + 'title' => $this->randomName(), + 'node_recent_block_count' => 2, + ); + $this->drupalPost('admin/structure/block/manage/node/recent/configure', $block, t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block saved.')); + + // Test that block is not visible without nodes + $this->drupalGet(''); + $this->assertText(t('No content available.'), t('Block with "No content available." found.')); + + // Add some test nodes. + $default_settings = array('uid' => $this->web_user->uid, 'type' => 'article'); + $node1 = $this->drupalCreateNode($default_settings); + $node2 = $this->drupalCreateNode($default_settings); + $node3 = $this->drupalCreateNode($default_settings); + + // Change the changed time for node so that we can test ordering. + db_update('node') + ->fields(array( + 'changed' => $node1->changed + 100, + )) + ->condition('nid', $node2->nid) + ->execute(); + db_update('node') + ->fields(array( + 'changed' => $node1->changed + 200, + )) + ->condition('nid', $node3->nid) + ->execute(); + + // Test that a user without the 'access content' permission cannot + // see the block. + $this->drupalLogout(); + $this->drupalGet(''); + $this->assertNoText($block['title'], t('Block was not found.')); + + // Test that only the 2 latest nodes are shown. + $this->drupalLogin($this->web_user); + $this->assertNoText($node1->title, t('Node not found in block.')); + $this->assertText($node2->title, t('Node found in block.')); + $this->assertText($node3->title, t('Node found in block.')); + + // Check to make sure nodes are in the right order. + $this->assertTrue($this->xpath('//div[@id="block-node-recent"]/div/table/tbody/tr[position() = 1]/td/div/a[text() = "' . $node3->title . '"]'), t('Nodes were ordered correctly in block.')); + + // Set the number of recent nodes to show to 10. + $this->drupalLogout(); + $this->drupalLogin($this->admin_user); + $block = array( + 'node_recent_block_count' => 10, + ); + $this->drupalPost('admin/structure/block/manage/node/recent/configure', $block, t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block saved.')); + + // Post an additional node. + $node4 = $this->drupalCreateNode($default_settings); + + // Test that all four nodes are shown. + $this->drupalGet(''); + $this->assertText($node1->title, t('Node found in block.')); + $this->assertText($node2->title, t('Node found in block.')); + $this->assertText($node3->title, t('Node found in block.')); + $this->assertText($node4->title, t('Node found in block.')); + } +} diff --git a/profiles/standard/standard.install b/profiles/standard/standard.install index 79bb13122..bd5b8cd77 100644 --- a/profiles/standard/standard.install +++ b/profiles/standard/standard.install @@ -105,6 +105,16 @@ function standard_install() { 'pages' => '', 'cache' => -1, ), + array( + 'module' => 'node', + 'delta' => 'recent', + 'theme' => 'seven', + 'status' => 1, + 'weight' => 10, + 'region' => 'dashboard_main', + 'pages' => '', + 'cache' => -1, + ), array( 'module' => 'user', 'delta' => 'login', diff --git a/themes/seven/style.css b/themes/seven/style.css index ac5703739..148adbcdd 100644 --- a/themes/seven/style.css +++ b/themes/seven/style.css @@ -33,12 +33,7 @@ legend { font-weight: bold; } -#page h1, -#page h2, -#page h3, -#page h4, -#page h5, -#page h6 { +h1, h2, h3, h4, h5, h6 { font-weight: bold; margin: 10px 0; } @@ -125,16 +120,36 @@ abbr, acronym { border-bottom: dotted 1px; } -ul li, .item-list ul li { +ul, .block ul, .item-list ul, .item-list ul { list-style-type: disc; + list-style-image: none; margin: 0.25em 0 0.25em 1.5em; } +.item-list ul li, li.leaf, ul.menu li { + list-style-type: disc; + list-style-image: none; +} + +ul.menu li { + margin: 0; +} + ol { list-style-type: decimal; margin: 0.25em 0 0.25em 2em; } +.item-list ul li.collapsed, ul.menu li.collapsed { + list-style-image:url(../../misc/menu-collapsed.png); + list-style-type:disc; +} + +.item-list ul li.expanded, ul.menu li.expanded { + list-style-image:url(../../misc/menu-expanded.png); + list-style-type:circle; +} + quote, code { margin: .5em 0; } @@ -292,6 +307,7 @@ div.status { #branding h1.page-title { color: #000; + margin: 0; padding-bottom: 10px; font-size: 18px; font-weight: normal; @@ -396,31 +412,18 @@ ul.secondary li.active a.active { * Page layout. */ #page { - padding-bottom: 40px; + padding: 20px 0 40px 0; margin-right: 40px; margin-left: 40px; - position: relative; -} - -#page { - padding: 20px 0; background: #fff; + position: relative; color: #333; } -#page ul.menu li, -#page ul.menu li a, -#secondary-links ul.links li, -#secondary-links ul.links li a { - float: left; -} - -#page ul.menu li, #secondary-links ul.links li { padding: 0 10px 10px 0; } -#page ul.menu li a, #secondary-links ul.links li a { font-size: 9px; line-height: 10px; @@ -439,8 +442,8 @@ ul.secondary li.active a.active { background: #999; } -#page ul.links li, -#page ul.inline li { +ul.links li, +ul.inline li { padding-right: 1em; } @@ -448,22 +451,13 @@ ul.inline li { display: inline; } -#page ul.menu li a { - background: #f8f8f8; - color: #05a; -} - -#page ul.menu li a:hover { - background: #eee; -} - #secondary-links ul.links li.active-trail a, #secondary-links ul.links li a.active { background: #333; } -#page ul.node-type-list li, -#page ul.admin-list li { +ul.node-type-list li, +ul.admin-list li { position: relative; padding-left: 30px; padding-top: 9px; @@ -475,32 +469,46 @@ ul.inline li { list-style-image: none; } -#page ul.admin-list.compact { +.admin-panel .item-list ul, +ul.admin-list { + margin: 0; + padding: 0; +} + +.admin-panel .item-list ul, +ul.admin-list.compact { margin-bottom: 8px; } -#page ul.admin-list.compact li { - border: 0; +.admin-panel .item-list li, +ul.admin-list.compact li { + border: none; background: none; - margin-bottom: 2px; - padding-top: 2px; + margin: 0.25em 0 0.25em 1.5em; + padding: 0; + list-style-type: disc; } -#page ul.admin-list li:last-child { +ul.admin-list li:last-child { border-bottom: none; } -#page ul.node-type-list .label { +ul.node-type-list .label { font-size: 15px; } -#page ul.node-type-list li a, #page ul.admin-list li a { +ul.node-type-list li a, ul.admin-list li a { margin-left: -30px; padding: 0px 0 4px 30px; min-height: 0; } -#page ul.node-type-list li div.description a, #page ul.admin-list li div.description a { +ul.admin-list.compact li a { + margin-left: 0; + padding: 0; +} + +ul.node-type-list li div.description a, ul.admin-list li div.description a { margin-left: 0px; padding: 0px; min-height: inherit; @@ -708,6 +716,10 @@ div.form-item div.description { color: #666; } +ul.tips li { + margin: 0.25em 0 0.25em 1.5em; +} + body div.form-type-radio div.description, body div.form-type-checkbox div.description { margin-left: 1.5em; } @@ -716,6 +728,7 @@ body div.form-type-radio div.description, body div.form-type-checkbox div.descri input.form-submit, a.button { cursor: pointer; padding: 4px 17px; + margin-bottom: 1em; color: #5a5a5a; text-align: center; font-weight: normal; @@ -797,10 +810,6 @@ ul.action-links { overflow: hidden; } -#page ul.action-links { - padding: 0; -} - ul.action-links li { float: left; margin: 0 1em 0 0; @@ -814,7 +823,7 @@ ul.action-links a { /* Exceptions */ #diff-inline-form select, -#page div.filter-options select { +div.filter-options select { padding: 0; } @@ -840,7 +849,7 @@ div.admin-panel { border: 1px solid #ccc; } -#page div.admin-panel h3 { +div.admin-panel h3 { font-size: 12px; text-transform: uppercase; margin: 0; @@ -852,28 +861,29 @@ div.admin-panel { } /* admin/appearance */ -#page #system-themes-page h2 { +#system-themes-page h2 { font-weight: normal; text-transform: uppercase; } -#page .theme-selector h3 { +.theme-selector h3 { font-weight: normal; } -#page .theme-default h3 { +.theme-default h3 { font-weight: bold; } -#page .system-themes-list-enabled .theme-selector h3 { +.system-themes-list-enabled .theme-selector h3 { margin-top: 0; } /* admin/content and admin/people */ -#page dl.multiselect, -#page dl.multiselect dt, -#page dl.multiselect dd { +dl.multiselect, +dl.multiselect dt, +dl.multiselect dd { margin: 0 10px 0 0; } -#page dl.multiselect select { +dl.multiselect select, +dl.multiselect dd select { font-size: 12px; background: #fff; border: 1px solid #ccc; @@ -976,12 +986,13 @@ ol.task-list li.done { } .overlay .primary, .overlay #branding h1.page-title, -.overlay #page #left, -.overlay #page #footer { +.overlay #left, +.overlay #footer { display: none; } .overlay #page { margin: 0; + padding: 0 20px; } .overlay #branding div.breadcrumb { float: left; @@ -990,13 +1001,10 @@ ol.task-list li.done { } .overlay ul.secondary { background: transparent none; - margin: -2.4em 0 0; + margin: -2.4em 0 0.5em 0; padding: 3px 10px; } .overlay #content { - padding: 0 20px; -} -.overlay #page { padding: 0; } @@ -1007,8 +1015,8 @@ div.add-or-remove-shortcuts { padding-left: 6px; } -/* Blocks */ - -#page div.block h2 { - margin-top:0; +/* Dashboard */ +#dashboard div.block h2 { + margin: 0; + font-size: 1em; } diff --git a/themes/seven/vertical-tabs.css b/themes/seven/vertical-tabs.css index 4ee074fc6..9349b609c 100644 --- a/themes/seven/vertical-tabs.css +++ b/themes/seven/vertical-tabs.css @@ -21,6 +21,7 @@ div.vertical-tabs .vertical-tabs-list { width: 25%; float: left; list-style-type: none; + margin: 0; } div.vertical-tabs ul li.vertical-tab-button { -- cgit v1.2.3