1: <?php
2: /*
3: You may not change or alter any portion of this comment or credits
4: of supporting developers from this source code or any supporting source code
5: which is considered copyrighted (c) material of the original comment or credit authors.
6:
7: This program is distributed in the hope that it will be useful,
8: but WITHOUT ANY WARRANTY; without even the implied warranty of
9: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10: */
11:
12: namespace Xoops\Core;
13:
14: /**
15: * Xoops Event processing, including preload mechanism
16: *
17: * @category Xoops\Core\Events
18: * @package Xoops\Core
19: * @author trabis <lusopoemas@gmail.com>
20: * @author Richard Griffith <richard@geekwright.com>
21: * @copyright 2013 XOOPS Project (http://xoops.org)
22: * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
23: * @version Release: 1.0
24: * @link http://xoops.org
25: * @since 1.0
26: */
27: class Events
28: {
29: /**
30: * @var array $preloadList array containing information about the event observers
31: */
32: protected $preloadList = array();
33:
34: /**
35: * @var array $eventListeners - $eventListeners['eventName'][]=Closure
36: * key is event name, value is array of callables
37: */
38: protected $eventListeners = array();
39:
40: /**
41: * @type bool $eventsEnabled
42: */
43: protected $eventsEnabled = true;
44:
45: /**
46: * Constructor
47: */
48: protected function __construct()
49: {
50: }
51:
52: /**
53: * Allow one instance only!
54: *
55: * @return Events instance
56: */
57: public static function getInstance()
58: {
59: static $instance = false;
60:
61: if (!$instance) {
62: $instance = new Events();
63: $instance->initializeListeners();
64: }
65:
66: return $instance;
67: }
68:
69: /**
70: * initializePreloads - Initialize listeners with preload mapped events.
71: *
72: * We suppress event processing during establishing listener map. A a cache miss (on
73: * system_modules_active, for example) triggers regeneration, which may trigger events
74: * that listeners are not prepared to handle. In such circumstances, module level class
75: * mapping will not have been done.
76: *
77: * @return void
78: */
79: protected function initializeListeners()
80: {
81: $this->eventsEnabled = false;
82: // clear state in case this is invoked more than once
83: $this->preloadList = array();
84: $this->eventListeners = array();
85: $this->setPreloads();
86: $this->setEvents();
87: $this->eventsEnabled = true;
88: }
89:
90: /**
91: * Get list of all available preload files
92: *
93: * @return void
94: */
95: protected function setPreloads()
96: {
97: $cache = \Xoops::getInstance()->cache();
98: $key = 'system/modules/preloads';
99: if (!$this->preloadList = $cache->read($key)) {
100: // get active modules from the xoops instance
101: $modules_list = \Xoops::getInstance()->getActiveModules();
102: if (empty($modules_list)) {
103: // this should only happen if an exception was thrown in setActiveModules()
104: $modules_list = array ('system');
105: }
106: $this->preloadList =array();
107: $i = 0;
108: foreach ($modules_list as $module) {
109: if (is_dir($dir = \XoopsBaseConfig::get('root-path') . "/modules/{$module}/preloads/")) {
110: $file_list = Lists\File::getList($dir);
111: foreach ($file_list as $file) {
112: if (preg_match('/(\.php)$/i', $file)) {
113: $file = substr($file, 0, -4);
114: $this->preloadList[$i]['module'] = $module;
115: $this->preloadList[$i]['file'] = $file;
116: ++$i;
117: }
118: }
119: }
120: }
121: $cache->write($key, $this->preloadList);
122: }
123: }
124:
125: /**
126: * Load all preload files and add all listener methods to eventListeners
127: *
128: * Preload classes contain methods based on event names. We extract those method
129: * names and store to compare against when an event is triggered.
130: *
131: * Example:
132: * An event is triggered as 'core.include.common.end'
133: * A PreloadItem class can listen for this event by declaring a static method
134: * 'eventCoreIncludeCommonEnd()'
135: *
136: * PreloadItem class files can be named for the specific source of the
137: * events, such as core.php, system.php, etc. In such case the class name is
138: * built from the concatenation of the module name, the source and the literal
139: * 'Preload'. This mechanism is now considered deprecated. As an example,
140: * a module named 'Example' can listen for 'core' events with a file named
141: * preloads/core.php, containing a class ExampleCorePreload
142: *
143: * The prefered preload definition is the unified preloads/preload.php file
144: * containing a single PreloadItem class name concatenating the module name and
145: * the literal 'Preload'. This class can listen for events from any source.
146: *
147: * @return void
148: */
149: protected function setEvents()
150: {
151: $xoops = \Xoops::getInstance();
152: foreach ($this->preloadList as $preload) {
153: $path = $xoops->path('modules/' . $preload['module'] . '/preloads/' . $preload['file']. '.php');
154: include_once $path;
155: $class_name = ucfirst($preload['module'])
156: . ($preload['file'] === 'preload' ? '' : ucfirst($preload['file']) )
157: . 'Preload';
158: if (!class_exists($class_name)) {
159: continue;
160: }
161: $class_methods = get_class_methods($class_name);
162: foreach ($class_methods as $method) {
163: if (strpos($method, 'event') === 0) {
164: $eventName = strtolower(str_replace('event', '', $method));
165: $event = array($class_name, $method);
166: $this->eventListeners[$eventName][] = $event;
167: }
168: }
169: }
170: }
171:
172: /**
173: * Trigger a specific event
174: *
175: * @param string $eventName Name of the event to trigger
176: * @param mixed $args Method arguments
177: *
178: * @return void
179: */
180: public function triggerEvent($eventName, $args = array())
181: {
182: if ($this->eventsEnabled) {
183: $eventName = $this->toInternalEventName($eventName);
184: if (isset($this->eventListeners[$eventName])) {
185: foreach ($this->eventListeners[$eventName] as $event) {
186: if (is_callable($event)) {
187: call_user_func($event, $args);
188: }
189: }
190: }
191: }
192: }
193:
194: /**
195: * toInternalEventName - convert event name to internal form
196: * i.e. core.include.common.end becomes coreincludecommonend
197: *
198: * @param string $eventName the event name
199: *
200: * @return string converted name
201: */
202: protected function toInternalEventName($eventName)
203: {
204: return strtolower(str_replace('.', '', $eventName));
205: }
206:
207: /**
208: * addListener - add a listener, providing a callback for a specific event.
209: *
210: * @param string $eventName the event name
211: * @param callable $callback any callable acceptable for call_user_func
212: *
213: * @return void
214: */
215: public function addListener($eventName, $callback)
216: {
217: $eventName = $this->toInternalEventName($eventName);
218: $this->eventListeners[$eventName][]=$callback;
219: }
220:
221: /**
222: * getEvents - for debugging only, return list of event listeners
223: *
224: * @return array of events and listeners
225: */
226: public function getEvents()
227: {
228: return $this->eventListeners;
229: }
230:
231: /**
232: * hasListeners - for debugging only, return list of event listeners
233: *
234: * @param string $eventName event name
235: *
236: * @return boolean true if one or more listeners are registered for the event
237: */
238: public function hasListeners($eventName)
239: {
240: $eventName = $this->toInternalEventName($eventName);
241: return array_key_exists($eventName, $this->eventListeners);
242: }
243: }
244: