Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
9.44% |
39 / 413 |
|
0.00% |
0 / 23 |
CRAP | |
0.00% |
0 / 1 |
p_master | |
9.44% |
39 / 413 |
|
0.00% |
0 / 23 |
35186.07 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
set_custom_include_path | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
list_modules | |
0.00% |
0 / 101 |
|
0.00% |
0 / 1 |
1406 | |||
loaded | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
110 | |||
module_auth_self | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
module_auth | |
92.86% |
39 / 42 |
|
0.00% |
0 / 1 |
10.04 | |||
set_active | |
0.00% |
0 / 36 |
|
0.00% |
0 / 1 |
1260 | |||
load_active | |
0.00% |
0 / 51 |
|
0.00% |
0 / 1 |
462 | |||
adjust_url | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
is_active | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
72 | |||
get_parents | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
42 | |||
get_branch | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
build_tree | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
56 | |||
assign_tpl_vars | |
0.00% |
0 / 64 |
|
0.00% |
0 / 1 |
1406 | |||
get_tpl_name | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
get_page_title | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
load | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
display | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
56 | |||
set_display | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
56 | |||
add_mod_info | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
30 | |||
get_short_name | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
get_module_identifier | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
is_full_class | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | /** |
3 | * |
4 | * This file is part of the phpBB Forum Software package. |
5 | * |
6 | * @copyright (c) phpBB Limited <https://www.phpbb.com> |
7 | * @license GNU General Public License, version 2 (GPL-2.0) |
8 | * |
9 | * For full copyright and license information, please see |
10 | * the docs/CREDITS.txt file. |
11 | * |
12 | */ |
13 | |
14 | /** |
15 | * @ignore |
16 | */ |
17 | if (!defined('IN_PHPBB')) |
18 | { |
19 | exit; |
20 | } |
21 | |
22 | /** |
23 | * Class handling all types of 'plugins' (a future term) |
24 | */ |
25 | class p_master |
26 | { |
27 | var $p_id; |
28 | var $p_class; |
29 | var $p_name; |
30 | var $p_mode; |
31 | var $p_parent; |
32 | |
33 | var $include_path = false; |
34 | var $active_module = false; |
35 | var $active_module_row_id = false; |
36 | var $acl_forum_id = false; |
37 | var $module_ary = array(); |
38 | |
39 | /** |
40 | * Constuctor |
41 | * Set module include path |
42 | */ |
43 | function __construct($include_path = false) |
44 | { |
45 | global $phpbb_root_path; |
46 | |
47 | $this->include_path = ($include_path !== false) ? $include_path : $phpbb_root_path . 'includes/'; |
48 | |
49 | // Make sure the path ends with / |
50 | if (substr($this->include_path, -1) !== '/') |
51 | { |
52 | $this->include_path .= '/'; |
53 | } |
54 | } |
55 | |
56 | /** |
57 | * Set custom include path for modules |
58 | * Schema for inclusion is include_path . modulebase |
59 | * |
60 | * @param string $include_path include path to be used. |
61 | * @access public |
62 | */ |
63 | function set_custom_include_path($include_path) |
64 | { |
65 | $this->include_path = $include_path; |
66 | |
67 | // Make sure the path ends with / |
68 | if (substr($this->include_path, -1) !== '/') |
69 | { |
70 | $this->include_path .= '/'; |
71 | } |
72 | } |
73 | |
74 | /** |
75 | * List modules |
76 | * |
77 | * This creates a list, stored in $this->module_ary of all available |
78 | * modules for the given class (ucp, mcp and acp). Additionally |
79 | * $this->module_y_ary is created with indentation information for |
80 | * displaying the module list appropriately. Only modules for which |
81 | * the user has access rights are included in these lists. |
82 | */ |
83 | function list_modules($p_class) |
84 | { |
85 | global $db, $user, $cache; |
86 | global $phpbb_dispatcher; |
87 | |
88 | // Sanitise for future path use, it's escaped as appropriate for queries |
89 | $this->p_class = str_replace(array('.', '/', '\\'), '', basename($p_class)); |
90 | |
91 | // Get cached modules |
92 | if (($this->module_cache = $cache->get('_modules_' . $this->p_class)) === false) |
93 | { |
94 | // Get modules |
95 | $sql = 'SELECT * |
96 | FROM ' . MODULES_TABLE . " |
97 | WHERE module_class = '" . $db->sql_escape($this->p_class) . "' |
98 | ORDER BY left_id ASC"; |
99 | $result = $db->sql_query($sql); |
100 | |
101 | $rows = array(); |
102 | while ($row = $db->sql_fetchrow($result)) |
103 | { |
104 | $rows[$row['module_id']] = $row; |
105 | } |
106 | $db->sql_freeresult($result); |
107 | |
108 | $this->module_cache = array(); |
109 | foreach ($rows as $module_id => $row) |
110 | { |
111 | $this->module_cache['modules'][] = $row; |
112 | $this->module_cache['parents'][$row['module_id']] = $this->get_parents($row['parent_id'], $row['left_id'], $row['right_id'], $rows); |
113 | } |
114 | unset($rows); |
115 | |
116 | $cache->put('_modules_' . $this->p_class, $this->module_cache); |
117 | } |
118 | |
119 | if (empty($this->module_cache)) |
120 | { |
121 | $this->module_cache = array('modules' => array(), 'parents' => array()); |
122 | } |
123 | |
124 | // We "could" build a true tree with this function - maybe mod authors want to use this... |
125 | // Functions for traversing and manipulating the tree are not available though |
126 | // We might re-structure the module system to use true trees in 4.0 |
127 | // $tree = $this->build_tree($this->module_cache['modules'], $this->module_cache['parents']); |
128 | |
129 | // Clean up module cache array to only let survive modules the user can access |
130 | $right_id = false; |
131 | |
132 | $hide_categories = array(); |
133 | foreach ($this->module_cache['modules'] as $key => $row) |
134 | { |
135 | // When the module has no mode (category) we check whether it has visible children |
136 | // before listing it as well. |
137 | if (!$row['module_mode']) |
138 | { |
139 | $hide_categories[(int) $row['module_id']] = $key; |
140 | } |
141 | |
142 | // Not allowed to view module? |
143 | if (!$this->module_auth_self($row['module_auth'])) |
144 | { |
145 | unset($this->module_cache['modules'][$key]); |
146 | continue; |
147 | } |
148 | |
149 | // Category with no members, ignore |
150 | if (!$row['module_basename'] && ($row['left_id'] + 1 == $row['right_id'])) |
151 | { |
152 | unset($this->module_cache['modules'][$key]); |
153 | continue; |
154 | } |
155 | |
156 | // Skip branch |
157 | if ($right_id !== false) |
158 | { |
159 | if ($row['left_id'] < $right_id) |
160 | { |
161 | unset($this->module_cache['modules'][$key]); |
162 | continue; |
163 | } |
164 | |
165 | $right_id = false; |
166 | } |
167 | |
168 | // Not enabled? |
169 | if (!$row['module_enabled']) |
170 | { |
171 | // If category is disabled then disable every child too |
172 | unset($this->module_cache['modules'][$key]); |
173 | $right_id = $row['right_id']; |
174 | continue; |
175 | } |
176 | |
177 | if ($row['module_mode']) |
178 | { |
179 | // The parent category has a visible child |
180 | // So remove it and all its parents from the hide array |
181 | unset($hide_categories[(int) $row['parent_id']]); |
182 | foreach ($this->module_cache['parents'][$row['module_id']] as $module_id => $row_id) |
183 | { |
184 | unset($hide_categories[$module_id]); |
185 | } |
186 | } |
187 | } |
188 | |
189 | foreach ($hide_categories as $module_id => $row_id) |
190 | { |
191 | unset($this->module_cache['modules'][$row_id]); |
192 | } |
193 | |
194 | // Re-index (this is needed, else we are not able to array_slice later) |
195 | $this->module_cache['modules'] = array_merge($this->module_cache['modules']); |
196 | |
197 | // Include MOD _info files for populating language entries within the menus |
198 | $this->add_mod_info($this->p_class); |
199 | |
200 | // Now build the module array, but exclude completely empty categories... |
201 | $right_id = false; |
202 | $names = array(); |
203 | |
204 | foreach ($this->module_cache['modules'] as $key => $row) |
205 | { |
206 | // Skip branch |
207 | if ($right_id !== false) |
208 | { |
209 | if ($row['left_id'] < $right_id) |
210 | { |
211 | continue; |
212 | } |
213 | |
214 | $right_id = false; |
215 | } |
216 | |
217 | // Category with no members on their way down (we have to check every level) |
218 | if (!$row['module_basename']) |
219 | { |
220 | $empty_category = true; |
221 | |
222 | // We go through the branch and look for an activated module |
223 | foreach (array_slice($this->module_cache['modules'], $key + 1) as $temp_row) |
224 | { |
225 | if ($temp_row['left_id'] > $row['left_id'] && $temp_row['left_id'] < $row['right_id']) |
226 | { |
227 | // Module there |
228 | if ($temp_row['module_basename'] && $temp_row['module_enabled']) |
229 | { |
230 | $empty_category = false; |
231 | break; |
232 | } |
233 | continue; |
234 | } |
235 | break; |
236 | } |
237 | |
238 | // Skip the branch |
239 | if ($empty_category) |
240 | { |
241 | $right_id = $row['right_id']; |
242 | continue; |
243 | } |
244 | } |
245 | |
246 | $depth = count($this->module_cache['parents'][$row['module_id']]); |
247 | |
248 | // We need to prefix the functions to not create a naming conflict |
249 | |
250 | // Function for building 'url_extra' |
251 | $short_name = $this->get_short_name($row['module_basename']); |
252 | |
253 | $url_func = 'phpbb_module_' . $short_name . '_url'; |
254 | if (!function_exists($url_func)) |
255 | { |
256 | $url_func = '_module_' . $short_name . '_url'; |
257 | } |
258 | |
259 | // Function for building the language name |
260 | $lang_func = 'phpbb_module_' . $short_name . '_lang'; |
261 | if (!function_exists($lang_func)) |
262 | { |
263 | $lang_func = '_module_' . $short_name . '_lang'; |
264 | } |
265 | |
266 | // Custom function for calling parameters on module init (for example assigning template variables) |
267 | $custom_func = 'phpbb_module_' . $short_name; |
268 | if (!function_exists($custom_func)) |
269 | { |
270 | $custom_func = '_module_' . $short_name; |
271 | } |
272 | |
273 | $names[$row['module_basename'] . '_' . $row['module_mode']][] = true; |
274 | |
275 | $module_row = array( |
276 | 'depth' => $depth, |
277 | |
278 | 'id' => (int) $row['module_id'], |
279 | 'parent' => (int) $row['parent_id'], |
280 | 'cat' => ($row['right_id'] > $row['left_id'] + 1) ? true : false, |
281 | |
282 | 'is_duplicate' => ($row['module_basename'] && count($names[$row['module_basename'] . '_' . $row['module_mode']]) > 1) ? true : false, |
283 | |
284 | 'name' => (string) $row['module_basename'], |
285 | 'mode' => (string) $row['module_mode'], |
286 | 'display' => (int) $row['module_display'], |
287 | |
288 | 'url_extra' => (function_exists($url_func)) ? $url_func($row['module_mode'], $row) : '', |
289 | |
290 | 'lang' => ($row['module_basename'] && function_exists($lang_func)) ? $lang_func($row['module_mode'], $row['module_langname']) : ((!empty($user->lang[$row['module_langname']])) ? $user->lang[$row['module_langname']] : $row['module_langname']), |
291 | 'langname' => $row['module_langname'], |
292 | |
293 | 'left' => $row['left_id'], |
294 | 'right' => $row['right_id'], |
295 | ); |
296 | |
297 | if (function_exists($custom_func)) |
298 | { |
299 | $custom_func($row['module_mode'], $module_row); |
300 | } |
301 | |
302 | /** |
303 | * This event allows to modify parameters for building modules list |
304 | * |
305 | * @event core.modify_module_row |
306 | * @var string url_func Function for building 'url_extra' |
307 | * @var string lang_func Function for building the language name |
308 | * @var string custom_func Custom function for calling parameters on module init |
309 | * @var array row Array holding the basic module data |
310 | * @var array module_row Array holding the module display parameters |
311 | * @since 3.1.0-b3 |
312 | */ |
313 | $vars = array('url_func', 'lang_func', 'custom_func', 'row', 'module_row'); |
314 | extract($phpbb_dispatcher->trigger_event('core.modify_module_row', compact($vars))); |
315 | |
316 | $this->module_ary[] = $module_row; |
317 | } |
318 | |
319 | unset($this->module_cache['modules'], $names); |
320 | } |
321 | |
322 | /** |
323 | * Check if a certain main module is accessible/loaded |
324 | * By giving the module mode you are able to additionally check for only one mode within the main module |
325 | * |
326 | * @param string $module_basename The module base name, for example logs, reports, main (for the mcp). |
327 | * @param mixed $module_mode The module mode to check. If provided the mode will be checked in addition for presence. |
328 | * |
329 | * @return bool Returns true if module is loaded and accessible, else returns false |
330 | */ |
331 | function loaded($module_basename, $module_mode = false) |
332 | { |
333 | if (!$this->is_full_class($module_basename)) |
334 | { |
335 | $module_basename = $this->p_class . '_' . $module_basename; |
336 | } |
337 | |
338 | if (empty($this->loaded_cache)) |
339 | { |
340 | $this->loaded_cache = array(); |
341 | |
342 | foreach ($this->module_ary as $row) |
343 | { |
344 | if (!$row['name']) |
345 | { |
346 | continue; |
347 | } |
348 | |
349 | if (!isset($this->loaded_cache[$row['name']])) |
350 | { |
351 | $this->loaded_cache[$row['name']] = array(); |
352 | } |
353 | |
354 | if (!$row['mode']) |
355 | { |
356 | continue; |
357 | } |
358 | |
359 | $this->loaded_cache[$row['name']][$row['mode']] = true; |
360 | } |
361 | } |
362 | |
363 | if ($module_mode === false) |
364 | { |
365 | return (isset($this->loaded_cache[$module_basename])) ? true : false; |
366 | } |
367 | |
368 | return (!empty($this->loaded_cache[$module_basename][$module_mode])) ? true : false; |
369 | } |
370 | |
371 | /** |
372 | * Check module authorisation. |
373 | * |
374 | * This is a non-static version that uses $this->acl_forum_id |
375 | * for the forum id. |
376 | */ |
377 | function module_auth_self($module_auth) |
378 | { |
379 | return self::module_auth($module_auth, $this->acl_forum_id); |
380 | } |
381 | |
382 | /** |
383 | * Check module authorisation. |
384 | * |
385 | * This is a static version, it must be given $forum_id. |
386 | * See also module_auth_self. |
387 | */ |
388 | static function module_auth($module_auth, $forum_id) |
389 | { |
390 | global $auth, $config; |
391 | global $request, $phpbb_extension_manager, $phpbb_dispatcher; |
392 | |
393 | $module_auth = trim($module_auth); |
394 | |
395 | // Generally allowed to access module if module_auth is empty |
396 | if (!$module_auth) |
397 | { |
398 | return true; |
399 | } |
400 | |
401 | // With the code below we make sure only those elements get eval'd we really want to be checked |
402 | preg_match_all('/(?: |
403 | "[^"\\\\]*(?:\\\\.[^"\\\\]*)*" | |
404 | \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\' | |
405 | [(),] | |
406 | [^\s(),]+)/x', $module_auth, $match); |
407 | |
408 | // Valid tokens for auth and their replacements |
409 | $valid_tokens = array( |
410 | 'acl_([a-z0-9_]+)(,\$id)?' => '(int) $auth->acl_get(\'\\1\'\\2)', |
411 | '\$id' => '(int) $forum_id', |
412 | 'aclf_([a-z0-9_]+)' => '(int) $auth->acl_getf_global(\'\\1\')', |
413 | 'cfg_([a-z0-9_]+)' => '(int) $config[\'\\1\']', |
414 | 'request_([a-zA-Z0-9_]+)' => '$request->variable(\'\\1\', false)', |
415 | 'ext_([a-zA-Z0-9_/]+)' => 'array_key_exists(\'\\1\', $phpbb_extension_manager->all_enabled())', |
416 | 'authmethod_([a-z0-9_\\\\]+)' => '($config[\'auth_method\'] === \'\\1\')', |
417 | ); |
418 | |
419 | /** |
420 | * Alter tokens for module authorisation check |
421 | * |
422 | * @event core.module_auth |
423 | * @var array valid_tokens Valid tokens and their auth check |
424 | * replacements |
425 | * @var string module_auth The module_auth of the current |
426 | * module |
427 | * @var int forum_id The current forum_id |
428 | * @since 3.1.0-a3 |
429 | */ |
430 | $vars = array('valid_tokens', 'module_auth', 'forum_id'); |
431 | extract($phpbb_dispatcher->trigger_event('core.module_auth', compact($vars))); |
432 | |
433 | $tokens = $match[0]; |
434 | for ($i = 0, $size = count($tokens); $i < $size; $i++) |
435 | { |
436 | $token = &$tokens[$i]; |
437 | |
438 | switch ($token) |
439 | { |
440 | case ')': |
441 | case '(': |
442 | case '&&': |
443 | case '||': |
444 | case ',': |
445 | break; |
446 | |
447 | default: |
448 | if (!preg_match('#(?:' . implode(')|(?:', array_keys($valid_tokens)) . ')#', $token)) |
449 | { |
450 | $token = ''; |
451 | } |
452 | break; |
453 | } |
454 | } |
455 | |
456 | $module_auth = implode(' ', $tokens); |
457 | |
458 | // Make sure $id separation is working fine |
459 | $module_auth = str_replace(' , ', ',', $module_auth); |
460 | |
461 | $module_auth = preg_replace( |
462 | // Array keys with # prepended/appended |
463 | array_map(function($value) { |
464 | return '#' . $value . '#'; |
465 | }, array_keys($valid_tokens)), |
466 | array_values($valid_tokens), |
467 | $module_auth |
468 | ); |
469 | |
470 | $is_auth = false; |
471 | // @codingStandardsIgnoreStart |
472 | eval('$is_auth = (int) (' . $module_auth . ');'); |
473 | // @codingStandardsIgnoreEnd |
474 | |
475 | return $is_auth; |
476 | } |
477 | |
478 | /** |
479 | * Set active module |
480 | */ |
481 | function set_active($id = false, $mode = false) |
482 | { |
483 | global $auth, $request, $user; |
484 | |
485 | $icat = false; |
486 | $this->active_module = false; |
487 | |
488 | if ($request->variable('icat', '')) |
489 | { |
490 | $icat = $id; |
491 | $id = $request->variable('icat', ''); |
492 | } |
493 | |
494 | // Restore the backslashes in class names |
495 | if (strpos($id, '-') !== false) |
496 | { |
497 | $id = str_replace('-', '\\', $id); |
498 | } |
499 | |
500 | if ($id && !is_numeric($id) && !$this->is_full_class($id)) |
501 | { |
502 | $id = $this->p_class . '_' . $id; |
503 | } |
504 | |
505 | // Fallback to acp main page for special test permission mode |
506 | if ($this->p_class === 'acp' && $user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) |
507 | { |
508 | $id = ''; |
509 | $mode = ''; |
510 | $icat = false; |
511 | } |
512 | |
513 | $category = false; |
514 | foreach ($this->module_ary as $row_id => $item_ary) |
515 | { |
516 | // If this is a module and it's selected, active |
517 | // If this is a category and the module is the first within it, active |
518 | // If this is a module and no mode selected, select first mode |
519 | // If no category or module selected, go active for first module in first category |
520 | if ( |
521 | (($item_ary['name'] === $id || $item_ary['name'] === $this->p_class . '_' . $id || $item_ary['id'] === (int) $id) && (($item_ary['mode'] == $mode && !$item_ary['cat']) || ($icat && $item_ary['cat']))) || |
522 | ($item_ary['parent'] === $category && !$item_ary['cat'] && !$icat && $item_ary['display']) || |
523 | (($item_ary['name'] === $id || $item_ary['name'] === $this->p_class . '_' . $id || $item_ary['id'] === (int) $id) && !$mode && !$item_ary['cat']) || |
524 | (!$id && !$mode && !$item_ary['cat'] && $item_ary['display']) |
525 | ) |
526 | { |
527 | if ($item_ary['cat']) |
528 | { |
529 | $id = $icat; |
530 | $icat = false; |
531 | |
532 | continue; |
533 | } |
534 | |
535 | $this->p_id = $item_ary['id']; |
536 | $this->p_parent = $item_ary['parent']; |
537 | $this->p_name = $item_ary['name']; |
538 | $this->p_mode = $item_ary['mode']; |
539 | $this->p_left = $item_ary['left']; |
540 | $this->p_right = $item_ary['right']; |
541 | |
542 | $this->module_cache['parents'] = $this->module_cache['parents'][$this->p_id]; |
543 | $this->active_module = $item_ary['id']; |
544 | $this->active_module_row_id = $row_id; |
545 | |
546 | break; |
547 | } |
548 | else if (($item_ary['cat'] && $item_ary['id'] === (int) $id) || ($item_ary['parent'] === $category && $item_ary['cat'])) |
549 | { |
550 | $category = $item_ary['id']; |
551 | } |
552 | } |
553 | } |
554 | |
555 | /** |
556 | * Loads currently active module |
557 | * |
558 | * This method loads a given module, passing it the relevant id and mode. |
559 | * |
560 | * @param string|false $mode mode, as passed through to the module |
561 | * @param string|false $module_url If supplied, we use this module url |
562 | * @param bool $execute_module If true, at the end we execute the main method for the new instance |
563 | */ |
564 | function load_active($mode = false, $module_url = false, $execute_module = true) |
565 | { |
566 | global $phpbb_root_path, $phpbb_admin_path, $phpEx, $user, $template, $request; |
567 | |
568 | $module_path = $this->include_path . $this->p_class; |
569 | $icat = $request->variable('icat', ''); |
570 | |
571 | if ($this->active_module === false) |
572 | { |
573 | trigger_error('MODULE_NOT_ACCESS', E_USER_ERROR); |
574 | } |
575 | |
576 | // new modules use the full class names, old ones are always called <type>_<name>, e.g. acp_board |
577 | if (!class_exists($this->p_name)) |
578 | { |
579 | if (!file_exists("$module_path/{$this->p_name}.$phpEx")) |
580 | { |
581 | trigger_error($user->lang('MODULE_NOT_FIND', "$module_path/{$this->p_name}.$phpEx"), E_USER_ERROR); |
582 | } |
583 | |
584 | include("$module_path/{$this->p_name}.$phpEx"); |
585 | |
586 | if (!class_exists($this->p_name)) |
587 | { |
588 | trigger_error($user->lang('MODULE_FILE_INCORRECT_CLASS', "$module_path/{$this->p_name}.$phpEx", $this->p_name), E_USER_ERROR); |
589 | } |
590 | } |
591 | |
592 | if (!empty($mode)) |
593 | { |
594 | $this->p_mode = $mode; |
595 | } |
596 | |
597 | // Create a new instance of the desired module ... |
598 | $class_name = $this->p_name; |
599 | |
600 | $this->module = new $class_name($this); |
601 | |
602 | // We pre-define the action parameter we are using all over the place |
603 | if (defined('IN_ADMIN')) |
604 | { |
605 | /* |
606 | * If this is an extension module, we'll try to automatically set |
607 | * the style paths for the extension (the ext author can change them |
608 | * if necessary). |
609 | */ |
610 | $module_dir = explode('\\', get_class($this->module)); |
611 | |
612 | // 0 vendor, 1 extension name, ... |
613 | if (isset($module_dir[1])) |
614 | { |
615 | $module_style_dir = $phpbb_root_path . 'ext/' . $module_dir[0] . '/' . $module_dir[1] . '/adm/style'; |
616 | |
617 | if (is_dir($module_style_dir)) |
618 | { |
619 | $template->set_custom_style( |
620 | [ |
621 | [ |
622 | 'name' => 'adm', |
623 | 'ext_path' => 'adm/style/', |
624 | ], |
625 | ], |
626 | [ |
627 | $module_style_dir, |
628 | $phpbb_admin_path . 'style', |
629 | $phpbb_root_path . 'styles/all/template/', |
630 | ] |
631 | ); |
632 | } |
633 | } |
634 | |
635 | // Is first module automatically enabled a duplicate and the category not passed yet? |
636 | if (!$icat && $this->module_ary[$this->active_module_row_id]['is_duplicate']) |
637 | { |
638 | $icat = $this->module_ary[$this->active_module_row_id]['parent']; |
639 | } |
640 | |
641 | // Not being able to overwrite ;) |
642 | $this->module->u_action = append_sid("{$phpbb_admin_path}index.$phpEx", 'i=' . $this->get_module_identifier($this->p_name)) . (($icat) ? '&icat=' . $icat : '') . "&mode={$this->p_mode}"; |
643 | } |
644 | else |
645 | { |
646 | /* |
647 | * If this is an extension module, we'll try to automatically set |
648 | * the style paths for the extension (the ext author can change them |
649 | * if necessary). |
650 | */ |
651 | $module_dir = explode('\\', get_class($this->module)); |
652 | |
653 | // 0 vendor, 1 extension name, ... |
654 | if (isset($module_dir[1])) |
655 | { |
656 | $module_style_dir = 'ext/' . $module_dir[0] . '/' . $module_dir[1] . '/styles'; |
657 | |
658 | if (is_dir($phpbb_root_path . $module_style_dir)) |
659 | { |
660 | $template->set_style(array($module_style_dir, 'styles')); |
661 | } |
662 | } |
663 | |
664 | // If user specified the module url we will use it... |
665 | if ($module_url !== false) |
666 | { |
667 | $this->module->u_action = $module_url; |
668 | } |
669 | else |
670 | { |
671 | $this->module->u_action = $phpbb_root_path . (($user->page['page_dir']) ? $user->page['page_dir'] . '/' : '') . $user->page['page_name']; |
672 | } |
673 | |
674 | $this->module->u_action = append_sid($this->module->u_action, 'i=' . $this->get_module_identifier($this->p_name)) . (($icat) ? '&icat=' . $icat : '') . "&mode={$this->p_mode}"; |
675 | } |
676 | |
677 | // Add url_extra parameter to u_action url |
678 | if (!empty($this->module_ary) && $this->active_module !== false && $this->module_ary[$this->active_module_row_id]['url_extra']) |
679 | { |
680 | $this->module->u_action .= '&' . $this->module_ary[$this->active_module_row_id]['url_extra']; |
681 | } |
682 | |
683 | // Assign the module path for re-usage |
684 | $this->module->module_path = $module_path . '/'; |
685 | |
686 | // Execute the main method for the new instance, we send the module id and mode as parameters |
687 | // Users are able to call the main method after this function to be able to assign additional parameters manually |
688 | if ($execute_module) |
689 | { |
690 | $short_name = preg_replace("#^{$this->p_class}_#", '', $this->p_name); |
691 | $this->module->main($short_name, $this->p_mode); |
692 | } |
693 | } |
694 | |
695 | /** |
696 | * Appending url parameter to the currently active module. |
697 | * |
698 | * This function is called for adding specific url parameters while executing the current module. |
699 | * It is doing the same as the _module_{name}_url() function, apart from being able to be called after |
700 | * having dynamically parsed specific parameters. This allows more freedom in choosing additional parameters. |
701 | * One example can be seen in /includes/mcp/mcp_notes.php - $this->p_master->adjust_url() call. |
702 | * |
703 | * @param string $url_extra Extra url parameters, e.g.: &u=$user_id |
704 | * |
705 | */ |
706 | function adjust_url($url_extra) |
707 | { |
708 | if (empty($this->module_ary[$this->active_module_row_id])) |
709 | { |
710 | return; |
711 | } |
712 | |
713 | $row = &$this->module_ary[$this->active_module_row_id]; |
714 | |
715 | // We check for the same url_extra in $row['url_extra'] to overcome doubled additions... |
716 | if (strpos($row['url_extra'], $url_extra) === false) |
717 | { |
718 | $row['url_extra'] .= $url_extra; |
719 | } |
720 | } |
721 | |
722 | /** |
723 | * Check if a module is active |
724 | */ |
725 | function is_active($id, $mode = false) |
726 | { |
727 | // If we find a name by this id and being enabled we have our active one... |
728 | foreach ($this->module_ary as $row_id => $item_ary) |
729 | { |
730 | if (($item_ary['name'] === $id || $item_ary['id'] === (int) $id) && $item_ary['display'] || $item_ary['name'] === $this->p_class . '_' . $id) |
731 | { |
732 | if ($mode === false || $mode === $item_ary['mode']) |
733 | { |
734 | return true; |
735 | } |
736 | } |
737 | } |
738 | |
739 | return false; |
740 | } |
741 | |
742 | /** |
743 | * Get parents |
744 | */ |
745 | function get_parents($parent_id, $left_id, $right_id, &$all_parents) |
746 | { |
747 | $parents = array(); |
748 | |
749 | if ($parent_id > 0) |
750 | { |
751 | foreach ($all_parents as $module_id => $row) |
752 | { |
753 | if ($row['left_id'] < $left_id && $row['right_id'] > $right_id) |
754 | { |
755 | $parents[$module_id] = $row['parent_id']; |
756 | } |
757 | |
758 | if ($row['left_id'] > $left_id) |
759 | { |
760 | break; |
761 | } |
762 | } |
763 | } |
764 | |
765 | return $parents; |
766 | } |
767 | |
768 | /** |
769 | * Get tree branch |
770 | */ |
771 | function get_branch($left_id, $right_id, $remaining) |
772 | { |
773 | $branch = array(); |
774 | |
775 | foreach ($remaining as $key => $row) |
776 | { |
777 | if ($row['left_id'] > $left_id && $row['left_id'] < $right_id) |
778 | { |
779 | $branch[] = $row; |
780 | continue; |
781 | } |
782 | break; |
783 | } |
784 | |
785 | return $branch; |
786 | } |
787 | |
788 | /** |
789 | * Build true binary tree from given array |
790 | * Not in use |
791 | */ |
792 | function build_tree(&$modules, &$parents) |
793 | { |
794 | $tree = array(); |
795 | |
796 | foreach ($modules as $row) |
797 | { |
798 | $branch = &$tree; |
799 | |
800 | if ($row['parent_id']) |
801 | { |
802 | // Go through the tree to find our branch |
803 | $parent_tree = $parents[$row['module_id']]; |
804 | |
805 | foreach ($parent_tree as $id => $value) |
806 | { |
807 | if (!isset($branch[$id]) && isset($branch['child'])) |
808 | { |
809 | $branch = &$branch['child']; |
810 | } |
811 | $branch = &$branch[$id]; |
812 | } |
813 | $branch = &$branch['child']; |
814 | } |
815 | |
816 | $branch[$row['module_id']] = $row; |
817 | if (!isset($branch[$row['module_id']]['child'])) |
818 | { |
819 | $branch[$row['module_id']]['child'] = array(); |
820 | } |
821 | } |
822 | |
823 | return $tree; |
824 | } |
825 | |
826 | /** |
827 | * Build navigation structure |
828 | */ |
829 | function assign_tpl_vars($module_url) |
830 | { |
831 | global $template; |
832 | |
833 | $current_id = $right_id = false; |
834 | |
835 | // Make sure the module_url has a question mark set, effectively determining the delimiter to use |
836 | $delim = (strpos($module_url, '?') === false) ? '?' : '&'; |
837 | |
838 | $current_depth = 0; |
839 | $linear_offset = 'l_block1'; |
840 | $tabular_offset = 't_block2'; |
841 | |
842 | // Generate the list of modules, we'll do this in two ways ... |
843 | // 1) In a linear fashion |
844 | // 2) In a combined tabbed + linear fashion ... tabs for the categories |
845 | // and a linear list for subcategories/items |
846 | foreach ($this->module_ary as $row_id => $item_ary) |
847 | { |
848 | // Skip hidden modules |
849 | if (!$item_ary['display']) |
850 | { |
851 | continue; |
852 | } |
853 | |
854 | // Skip branch |
855 | if ($right_id !== false) |
856 | { |
857 | if ($item_ary['left'] < $right_id) |
858 | { |
859 | continue; |
860 | } |
861 | |
862 | $right_id = false; |
863 | } |
864 | |
865 | // Category with no members on their way down (we have to check every level) |
866 | if (!$item_ary['name']) |
867 | { |
868 | $empty_category = true; |
869 | |
870 | // We go through the branch and look for an activated module |
871 | foreach (array_slice($this->module_ary, $row_id + 1) as $temp_row) |
872 | { |
873 | if ($temp_row['left'] > $item_ary['left'] && $temp_row['left'] < $item_ary['right']) |
874 | { |
875 | // Module there and displayed? |
876 | if ($temp_row['name'] && $temp_row['display']) |
877 | { |
878 | $empty_category = false; |
879 | break; |
880 | } |
881 | continue; |
882 | } |
883 | break; |
884 | } |
885 | |
886 | // Skip the branch |
887 | if ($empty_category) |
888 | { |
889 | $right_id = $item_ary['right']; |
890 | continue; |
891 | } |
892 | } |
893 | |
894 | // Select first id we can get |
895 | if (!$current_id && (isset($this->module_cache['parents'][$item_ary['id']]) || $item_ary['id'] == $this->p_id)) |
896 | { |
897 | $current_id = $item_ary['id']; |
898 | } |
899 | |
900 | $depth = $item_ary['depth']; |
901 | |
902 | if ($depth > $current_depth) |
903 | { |
904 | $linear_offset = $linear_offset . '.l_block' . ($depth + 1); |
905 | $tabular_offset = ($depth + 1 > 2) ? $tabular_offset . '.t_block' . ($depth + 1) : $tabular_offset; |
906 | } |
907 | else if ($depth < $current_depth) |
908 | { |
909 | for ($i = $current_depth - $depth; $i > 0; $i--) |
910 | { |
911 | $linear_offset = substr($linear_offset, 0, strrpos($linear_offset, '.')); |
912 | $tabular_offset = ($i + $depth > 1) ? substr($tabular_offset, 0, strrpos($tabular_offset, '.')) : $tabular_offset; |
913 | } |
914 | } |
915 | |
916 | $u_title = $module_url . $delim . 'i='; |
917 | // if the item has a name use it, else use its id |
918 | if (empty($item_ary['name'])) |
919 | { |
920 | $u_title .= $item_ary['id']; |
921 | } |
922 | else |
923 | { |
924 | // if the category has a name, then use it. |
925 | $u_title .= $this->get_module_identifier($item_ary['name']); |
926 | } |
927 | // If the item is not a category append the mode |
928 | if (!$item_ary['cat']) |
929 | { |
930 | if ($item_ary['is_duplicate']) |
931 | { |
932 | $u_title .= '&icat=' . $current_id; |
933 | } |
934 | $u_title .= '&mode=' . $item_ary['mode']; |
935 | } |
936 | |
937 | // Was not allowed in categories before - /*!$item_ary['cat'] && */ |
938 | $u_title .= (isset($item_ary['url_extra']) && $item_ary['url_extra']) ? '&' . $item_ary['url_extra'] : ''; |
939 | |
940 | // Only output a categories items if it's currently selected |
941 | if (!$depth || ($depth && (in_array($item_ary['parent'], array_values($this->module_cache['parents'])) || $item_ary['parent'] == $this->p_parent))) |
942 | { |
943 | $use_tabular_offset = (!$depth) ? 't_block1' : $tabular_offset; |
944 | |
945 | $tpl_ary = array( |
946 | 'L_TITLE' => $item_ary['lang'], |
947 | 'S_SELECTED' => (isset($this->module_cache['parents'][$item_ary['id']]) || $item_ary['id'] == $this->p_id) ? true : false, |
948 | 'U_TITLE' => $u_title |
949 | ); |
950 | |
951 | if (isset($this->module_cache['parents'][$item_ary['id']]) || $item_ary['id'] == $this->p_id) |
952 | { |
953 | $template->assign_block_vars('navlinks', array( |
954 | 'BREADCRUMB_NAME' => $item_ary['lang'], |
955 | 'U_BREADCRUMB' => $u_title, |
956 | )); |
957 | } |
958 | |
959 | $template->assign_block_vars($use_tabular_offset, array_merge($tpl_ary, array_change_key_case($item_ary, CASE_UPPER))); |
960 | } |
961 | |
962 | $tpl_ary = array( |
963 | 'L_TITLE' => $item_ary['lang'], |
964 | 'S_SELECTED' => (isset($this->module_cache['parents'][$item_ary['id']]) || $item_ary['id'] == $this->p_id) ? true : false, |
965 | 'U_TITLE' => $u_title |
966 | ); |
967 | |
968 | $template->assign_block_vars($linear_offset, array_merge($tpl_ary, array_change_key_case($item_ary, CASE_UPPER))); |
969 | |
970 | $current_depth = $depth; |
971 | } |
972 | } |
973 | |
974 | /** |
975 | * Returns desired template name |
976 | */ |
977 | function get_tpl_name() |
978 | { |
979 | return $this->module->tpl_name . '.html'; |
980 | } |
981 | |
982 | /** |
983 | * Returns the desired page title |
984 | */ |
985 | function get_page_title() |
986 | { |
987 | global $user; |
988 | |
989 | if (!isset($this->module->page_title)) |
990 | { |
991 | return ''; |
992 | } |
993 | |
994 | return (isset($user->lang[$this->module->page_title])) ? $user->lang[$this->module->page_title] : $this->module->page_title; |
995 | } |
996 | |
997 | /** |
998 | * Load module as the current active one without the need for registering it |
999 | * |
1000 | * @param string $class module class (acp/mcp/ucp) |
1001 | * @param string $name module name (class name of the module, or its basename |
1002 | * phpbb_ext_foo_acp_bar_module, ucp_zebra or zebra) |
1003 | * @param string $mode mode, as passed through to the module |
1004 | * |
1005 | */ |
1006 | function load($class, $name, $mode = false) |
1007 | { |
1008 | // new modules use the full class names, old ones are always called <class>_<name>, e.g. acp_board |
1009 | // in the latter case this function may be called as load('acp', 'board') |
1010 | if (!class_exists($name) && substr($name, 0, strlen($class) + 1) !== $class . '_') |
1011 | { |
1012 | $name = $class . '_' . $name; |
1013 | } |
1014 | |
1015 | $this->p_class = $class; |
1016 | $this->p_name = $name; |
1017 | |
1018 | // Set active module to true instead of using the id |
1019 | $this->active_module = true; |
1020 | |
1021 | $this->load_active($mode); |
1022 | } |
1023 | |
1024 | /** |
1025 | * Display module |
1026 | */ |
1027 | function display($page_title, $display_online_list = false) |
1028 | { |
1029 | global $template, $user; |
1030 | |
1031 | // Generate the page |
1032 | if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) |
1033 | { |
1034 | adm_page_header($page_title); |
1035 | } |
1036 | else |
1037 | { |
1038 | page_header($page_title, $display_online_list); |
1039 | } |
1040 | |
1041 | $template->set_filenames(array( |
1042 | 'body' => $this->get_tpl_name()) |
1043 | ); |
1044 | |
1045 | if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) |
1046 | { |
1047 | adm_page_footer(); |
1048 | } |
1049 | else |
1050 | { |
1051 | page_footer(); |
1052 | } |
1053 | } |
1054 | |
1055 | /** |
1056 | * Toggle whether this module will be displayed or not |
1057 | */ |
1058 | function set_display($id, $mode = false, $display = true) |
1059 | { |
1060 | foreach ($this->module_ary as $row_id => $item_ary) |
1061 | { |
1062 | if (($item_ary['name'] === $id || $item_ary['name'] === $this->p_class . '_' . $id || $item_ary['id'] === (int) $id) && (!$mode || $item_ary['mode'] === $mode)) |
1063 | { |
1064 | $this->module_ary[$row_id]['display'] = (int) $display; |
1065 | } |
1066 | } |
1067 | } |
1068 | |
1069 | /** |
1070 | * Add custom MOD info language file |
1071 | */ |
1072 | function add_mod_info($module_class) |
1073 | { |
1074 | global $config, $user, $phpEx, $phpbb_extension_manager; |
1075 | |
1076 | $finder = $phpbb_extension_manager->get_finder(); |
1077 | |
1078 | // We grab the language files from the default, English and user's language. |
1079 | // So we can fall back to the other files like we do when using add_lang() |
1080 | $default_lang_files = $english_lang_files = $user_lang_files = array(); |
1081 | |
1082 | // Search for board default language if it's not the user language |
1083 | if ($config['default_lang'] != $user->lang_name) |
1084 | { |
1085 | $default_lang_files = $finder |
1086 | ->prefix('info_' . strtolower($module_class) . '_') |
1087 | ->suffix(".$phpEx") |
1088 | ->extension_directory('/language/' . basename($config['default_lang'])) |
1089 | ->core_path('language/' . basename($config['default_lang']) . '/mods/') |
1090 | ->find(); |
1091 | } |
1092 | |
1093 | // Search for english, if its not the default or user language |
1094 | if ($config['default_lang'] != 'en' && $user->lang_name != 'en') |
1095 | { |
1096 | $english_lang_files = $finder |
1097 | ->prefix('info_' . strtolower($module_class) . '_') |
1098 | ->suffix(".$phpEx") |
1099 | ->extension_directory('/language/en') |
1100 | ->core_path('language/en/mods/') |
1101 | ->find(); |
1102 | } |
1103 | |
1104 | // Find files in the user's language |
1105 | $user_lang_files = $finder |
1106 | ->prefix('info_' . strtolower($module_class) . '_') |
1107 | ->suffix(".$phpEx") |
1108 | ->extension_directory('/language/' . $user->lang_name) |
1109 | ->core_path('language/' . $user->lang_name . '/mods/') |
1110 | ->find(); |
1111 | |
1112 | $lang_files = array_merge($english_lang_files, $default_lang_files, $user_lang_files); |
1113 | foreach ($lang_files as $lang_file => $ext_name) |
1114 | { |
1115 | $user->add_lang_ext($ext_name, $lang_file); |
1116 | } |
1117 | } |
1118 | |
1119 | /** |
1120 | * Retrieve shortened module basename for legacy basenames (with xcp_ prefix) |
1121 | * |
1122 | * @param string $basename A module basename |
1123 | * @return string The basename if it starts with phpbb_ or the basename with |
1124 | * the current p_class (e.g. acp_) stripped. |
1125 | */ |
1126 | protected function get_short_name($basename) |
1127 | { |
1128 | if (substr($basename, 0, 6) === 'phpbb\\' || strpos($basename, '\\') !== false) |
1129 | { |
1130 | return $basename; |
1131 | } |
1132 | |
1133 | // strip xcp_ prefix from old classes |
1134 | return substr($basename, strlen($this->p_class) + 1); |
1135 | } |
1136 | |
1137 | /** |
1138 | * If the basename contains a \ we don't use that for the URL. |
1139 | * |
1140 | * Firefox is currently unable to correctly copy a urlencoded \ |
1141 | * so users will be unable to post links to modules. |
1142 | * However we can replace them with dashes and re-replace them later |
1143 | * |
1144 | * @param string $basename Basename of the module |
1145 | * @return string Identifier that should be used for |
1146 | * module link creation |
1147 | */ |
1148 | protected function get_module_identifier($basename) |
1149 | { |
1150 | if (strpos($basename, '\\') === false) |
1151 | { |
1152 | return $basename; |
1153 | } |
1154 | |
1155 | return str_replace('\\', '-', $basename); |
1156 | } |
1157 | |
1158 | /** |
1159 | * Checks whether the given module basename is a correct class name |
1160 | * |
1161 | * @param string $basename A module basename |
1162 | * @return bool True if the basename starts with phpbb_ or (x)cp_, false otherwise |
1163 | */ |
1164 | protected function is_full_class($basename) |
1165 | { |
1166 | return (strpos($basename, '\\') !== false || preg_match('/^(ucp|mcp|acp)_/', $basename)); |
1167 | } |
1168 | } |