1 |
efrain |
1 |
<?php
|
|
|
2 |
// This file is part of Moodle - http://moodle.org/
|
|
|
3 |
//
|
|
|
4 |
// Moodle is free software: you can redistribute it and/or modify
|
|
|
5 |
// it under the terms of the GNU General Public License as published by
|
|
|
6 |
// the Free Software Foundation, either version 3 of the License, or
|
|
|
7 |
// (at your option) any later version.
|
|
|
8 |
//
|
|
|
9 |
// Moodle is distributed in the hope that it will be useful,
|
|
|
10 |
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
11 |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
12 |
// GNU General Public License for more details.
|
|
|
13 |
//
|
|
|
14 |
// You should have received a copy of the GNU General Public License
|
|
|
15 |
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
16 |
|
|
|
17 |
/**
|
|
|
18 |
* Helper functions to implement the complex get_user_capability_course function.
|
|
|
19 |
*
|
|
|
20 |
* @package core
|
|
|
21 |
* @copyright 2017 The Open University
|
|
|
22 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
23 |
*/
|
|
|
24 |
|
|
|
25 |
namespace core\access;
|
|
|
26 |
|
|
|
27 |
defined('MOODLE_INTERNAL') || die();
|
|
|
28 |
|
|
|
29 |
/**
|
|
|
30 |
* Helper functions to implement the complex get_user_capability_course function.
|
|
|
31 |
*
|
|
|
32 |
* @package core
|
|
|
33 |
* @copyright 2017 The Open University
|
|
|
34 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
35 |
*/
|
|
|
36 |
class get_user_capability_course_helper {
|
|
|
37 |
/**
|
|
|
38 |
* Based on the given user's access data (roles) and system role definitions, works out
|
|
|
39 |
* an array of capability values at each relevant context for the given user and capability.
|
|
|
40 |
*
|
|
|
41 |
* This is organised by the effective context path (the one at which the capability takes
|
|
|
42 |
* effect) and then by role id. Note, however, that the resulting array only has
|
|
|
43 |
* the information that will be needed later. If there are Prohibits present in some
|
|
|
44 |
* roles, then they cannot be overridden by other roles or role overrides in lower contexts,
|
|
|
45 |
* therefore, such information, if any, is absent from the results.
|
|
|
46 |
*
|
|
|
47 |
* @param int $userid User id
|
|
|
48 |
* @param string $capability Capability e.g. 'moodle/course:view'
|
|
|
49 |
* @return array Array of capability constants, indexed by context path and role id
|
|
|
50 |
*/
|
|
|
51 |
protected static function get_capability_info_at_each_context($userid, $capability) {
|
|
|
52 |
// Get access data for user.
|
|
|
53 |
$accessdata = get_user_accessdata($userid);
|
|
|
54 |
|
|
|
55 |
// Get list of roles for user (any location) and information about these roles.
|
|
|
56 |
$roleids = [];
|
|
|
57 |
foreach ($accessdata['ra'] as $path => $roles) {
|
|
|
58 |
foreach ($roles as $roleid) {
|
|
|
59 |
$roleids[$roleid] = true;
|
|
|
60 |
}
|
|
|
61 |
}
|
|
|
62 |
$rdefs = get_role_definitions(array_keys($roleids));
|
|
|
63 |
|
|
|
64 |
// A prohibit in any relevant role prevents the capability
|
|
|
65 |
// in that context and all subcontexts. We need to track that.
|
|
|
66 |
// Here, the array keys are the paths where there is a prohibit the values are the role id.
|
|
|
67 |
$prohibitpaths = [];
|
|
|
68 |
|
|
|
69 |
// Get data for required capability at each context path where the user has a role that can
|
|
|
70 |
// affect it.
|
|
|
71 |
$pathroleperms = [];
|
|
|
72 |
foreach ($accessdata['ra'] as $rapath => $roles) {
|
|
|
73 |
|
|
|
74 |
foreach ($roles as $roleid) {
|
|
|
75 |
// Get role definition for that role.
|
|
|
76 |
foreach ($rdefs[$roleid] as $rdefpath => $caps) {
|
|
|
77 |
// Ignore if this override/definition doesn't refer to the relevant cap.
|
|
|
78 |
if (!array_key_exists($capability, $caps)) {
|
|
|
79 |
continue;
|
|
|
80 |
}
|
|
|
81 |
|
|
|
82 |
// Check a role definition or override above ra.
|
|
|
83 |
if (self::path_is_above($rdefpath, $rapath)) {
|
|
|
84 |
// Note that $rdefs is sorted by path, so if a more specific override
|
|
|
85 |
// exists, it will be processed later and override this one.
|
|
|
86 |
$effectivepath = $rapath;
|
|
|
87 |
} else if (self::path_is_above($rapath, $rdefpath)) {
|
|
|
88 |
$effectivepath = $rdefpath;
|
|
|
89 |
} else {
|
|
|
90 |
// Not inside an area where the user has the role, so ignore.
|
|
|
91 |
continue;
|
|
|
92 |
}
|
|
|
93 |
|
|
|
94 |
// Check for already seen prohibits in higher context. Overrides can't change that.
|
|
|
95 |
if (self::any_path_is_above($prohibitpaths, $effectivepath)) {
|
|
|
96 |
continue;
|
|
|
97 |
}
|
|
|
98 |
|
|
|
99 |
// This is a releavant role assignment / permission combination. Save it.
|
|
|
100 |
if (!array_key_exists($effectivepath, $pathroleperms)) {
|
|
|
101 |
$pathroleperms[$effectivepath] = [];
|
|
|
102 |
}
|
|
|
103 |
$pathroleperms[$effectivepath][$roleid] = $caps[$capability];
|
|
|
104 |
|
|
|
105 |
// Update $prohibitpaths if necessary.
|
|
|
106 |
if ($caps[$capability] == CAP_PROHIBIT) {
|
|
|
107 |
// First remove any lower-context prohibits that might have come from other roles.
|
|
|
108 |
foreach ($prohibitpaths as $otherprohibitpath => $notused) {
|
|
|
109 |
if (self::path_is_above($effectivepath, $otherprohibitpath)) {
|
|
|
110 |
unset($prohibitpaths[$otherprohibitpath]);
|
|
|
111 |
}
|
|
|
112 |
}
|
|
|
113 |
$prohibitpaths[$effectivepath] = $roleid;
|
|
|
114 |
}
|
|
|
115 |
}
|
|
|
116 |
}
|
|
|
117 |
}
|
|
|
118 |
|
|
|
119 |
// Finally, if a later role had a higher-level prohibit that an earlier role,
|
|
|
120 |
// there may be more bits we can prune - but don't prune the prohibits!
|
|
|
121 |
foreach ($pathroleperms as $effectivepath => $roleperms) {
|
|
|
122 |
if ($roleid = self::any_path_is_above($prohibitpaths, $effectivepath)) {
|
|
|
123 |
unset($pathroleperms[$effectivepath]);
|
|
|
124 |
$pathroleperms[$effectivepath][$roleid] = CAP_PROHIBIT;
|
|
|
125 |
}
|
|
|
126 |
}
|
|
|
127 |
|
|
|
128 |
return $pathroleperms;
|
|
|
129 |
}
|
|
|
130 |
|
|
|
131 |
/**
|
|
|
132 |
* Test if a context path $otherpath is the same as, or underneath, $parentpath.
|
|
|
133 |
*
|
|
|
134 |
* @param string $parentpath the path of the parent context.
|
|
|
135 |
* @param string $otherpath the path of another context.
|
|
|
136 |
* @return bool true if $otherpath is underneath (or equal to) $parentpath.
|
|
|
137 |
*/
|
|
|
138 |
protected static function path_is_above($parentpath, $otherpath) {
|
|
|
139 |
return preg_match('~^' . $parentpath . '($|/)~', $otherpath);
|
|
|
140 |
}
|
|
|
141 |
|
|
|
142 |
/**
|
|
|
143 |
* Test if a context path $otherpath is the same as, or underneath, any of $prohibitpaths.
|
|
|
144 |
*
|
|
|
145 |
* @param array $prohibitpaths array keys are context paths.
|
|
|
146 |
* @param string $otherpath the path of another context.
|
|
|
147 |
* @return int releavant $roleid if $otherpath is underneath (or equal to)
|
|
|
148 |
* any of the $prohibitpaths, 0 otherwise (so, can be used as a bool).
|
|
|
149 |
*/
|
|
|
150 |
protected static function any_path_is_above($prohibitpaths, $otherpath) {
|
|
|
151 |
foreach ($prohibitpaths as $prohibitpath => $roleid) {
|
|
|
152 |
if (self::path_is_above($prohibitpath, $otherpath)) {
|
|
|
153 |
return $roleid;
|
|
|
154 |
}
|
|
|
155 |
}
|
|
|
156 |
return 0;
|
|
|
157 |
}
|
|
|
158 |
|
|
|
159 |
/**
|
|
|
160 |
* Calculates a permission tree based on an array of information about role permissions.
|
|
|
161 |
*
|
|
|
162 |
* The input parameter must be in the format returned by get_capability_info_at_each_context.
|
|
|
163 |
*
|
|
|
164 |
* The output is the root of a tree of stdClass objects with the fields 'path' (a context path),
|
|
|
165 |
* 'allow' (true or false), and 'children' (an array of similar objects).
|
|
|
166 |
*
|
|
|
167 |
* @param array $pathroleperms Array of permissions
|
|
|
168 |
* @return \stdClass Root object of permission tree
|
|
|
169 |
*/
|
|
|
170 |
protected static function calculate_permission_tree(array $pathroleperms) {
|
|
|
171 |
// Considering each discovered context path as an inflection point, evaluate the user's
|
|
|
172 |
// permission (based on all roles) at each point.
|
|
|
173 |
$pathallows = [];
|
|
|
174 |
$mindepth = 1000;
|
|
|
175 |
$maxdepth = 0;
|
|
|
176 |
foreach ($pathroleperms as $path => $roles) {
|
|
|
177 |
$evaluatedroleperms = [];
|
|
|
178 |
|
|
|
179 |
// Walk up the tree starting from this path.
|
|
|
180 |
$innerpath = $path;
|
|
|
181 |
while ($innerpath !== '') {
|
|
|
182 |
$roles = $pathroleperms[$innerpath];
|
|
|
183 |
|
|
|
184 |
// Evaluate roles at this path level.
|
|
|
185 |
foreach ($roles as $roleid => $perm) {
|
|
|
186 |
if (!array_key_exists($roleid, $evaluatedroleperms)) {
|
|
|
187 |
$evaluatedroleperms[$roleid] = $perm;
|
|
|
188 |
} else {
|
|
|
189 |
// The existing one is at a more specific level so it takes precedence
|
|
|
190 |
// UNLESS this is a prohibit.
|
|
|
191 |
if ($perm == CAP_PROHIBIT) {
|
|
|
192 |
$evaluatedroleperms[$roleid] = $perm;
|
|
|
193 |
}
|
|
|
194 |
}
|
|
|
195 |
}
|
|
|
196 |
|
|
|
197 |
// Go up to next path level (if any).
|
|
|
198 |
do {
|
|
|
199 |
$innerpath = substr($innerpath, 0, strrpos($innerpath, '/'));
|
|
|
200 |
if ($innerpath === '') {
|
|
|
201 |
// No higher level data.
|
|
|
202 |
break;
|
|
|
203 |
}
|
|
|
204 |
} while (!array_key_exists($innerpath, $pathroleperms));
|
|
|
205 |
}
|
|
|
206 |
|
|
|
207 |
// If we have an allow from any role, and no prohibits, then user can access this path,
|
|
|
208 |
// else not.
|
|
|
209 |
$allow = false;
|
|
|
210 |
foreach ($evaluatedroleperms as $perm) {
|
|
|
211 |
if ($perm == CAP_ALLOW) {
|
|
|
212 |
$allow = true;
|
|
|
213 |
} else if ($perm == CAP_PROHIBIT) {
|
|
|
214 |
$allow = false;
|
|
|
215 |
break;
|
|
|
216 |
}
|
|
|
217 |
}
|
|
|
218 |
|
|
|
219 |
// Store the result based on path and depth so that we can process in depth order in
|
|
|
220 |
// the next step.
|
|
|
221 |
$depth = strlen(preg_replace('~[^/]~', '', $path));
|
|
|
222 |
$mindepth = min($depth, $mindepth);
|
|
|
223 |
$maxdepth = max($depth, $maxdepth);
|
|
|
224 |
$pathallows[$depth][$path] = $allow;
|
|
|
225 |
}
|
|
|
226 |
|
|
|
227 |
// Organise into a tree structure, processing in depth order so that we have ancestors
|
|
|
228 |
// set up before we encounter their children.
|
|
|
229 |
$root = (object)['allow' => false, 'path' => null, 'children' => []];
|
|
|
230 |
$nodesbypath = [];
|
|
|
231 |
for ($depth = $mindepth; $depth <= $maxdepth; $depth++) {
|
|
|
232 |
// Skip any missing depth levels.
|
|
|
233 |
if (!array_key_exists($depth, $pathallows)) {
|
|
|
234 |
continue;
|
|
|
235 |
}
|
|
|
236 |
foreach ($pathallows[$depth] as $path => $allow) {
|
|
|
237 |
// Value for new tree node.
|
|
|
238 |
$leaf = (object)['allow' => $allow, 'path' => $path, 'children' => []];
|
|
|
239 |
|
|
|
240 |
// Try to find a place to join it on if there is one.
|
|
|
241 |
$ancestorpath = $path;
|
|
|
242 |
$found = false;
|
|
|
243 |
while ($ancestorpath) {
|
|
|
244 |
$ancestorpath = substr($ancestorpath, 0, strrpos($ancestorpath, '/'));
|
|
|
245 |
if (array_key_exists($ancestorpath, $nodesbypath)) {
|
|
|
246 |
$found = true;
|
|
|
247 |
break;
|
|
|
248 |
}
|
|
|
249 |
}
|
|
|
250 |
|
|
|
251 |
if ($found) {
|
|
|
252 |
$nodesbypath[$ancestorpath]->children[] = $leaf;
|
|
|
253 |
} else {
|
|
|
254 |
$root->children[] = $leaf;
|
|
|
255 |
}
|
|
|
256 |
$nodesbypath[$path] = $leaf;
|
|
|
257 |
}
|
|
|
258 |
}
|
|
|
259 |
|
|
|
260 |
return $root;
|
|
|
261 |
}
|
|
|
262 |
|
|
|
263 |
/**
|
|
|
264 |
* Given a permission tree (in calculate_permission_tree format), removes any subtrees that
|
|
|
265 |
* are negative from the root. For example, if a top-level node of the permission tree has
|
|
|
266 |
* 'false' permission then it is meaningless because the default permission is already false;
|
|
|
267 |
* this function will remove it. However, if there is a child within that node that is positive,
|
|
|
268 |
* then that will need to be kept.
|
|
|
269 |
*
|
|
|
270 |
* @param \stdClass $root Root object
|
|
|
271 |
* @return \stdClass Filtered tree root
|
|
|
272 |
*/
|
|
|
273 |
protected static function remove_negative_subtrees($root) {
|
|
|
274 |
// If a node 'starts' negative, we don't need it (as negative is the default) - extract only
|
|
|
275 |
// subtrees that start with a positive value.
|
|
|
276 |
$positiveroot = (object)['allow' => false, 'path' => null, 'children' => []];
|
|
|
277 |
$consider = [$root];
|
|
|
278 |
while ($consider) {
|
|
|
279 |
$first = array_shift($consider);
|
|
|
280 |
foreach ($first->children as $node) {
|
|
|
281 |
if ($node->allow) {
|
|
|
282 |
// Add directly to new root.
|
|
|
283 |
$positiveroot->children[] = $node;
|
|
|
284 |
} else {
|
|
|
285 |
// Consider its children for adding to root (if there are any positive ones).
|
|
|
286 |
$consider[] = $node;
|
|
|
287 |
}
|
|
|
288 |
}
|
|
|
289 |
}
|
|
|
290 |
return $positiveroot;
|
|
|
291 |
}
|
|
|
292 |
|
|
|
293 |
/**
|
|
|
294 |
* Removes duplicate nodes of a tree - where a child node has the same permission as its
|
|
|
295 |
* parent.
|
|
|
296 |
*
|
|
|
297 |
* @param \stdClass $parent Tree root node
|
|
|
298 |
*/
|
|
|
299 |
protected static function remove_duplicate_nodes($parent) {
|
|
|
300 |
$length = count($parent->children);
|
|
|
301 |
$index = 0;
|
|
|
302 |
while ($index < $length) {
|
|
|
303 |
$child = $parent->children[$index];
|
|
|
304 |
if ($child->allow === $parent->allow) {
|
|
|
305 |
// Remove child node, but add its children to this node instead.
|
|
|
306 |
array_splice($parent->children, $index, 1);
|
|
|
307 |
$length--;
|
|
|
308 |
$index--;
|
|
|
309 |
foreach ($child->children as $grandchild) {
|
|
|
310 |
$parent->children[] = $grandchild;
|
|
|
311 |
$length++;
|
|
|
312 |
}
|
|
|
313 |
} else {
|
|
|
314 |
// Keep child node, but recurse to remove its unnecessary children.
|
|
|
315 |
self::remove_duplicate_nodes($child);
|
|
|
316 |
}
|
|
|
317 |
$index++;
|
|
|
318 |
}
|
|
|
319 |
}
|
|
|
320 |
|
|
|
321 |
/**
|
|
|
322 |
* Gets a permission tree for the given user and capability, representing the value of that
|
|
|
323 |
* capability at different contexts across the system. The tree will be simplified as far as
|
|
|
324 |
* possible.
|
|
|
325 |
*
|
|
|
326 |
* The output is the root of a tree of stdClass objects with the fields 'path' (a context path),
|
|
|
327 |
* 'allow' (true or false), and 'children' (an array of similar objects).
|
|
|
328 |
*
|
|
|
329 |
* @param int $userid User id
|
|
|
330 |
* @param string $capability Capability e.g. 'moodle/course:view'
|
|
|
331 |
* @return \stdClass Root node of tree
|
|
|
332 |
*/
|
|
|
333 |
protected static function get_tree($userid, $capability) {
|
|
|
334 |
// Extract raw capability data for this user and capability.
|
|
|
335 |
$pathroleperms = self::get_capability_info_at_each_context($userid, $capability);
|
|
|
336 |
|
|
|
337 |
// Convert the raw data into a permission tree based on context.
|
|
|
338 |
$root = self::calculate_permission_tree($pathroleperms);
|
|
|
339 |
unset($pathroleperms);
|
|
|
340 |
|
|
|
341 |
// Simplify the permission tree by removing unnecessary nodes.
|
|
|
342 |
$root = self::remove_negative_subtrees($root);
|
|
|
343 |
self::remove_duplicate_nodes($root);
|
|
|
344 |
|
|
|
345 |
// Return the tree.
|
|
|
346 |
return $root;
|
|
|
347 |
}
|
|
|
348 |
|
|
|
349 |
/**
|
|
|
350 |
* Creates SQL suitable for restricting by contexts listed in the given permission tree.
|
|
|
351 |
*
|
|
|
352 |
* This function relies on the permission tree being in the format created by get_tree.
|
|
|
353 |
* Specifically, all the children of the root element must be set to 'allow' permission,
|
|
|
354 |
* children of those children must be 'not allow', children of those grandchildren 'allow', etc.
|
|
|
355 |
*
|
|
|
356 |
* @param \stdClass $parent Root node of permission tree
|
|
|
357 |
* @return array Two-element array of SQL (containing ? placeholders) and then a params array
|
|
|
358 |
*/
|
|
|
359 |
protected static function create_sql($parent) {
|
|
|
360 |
global $DB;
|
|
|
361 |
|
|
|
362 |
$sql = '';
|
|
|
363 |
$params = [];
|
|
|
364 |
if ($parent->path !== null) {
|
|
|
365 |
// Except for the root element, create the condition that it applies to the context of
|
|
|
366 |
// this element (or anything within it).
|
|
|
367 |
$sql = ' (x.path = ? OR ' . $DB->sql_like('x.path', '?') .')';
|
|
|
368 |
$params[] = $parent->path;
|
|
|
369 |
$params[] = $parent->path . '/%';
|
|
|
370 |
if ($parent->children) {
|
|
|
371 |
// When there are children, these are assumed to have the opposite sign i.e. if we
|
|
|
372 |
// are allowing the parent, we are not allowing the children, and vice versa. So
|
|
|
373 |
// the 'OR' clause for children will be inside this 'AND NOT'.
|
|
|
374 |
$sql .= ' AND NOT (';
|
|
|
375 |
}
|
|
|
376 |
} else if (count($parent->children) > 1) {
|
|
|
377 |
// Place brackets in the query when it is going to be an OR of multiple conditions.
|
|
|
378 |
$sql .= ' (';
|
|
|
379 |
}
|
|
|
380 |
if ($parent->children) {
|
|
|
381 |
$first = true;
|
|
|
382 |
foreach ($parent->children as $child) {
|
|
|
383 |
if ($first) {
|
|
|
384 |
$first = false;
|
|
|
385 |
} else {
|
|
|
386 |
$sql .= ' OR';
|
|
|
387 |
}
|
|
|
388 |
|
|
|
389 |
// Recuse to get the child requirements - this will be the check that the context
|
|
|
390 |
// is within the child, plus possibly and 'AND NOT' for any different contexts
|
|
|
391 |
// within the child.
|
|
|
392 |
list ($childsql, $childparams) = self::create_sql($child);
|
|
|
393 |
$sql .= $childsql;
|
|
|
394 |
$params = array_merge($params, $childparams);
|
|
|
395 |
}
|
|
|
396 |
// Close brackets if opened above.
|
|
|
397 |
if ($parent->path !== null || count($parent->children) > 1) {
|
|
|
398 |
$sql .= ')';
|
|
|
399 |
}
|
|
|
400 |
}
|
|
|
401 |
return [$sql, $params];
|
|
|
402 |
}
|
|
|
403 |
|
|
|
404 |
/**
|
|
|
405 |
* Gets SQL to restrict a query to contexts in which the user has a capability.
|
|
|
406 |
*
|
|
|
407 |
* This returns an array with two elements (SQL containing ? placeholders, and a params array).
|
|
|
408 |
* The SQL is intended to be used as part of a WHERE clause. It relies on the prefix 'x' being
|
|
|
409 |
* used for the Moodle context table.
|
|
|
410 |
*
|
|
|
411 |
* If the user does not have the permission anywhere at all (so that there is no point doing
|
|
|
412 |
* the query) then the two returned values will both be false.
|
|
|
413 |
*
|
|
|
414 |
* @param int $userid User id
|
|
|
415 |
* @param string $capability Capability e.g. 'moodle/course:view'
|
|
|
416 |
* @return array Two-element array of SQL (containing ? placeholders) and then a params array
|
|
|
417 |
*/
|
|
|
418 |
public static function get_sql($userid, $capability) {
|
|
|
419 |
// Get a tree of capability permission at various contexts for current user.
|
|
|
420 |
$root = self::get_tree($userid, $capability);
|
|
|
421 |
|
|
|
422 |
// The root node always has permission false. If there are no child nodes then the user
|
|
|
423 |
// cannot access anything.
|
|
|
424 |
if (!$root->children) {
|
|
|
425 |
return [false, false];
|
|
|
426 |
}
|
|
|
427 |
|
|
|
428 |
// Get SQL to limit contexts based on the permission tree.
|
|
|
429 |
return self::create_sql($root);
|
|
|
430 |
|
|
|
431 |
}
|
|
|
432 |
|
|
|
433 |
/**
|
|
|
434 |
* Map fieldnames to get ready for the SQL query.
|
|
|
435 |
*
|
|
|
436 |
* @param string $fieldsexceptid A comma-separated list of the fields you require, not including id.
|
|
|
437 |
* Add ctxid, ctxpath, ctxdepth etc to return course context information for preloading.
|
|
|
438 |
* @return string Mapped field list for the SQL query.
|
|
|
439 |
*/
|
|
|
440 |
public static function map_fieldnames(string $fieldsexceptid = ''): string {
|
|
|
441 |
// Convert fields list and ordering.
|
|
|
442 |
$fieldlist = '';
|
|
|
443 |
if ($fieldsexceptid) {
|
|
|
444 |
$fields = array_map('trim', explode(',', $fieldsexceptid));
|
|
|
445 |
foreach ($fields as $field) {
|
|
|
446 |
// Context fields have a different alias.
|
|
|
447 |
if (strpos($field, 'ctx') === 0) {
|
|
|
448 |
switch($field) {
|
|
|
449 |
case 'ctxlevel' :
|
|
|
450 |
$realfield = 'contextlevel';
|
|
|
451 |
break;
|
|
|
452 |
case 'ctxinstance' :
|
|
|
453 |
$realfield = 'instanceid';
|
|
|
454 |
break;
|
|
|
455 |
default:
|
|
|
456 |
$realfield = substr($field, 3);
|
|
|
457 |
break;
|
|
|
458 |
}
|
|
|
459 |
$fieldlist .= ',x.' . $realfield . ' AS ' . $field;
|
|
|
460 |
} else {
|
|
|
461 |
$fieldlist .= ',c.'.$field;
|
|
|
462 |
}
|
|
|
463 |
}
|
|
|
464 |
}
|
|
|
465 |
return $fieldlist;
|
|
|
466 |
}
|
|
|
467 |
}
|