1: <?php
2:
3: /**
4: * Module management classes
5: * @package framework
6: * @subpackage modules
7: */
8:
9:
10: /**
11: * Trait that manages queued modules
12: */
13: trait Hm_Modules_Queue {
14:
15: /* number of allowed queued module retries */
16: private static $queue_limit = 2;
17:
18: /* number of queue processing attempts */
19: private static $queue_attempts = 0;
20:
21: /* holds the module to page assignment list */
22: private static $module_list = [];
23:
24: /* current module set name, used for error tracking and limiting php file inclusion */
25: private static $source = '';
26:
27: /* a retry queue for modules that fail to insert immediately */
28: private static $module_queue = [];
29:
30: /* queue for delayed module insertion for all pages */
31: private static $all_page_queue = [];
32:
33: /* queue for module replacement */
34: private static $replace_queue = [];
35:
36: /**
37: * Queue a module to be added to all defined pages
38: * @param string $module the module to add
39: * @param bool $logged_in true if the module requires the user to be logged in
40: * @param string $marker the module to insert before or after
41: * @param string $placement "before" or "after" the $marker module
42: * @param string $source the module set containing this module
43: * return void
44: */
45: public static function queue_module_for_all_pages($module, $logged_in, $marker, $placement, $source) {
46: self::$all_page_queue[] = [$module, $logged_in, $marker, $placement, $source];
47: }
48:
49: /**
50: * Process queued modules and add them to all pages
51: * @return void
52: */
53: public static function process_all_page_queue() {
54: foreach (self::$all_page_queue as $mod) {
55: self::add_to_all_pages($mod[0], $mod[1], $mod[2], $mod[3], $mod[4]);
56: }
57: }
58:
59: /**
60: * @param bool $queue true to attempt to re-insert the module later on failure
61: * @param string $page the page to assign the module to
62: * @param string $module the module to add
63: * @param bool $logged_in true if the module requires the user to be logged in
64: * @param string|false $marker the module to insert before or after
65: * @param string $placement "before" or "after" the $marker module
66: * @param string $source the module set containing this module
67: * @return boolean
68: */
69: private static function queue_module($queue, $page, $module, $logged_in, $marker, $placement, $source) {
70: if ($queue) {
71: self::$module_queue[] = [$page, $module, $logged_in, $marker, $placement, $source];
72: return true;
73: } else {
74: Hm_Debug::add(sprintf('failed to insert module %s on %s', $module, $page));
75: }
76: return false;
77: }
78:
79: /**
80: * Queue a module replacement for retry
81: * @param string $target the module to replace
82: * @param string $replacement the module to replace the target with
83: * @param string $page the page to do the replacement for
84: * @return void
85: */
86: private static function queue_replacement_module($target, $replacement, $page) {
87: self::$replace_queue[] = [$target, $replacement, $page, self::$source];
88: }
89:
90: /**
91: * Process the replacement queue and try to replace modules
92: * @return void
93: */
94: public static function process_replace_queue() {
95: foreach (self::$replace_queue as $vals) {
96: self::replace($vals[0], $vals[1], $vals[2], $vals[3]);
97: }
98: }
99:
100: /**
101: * Attempt to insert modules that initially failed
102: * @return void
103: */
104: public static function try_queued_modules() {
105: $requeue = true;
106: $new_queue = [];
107: if (self::$queue_attempts >= self::$queue_limit) {
108: $requeue = false;
109: }
110: foreach (self::$module_queue as $vals) {
111: if (!self::add($vals[0], $vals[1], $vals[2], $vals[3], $vals[4], $requeue, $vals[5])) {
112: $new_queue[] = $vals;
113: }
114: }
115: self::$module_queue = $new_queue;
116: self::$queue_attempts++;
117: }
118: }
119:
120: /**
121: * Trait used as the basic logic for module management
122: *
123: * Two classes use this trait, Hm_Handler_Modules and Hm_Output_Modules.
124: * These are the interfaces module sets use (indirectly) to interact with a request
125: * and produce output to the browser.
126: */
127: trait Hm_Modules {
128:
129: use Hm_Modules_Queue;
130:
131: /**
132: * Load a complete formatted module list
133: * @param array $mod_list list of module assignments
134: * @return void
135: */
136: public static function load($mod_list) {
137: self::$module_list = $mod_list;
138: }
139:
140: /**
141: * Assign the module set name
142: * @param string $source the name of the module set (imap, core, etc)
143: * @return void
144: */
145: public static function set_source($source) {
146: self::$source = $source;
147: }
148:
149: /**
150: * Add a module to every defined page
151: * @param string $module the module to add
152: * @param bool $logged_in true if the module requires the user to be logged in
153: * @param string $marker the module to insert before or after
154: * @param string $placement "before" or "after" the $marker module
155: * @param string $source the module set containing this module
156: * @return void
157: */
158: public static function add_to_all_pages($module, $logged_in, $marker, $placement, $source) {
159: foreach (self::$module_list as $page => $modules) {
160: if (!preg_match("/^ajax_/", $page)) {
161: self::add($page, $module, $logged_in, $marker, $placement, true, $source);
162: }
163: }
164: }
165:
166: /**
167: * Add a module to a single page
168: * @param string $page the page to assign the module to
169: * @param string $module the module to add
170: * @param bool $logged_in true if the module requires the user to be logged in
171: * @param string $marker the module to insert before or after
172: * @param string $placement "before" or "after" the $marker module
173: * @param bool $queue true to attempt to re-insert the module later on failure
174: * @param string $source the module set containing this module
175: * @return bool true if the module was added or was already registered
176: */
177: public static function add($page, $module, $logged_in, $marker=false, $placement='after', $queue=true, $source=false) {
178:
179: self::add_page($page);
180:
181: if (array_key_exists($module, self::$module_list[$page])) {
182: Hm_Debug::add(sprintf("Already registered module for %s re-attempted: %s", $page, $module), 'warning');
183: return true;
184: }
185: $source === false ? $source = self::$source : false;
186: $inserted = self::insert_module($marker, $page, $module, $logged_in, $placement, $source);
187: if (!$inserted) {
188: self::queue_module($queue, $page, $module, $logged_in, $marker, $placement, $source);
189: }
190: return $inserted;
191: }
192:
193: /**
194: * @param string $page page id to add
195: * @return boolean
196: */
197: private static function add_page($page) {
198: if (!array_key_exists($page, self::$module_list)) {
199: self::$module_list[$page] = [];
200: return true;
201: }
202: return false;
203: }
204:
205: /**
206: * @param string|false $marker the module to insert before or after
207: * @param string $page the page to assign the module to
208: * @param string $module the module to add
209: * @param bool $logged_in true if the module requires the user to be logged in
210: * @param string $placement "before" or "after" the $marker module
211: * @param string $source the module set containing this module
212: * @return boolean
213: */
214: private static function insert_module($marker, $page, $module, $logged_in, $placement, $source) {
215: if ($marker !== false) {
216: $inserted = self::insert_at_marker($marker, $page, $module, $logged_in, $placement, $source);
217: } else {
218: self::$module_list[$page][$module] = [$source, $logged_in];
219: $inserted = true;
220: }
221: return $inserted;
222: }
223:
224: /**
225: * Insert a module before or after another one
226: * @param string $marker the module to insert before or after
227: * @param string $page the page to assign the module to
228: * @param string $module the module to add
229: * @param bool $logged_in true if the module requires the user to be logged in
230: * @param string $placement "before" or "after" the $marker module
231: * @param string $source the module set containing this module
232: * @return boolean
233: */
234: private static function insert_at_marker($marker, $page, $module, $logged_in, $placement, $source) {
235: $inserted = false;
236: $mods = array_keys(self::$module_list[$page]);
237: $index = array_search($marker, $mods);
238: if ($index !== false) {
239: if ($placement == 'after') {
240: $index++;
241: }
242: $list = self::$module_list[$page];
243: self::$module_list[$page] = array_merge(array_slice($list, 0, $index),
244: [$module => [$source, $logged_in]],
245: array_slice($list, $index));
246: $inserted = true;
247: }
248: return $inserted;
249: }
250:
251: /**
252: * Replace an already assigned module with a different one
253: * @param string $target module name to replace
254: * @param string $replacement module name to swap in
255: * @param string $page page to replace assignment on, try all pages if false
256: * @param string $source module set source
257: * @return void
258: */
259: public static function replace($target, $replacement, $page=false, $source=false) {
260: $found = false;
261: if ($page !== false) {
262: if (array_key_exists($page, self::$module_list) && array_key_exists($target, self::$module_list[$page])) {
263: self::$module_list[$page] = self::swap_key($target, $replacement, self::$module_list[$page], $source);
264: $found = true;
265: }
266: } else {
267: foreach (self::$module_list as $page => $modules) {
268: if (array_key_exists($target, $modules)) {
269: self::$module_list[$page] = self::swap_key($target, $replacement, self::$module_list[$page], $source);
270: $found = true;
271: }
272: }
273: }
274: if (!$found) {
275: self::queue_replacement_module($target, $replacement, $page);
276: }
277: }
278:
279: /**
280: * Helper function to swap the key of an array and maintain it's value
281: * @param string $target array key to replace
282: * @param string $replacement array key to swap in
283: * @param array $modules list of modules
284: * @param string $source module set source
285: * @return array new list with the key swapped out
286: */
287: private static function swap_key($target, $replacement, $modules, $source) {
288: if (!$source) {
289: $source = self::$source;
290: }
291: $keys = array_keys($modules);
292: $values = array_values($modules);
293: $size = count($modules);
294: for ($i = 0; $i < $size; $i++) {
295: if ($keys[$i] == $target) {
296: $keys[$i] = $replacement;
297: $values[$i][0] = $source;
298: break;
299: }
300: }
301: return array_combine($keys, $values);
302: }
303:
304: /**
305: * Delete a module from the internal list
306: * @param string $page page to delete from
307: * @param string $module module name to delete
308: * @return void
309: */
310: public static function del($page, $module) {
311: if (array_key_exists($page, self::$module_list) && array_key_exists($module, self::$module_list[$page])) {
312: unset(self::$module_list[$page][$module]);
313: }
314: }
315:
316: /**
317: * Return all the modules assigned to a given page
318: * @param string $page the request name
319: * @return array list of assigned modules
320: */
321: public static function get_for_page($page) {
322: $res = [];
323: if (array_key_exists($page, self::$module_list)) {
324: $res = array_merge($res, self::$module_list[$page]);
325: }
326: return $res;
327: }
328:
329: /**
330: * Return all modules for all pages
331: * @return array list of all modules
332: */
333: public static function dump() {
334: return self::$module_list;
335: }
336: }
337:
338: /**
339: * Class to manage all the input processing modules
340: */
341: class Hm_Handler_Modules { use Hm_Modules; }
342:
343: /**
344: * Class to manage all the output modules
345: */
346: class Hm_Output_Modules { use Hm_Modules; }
347:
348: /**
349: * MODULE SET FUNCTIONS
350: *
351: * This is the functional interface used by module sets to
352: * setup data handlers and output modules in their setup.php files.
353: * They are easier to use than dealing directly with the class instances
354: */
355:
356: /**
357: * Add a module set name to the input processing manager
358: * @param string $source module set name
359: * @return void
360: */
361: function handler_source($source) {
362: Hm_Handler_Modules::set_source($source);
363: }
364:
365: /**
366: * Add a module set name to the output module manager
367: * @param string $source module set name
368: * @return void
369: */
370: function output_source($source) {
371: Hm_Output_Modules::set_source($source);
372: }
373:
374: /**
375: * Replace an already assigned module with a different one
376: * @param string $type either output or handler
377: * @param string $target module name to replace
378: * @param string $replacement module to swap in
379: * @param string $page request id, otherwise try all page names
380: * $return void
381: */
382: function replace_module($type, $target, $replacement, $page=false) {
383: if ($type == 'handler') {
384: Hm_Handler_Modules::replace($target, $replacement, $page);
385: }
386: elseif ($type == 'output') {
387: Hm_Output_Modules::replace($target, $replacement, $page);
388: }
389: }
390:
391: /**
392: * Add an input handler module to a specific page
393: * @param string $mod name of the module to add
394: * @param bool $logged_in true if the module should only fire when logged in
395: * @param string $source the module set containing the module code
396: * @param string $marker the module name used to determine where to insert
397: * @param string $placement "before" or "after" the $marker module name
398: * @param bool $queue true if the module should be queued and retryed on failure
399: * @return void
400: */
401: function add_handler($page, $mod, $logged_in, $source=false, $marker=false, $placement='after', $queue=true) {
402: Hm_Handler_Modules::add($page, $mod, $logged_in, $marker, $placement, $queue, $source);
403: }
404:
405: /**
406: * Add a repository to the module
407: *
408: * Repository name for ProfileRepository class is 'profile'
409: * Filename for ProfileRepository class is 'profile'
410: *
411: * @param $class_name
412: * @return void
413: */
414: function add_repository($repository_name) {
415:
416: }
417:
418: /**
419: * Add an output module to a specific page
420: * @param string $mod name of the module to add
421: * @param bool $logged_in true if the module should only fire when logged in
422: * @param string $source the module set containing the module code
423: * @param string $marker the module name used to determine where to insert
424: * @param string $placement "before" or "after" the $marker module name
425: * @param bool $queue true if the module should be queued and retryed on failure
426: * @return void
427: */
428: function add_output($page, $mod, $logged_in, $source=false, $marker=false, $placement='after', $queue=true) {
429: Hm_Output_Modules::add($page, $mod, $logged_in, $marker, $placement, $queue, $source);
430: }
431:
432: /**
433: * Add an input or output module to all possible pages
434: * @param string $type either output or handler
435: * @param string $mod name of the module to add
436: * @param bool $logged_in true if the module should only fire when logged in
437: * @param string $source the module set containing the module code
438: * @param string $marker the module name used to determine where to insert
439: * @param string $placement "before" or "after" the $marker module name
440: * @return void
441: */
442: function add_module_to_all_pages($type, $mod, $logged_in, $source, $marker, $placement) {
443: if ($type == 'output') {
444: Hm_Output_Modules::queue_module_for_all_pages($mod, $logged_in, $marker, $placement, $source);
445: }
446: elseif ( $type == 'handler') {
447: Hm_Handler_Modules::queue_module_for_all_pages($mod, $logged_in, $marker, $placement, $source);
448: }
449: }
450: