1<?php
2/**
3 * @package     Joomla.Platform
4 * @subpackage  Event
5 *
6 * @copyright   Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
7 * @license     GNU General Public License version 2 or later; see LICENSE
8 */
9
10defined('JPATH_PLATFORM') or die;
11
12/**
13 * Class to handle dispatching of events.
14 *
15 * This is the Observable part of the Observer design pattern
16 * for the event architecture.
17 *
18 * @see         JPlugin
19 * @since       3.0.0
20 * @deprecated  4.0  The CMS' Event classes will be replaced with the `joomla/event` package
21 */
22class JEventDispatcher extends JObject
23{
24	/**
25	 * An array of Observer objects to notify
26	 *
27	 * @var    array
28	 * @since  3.0.0
29	 */
30	protected $_observers = array();
31
32	/**
33	 * The state of the observable object
34	 *
35	 * @var    mixed
36	 * @since  3.0.0
37	 */
38	protected $_state = null;
39
40	/**
41	 * A multi dimensional array of [function][] = key for observers
42	 *
43	 * @var    array
44	 * @since  3.0.0
45	 */
46	protected $_methods = array();
47
48	/**
49	 * Stores the singleton instance of the dispatcher.
50	 *
51	 * @var    JEventDispatcher
52	 * @since  3.0.0
53	 */
54	protected static $instance = null;
55
56	/**
57	 * Returns the global Event Dispatcher object, only creating it
58	 * if it doesn't already exist.
59	 *
60	 * @return  JEventDispatcher  The EventDispatcher object.
61	 *
62	 * @since   3.0.0
63	 */
64	public static function getInstance()
65	{
66		if (self::$instance === null)
67		{
68			self::$instance = new static;
69		}
70
71		return self::$instance;
72	}
73
74	/**
75	 * Get the state of the JEventDispatcher object
76	 *
77	 * @return  mixed    The state of the object.
78	 *
79	 * @since   3.0.0
80	 */
81	public function getState()
82	{
83		return $this->_state;
84	}
85
86	/**
87	 * Registers an event handler to the event dispatcher
88	 *
89	 * @param   string  $event    Name of the event to register handler for
90	 * @param   string  $handler  Name of the event handler
91	 *
92	 * @return  void
93	 *
94	 * @since   3.0.0
95	 * @throws  InvalidArgumentException
96	 */
97	public function register($event, $handler)
98	{
99		// Are we dealing with a class or callback type handler?
100		if (is_callable($handler))
101		{
102			// Ok, function type event handler... let's attach it.
103			$method = array('event' => $event, 'handler' => $handler);
104			$this->attach($method);
105		}
106		elseif (class_exists($handler))
107		{
108			// Ok, class type event handler... let's instantiate and attach it.
109			$this->attach(new $handler($this));
110		}
111		else
112		{
113			throw new InvalidArgumentException('Invalid event handler.');
114		}
115	}
116
117	/**
118	 * Triggers an event by dispatching arguments to all observers that handle
119	 * the event and returning their return values.
120	 *
121	 * @param   string  $event  The event to trigger.
122	 * @param   array   $args   An array of arguments.
123	 *
124	 * @return  array  An array of results from each function call.
125	 *
126	 * @since   3.0.0
127	 */
128	public function trigger($event, $args = array())
129	{
130		$result = array();
131
132		/*
133		 * If no arguments were passed, we still need to pass an empty array to
134		 * the call_user_func_array function.
135		 */
136		$args = (array) $args;
137
138		$event = strtolower($event);
139
140		// Check if any plugins are attached to the event.
141		if (!isset($this->_methods[$event]) || empty($this->_methods[$event]))
142		{
143			// No Plugins Associated To Event!
144			return $result;
145		}
146
147		// Loop through all plugins having a method matching our event
148		foreach ($this->_methods[$event] as $key)
149		{
150			// Check if the plugin is present.
151			if (!isset($this->_observers[$key]))
152			{
153				continue;
154			}
155
156			// Fire the event for an object based observer.
157			if (is_object($this->_observers[$key]))
158			{
159				$args['event'] = $event;
160				$value = $this->_observers[$key]->update($args);
161			}
162			// Fire the event for a function based observer.
163			elseif (is_array($this->_observers[$key]))
164			{
165				$value = call_user_func_array($this->_observers[$key]['handler'], array_values($args));
166			}
167
168			if (isset($value))
169			{
170				$result[] = $value;
171			}
172		}
173
174		return $result;
175	}
176
177	/**
178	 * Attach an observer object
179	 *
180	 * @param   object  $observer  An observer object to attach
181	 *
182	 * @return  void
183	 *
184	 * @since   3.0.0
185	 */
186	public function attach($observer)
187	{
188		if (is_array($observer))
189		{
190			if (!isset($observer['handler']) || !isset($observer['event']) || !is_callable($observer['handler']))
191			{
192				return;
193			}
194
195			// Make sure we haven't already attached this array as an observer
196			foreach ($this->_observers as $check)
197			{
198				if (is_array($check) && $check['event'] === $observer['event'] && $check['handler'] === $observer['handler'])
199				{
200					return;
201				}
202			}
203
204			$this->_observers[] = $observer;
205			$methods = array($observer['event']);
206		}
207		else
208		{
209			if (!($observer instanceof JEvent))
210			{
211				return;
212			}
213
214			// Make sure we haven't already attached this object as an observer
215			$class = get_class($observer);
216
217			foreach ($this->_observers as $check)
218			{
219				if ($check instanceof $class)
220				{
221					return;
222				}
223			}
224
225			$this->_observers[] = $observer;
226			$methods = array_diff(get_class_methods($observer), get_class_methods('JPlugin'));
227		}
228
229		end($this->_observers);
230		$key = key($this->_observers);
231
232		foreach ($methods as $method)
233		{
234			$method = strtolower($method);
235
236			if (!isset($this->_methods[$method]))
237			{
238				$this->_methods[$method] = array();
239			}
240
241			$this->_methods[$method][] = $key;
242		}
243	}
244
245	/**
246	 * Detach an observer object
247	 *
248	 * @param   object  $observer  An observer object to detach.
249	 *
250	 * @return  boolean  True if the observer object was detached.
251	 *
252	 * @since   3.0.0
253	 */
254	public function detach($observer)
255	{
256		$retval = false;
257
258		$key = array_search($observer, $this->_observers);
259
260		if ($key !== false)
261		{
262			unset($this->_observers[$key]);
263			$retval = true;
264
265			foreach ($this->_methods as &$method)
266			{
267				$k = array_search($key, $method);
268
269				if ($k !== false)
270				{
271					unset($method[$k]);
272				}
273			}
274		}
275
276		return $retval;
277	}
278}
279