1 <?php
2 /**
3 * @package Joomla.Libraries
4 * @subpackage Captcha
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE.txt
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 use Joomla\Registry\Registry;
13
14 /**
15 * Joomla! Captcha base object
16 *
17 * @abstract
18 * @package Joomla.Libraries
19 * @subpackage Captcha
20 * @since 2.5
21 */
22 class JCaptcha extends JObject
23 {
24 /**
25 * An array of Observer objects to notify
26 *
27 * @var array
28 * @since 2.5
29 */
30 protected $_observers = array();
31
32 /**
33 * The state of the observable object
34 *
35 * @var mixed
36 * @since 2.5
37 */
38 protected $_state = null;
39
40 /**
41 * A multi dimensional array of [function][] = key for observers
42 *
43 * @var array
44 * @since 2.5
45 */
46 protected $_methods = array();
47
48 /**
49 * Captcha Plugin object
50 *
51 * @var JPlugin
52 * @since 2.5
53 */
54 private $_captcha;
55
56 /**
57 * Editor Plugin name
58 *
59 * @var string
60 * @since 2.5
61 */
62 private $_name;
63
64 /**
65 * Array of instances of this class.
66 *
67 * @var JCaptcha[]
68 * @since 2.5
69 */
70 private static $_instances = array();
71
72 /**
73 * Class constructor.
74 *
75 * @param string $captcha The editor to use.
76 * @param array $options Associative array of options.
77 *
78 * @since 2.5
79 */
80 public function __construct($captcha, $options)
81 {
82 $this->_name = $captcha;
83 $this->_load($options);
84 }
85
86 /**
87 * Returns the global Captcha object, only creating it
88 * if it doesn't already exist.
89 *
90 * @param string $captcha The plugin to use.
91 * @param array $options Associative array of options.
92 *
93 * @return JCaptcha|null Instance of this class.
94 *
95 * @since 2.5
96 */
97 public static function getInstance($captcha, array $options = array())
98 {
99 $signature = md5(serialize(array($captcha, $options)));
100
101 if (empty(self::$_instances[$signature]))
102 {
103 try
104 {
105 self::$_instances[$signature] = new JCaptcha($captcha, $options);
106 }
107 catch (RuntimeException $e)
108 {
109 JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error');
110
111 return;
112 }
113 }
114
115 return self::$_instances[$signature];
116 }
117
118 /**
119 * Fire the onInit event to initialise the captcha plugin.
120 *
121 * @param string $id The id of the field.
122 *
123 * @return boolean True on success
124 *
125 * @since 2.5
126 */
127 public function initialise($id)
128 {
129 $args['id'] = $id;
130 $args['event'] = 'onInit';
131
132 try
133 {
134 $this->_captcha->update($args);
135 }
136 catch (Exception $e)
137 {
138 JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error');
139
140 return false;
141 }
142
143 return true;
144 }
145
146 /**
147 * Get the HTML for the captcha.
148 *
149 * @param string $name The control name.
150 * @param string $id The id for the control.
151 * @param string $class Value for the HTML class attribute
152 *
153 * @return mixed The return value of the function "onDisplay" of the selected Plugin.
154 *
155 * @since 2.5
156 */
157 public function display($name, $id, $class = '')
158 {
159 // Check if captcha is already loaded.
160 if ($this->_captcha === null)
161 {
162 return;
163 }
164
165 // Initialise the Captcha.
166 if (!$this->initialise($id))
167 {
168 return;
169 }
170
171 $args['name'] = $name;
172 $args['id'] = $id ?: $name;
173 $args['class'] = $class ? 'class="' . $class . '"' : '';
174 $args['event'] = 'onDisplay';
175
176 return $this->_captcha->update($args);
177 }
178
179 /**
180 * Checks if the answer is correct.
181 *
182 * @param string $code The answer.
183 *
184 * @return mixed The return value of the function "onCheckAnswer" of the selected Plugin.
185 *
186 * @since 2.5
187 */
188 public function checkAnswer($code)
189 {
190 // Check if captcha is already loaded
191 if ($this->_captcha === null)
192 {
193 return;
194 }
195
196 $args['code'] = $code;
197 $args['event'] = 'onCheckAnswer';
198
199 return $this->_captcha->update($args);
200 }
201
202 /**
203 * Load the Captcha plugin.
204 *
205 * @param array $options Associative array of options.
206 *
207 * @return void
208 *
209 * @since 2.5
210 * @throws RuntimeException
211 */
212 private function _load(array $options = array())
213 {
214 // Build the path to the needed captcha plugin
215 $name = JFilterInput::getInstance()->clean($this->_name, 'cmd');
216 $path = JPATH_PLUGINS . '/captcha/' . $name . '/' . $name . '.php';
217
218 if (!is_file($path))
219 {
220 throw new RuntimeException(JText::sprintf('JLIB_CAPTCHA_ERROR_PLUGIN_NOT_FOUND', $name));
221 }
222
223 // Require plugin file
224 require_once $path;
225
226 // Get the plugin
227 $plugin = JPluginHelper::getPlugin('captcha', $this->_name);
228
229 if (!$plugin)
230 {
231 throw new RuntimeException(JText::sprintf('JLIB_CAPTCHA_ERROR_PLUGIN_NOT_FOUND', $name));
232 }
233
234 // Check for already loaded params
235 if (!($plugin->params instanceof Registry))
236 {
237 $params = new Registry($plugin->params);
238 $plugin->params = $params;
239 }
240
241 // Build captcha plugin classname
242 $name = 'PlgCaptcha' . $this->_name;
243 $this->_captcha = new $name($this, (array) $plugin, $options);
244 }
245
246 /**
247 * Get the state of the JEditor object
248 *
249 * @return mixed The state of the object.
250 *
251 * @since 2.5
252 */
253 public function getState()
254 {
255 return $this->_state;
256 }
257
258 /**
259 * Attach an observer object
260 *
261 * @param object $observer An observer object to attach
262 *
263 * @return void
264 *
265 * @since 2.5
266 */
267 public function attach($observer)
268 {
269 if (is_array($observer))
270 {
271 if (!isset($observer['handler']) || !isset($observer['event']) || !is_callable($observer['handler']))
272 {
273 return;
274 }
275
276 // Make sure we haven't already attached this array as an observer
277 foreach ($this->_observers as $check)
278 {
279 if (is_array($check) && $check['event'] == $observer['event'] && $check['handler'] == $observer['handler'])
280 {
281 return;
282 }
283 }
284
285 $this->_observers[] = $observer;
286 end($this->_observers);
287 $methods = array($observer['event']);
288 }
289 else
290 {
291 if (!($observer instanceof JEditor))
292 {
293 return;
294 }
295
296 // Make sure we haven't already attached this object as an observer
297 $class = get_class($observer);
298
299 foreach ($this->_observers as $check)
300 {
301 if ($check instanceof $class)
302 {
303 return;
304 }
305 }
306
307 $this->_observers[] = $observer;
308 $methods = array_diff(get_class_methods($observer), get_class_methods('JPlugin'));
309 }
310
311 $key = key($this->_observers);
312
313 foreach ($methods as $method)
314 {
315 $method = strtolower($method);
316
317 if (!isset($this->_methods[$method]))
318 {
319 $this->_methods[$method] = array();
320 }
321
322 $this->_methods[$method][] = $key;
323 }
324 }
325
326 /**
327 * Detach an observer object
328 *
329 * @param object $observer An observer object to detach.
330 *
331 * @return boolean True if the observer object was detached.
332 *
333 * @since 2.5
334 */
335 public function detach($observer)
336 {
337 $retval = false;
338
339 $key = array_search($observer, $this->_observers);
340
341 if ($key !== false)
342 {
343 unset($this->_observers[$key]);
344 $retval = true;
345
346 foreach ($this->_methods as &$method)
347 {
348 $k = array_search($key, $method);
349
350 if ($k !== false)
351 {
352 unset($method[$k]);
353 }
354 }
355 }
356
357 return $retval;
358 }
359 }
360