Projet

Général

Profil

Paste
Télécharger (6,04 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / chain_menu_access / chain_menu_access.module @ 87dbc3bf

1
<?php
2

    
3
/**
4
 * @file
5
 * An API module to help client modules chain their access callbacks into
6
 * other modules' menu items.
7
 */
8

    
9
/**
10
 * @class Exception class used to throw error.
11
 */
12
class ChainMenuAccessChainException extends Exception {}
13

    
14
/**
15
 * Prepend the given access callback to the chain.
16
 *
17
 * The client module should call this function from its hook_menu_alter()
18
 * implementation to install its access callback.
19
 *
20
 * NOTE: hook_menu_alter() is called only when the menu router table is
21
 * rebuilt after the menu cache was flushed.
22
 *
23
 * NOTE: MENU_DEFAULT_LOCAL_TASK items should not specify access parameters
24
 * but inherit them from their parent item, because access should not be
25
 * different between the two. If a client module tries to chain to such an item,
26
 * the item's access parameters are cleared only.
27
 *
28
 * @param array $items
29
 *   The menu router items array.
30
 *   Do not try to chain MENU_DEFAULT_LOCAL_TASK items -- chain their parent
31
 *   items instead.
32
 * @param string $path
33
 *   The index into $items to the item to modify.
34
 * @param string $new_access_callback
35
 *   The name of the client's access callback function, as documented for
36
 *   the 'access callback' key in hook_menu().
37
 * @param array $new_access_arguments
38
 *   An array holding the arguments to be passed to the new access callback,
39
 *   as documented for the 'access arguments' key in hook_menu().
40
 * @param bool $or_or_pass_index
41
 *   Pass FALSE to evaluate ($new_access_callback() && $old_access_callback()).
42
 *   Pass TRUE to evaluate ($new_access_callback() || $old_access_callback()).
43
 *   Pass a number to evaluate $old_access_callback() first and then pass
44
 *   the result as the $pass_index-th argument in $new_access_arguments to
45
 *   $new_access_callback().
46
 *
47
 * @return bool
48
 *   TRUE if the chaining succeeded, FALSE if a MENU_DEFAULT_LOCAL_TASK item.
49
 */
50
function chain_menu_access_chain(array &$items, $path, $new_access_callback, array $new_access_arguments = array(), $or_or_pass_index = FALSE) {
51
  $item =& $items[$path];
52
  if (isset($item['type']) && $item['type'] == MENU_DEFAULT_LOCAL_TASK) {
53
    // Inherit the parent's access! See http://drupal.org/node/1186208.
54
    unset($item['access callback'], $item['access arguments']);
55
    return FALSE;
56
  }
57

    
58
  // Look through the child items for the MENU_DEFAULT_LOCAL_TASK.
59
  $child_paths = array_filter(array_keys($items), create_function('$p', "return preg_match('#^$path/[^/]*$#', \$p);"));
60
  foreach ($child_paths as $child_path) {
61
    $child_item =& $items[$child_path];
62
    if (isset($child_item['type']) && $child_item['type'] == MENU_DEFAULT_LOCAL_TASK) {
63
      // The default local task must inherit its access from its parent.
64
      unset($child_item['access callback'], $child_item['access arguments']);
65
    }
66
  }
67

    
68
  // Normalize the menu router item.
69
  if (!isset($item['access callback']) && isset($item['access arguments'])) {
70
    // Default callback.
71
    $item['access callback'] = 'user_access';
72
  }
73
  if (!isset($item['access callback'])) {
74
    $item['access callback'] = 0;
75
  }
76
  if (is_bool($item['access callback'])) {
77
    $item['access callback'] = intval($item['access callback']);
78
  }
79
  $old_access_arguments = isset($item['access arguments']) ? $item['access arguments'] : array();
80
  if (is_bool($new_access_callback)) {
81
    $new_access_callback = intval($new_access_callback);
82
  }
83

    
84
  // Prepend a parameter array plus the $new_access_arguments to the existing
85
  // access arguments array. This works repeatedly, too.
86
  $or = ($or_or_pass_index === TRUE);
87
  $pass_index = ($or_or_pass_index === TRUE ? FALSE : $or_or_pass_index);
88
  $item['access arguments'] = array_merge(
89
    array(array($item['access callback'], $new_access_callback, count($new_access_arguments), $or, $pass_index)),
90
    $new_access_arguments,
91
    $old_access_arguments
92
  );
93
  $item['access callback'] = '_chain_menu_access_callback';
94
  return TRUE;
95
}
96

    
97
/*
98
 * Internal helper function to recursively unwrap and call the chained
99
 * callbacks, LIFO style.
100
 */
101
function _chain_menu_access_callback() {
102
  $args = func_get_args();
103
  // Recover the parameters from the array, plus the $new_access_arguments.
104
  $parms = array_shift($args);
105
  if (count($parms) != 5) {
106
    // Something's wrong (see chain_menu_access_chain() above).
107
    throw new ChainMenuAccessChainException('Unexpected arguments; client module probably missed calling chain_menu_access_chain() on MENU_DEFAULT_LOCAL_TASK.');
108
  }
109
  list($old_access_callback, $new_access_callback, $count, $or, $pass_index) = $parms;
110
  $new_access_arguments = array_splice($args, 0, (int) $count, array());
111
  if ($pass_index !== FALSE || $old_access_callback == 'user_access' || is_numeric($old_access_callback)) {
112
    // Call the $old_access_callback first either if we need to pass its result
113
    // to the $new_access_callback or if it's a user_access() call or constant
114
    // number (which would be very quick to evaluate).
115
    $old_result = (bool) _chain_menu_access_callback_call($old_access_callback, $args);
116
    if ($pass_index !== FALSE) {
117
      $new_access_arguments[$pass_index] = $old_result;
118
    }
119
    elseif ($or == $old_result) {
120
      // Do shortcut evaluation on the second operand first!
121
      return $or;
122
    }
123
  }
124
  $new_result = _chain_menu_access_callback_call($new_access_callback, $new_access_arguments);
125
  // Do normal shortcut evaluation on the first operand (or simply return the
126
  // result if we have a $pass_index).
127
  if ($pass_index !== FALSE || $or == $new_result) {
128
    return $new_result;
129
  }
130
  // Call $old_access_callback if we haven't called it yet.
131
  if (!isset($old_result)) {
132
    $old_result = _chain_menu_access_callback_call($old_access_callback, $args);
133
  }
134
  return $old_result;
135
}
136

    
137
/**
138
 * Internal helper function to call one callback.
139
 */
140
function _chain_menu_access_callback_call($access_callback, $access_arguments) {
141
  $access_callback = empty($access_callback) ? 0 : trim($access_callback);
142
  if (is_numeric($access_callback)) {
143
    // It's a number (see hook_menu()).
144
    return (bool) $access_callback;
145
  }
146
  if (function_exists($access_callback)) {
147
    return call_user_func_array($access_callback, $access_arguments);
148
  }
149
}