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
|
}
|