array( 'title' => t('Administer shortcuts'), 'description' => t('Manage all shortcut and shortcut sets.'), ), 'customize shortcut links' => array( 'title' => t('Customize shortcut links'), 'description' => t("Edit, add and delete the links in shortcut set the user is using."), ), 'switch shortcut sets' => array( 'title' => t('Choose a different shortcut set'), 'description' => t('Choose which set of shortcuts are displayed for the user.') ), ); } /** * Implement hook_menu(). */ function shortcut_menu() { $items['admin/config/system/shortcut'] = array( 'title' => 'Shortcuts', 'description' => 'List the available shortcut sets and switch between them.', 'page callback' => 'drupal_get_form', 'page arguments' => array('shortcut_set_switch'), 'access arguments' => array('administer shortcuts'), 'file' => 'shortcut.admin.inc', ); $items['admin/config/system/shortcut/%shortcut_set'] = array( 'title' => 'Customize shortcuts', 'page callback' => 'drupal_get_form', 'page arguments' => array('shortcut_set_customize', 4), 'access callback' => 'shortcut_set_edit_access', 'access arguments' => array(4), 'type' => MENU_CALLBACK, 'file' => 'shortcut.admin.inc', ); $items['admin/config/system/shortcut/%shortcut_set/add-link'] = array( 'title' => 'Add shortcut', 'page callback' => 'drupal_get_form', 'page arguments' => array('shortcut_link_add', 4), 'access callback' => 'shortcut_set_edit_access', 'access arguments' => array(4), 'type' => MENU_LOCAL_ACTION, 'file' => 'shortcut.admin.inc', ); $items['admin/config/system/shortcut/%shortcut_set/add-link-inline'] = array( 'title' => 'Add shortcut', 'page callback' => 'shortcut_link_add_inline', 'page arguments' => array(4), 'access callback' => 'shortcut_set_edit_access', 'access arguments' => array(4), 'type' => MENU_CALLBACK, 'file' => 'shortcut.admin.inc', ); $items['admin/config/system/shortcut/link/%menu_link'] = array( 'title' => 'Edit shortcut', 'page callback' => 'drupal_get_form', 'page arguments' => array('shortcut_link_edit', 5), 'access callback' => 'shortcut_link_access', 'access arguments' => array(5), 'type' => MENU_CALLBACK, 'file' => 'shortcut.admin.inc', ); $items['admin/config/system/shortcut/link/%menu_link/delete'] = array( 'title' => 'Delete shortcut', 'page callback' => 'drupal_get_form', 'page arguments' => array('shortcut_link_delete', 5), 'access callback' => 'shortcut_link_access', 'access arguments' => array(5), 'type' => MENU_CALLBACK, 'file' => 'shortcut.admin.inc', ); $items['user/%user/shortcuts'] = array( 'title' => 'Shortcuts', 'page callback' => 'drupal_get_form', 'page arguments' => array('shortcut_set_switch', 1), 'access callback' => 'shortcut_set_switch_access', 'access arguments' => array(1), 'type' => MENU_LOCAL_TASK, 'file' => 'shortcut.admin.inc', ); return $items; } /** * Implement hook_theme(). */ function shortcut_theme() { return array( 'shortcut_set_switch' => array( 'render element' => 'form', 'file' => 'shortcut.admin.inc', ), 'shortcut_set_customize' => array( 'render element' => 'form', 'file' => 'shortcut.admin.inc', ), ); } /** * Implement hook_block_info(). */ function shortcut_block_info() { $blocks['shortcuts']['info'] = t('Shortcuts'); // Shortcut blocks can't be cached because each menu item can have a custom // access callback. menu.inc manages its own caching. $blocks['shortcuts']['cache'] = DRUPAL_NO_CACHE; return $blocks; } /** * Implement hook_block_view(). */ function shortcut_block_view($delta = '') { if ($delta == 'shortcuts') { $shortcut_set = shortcut_current_displayed_set(); $data['subject'] = t('@shortcut_set shortcuts', array('@shortcut_set' => $shortcut_set->title)); $data['content'] = shortcut_renderable_links($shortcut_set); return $data; } } /** * Access callback for editing a shortcut set. * * @param $shortcut_set * (optional) The shortcut set to be edited. If not set, the current * displayed shortcut set will be assumed. * @return * TRUE if the current user has access to edit the shortcut set, FALSE * otherwise. */ function shortcut_set_edit_access($shortcut_set = NULL) { // Sufficiently-privileged users can edit their currently displayed shortcut // set, but not other sets. Shortcut administrators can edit any set. if (user_access('administer shortcuts')) { return TRUE; } if (user_access('customize shortcut links')) { return !isset($shortcut_set) || $shortcut_set == shortcut_current_displayed_set(); } return FALSE; } /** * Access callback for switching the shortcut set assigned to a user account. * * @param $account * (optional) The user account whose shortcuts will be switched. If not set, * the account of the current logged-in user will be assumed. * @return * TRUE if the current user has access to switch the shortcut set of the * provided account, FALSE otherwise. */ function shortcut_set_switch_access($account = NULL) { global $user; // Sufficiently-privileged users can switch their own shortcut sets, but not // those of other users. Shortcut administrators can switch any user's set. return user_access('administer shortcuts') || (user_access('switch shortcut sets') && (!isset($account) || $user->uid == $account->uid)); } /** * Access callback for editing a link in a shortcut set. */ function shortcut_link_access($menu_link) { // The link must belong to a shortcut set that the current user has access // to edit. if ($shortcut_set = shortcut_set_load($menu_link['menu_name'])) { return shortcut_set_edit_access($shortcut_set); } return FALSE; } /** * Loads the data for a shortcut set. * * @param $set_name * The name of the shortcut set to load. * @return * If the shortcut set exists, an object of type stdClass containing the * following properties: * - 'set_name': The internal name of the shortcut set. * - 'title': The title of the shortcut set. * - 'links': An array of links associated with this shortcut set. * If the shortcut set does not exist, the function returns FALSE. */ function shortcut_set_load($set_name) { $set = db_select('shortcut_set', 'ss') ->fields('ss') ->condition('set_name', $set_name) ->execute() ->fetchObject(); if (!$set) { return FALSE; } $set->links = menu_load_links($set_name); return $set; } /** * Saves a shortcut set. * * @param $shortcut_set * An object containing the following properties: * - 'title': The title of the shortcut set. * - 'set_name': (optional) The internal name of the shortcut set. If * omitted, a new shortcut set will be created, and the 'set_name' property * will be added to the passed-in array. * - 'links': (optional) An array of menu links to save for the shortcut set. * Each link is an array containing at least the following keys (which will * be expanded to fill in other default values after the shortcut set is * saved): * - 'link_path': The Drupal path or external path that the link points to. * - 'link_title': The title of the link. * Any other keys accepted by menu_link_save() may also be provided. * @return * A constant which is either SAVED_NEW or SAVED_UPDATED depending on whether * a new set was created or an existing one was updated. * * @see menu_link_save() */ function shortcut_set_save(&$shortcut_set) { // First save the shortcut set itself. if (isset($shortcut_set->set_name)) { $return = drupal_write_record('shortcut_set', $shortcut_set, 'set_name'); } else { $shortcut_set->set_name = shortcut_set_get_unique_name(); $return = drupal_write_record('shortcut_set', $shortcut_set); } // If links were provided for the set, save them, replacing any that were // there before. if (isset($shortcut_set->links)) { menu_delete_links($shortcut_set->set_name); foreach ($shortcut_set->links as &$link) { // Do not specifically associate these links with the shortcut module, // since other modules may make them editable via the menu system. // However, we do need to specify the correct menu name. $link['menu_name'] = $shortcut_set->set_name; menu_link_save($link); } // Make sure that we have a return value, since if the links were updated // but the shortcut set was not, the call to drupal_write_record() above // would not return an indication that anything had changed. if (empty($return)) { $return = SAVED_UPDATED; } } return $return; } /** * Deletes a shortcut set. * * Note that the default set cannot be deleted. * * @param $shortcut_set * An object representing the shortcut set to delete. * @return * TRUE if the set was deleted, FALSE otherwise. */ function shortcut_set_delete($shortcut_set) { // Make sure not to delete the default set. $default_set = shortcut_default_set(); if ($shortcut_set->set_name == $default_set->set_name) { return FALSE; } // First, delete any user assignments for this set, so that each of these // users will go back to using whatever default set applies. db_delete('shortcut_set_users') ->condition('set_name', $shortcut_set->set_name) ->execute(); // Next, delete the menu links for this set. menu_delete_links($shortcut_set->set_name); // Finally, delete the set itself. $deleted = db_delete('shortcut_set') ->condition('set_name', $shortcut_set->set_name) ->execute(); return (bool) $deleted; } /** * Reset the link weights in a shortcut set to match their current order. * * This function can be used, for example, when a new shortcut link is added to * the set. If the link is added to the end of the array and this function is * called, it will force that link to display at the end of the list. * * @param $shortcut_set * An object representing a shortcut set. The link weights of the passed-in * object will be reset as described above. */ function shortcut_set_reset_link_weights(&$shortcut_set) { $weight = -50; foreach ($shortcut_set->links as &$link) { $link['weight'] = $weight; $weight++; } } /** * Assign a user to a particular shortcut set. * * @param $shortcut_set * An object representing the shortcut set. * @param $account * A user account that will be assigned to use the set. */ function shortcut_set_assign_user($shortcut_set, $account) { db_merge('shortcut_set_users') ->key(array('uid' => $account->uid)) ->fields(array('set_name' => $shortcut_set->set_name)) ->execute(); } /** * Unassign a user from any shortcut set they may have been assigned to. * * The user will go back to using whatever default set applies. * * @param $account * A user account that will be removed from the shortcut set assignment. * @return * TRUE if the user was previously assigned to a shortcut set and has been * successfully removed from it. FALSE if the user was already not assigned * to any set. */ function shortcut_set_unassign_user($account) { $deleted = db_delete('shortcut_set') ->condition('uid', $account->uid) ->execute(); return (bool) $deleted; } /** * Returns the current displayed shortcut set for the provided user account. * * @param $account * (optional) The user account whose shortcuts will be returned. Defaults to * the current logged-in user. * @return * An object representing the shortcut set that should be displayed to the * current user. If the user does not have an explicit shortcut set defined, * the default set is returned. */ function shortcut_current_displayed_set($account = NULL) { $shortcut_sets = &drupal_static(__FUNCTION__, array()); global $user; if (!isset($account)) { $account = $user; } // Try to return a shortcut set from the static cache. if (isset($shortcut_sets[$account->uid])) { return $shortcut_sets[$account->uid]; } // If none was found, try to find a shortcut set that is explicitly assigned // to this user. $query = db_select('shortcut_set', 's'); $query->fields('s'); $query->join('shortcut_set_users', 'u', 's.set_name = u.set_name'); $query->condition('u.uid', $account->uid); $shortcut_set = $query->execute()->fetchObject(); // Otherwise, use the default set. if (!$shortcut_set) { $shortcut_set = shortcut_default_set($account); } $shortcut_sets[$account->uid] = $shortcut_set; return $shortcut_set; } /** * Returns the default shortcut set for a given user account. * * @param $account * (optional) The user account whose shortcuts will be returned. Defaults to * the current logged-in user. * @return * An object representing the default shortcut set. */ function shortcut_default_set($account = NULL) { global $user; if (!isset($account)) { $account = $user; } // Allow modules to return a default shortcut set name. Since we can only // have one, we allow the last module which returns a valid result to take // precedence. If no module returns a valid set, fall back on the site-wide // default. $shortcut_set_names = array_reverse(array_merge(array(SHORTCUT_DEFAULT_SET_NAME), module_invoke_all('shortcut_default_set', $account))); foreach ($shortcut_set_names as $name) { if ($shortcut_set = shortcut_set_load($name)) { break; } } return $shortcut_set; } /** * Returns a unique, machine-readable shortcut set name. */ function shortcut_set_get_unique_name() { // Shortcut sets are numbered sequentially, so we keep trying until we find // one that is available. For better performance, we start with a number // equal to one more than the current number of shortcut sets, so that if // no shortcut sets have been deleted from the database, this will // automatically give us the correct one. $number = db_query("SELECT COUNT(*) FROM {shortcut_set}")->fetchField() + 1; do { $name = shortcut_set_name($number); $number++; } while ($shortcut_set = shortcut_set_load($name)); return $name; } /** * Returns the name of a shortcut set, based on a provided number. * * All shortcut sets have names like "shortcut-set-N" so that they can be * matched with a properly-namespaced entry in the {menu_links} table. * * @param $number * A number representing the shortcut set whose name should be retrieved. * @return * A string representing the expected shortcut name. */ function shortcut_set_name($number) { return "shortcut-set-$number"; } /** * Returns an array of all shortcut sets, keyed by the set name. * * @return * An array of shortcut sets. Note that only the basic shortcut set * properties (name and title) are returned by this function, not the list * of menu links that belong to the set. */ function shortcut_sets() { return db_select('shortcut_set', 'ss') ->fields('ss') ->execute() ->fetchAllAssoc('set_name'); } /** * Determines if a path corresponds to a valid shortcut link. * * @param $path * The path to the link. * @return * TRUE if the shortcut link is valid, FALSE otherwise. Valid links are ones * that correspond to actual paths on the site. * * @see menu_edit_item_validate() */ function shortcut_valid_link($path) { // Do not use URL aliases. $normal_path = drupal_get_normal_path($path); if ($path != $normal_path) { $path = $normal_path; } // Only accept links that correspond to valid paths on the site itself. return !url_is_external($path) && menu_get_item($path); } /** * Returns an array of shortcut links, suitable for rendering. * * @param $shortcut_set * (optional) An object representing the set whose links will be displayed. * If not provided, the user's current set will be displayed. * @return * An array of shortcut links, in the format returned by the menu system. * * @see menu_tree() */ function shortcut_renderable_links($shortcut_set = NULL) { if (!isset($shortcut_set)) { $shortcut_set = shortcut_current_displayed_set(); } return menu_tree($shortcut_set->set_name); } /** * Implement hook_page_build(). */ function shortcut_page_build(&$page) { if (shortcut_set_edit_access()) { $link = $_GET['q']; $query_parameters = drupal_get_query_parameters(); if (!empty($query_parameters)) { $link .= '?' . drupal_http_build_query($query_parameters); } $query = array( 'link' => $link, 'name' => drupal_get_title(), 'token' => drupal_get_token('shortcut-add-link'), ); $query += drupal_get_destination(); $shortcut_set = shortcut_current_displayed_set(); $link_text = shortcut_set_switch_access() ? t('Add to %shortcut_set shortcuts', array('%shortcut_set' => $shortcut_set->title)) : t('Add to shortcuts'); $page['add_to_shortcuts'] = array( '#prefix' => '