1 |
85ad3d82
|
Assos Assos
|
<?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 |
|
|
} |