- Patch #301902 by TheRec, beeradb, catch, sun, skilip, alpritt, JacobSingh, Senpai: usability improvement: allow more users to see the node admin page.
parent
fc980fa793
commit
8514a7e283
|
@ -396,7 +396,7 @@ function node_admin_content($form, $form_state) {
|
|||
'#access' => _node_add_access(),
|
||||
'#markup' => theme('links', array('links' => array(array('title' => t('Add new content'), 'href' => 'node/add')), 'attributes' => array('class' => array('action-links')))),
|
||||
);
|
||||
$form[] = node_filter_form();
|
||||
$form['filter'] = node_filter_form();
|
||||
$form['#submit'][] = 'node_filter_form_submit';
|
||||
$form['#theme'] = 'node_filter_form';
|
||||
$form['admin'] = node_admin_nodes();
|
||||
|
@ -408,6 +408,32 @@ function node_admin_content($form, $form_state) {
|
|||
* Form builder: Builds the node administration overview.
|
||||
*/
|
||||
function node_admin_nodes() {
|
||||
$admin_access = user_access('administer nodes');
|
||||
|
||||
// Build the 'Update options' form.
|
||||
$form['options'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Update options'),
|
||||
'#prefix' => '<div class="container-inline">',
|
||||
'#suffix' => '</div>',
|
||||
'#access' => $admin_access,
|
||||
);
|
||||
$options = array();
|
||||
foreach (module_invoke_all('node_operations') as $operation => $array) {
|
||||
$options[$operation] = $array['label'];
|
||||
}
|
||||
$form['options']['operation'] = array(
|
||||
'#type' => 'select',
|
||||
'#options' => $options,
|
||||
'#default_value' => 'approve',
|
||||
);
|
||||
$form['options']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Update'),
|
||||
'#validate' => array('node_admin_nodes_validate'),
|
||||
'#submit' => array('node_admin_nodes_submit'),
|
||||
);
|
||||
|
||||
// Enable language column if translation module is enabled
|
||||
// or if we have any node with language.
|
||||
$multilanguage = (module_exists('translation') || db_query("SELECT COUNT(*) FROM {node} WHERE language <> ''")->fetchField());
|
||||
|
@ -424,80 +450,125 @@ function node_admin_nodes() {
|
|||
$header['language'] = array('data' => t('Language'), 'field' => 'n.language');
|
||||
}
|
||||
$header['operations'] = array('data' => t('Operations'));
|
||||
|
||||
|
||||
$query = db_select('node', 'n')->extend('PagerDefault')->extend('TableSort');
|
||||
$query->join('users', 'u', 'n.uid = u.uid');
|
||||
node_build_filter_query($query);
|
||||
|
||||
$result = $query
|
||||
->fields('n')
|
||||
->fields('u', array('name'))
|
||||
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' => 0))->fetchCol()) {
|
||||
$query->condition(db_or()
|
||||
->condition('n.status', 1)
|
||||
->condition('n.nid', $own_unpublished, 'IN')
|
||||
);
|
||||
}
|
||||
else {
|
||||
// If not, restrict the query to published nodes.
|
||||
$query->condition('n.status', 1);
|
||||
}
|
||||
}
|
||||
$nids = $query
|
||||
->fields('n',array('nid'))
|
||||
->limit(50)
|
||||
->orderByHeader($header)
|
||||
->execute();
|
||||
|
||||
// Build the 'Update options' form.
|
||||
$form['options'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Update options'),
|
||||
'#prefix' => '<div class="container-inline">',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
$options = array();
|
||||
foreach (module_invoke_all('node_operations') as $operation => $array) {
|
||||
$options[$operation] = $array['label'];
|
||||
}
|
||||
$form['options']['operation'] = array(
|
||||
'#type' => 'select',
|
||||
'#options' => $options,
|
||||
'#default_value' => 'approve',
|
||||
);
|
||||
$options = array();
|
||||
$form['options']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Update'),
|
||||
'#submit' => array('node_admin_nodes_submit'),
|
||||
'#validate' => array('node_admin_nodes_validate'),
|
||||
);
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$nodes = node_load_multiple($nids);
|
||||
|
||||
// Prepare the list of nodes.
|
||||
$languages = language_list();
|
||||
$destination = drupal_get_destination();
|
||||
$nodes = array();
|
||||
foreach ($result as $node) {
|
||||
$l_options = empty($node->language) ? array() : array('language' => $languages[$node->language]);
|
||||
$options = array();
|
||||
foreach ($nodes as $node) {
|
||||
$l_options = !empty($node->language) ? array('language' => $languages[$node->language]) : array();
|
||||
$options[$node->nid] = array(
|
||||
'title' => array(
|
||||
'data' => array(
|
||||
'#type' => 'link',
|
||||
'#title' => $node->title,
|
||||
'#title' => $node->title[FIELD_LANGUAGE_NONE][0]['value'],
|
||||
'#href' => 'node/' . $node->nid,
|
||||
'#options' => $l_options,
|
||||
'#suffix' => ' ' . theme('mark', array('type' => node_mark($node->nid, $node->changed))),
|
||||
),
|
||||
),
|
||||
'type' => check_plain(node_type_get_name($node)),
|
||||
'type' => check_plain(node_type_get_name($node)),
|
||||
'author' => theme('username', array('account' => $node)),
|
||||
'status' => $node->status ? t('published') : t('not published'),
|
||||
'status' => $node->status ? t('published') : t('not published'),
|
||||
'changed' => format_date($node->changed, 'short'),
|
||||
);
|
||||
if ($multilanguage) {
|
||||
$options[$node->nid]['language'] = empty($node->language) ? t('Language neutral') : t($languages[$node->language]->name);
|
||||
}
|
||||
$options[$node->nid]['operations'] = array(
|
||||
'data' => array(
|
||||
'#type' => 'link',
|
||||
'#title' => t('edit'),
|
||||
'#href' => 'node/' . $node->nid . '/edit',
|
||||
'#options' => array('query' => $destination),
|
||||
),
|
||||
// Build a list of all the accessible operations for the current node.
|
||||
$operations = array();
|
||||
if (node_access('update', $node)) {
|
||||
$operations['edit'] = array(
|
||||
'title' => t('edit'),
|
||||
'href' => 'node/' . $node->nid . '/edit',
|
||||
'query' => $destination,
|
||||
);
|
||||
}
|
||||
if (node_access('delete', $node)) {
|
||||
$operations['delete'] = array(
|
||||
'title' => t('delete'),
|
||||
'href' => 'node/' . $node->nid . '/delete',
|
||||
'query' => $destination,
|
||||
);
|
||||
}
|
||||
$options[$node->nid]['operations'] = array();
|
||||
if (count($operations) > 1) {
|
||||
// Render an unordered list of operations links.
|
||||
$options[$node->nid]['operations'] = array(
|
||||
'data' => array(
|
||||
'#theme' => 'links',
|
||||
'#links' => $operations,
|
||||
'#attributes' => array('class' => array('links', 'inline')),
|
||||
),
|
||||
);
|
||||
}
|
||||
elseif (!empty($operations)) {
|
||||
// Render the first and only operation as a link.
|
||||
$link = reset($operations);
|
||||
$options[$node->nid]['operations'] = array(
|
||||
'data' => array(
|
||||
'#type' => 'link',
|
||||
'#title' => $link['title'],
|
||||
'#href' => $link['href'],
|
||||
'#options' => array('query' => $link['query']),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Only use a tableselect when the current user is able to perform any
|
||||
// operations.
|
||||
if ($admin_access) {
|
||||
$form['nodes'] = array(
|
||||
'#type' => 'tableselect',
|
||||
'#header' => $header,
|
||||
'#options' => $options,
|
||||
'#empty' => t('No content available.'),
|
||||
);
|
||||
}
|
||||
$form['nodes'] = array(
|
||||
'#type' => 'tableselect',
|
||||
'#header' => $header,
|
||||
'#options' => $options,
|
||||
'#empty' => t('No content available.'),
|
||||
);
|
||||
// Otherwise, use a simple table.
|
||||
else {
|
||||
// Display an empty message like in the tableselect.
|
||||
if (empty($options)) {
|
||||
$row = array(
|
||||
'data' => t('No content available.'),
|
||||
'colspan' => count($header),
|
||||
);
|
||||
$options = array($row);
|
||||
}
|
||||
$form['nodes'] = array(
|
||||
'#theme' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $options,
|
||||
);
|
||||
}
|
||||
|
||||
$form['pager'] = array('#markup' => theme('pager', array('tags' => NULL)));
|
||||
return $form;
|
||||
}
|
||||
|
|
|
@ -555,6 +555,16 @@ function node_update_7007() {
|
|||
db_drop_field('node_type', 'min_word_count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the 'administer nodes' permission from 'access content overview'.
|
||||
*/
|
||||
function node_update_7008() {
|
||||
$roles = user_roles(FALSE, 'administer nodes');
|
||||
foreach ($roles as $rid => $role) {
|
||||
user_role_grant_permissions($rid, array('access content overview'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup updates-6.x-to-7.x"
|
||||
* The next series of updates should start at 8000.
|
||||
|
|
|
@ -1391,6 +1391,10 @@ function node_permission() {
|
|||
'title' => t('Access content'),
|
||||
'description' => t('View published content.'),
|
||||
),
|
||||
'access content overview' => array(
|
||||
'title' => t('Access content overview'),
|
||||
'description' => t('Access the content overview page.'),
|
||||
),
|
||||
'bypass node access' => array(
|
||||
'title' => t('Bypass node access'),
|
||||
'description' => t('View, edit and delete all site content. Users with this permission will bypass any content-related access control. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
|
||||
|
@ -1741,7 +1745,7 @@ function node_menu() {
|
|||
'description' => 'Find and manage content.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('node_admin_content'),
|
||||
'access arguments' => array('administer nodes'),
|
||||
'access arguments' => array('access content overview'),
|
||||
'weight' => -10,
|
||||
'file' => 'node.admin.inc',
|
||||
);
|
||||
|
|
|
@ -950,57 +950,118 @@ class NodeAccessRebuildTestCase extends DrupalWebTestCase {
|
|||
* Test node administration page functionality.
|
||||
*/
|
||||
class NodeAdminTestCase extends DrupalWebTestCase {
|
||||
protected $admin_user;
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Node administration',
|
||||
'description' => 'Test node administration page functionality.',
|
||||
'group' => 'Node'
|
||||
'group' => 'Node',
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
$this->admin_user = $this->drupalCreateUser(array('administer nodes', 'create article content', 'create page content'));
|
||||
$this->drupalLogin($this->admin_user);
|
||||
|
||||
// Remove the "view own unpublished content" permission which is set
|
||||
// by default for authenticated users so we can test this permission
|
||||
// correctly.
|
||||
user_role_revoke_permissions(DRUPAL_AUTHENTICATED_RID, array('view own unpublished content'));
|
||||
|
||||
$this->admin_user = $this->drupalCreateUser(array('access administration pages', 'access content overview', 'administer nodes', 'bypass node access'));
|
||||
$this->base_user_1 = $this->drupalCreateUser(array('access content overview'));
|
||||
$this->base_user_2 = $this->drupalCreateUser(array('access content overview', 'view own unpublished content'));
|
||||
$this->base_user_3 = $this->drupalCreateUser(array('access content overview', 'bypass node access'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 3 nodes and test if they are listed on the node admistration page.
|
||||
* Tests content overview with different user permissions.
|
||||
*/
|
||||
function testNodeAdmin() {
|
||||
$this->drupalPost('admin/content', array(), t('Update'));
|
||||
$this->assertText(t('No items selected.'), t('Clicking update with no nodes displays error message on the node administration listing.'));
|
||||
function testContentAdminPages() {
|
||||
$this->drupalLogin($this->admin_user);
|
||||
|
||||
$node1 = $this->drupalCreateNode(array('type' => 'article', 'status' => 1));
|
||||
$node2 = $this->drupalCreateNode(array('type' => 'article', 'status' => 0));
|
||||
$node3 = $this->drupalCreateNode(array('type' => 'page'));
|
||||
$nodes['published_page'] = $this->drupalCreateNode(array('type' => 'page'));
|
||||
$nodes['published_article'] = $this->drupalCreateNode(array('type' => 'article'));
|
||||
$nodes['unpublished_page_1'] = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->base_user_1->uid, 'status' => 0));
|
||||
$nodes['unpublished_page_2'] = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->base_user_2->uid, 'status' => 0));
|
||||
|
||||
// Verify view, edit, and delete links for any content.
|
||||
$this->drupalGet('admin/content');
|
||||
$this->assertText($node1->title[FIELD_LANGUAGE_NONE][0]['value'], t('Node appears on the node administration listing.'));
|
||||
$this->assertResponse(200);
|
||||
foreach ($nodes as $node) {
|
||||
$this->assertLinkByHref('node/' . $node->nid);
|
||||
$this->assertLinkByHref('node/' . $node->nid . '/edit');
|
||||
$this->assertLinkByHref('node/' . $node->nid . '/delete');
|
||||
// Verify tableselect.
|
||||
$this->assertFieldByName('nodes[' . $node->nid . ']', '', t('Tableselect found.'));
|
||||
}
|
||||
|
||||
$this->drupalPost('admin/content', array(), t('Update'));
|
||||
$this->assertText(t('No items selected.'), t('Clicking update with no selected nodes displays error message on the node administration listing.'));
|
||||
|
||||
// Filter the node listing by status.
|
||||
// Verify filtering by publishing status.
|
||||
$edit = array(
|
||||
'status' => 'status-1',
|
||||
);
|
||||
$this->drupalPost('admin/content', $edit, t('Filter'));
|
||||
$this->assertRaw(t('<strong>%type</strong> is <strong>%value</strong>', array('%type' => t('status'), '%value' => t('published'))), t('The node administration listing is filtered by status.'));
|
||||
$this->assertText($node1->title[FIELD_LANGUAGE_NONE][0]['value'], t('Published node appears on the node administration listing.'));
|
||||
$this->assertNoText($node2->title[FIELD_LANGUAGE_NONE][0]['value'], t('Unpublished node does not appear on the node administration listing.'));
|
||||
$this->drupalPost(NULL, $edit, t('Filter'));
|
||||
$this->assertRaw(t('<strong>%type</strong> is <strong>%value</strong>', array('%type' => t('status'), '%value' => t('published'))), t('Content list is filtered by status.'));
|
||||
$this->assertLinkByHref('node/' . $nodes['published_page']->nid . '/edit');
|
||||
$this->assertLinkByHref('node/' . $nodes['published_article']->nid . '/edit');
|
||||
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit');
|
||||
|
||||
// Filter the node listing by content type.
|
||||
// Verify filtering by status and content type.
|
||||
$edit = array(
|
||||
'type' => 'article',
|
||||
'type' => 'page',
|
||||
);
|
||||
$this->drupalPost('admin/content', $edit, t('Refine'));
|
||||
$this->assertRaw(t('<strong>%type</strong> is <strong>%value</strong>', array('%type' => t('status'), '%value' => t('published'))), t('The node administration listing is filtered by status.'));
|
||||
$this->assertRaw(t('<strong>%type</strong> is <strong>%value</strong>', array('%type' => t('type'), '%value' => 'Article')), t('The node administration listing is filtered by content type.'));
|
||||
$this->assertText($node1->title[FIELD_LANGUAGE_NONE][0]['value'], t('Article node appears on the node administration listing.'));
|
||||
$this->assertNoText($node3->title[FIELD_LANGUAGE_NONE][0]['value'], t('Page node does not appear on the node administration listing.'));
|
||||
$this->drupalPost(NULL, $edit, t('Refine'));
|
||||
$this->assertRaw(t('<strong>%type</strong> is <strong>%value</strong>', array('%type' => t('status'), '%value' => t('published'))), t('Content list is filtered by status.'));
|
||||
$this->assertRaw(t('<strong>%type</strong> is <strong>%value</strong>', array('%type' => t('type'), '%value' => 'Page')), t('Content list is filtered by content type.'));
|
||||
$this->assertLinkByHref('node/' . $nodes['published_page']->nid . '/edit');
|
||||
$this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/edit');
|
||||
|
||||
// Verify no operation links are displayed for regular users.
|
||||
$this->drupalLogout();
|
||||
$this->drupalLogin($this->base_user_1);
|
||||
$this->drupalGet('admin/content');
|
||||
$this->assertResponse(200);
|
||||
$this->assertLinkByHref('node/' . $nodes['published_page']->nid);
|
||||
$this->assertLinkByHref('node/' . $nodes['published_article']->nid);
|
||||
$this->assertNoLinkByHref('node/' . $nodes['published_page']->nid . '/edit');
|
||||
$this->assertNoLinkByHref('node/' . $nodes['published_page']->nid . '/delete');
|
||||
$this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/edit');
|
||||
$this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/delete');
|
||||
|
||||
// Verify no unpublished content is displayed without permission.
|
||||
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid);
|
||||
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit');
|
||||
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/delete');
|
||||
|
||||
// Verify no tableselect.
|
||||
$this->assertNoFieldByName('nodes[' . $nodes['published_page']->nid . ']', '', t('No tableselect found.'));
|
||||
|
||||
// Verify unpublished content is displayed with permission.
|
||||
$this->drupalLogout();
|
||||
$this->drupalLogin($this->base_user_2);
|
||||
$this->drupalGet('admin/content');
|
||||
$this->assertResponse(200);
|
||||
$this->assertLinkByHref('node/' . $nodes['unpublished_page_2']->nid);
|
||||
// Verify no operation links are displayed.
|
||||
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_2']->nid . '/edit');
|
||||
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_2']->nid . '/delete');
|
||||
|
||||
// Verify user cannot see unpublished content of other users.
|
||||
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid);
|
||||
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit');
|
||||
$this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/delete');
|
||||
|
||||
// Verify no tableselect.
|
||||
$this->assertNoFieldByName('nodes[' . $nodes['unpublished_page_2']->nid . ']', '', t('No tableselect found.'));
|
||||
|
||||
// Verify node access can be bypassed.
|
||||
$this->drupalLogout();
|
||||
$this->drupalLogin($this->base_user_3);
|
||||
$this->drupalGet('admin/content');
|
||||
$this->assertResponse(200);
|
||||
foreach ($nodes as $node) {
|
||||
$this->assertLinkByHref('node/' . $node->nid);
|
||||
$this->assertLinkByHref('node/' . $node->nid . '/edit');
|
||||
$this->assertLinkByHref('node/' . $node->nid . '/delete');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1763,6 +1763,46 @@ class DrupalWebTestCase extends DrupalTestCase {
|
|||
return $this->assert(empty($links), $message, $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass if a link containing a given href (part) is found.
|
||||
*
|
||||
* @param $href
|
||||
* The full or partial value of the 'href' attribute of the anchor tag.
|
||||
* @param $index
|
||||
* Link position counting from zero.
|
||||
* @param $message
|
||||
* Message to display.
|
||||
* @param $group
|
||||
* The group this message belongs to, defaults to 'Other'.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the assertion succeeded, FALSE otherwise.
|
||||
*/
|
||||
protected function assertLinkByHref($href, $index = 0, $message = '', $group = 'Other') {
|
||||
$links = $this->xpath('//a[contains(@href, "' . $href . '")]');
|
||||
$message = ($message ? $message : t('Link containing href %href found.', array('%href' => $href)));
|
||||
return $this->assert(isset($links[$index]), $message, $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass if a link containing a given href (part) is not found.
|
||||
*
|
||||
* @param $href
|
||||
* The full or partial value of the 'href' attribute of the anchor tag.
|
||||
* @param $message
|
||||
* Message to display.
|
||||
* @param $group
|
||||
* The group this message belongs to, defaults to 'Other'.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the assertion succeeded, FALSE otherwise.
|
||||
*/
|
||||
protected function assertNoLinkByHref($href, $message = '', $group = 'Other') {
|
||||
$links = $this->xpath('//a[contains(@href, "' . $href . '")]');
|
||||
$message = ($message ? $message : t('No link containing href %href found.', array('%href' => $href)));
|
||||
return $this->assert(empty($links), $message, $group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Follows a link by name.
|
||||
*
|
||||
|
|
|
@ -213,7 +213,7 @@ class TrackerTest extends DrupalWebTestCase {
|
|||
* Test that publish/unpublish works at admin/content/node
|
||||
*/
|
||||
function testTrackerAdminUnpublish() {
|
||||
$admin_user = $this->drupalCreateUser(array('administer nodes'));
|
||||
$admin_user = $this->drupalCreateUser(array('access content overview', 'administer nodes', 'bypass node access'));
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$node = $this->drupalCreateNode(array(
|
||||
|
|
Loading…
Reference in New Issue