you created and still have at least view access to, filtered by publication status.'); break; case 'accessible-content/i-created/all': $s = t('Showing all content you created and still have at least view access to.'); break; case 'accessible-content': // default case 'accessible-content/i-last-modified': case 'accessible-content/i-last-modified/published': case 'accessible-content/i-last-modified/not-published': $s = t('Showing all content you last modified and still have at least view access to, filtered by publication status.'); break; case 'accessible-content/i-last-modified/all': $s = t('Showing all content you last modified and still have at least view access to.'); break; case 'accessible-content': case 'accessible-content/i-can-edit': case 'accessible-content/i-can-edit/published': case 'accessible-content/i-can-edit/not-published': $s = t('Showing all content you can edit, filtered by publication status.'); break; case 'accessible-content/i-can-edit/all': $s = t('Showing all content you can edit.'); break; case 'accessible-content/i-can-view': case 'accessible-content/i-can-view/published': case 'accessible-content/i-can-view/not-published': $s = t('Showing all content you have at least view access to, filtered by publication status.'); break; case 'accessible-content/i-can-view/all': $s = t('Showing all content you have at least view access to.'); break; } return empty($s) ? '' : '
'. $s .'
'; } /** * Implementation of hook_perm(). */ function module_grants_perm() { return array("access 'I created' tab", "access 'I last modified' tab", "access 'I can edit' tab", "access 'I can view' tab", "access 'Published' tab", "access 'Unpublished' tab", "access 'All' tab"); } /** * Implementation of hook_menu(). * * Define new menu items. * Existing menu items are modified through hook_menu_alter(). */ function module_grants_menu() { $items = array(); // Create an 'Acessible content' menu item in the navigation menu. // Add tabs: 'I created', 'I last modified', 'I can edit', 'I can view', each // with sub tabs 'Published', 'Unpublished', 'All' $items['accessible-content'] = array( 'title' => 'Accessible content', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '1', I_LAST_MODIFIED), // i-last-modified/published 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I last modified' tab", "access 'Published' tab")), 'weight' => 2 ); // 'I created' and sub tabs $items['accessible-content/i-created'] = array( 'title' => 'I created', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '1', I_CREATED), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I created' tab", "access 'Published' tab")), 'type' => MENU_LOCAL_TASK, 'weight' => 0 ); $items['accessible-content/i-created/published'] = array( 'title' => 'Published', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '1', I_CREATED), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I created' tab", "access 'Published' tab")), // smart_tabs module handles dynamic setting of default 'type' => module_exists("smart_tabs") ? MENU_LOCAL_TASK : MENU_DEFAULT_LOCAL_TASK, ); $items['accessible-content/i-created/not-published'] = array( 'title' => 'Unpublished', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '0', I_CREATED), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I created' tab", "access 'Unpublished' tab")), 'type' => MENU_LOCAL_TASK, 'weight' => 1 ); $items['accessible-content/i-created/all'] = array( 'title' => 'All', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '-1', I_CREATED), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I created' tab", "access 'All' tab")), 'type' => MENU_LOCAL_TASK, 'weight' => 5 ); // 'I last modified' and sub tabs $items['accessible-content/i-last-modified'] = array( 'title' => 'I last modified', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '1', I_LAST_MODIFIED), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I last modified' tab", "access 'Published' tab")), 'type' => module_exists("smart_tabs") ? MENU_LOCAL_TASK : MENU_DEFAULT_LOCAL_TASK, 'weight' => 5 ); $items['accessible-content/i-last-modified/published'] = array( 'title' => 'Published', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '1', I_LAST_MODIFIED), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I last modified' tab", "access 'Published' tab")), 'type' => module_exists("smart_tabs") ? MENU_LOCAL_TASK : MENU_DEFAULT_LOCAL_TASK, ); $items['accessible-content/i-last-modified/not-published'] = array( 'title' => 'Unpublished', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '0', I_LAST_MODIFIED), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I last modified' tab", "access 'Unpublished' tab")), 'type' => MENU_LOCAL_TASK, 'weight' => 1 ); $items['accessible-content/i-last-modified/all'] = array( 'title' => 'All', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '-1', I_LAST_MODIFIED), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I last modified' tab", "access 'All' tab")), 'type' => MENU_LOCAL_TASK, 'weight' => 5 ); // 'I can edit' and sub tabs $items['accessible-content/i-can-edit'] = array( 'title' => 'I can edit', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('update', '1'), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I can edit' tab", "access 'Published' tab")), 'type' => MENU_LOCAL_TASK, 'weight' => 10 ); $items['accessible-content/i-can-edit/published'] = array( 'title' => 'Published', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('update', '1'), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I can edit' tab", "access 'Published' tab")), 'type' => module_exists("smart_tabs") ? MENU_LOCAL_TASK : MENU_DEFAULT_LOCAL_TASK, ); $items['accessible-content/i-can-edit/not-published'] = array( 'title' => 'Unpublished', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('update', '0'), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I can edit' tab", "access 'Unpublished' tab")), 'type' => MENU_LOCAL_TASK, 'weight' => 1 ); $items['accessible-content/i-can-edit/all'] = array( 'title' => 'All', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('update', '-1'), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I can edit' tab", "access 'All' tab")), 'type' => MENU_LOCAL_TASK, 'weight' => 5 ); // 'I can view' and sub tabs $items['accessible-content/i-can-view'] = array( 'title' => 'I can view', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '1'), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I can view' tab", "access 'Published' tab")), 'type' => MENU_LOCAL_TASK, 'weight' => 15 ); $items['accessible-content/i-can-view/published'] = array( 'title' => 'Published', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '1'), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I can view' tab", "access 'Published' tab")), 'type' => module_exists("smart_tabs") ? MENU_LOCAL_TASK : MENU_DEFAULT_LOCAL_TASK, ); $items['accessible-content/i-can-view/not-published'] = array( 'title' => 'Unpublished', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '0'), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I can view' tab", "access 'Unpublished' tab")), 'type' => MENU_LOCAL_TASK, 'weight' => 1 ); $items['accessible-content/i-can-view/all'] = array( 'title' => 'All', 'page callback' => 'show_accessible_content_summary', 'page arguments' => array('view', '-1'), 'access callback' => 'user_all_access', 'access arguments' => array(array("access 'I can view' tab", "access 'All' tab")), 'type' => MENU_LOCAL_TASK, 'weight' => 5 ); $items['admin/settings/module_grants'] = array( 'title' => 'Module grants', 'description' => 'Configure how multiple node access modules interact.', 'page callback' => 'drupal_get_form', 'page arguments' => array('module_grants_admin_settings'), 'access arguments' => array('administer site configuration'), ); return $items; } /** * Return TRUE only if the user account has ALL of the supplied permissions. * * @param $permissions * An array of permissions (strings) * @param $account * The user account object. Defaults to the logged-in user if omitted. * @return bool */ function user_all_access($permissions, $account = NULL) { foreach ($permissions as $permission) { if (!user_access($permission, $account)) { return FALSE; } } return TRUE; } /** * Return if the user account has at least one of the supplied permissions. * * @param $permissions * An array of permissions (strings) * @param $account * The user account object. Defaults to the logged-in user if omitted. * @return bool */ function user_any_access($permissions, $account = NULL) { foreach ($permissions as $permission) { if (user_access($permission, $account)) { return TRUE; } } return FALSE; } /** * Return as a themed table a content summary of the site filtered by the * access rights of the logged-in user. * * @param $access * one of 'view', 'update' or 'delete' * @param $is_published * 1 for published-only, 0 for unpublished-only, -1 for both * @param $user_filter * One of NO_FILTER, I_CREATED or I_LAST_MODIFIED * @return * themed HTML */ function show_accessible_content_summary($access = 'view', $is_published = -1, $user_filter = NO_FILTER, $is_moderated = NO_FILTER) { global $user; $nodes = get_nodes($access, (int)$is_published, $user_filter == I_CREATED ? $user->uid : -1, $user_filter == I_LAST_MODIFIED ? $user->uid : -1, (int)$is_moderated); return theme('nodes_summary', $nodes); } /** * Menu callback for admin settings. */ function module_grants_admin_settings() { $form['module_grants_multiple_modules'] = array( '#type' => 'fieldset', '#title' => t('Configure behaviour when multiple content access modules are enabled'), ); $form['module_grants_multiple_modules']['module_grants_lenient'] = array( '#type' => 'checkbox', '#title' => t('Interpret absence of access grants as a "don\'t care", rather than a "deny access".'), '#default_value' => variable_get('module_grants_lenient', TRUE), '#description' => t('Only applies when two or more content access modules are enabled and one of the modules makes no statement about a node that is about to be accessed. If this box is checked, then a content access module saying nothing (via the node_access table) about the node in question will be deemed to be ok with the user having access to it. If not checked (i.e. "strict"), then a module saying nothing will be taken as a "deny access".') ); $form['module_grants_multiple_modules']['module_grants_OR_modules'] = array( '#type' => 'checkbox', '#title' => t('For published content, behave like core, that is OR as opposed to AND the access grants from multiple modules.'), '#default_value' => variable_get('module_grants_OR_modules', FALSE), '#description' => t('Note that normally core behaviour is not what you want -- it is the reason Module Grants was written in the first place.') ); return system_settings_form($form); } /** * Implementation of hook_menu_alter(). * * Modify menu items defined in other modules (in particular the node module). */ function module_grants_menu_alter(&$items) { // As module_grants_node_access() fixes the problem of grants not being // checked when a node isn't published, all node access menu links are // altered to use this function. // For normal view/edit/delete operations module_grant_node_access() is // called directly, for the revision-specific operations the function is // called via mode_grants_node_revision_access(). $items['node/%node']['access callback'] = 'module_grants_node_access'; $items['node/%node/view']['access callback'] = 'module_grants_node_access'; $items['node/%node/view']['access arguments'] = array('view', 1); // don't remove! $items['node/%node/view']['title callback'] = '_title_for_view_tab'; $items['node/%node/view']['title arguments'] = array(1); $items['node/%node/edit']['access callback'] = 'module_grants_node_access'; $items['node/%node/edit']['title callback'] = '_title_for_edit_tab'; $items['node/%node/edit']['title arguments'] = array(1); $items['node/%node/delete']['access callback'] = 'module_grants_node_access'; $items['node/%node/revisions']['access callback'] = 'module_grants_node_revision_access'; $items['node/%node/revisions']['access arguments'] = array(array('view revision summary', 'view own content revisions'), 1); // Point /%node/revisions/%/view page to same callback as /%node/view for a // consistent view of current and other revisions $items['node/%node/revisions/%/view']['page callback'] = 'node_page_view'; // as used by /%node/view $items['node/%node/revisions/%/view']['page arguments'] = array(1); $items['node/%node/revisions/%/view']['access callback'] = 'module_grants_node_revision_access'; $items['node/%node/revisions/%/view']['access arguments'] = array(array('view revisions', 'view own content revisions'), 1); $items['node/%node/revisions/%/delete']['access callback'] = 'module_grants_node_revision_access'; $items['node/%node/revisions/%/delete']['access arguments'] = array('delete revisions', 1); $items['node/%node/revisions/%/revert']['access callback'] = 'module_grants_node_revision_access'; $items['node/%node/revisions/%/revert']['access arguments'] = array('revert revisions', 1); } /** * Menu options dealing with revisions have their revision-specific * permission checked before being tested for the associated node-specific * operation. * Return a boolean indicating whether the current user has the requested * permission AND access to the given node (regardless of its published * status). * * @param $revision_permission * The requested permission, as a string eg 'delete revisions' * @param $node * Node object for which access right is requested * @return * TRUE when the current user has the requested access to the supplied node */ function module_grants_node_revision_access($revision_permissions, $node) { // Map revision-permission to node access operation used in grants switch (true) { // Added case for allowing users to view revisions for nodes that they // are the orginial author. case in_array('view own content revisions',$revision_permissions): // Don't show Revisions local task if there's only one revision if (get_number_of_revisions($node->nid) <= 1) { return FALSE; } if (user_access('view own content revisions')) { $revision_permission = 'view own content revisions'; $op = 'view'; } break; // Added case for allowing users to view revisions for nodes that they // have 'view revisions' permission for. case in_array('view revisions',$revision_permissions): // Don't show Revisions local task if there's only one revision if (get_number_of_revisions($node->nid) <= 1) { return FALSE; } // Don't show Revisions local task if there's only one revision if (user_access('view revisions')) { $revision_permission = 'view revisions'; $op = 'view'; } break; //case 'view revision summary': case in_array('view revision summary',$revision_permissions): // Don't show Revisions local task if there's only one revision if (get_number_of_revisions($node->nid) <= 1) { return FALSE; } $revision_permission = 'view revisions'; $op = 'view'; break; //case 'edit revisions': case in_array('edit revisions',$revision_permissions): $op = 'update'; // 'edit revisions' when it's the current revision is an old-style 'edit' if ($node->vid == get_current_revision_id($node->nid)) { return module_grants_node_access($op, $node); } break; //case 'delete revisions': case in_array('delete revisions',$revision_permissions): $op = 'delete'; // 'delete revision' when there's only one is a 'delete' (node) if (get_number_of_revisions($node->nid) == 1) { return module_grants_node_access($op, $node); } break; default: // Not so elegant: checking for permissions that apply to Revisioning // Note that 'revert revisions' is a core permission, not related to // revision moderation and therefore not part of this check. if (!user_access('administer nodes') && in_array($revision_permission, array('publish revisions', 'unpublish current revision')) && !is_moderated($node->type)) { return FALSE; } $op = 'view'; break; } return user_access($revision_permission) && module_grants_node_access($op, $node); } function module_grants_form_alter(&$form, &$form_state, $form_id) { if (isset($form['#id']) && $form['#id'] == 'node-form') { $nid = $form['#node']->nid; if (isset($form['buttons']['delete']) && get_number_of_revisions($nid) > 1) { // Special treatment for Delete button when there's multiple revisions. // Make it obvious to the user that a 'Delete' is in fact 'Delete all'. $form['buttons']['delete']['#value'] = 'Delete all revisions'; } } } /** * Implementation of hook_form_FORM_ID_alter(), see node.pages.inc/node_delete_confirm() * This is called when 'Delete' or 'Delete all revisions' is pressed */ function module_grants_form_node_delete_confirm_alter(&$form, &$form_state) { if (user_any_access(module_grants_perm())) { $form['#redirect'] = 'accessible-content'; } } /** * Get the id of the current revision that the supplied node is pointing to. * Used in cases where the node object wasn't fully loaded or was loaded * with a different revision. * * @param $nid * The id of the node whose current revision id is to be returned. * @return * A single number being the current revision id (vid). */ function get_current_revision_id($nid) { return db_result(db_query('SELECT vid FROM {node} WHERE nid=%d', $nid)); } /** * Get the number of revisions belonging to a node. * @param * $nid, id of the node * @return * A count representing the number of revisions associated with the node */ function get_number_of_revisions($nid) { return db_result(db_query("SELECT COUNT(vid) FROM {node_revisions} WHERE nid=%d", $nid)); } /** * Return whether the supplied content type is subject to moderation. * * @param $content_type * i.e. $node->type * @return * TRUE, if the supplied type has the "New revision in moderation" box ticked * on the Content management>>Content types>>edit page (see Revisioning) */ function is_moderated($content_type) { return empty($content_type) ? FALSE : in_array('revision_moderation', variable_get('node_options_'. $content_type, array())); } /** * Similar to node_access() in node.module but ANDs rather than ORs grants * together on a per module base to create more natural behaviour. * Also makes sure that published and unpublished content are treated * in the same way, i.e. that grants are checked in either case. * * @param $op * One of 'view', 'update' or 'delete'. 'create' isn't used. * @param $node * The node for which the supplied operation is checked * @param $account * user object, use NULL or omit for current user * @return * FALSE if the supplied operation isn't permitted on the node */ function module_grants_node_access($op, $node, $account = NULL) { global $user; if (!$node) { return FALSE; } // If the node is in a restricted format, disallow editing. if ($op == 'update' && !filter_access($node->format)) { return FALSE; } // If no user object is supplied, the access check is for the current user. if (empty($account)) { $account = $user; } if (user_access('administer nodes', $account)) { return TRUE; } if (!user_access('access content', $account)) { return FALSE; } $module = node_get_types('module', $node); if ($module == 'node') { $module = 'node_content'; } $access = module_invoke($module, 'access', $op, $node, $account); if (!is_null($access)) { //drupal_set_message("'$op' access=$access by $module: '$node->title'", 'warning'); return $access; } // If the node is NOT published and user does not have the "view revisions" // permission, deny access, UNLESS it's the author viewing their only revision /* if (!$node->status && !user_access('view revisions', $account) && !($op == 'view' && $account->uid == $node->uid && $account->uid > 0 && get_number_of_revisions($node->nid) == 1)) { return FALSE; } */ // If the node is NOT published and user does not have the "view revisions" // permission, deny access, UNLESS it's the author viewing their own revisions if (!$node->status && !user_access('view revisions', $account) && !($op == 'view' && $account->uid == $node->uid && $account->uid > 0 && user_access('view own content revisions', $account))) { return FALSE; } // If the module neither allows nor denies access, then find grants amongst // modules that implement hook_node_grants(). // If module_grants_lenient is set, then a content access module that has // nothing to say about the node in question will be deemed to be ok with // $account having access to $node. // If module_grants_lenient isn't set and a content access module has nothing // to say about the node in question this will be taken as a 'deny access'. $nid = variable_get('module_grants_lenient', TRUE) ? $node->nid : NULL; $all_grants = grants_by_module($op, $account, $nid); $base_sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid=0 OR nid=%d) AND ((gid=0 AND realm='all')"; if (count($all_grants) == 0) { // no module implements hook_node_grants() $sql = "$base_sql) AND grant_$op >=1"; $result = db_result(db_query($sql, $node->nid)); } $or_modules = variable_get('module_grants_OR_modules', FALSE); foreach ($all_grants as $module => $module_grants) { $sql = $base_sql . (empty($module_grants) ? "" : " OR ($module_grants)") .") AND grant_$op >=1"; // Effectively AND module_grants together by breaking loop as soon as one fails // A single SQL statement may be slightly quicker but won't tells us // which of the modules denied access. This is useful debug feedback. $result = db_result(db_query($sql, $node->nid)); //drupal_set_message("'$op' access=$result by $module-grants: '$node->title'", 'warning'); if ($or_modules) { if ($result > 0) { // OR module grants together: break as soon as one succeeds break; } } elseif ($result == 0) { // AND module grants together: break as soon as one fails break; } } return $result; } /** * Return a map, keyed by module name, of SQL clauses representing the grants * associated with the module, as returned by that module's hook_node_grants(). * * @param $op * The operation, i.e 'view', 'update' or 'delete' * @param $account * User account object * @param $nid * Optional. If passed in, only modules with at least one row in the * node_acces table for the supplied nid are included (lenient interpretation * of absence of node grants). If not passed in, then all modules implementing * hook_node_grants() will be included (strict). * @return * An array of module grants SQL, keyed by module name */ function grants_by_module($op, $account, $nid = NULL) { $hook = 'node_grants'; $all_grants = array(); foreach (module_implements($hook) as $module) { $module_grants = module_invoke($module, $hook, $account, $op); if (!empty($module_grants)) { // If a nid has been passed in, don't collect the grants for this module // unless it has at least one row in the node_access table for this nid. if ($nid) { $count = db_result(db_query("SELECT COUNT(*) FROM {node_access} WHERE nid=%d AND realm IN ('". implode("','", array_keys($module_grants)) ."')", $nid)); if ($count == 0) { // Module doesn't have a node_access row for this node, so continue // to next module. continue; } } $module_gids = array(); foreach ($module_grants as $realm => $gids) { foreach ($gids as $gid) { $module_gids[] = "(gid=$gid AND realm='$realm')"; } } // Within a module OR the gid/realm combinations together $all_grants[$module] = implode(' OR ', $module_gids); } } return $all_grants; } /** * Retrieve a list of nodes or revisions accessible to the logged-in user via * the supplied operation. * * @param $op * Operation, one of 'view', 'update' or 'delete' * @param $is_published * 1 to return only published content * 0 to return only content that isn't published * -1 (default) no filter, return content regardles of publication status * @param $creator_uid * Only return content created by the user with the supplied id. * Defaults to -1, which means don't care who the creator is. * @param $modifier_uid * Only return content last modified by the user with the supplied id. * Defaults to -1, which means don't care who last modifed the node. * @param $is_moderated * TRUE to return only content of types that are subject to moderation * FALSE to return only content that isn't subject to moderation * -1 (default) no filter, return content regardles of moderation flag * @param $is_pending * Boolean indicating whether only nodes pending publication should be * returned; a pending node is defined as a node that has a revision newer * than the current OR a single revision that is not published. * @param $max * Maximum number of nodes to be returned, defaults to 1000 * @param $show_message * Whether an informational message re publish permission should be shown * @return * An array of node objects each containing nid, content type, published flag, * creator-id, title+vid+modifier-id+timestamp of the current revision, plus taxonomy * term(s) and workflow state, if these modules are installed and enabled. * * @todo * Allow paging, improve performance */ function get_nodes($op, $is_published = -1, $creator_uid = -1, $modifier_uid = -1, $is_moderated = -1, $is_pending = FALSE, $max = 1000, $show_message = TRUE, $order_by_override = NULL) { //drupal_set_message("op='$op', is_published='$is_published', creator=$creator_uid, modifier=$modifier_uid, is_moderated='$is_moderated', is_pending='$is_pending', max=$max, show_msg='$show_message', order=$order_by_override", 'warning'); $sql_select = 'SELECT n.nid, r.vid, n.uid AS creator_uid, r.uid, n.type, n.status, r.title, r.timestamp'; $sql_from = ' FROM {node} n INNER JOIN {node_revisions} r '. ($is_pending ? 'ON n.nid=r.nid' : 'ON n.vid=r.vid'); $sql_where = ($is_published < 0) ? '' : " WHERE n.status=$is_published"; if ($creator_uid >= 0) { $sql_where = empty($sql_where) ? " WHERE n.uid=$creator_uid" : $sql_where ." AND n.uid=$creator_uid"; } if ($modifier_uid >= 0) { $sql_where = empty($sql_where) ? " WHERE r.uid=$modifier_uid" : $sql_where ." AND r.uid=$modifier_uid"; } if ($is_pending) { $sql_where = empty($sql_where) ? ' WHERE' : $sql_where .' AND'; $sql_where .= ' (r.vid>n.vid OR (n.status=0 AND (SELECT COUNT(vid) FROM {node_revisions} WHERE nid=n.nid)=1))'; } $sql_order = " ORDER BY " . (empty($order_by_override) ? _extract_order_clause_from_URI() : $order_by_override); $include_taxonomy_terms = module_exists('taxonomy'); $include_workflow_state = module_exists('workflow'); if ($include_taxonomy_terms) { $sql_select .= ', td.name AS term'; $sql_from .= ' LEFT JOIN {term_node} tn ON n.vid=tn.vid LEFT JOIN {term_data} td ON tn.tid=td.tid'; } if ($include_workflow_state) { $sql_select .= ', ws.state'; $sql_from .= ' LEFT JOIN {workflow_node} wn ON wn.nid=n.nid LEFT JOIN {workflow_states} ws ON wn.sid=ws.sid'; } $sql = $sql_select . $sql_from . $sql_where . $sql_order; $node_query_result = db_query_range($sql, 0, $max); $nodes = array(); while ($node = db_fetch_object($node_query_result)) { $filter = ($is_moderated < 0) || $is_moderated == is_moderated($node->type); if ($filter && module_grants_node_access($op, $node)) { // @todo rework into a single query from hell? if (empty($nodes[$node->nid])) { $nodes[$node->nid] = $node; } elseif ($include_taxonomy_terms && !empty($node->term)) { // If a node has more than one taxonomy term, these will be returned by // the query as seperate objects differing only in their terms. $existing_node = $nodes[$node->nid]; $existing_node->term .= '/'. $node->term; } } } if (!user_access('administer nodes') && $show_message && !empty($nodes)) { _set_info_message($is_published, $is_pending); } return $nodes; } function _set_info_message($is_published, $is_pending) { $moderated_types = array(); foreach (node_get_types() as $type) { if (is_moderated($type->type)) { $moderated_types[] = $type->type; // $type->name ? } } $moderated_types = implode(', ', $moderated_types); if ($is_pending) { if (user_access('publish revisions')) { drupal_set_message(t('You have permission to publish content of types: %moderated_types.', array('%moderated_types' => $moderated_types))); } } else { // not pending if ($is_published != 0 && user_access('unpublish current revision')) { drupal_set_message(t('You have permission to unpublish content of types: %moderated_types.', array('%moderated_types' => $moderated_types))); } if ($is_published <= 0 && user_access('publish revisions')) { drupal_set_message(t('You have permission to (re)publish content of types: %moderated_types.', array('%moderated_types' => $moderated_types))); } } } /** * Extract from the incoming URI (as in the table column header href) * the sort field and order for use in an SQL 'ORDER BY' clause. * @param * none * @return * db table field name and sort direction as a string */ function _extract_order_clause_from_URI() { // We shouldn't have to do this, as tablesort.inc/tablesort_header(), called // from theme_table() is meant to look after it, but it's got a bug [#480382]. // Note: this function is secure, as we're only allowing recognised values, // all unknown values, result in the a descending sort by 'timestamp'. switch ($order_by = drupal_strtolower($_REQUEST['order'])) { case 'creator': $order_by = 'n.uid'; break; case 'by': $order_by = 'r.uid'; break; case 'published?': $order_by = 'status'; break; case 'workflow state': $order_by = 'state'; break; // Listing names that are fine the way they are here: case 'title': case 'type': case 'term': break; default: $order_by = 'timestamp'; break; } $direction = (drupal_strtolower($_REQUEST['sort'])== 'asc') ? 'ASC' : 'DESC'; return "$order_by $direction"; } function _title_for_view_tab($node) { return get_number_of_revisions($node->nid) > 1 ? t('View current') : t('View'); } function _title_for_edit_tab($node) { return get_number_of_revisions($node->nid) > 1 ? t('Edit current') : t('Edit'); }