1 <?php
2 /**
3 * @package Joomla.Libraries
4 * @subpackage Application
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! CMS Application class
16 *
17 * @since 3.2
18 */
19 class JApplicationCms extends JApplicationWeb
20 {
21 /**
22 * Array of options for the JDocument object
23 *
24 * @var array
25 * @since 3.2
26 */
27 protected $docOptions = array();
28
29 /**
30 * Application instances container.
31 *
32 * @var JApplicationCms[]
33 * @since 3.2
34 */
35 protected static $instances = array();
36
37 /**
38 * The scope of the application.
39 *
40 * @var string
41 * @since 3.2
42 */
43 public $scope = null;
44
45 /**
46 * The client identifier.
47 *
48 * @var integer
49 * @since 3.2
50 * @deprecated 4.0 Will be renamed $clientId
51 */
52 protected $_clientId = null;
53
54 /**
55 * The application message queue.
56 *
57 * @var array
58 * @since 3.2
59 * @deprecated 4.0 Will be renamed $messageQueue
60 */
61 protected $_messageQueue = array();
62
63 /**
64 * The name of the application.
65 *
66 * @var array
67 * @since 3.2
68 * @deprecated 4.0 Will be renamed $name
69 */
70 protected $_name = null;
71
72 /**
73 * The profiler instance
74 *
75 * @var JProfiler
76 * @since 3.2
77 */
78 protected $profiler = null;
79
80 /**
81 * Currently active template
82 *
83 * @var object
84 * @since 3.2
85 */
86 protected $template = null;
87
88 /**
89 * Class constructor.
90 *
91 * @param JInput $input An optional argument to provide dependency injection for the application's
92 * input object. If the argument is a JInput object that object will become
93 * the application's input object, otherwise a default input object is created.
94 * @param Registry $config An optional argument to provide dependency injection for the application's
95 * config object. If the argument is a Registry object that object will become
96 * the application's config object, otherwise a default config object is created.
97 * @param JApplicationWebClient $client An optional argument to provide dependency injection for the application's
98 * client object. If the argument is a JApplicationWebClient object that object will become
99 * the application's client object, otherwise a default client object is created.
100 *
101 * @since 3.2
102 */
103 public function __construct(JInput $input = null, Registry $config = null, JApplicationWebClient $client = null)
104 {
105 parent::__construct($input, $config, $client);
106
107 // Load and set the dispatcher
108 $this->loadDispatcher();
109
110 // If JDEBUG is defined, load the profiler instance
111 if (defined('JDEBUG') && JDEBUG)
112 {
113 $this->profiler = JProfiler::getInstance('Application');
114 }
115
116 // Enable sessions by default.
117 if ($this->config->get('session') === null)
118 {
119 $this->config->set('session', true);
120 }
121
122 // Set the session default name.
123 if ($this->config->get('session_name') === null)
124 {
125 $this->config->set('session_name', $this->getName());
126 }
127
128 // Create the session if a session name is passed.
129 if ($this->config->get('session') !== false)
130 {
131 $this->loadSession();
132 }
133 }
134
135 /**
136 * After the session has been started we need to populate it with some default values.
137 *
138 * @return void
139 *
140 * @since 3.2
141 */
142 public function afterSessionStart()
143 {
144 $session = JFactory::getSession();
145
146 if ($session->isNew())
147 {
148 $session->set('registry', new Registry);
149 $session->set('user', new JUser);
150 }
151 }
152
153 /**
154 * Checks the user session.
155 *
156 * If the session record doesn't exist, initialise it.
157 * If session is new, create session variables
158 *
159 * @return void
160 *
161 * @since 3.2
162 * @throws RuntimeException
163 */
164 public function checkSession()
165 {
166 $db = JFactory::getDbo();
167 $session = JFactory::getSession();
168 $user = JFactory::getUser();
169
170 $query = $db->getQuery(true)
171 ->select($db->quoteName('session_id'))
172 ->from($db->quoteName('#__session'))
173 ->where($db->quoteName('session_id') . ' = ' . $db->quote($session->getId()));
174
175 $db->setQuery($query, 0, 1);
176 $exists = $db->loadResult();
177
178 // If the session record doesn't exist initialise it.
179 if (!$exists)
180 {
181 $query->clear();
182
183 $time = $session->isNew() ? time() : $session->get('session.timer.start');
184
185 $columns = array(
186 $db->quoteName('session_id'),
187 $db->quoteName('guest'),
188 $db->quoteName('time'),
189 $db->quoteName('userid'),
190 $db->quoteName('username'),
191 );
192
193 $values = array(
194 $db->quote($session->getId()),
195 (int) $user->guest,
196 $db->quote((int) $time),
197 (int) $user->id,
198 $db->quote($user->username),
199 );
200
201 if (!$this->get('shared_session', '0'))
202 {
203 $columns[] = $db->quoteName('client_id');
204 $values[] = (int) $this->getClientId();
205 }
206
207 $query->insert($db->quoteName('#__session'))
208 ->columns($columns)
209 ->values(implode(', ', $values));
210
211 $db->setQuery($query);
212
213 // If the insert failed, exit the application.
214 try
215 {
216 $db->execute();
217 }
218 catch (RuntimeException $e)
219 {
220 throw new RuntimeException(JText::_('JERROR_SESSION_STARTUP'), $e->getCode(), $e);
221 }
222 }
223 }
224
225 /**
226 * Enqueue a system message.
227 *
228 * @param string $msg The message to enqueue.
229 * @param string $type The message type. Default is message.
230 *
231 * @return void
232 *
233 * @since 3.2
234 */
235 public function enqueueMessage($msg, $type = 'message')
236 {
237 // Don't add empty messages.
238 if (trim($msg) === '')
239 {
240 return;
241 }
242
243 // For empty queue, if messages exists in the session, enqueue them first.
244 $messages = $this->getMessageQueue();
245
246 $message = array('message' => $msg, 'type' => strtolower($type));
247
248 if (!in_array($message, $this->_messageQueue))
249 {
250 // Enqueue the message.
251 $this->_messageQueue[] = $message;
252 }
253 }
254
255 /**
256 * Execute the application.
257 *
258 * @return void
259 *
260 * @since 3.2
261 */
262 public function execute()
263 {
264 // Perform application routines.
265 $this->doExecute();
266
267 // If we have an application document object, render it.
268 if ($this->document instanceof JDocument)
269 {
270 // Render the application output.
271 $this->render();
272 }
273
274 // If gzip compression is enabled in configuration and the server is compliant, compress the output.
275 if ($this->get('gzip') && !ini_get('zlib.output_compression') && ini_get('output_handler') !== 'ob_gzhandler')
276 {
277 $this->compress();
278
279 // Trigger the onAfterCompress event.
280 $this->triggerEvent('onAfterCompress');
281 }
282
283 // Send the application response.
284 $this->respond();
285
286 // Trigger the onAfterRespond event.
287 $this->triggerEvent('onAfterRespond');
288 }
289
290 /**
291 * Check if the user is required to reset their password.
292 *
293 * If the user is required to reset their password will be redirected to the page that manage the password reset.
294 *
295 * @param string $option The option that manage the password reset
296 * @param string $view The view that manage the password reset
297 * @param string $layout The layout of the view that manage the password reset
298 * @param string $tasks Permitted tasks
299 *
300 * @return void
301 */
302 protected function checkUserRequireReset($option, $view, $layout, $tasks)
303 {
304 if (JFactory::getUser()->get('requireReset', 0))
305 {
306 $redirect = false;
307
308 /*
309 * By default user profile edit page is used.
310 * That page allows you to change more than just the password and might not be the desired behavior.
311 * This allows a developer to override the page that manage the password reset.
312 * (can be configured using the file: configuration.php, or if extended, through the global configuration form)
313 */
314 $name = $this->getName();
315
316 if ($this->get($name . '_reset_password_override', 0))
317 {
318 $option = $this->get($name . '_reset_password_option', '');
319 $view = $this->get($name . '_reset_password_view', '');
320 $layout = $this->get($name . '_reset_password_layout', '');
321 $tasks = $this->get($name . '_reset_password_tasks', '');
322 }
323
324 $task = $this->input->getCmd('task', '');
325
326 // Check task or option/view/layout
327 if (!empty($task))
328 {
329 $tasks = explode(',', $tasks);
330
331 // Check full task version "option/task"
332 if (array_search($this->input->getCmd('option', '') . '/' . $task, $tasks) === false)
333 {
334 // Check short task version, must be on the same option of the view
335 if ($this->input->getCmd('option', '') !== $option || array_search($task, $tasks) === false)
336 {
337 // Not permitted task
338 $redirect = true;
339 }
340 }
341 }
342 else
343 {
344 if ($this->input->getCmd('option', '') !== $option || $this->input->getCmd('view', '') !== $view
345 || $this->input->getCmd('layout', '') !== $layout)
346 {
347 // Requested a different option/view/layout
348 $redirect = true;
349 }
350 }
351
352 if ($redirect)
353 {
354 // Redirect to the profile edit page
355 $this->enqueueMessage(JText::_('JGLOBAL_PASSWORD_RESET_REQUIRED'), 'notice');
356 $this->redirect(JRoute::_('index.php?option=' . $option . '&view=' . $view . '&layout=' . $layout, false));
357 }
358 }
359 }
360
361 /**
362 * Gets a configuration value.
363 *
364 * @param string $varname The name of the value to get.
365 * @param string $default Default value to return
366 *
367 * @return mixed The user state.
368 *
369 * @since 3.2
370 * @deprecated 4.0 Use get() instead
371 */
372 public function getCfg($varname, $default = null)
373 {
374 return $this->get($varname, $default);
375 }
376
377 /**
378 * Gets the client id of the current running application.
379 *
380 * @return integer A client identifier.
381 *
382 * @since 3.2
383 */
384 public function getClientId()
385 {
386 return $this->_clientId;
387 }
388
389 /**
390 * Returns a reference to the global JApplicationCms object, only creating it if it doesn't already exist.
391 *
392 * This method must be invoked as: $web = JApplicationCms::getInstance();
393 *
394 * @param string $name The name (optional) of the JApplicationCms class to instantiate.
395 *
396 * @return JApplicationCms
397 *
398 * @since 3.2
399 * @throws RuntimeException
400 */
401 public static function getInstance($name = null)
402 {
403 if (empty(static::$instances[$name]))
404 {
405 // Create a JApplicationCms object.
406 $classname = 'JApplication' . ucfirst($name);
407
408 if (!class_exists($classname))
409 {
410 throw new RuntimeException(JText::sprintf('JLIB_APPLICATION_ERROR_APPLICATION_LOAD', $name), 500);
411 }
412
413 static::$instances[$name] = new $classname;
414 }
415
416 return static::$instances[$name];
417 }
418
419 /**
420 * Returns the application JMenu object.
421 *
422 * @param string $name The name of the application/client.
423 * @param array $options An optional associative array of configuration settings.
424 *
425 * @return JMenu|null
426 *
427 * @since 3.2
428 */
429 public function getMenu($name = null, $options = array())
430 {
431 if (!isset($name))
432 {
433 $name = $this->getName();
434 }
435
436 // Inject this application object into the JMenu tree if one isn't already specified
437 if (!isset($options['app']))
438 {
439 $options['app'] = $this;
440 }
441
442 try
443 {
444 $menu = JMenu::getInstance($name, $options);
445 }
446 catch (Exception $e)
447 {
448 return;
449 }
450
451 return $menu;
452 }
453
454 /**
455 * Get the system message queue.
456 *
457 * @param boolean $clear Clear the messages currently attached to the application object
458 *
459 * @return array The system message queue.
460 *
461 * @since 3.2
462 */
463 public function getMessageQueue($clear = false)
464 {
465 // For empty queue, if messages exists in the session, enqueue them.
466 if (!count($this->_messageQueue))
467 {
468 $session = JFactory::getSession();
469 $sessionQueue = $session->get('application.queue');
470
471 if (count($sessionQueue))
472 {
473 $this->_messageQueue = $sessionQueue;
474 $session->set('application.queue', null);
475 }
476 }
477
478 $messageQueue = $this->_messageQueue;
479
480 if ($clear)
481 {
482 $this->_messageQueue = array();
483 }
484
485 return $messageQueue;
486 }
487
488 /**
489 * Gets the name of the current running application.
490 *
491 * @return string The name of the application.
492 *
493 * @since 3.2
494 */
495 public function getName()
496 {
497 return $this->_name;
498 }
499
500 /**
501 * Returns the application JPathway object.
502 *
503 * @param string $name The name of the application.
504 * @param array $options An optional associative array of configuration settings.
505 *
506 * @return JPathway|null
507 *
508 * @since 3.2
509 */
510 public function getPathway($name = null, $options = array())
511 {
512 if (!isset($name))
513 {
514 $name = $this->getName();
515 }
516
517 try
518 {
519 $pathway = JPathway::getInstance($name, $options);
520 }
521 catch (Exception $e)
522 {
523 return;
524 }
525
526 return $pathway;
527 }
528
529 /**
530 * Returns the application JRouter object.
531 *
532 * @param string $name The name of the application.
533 * @param array $options An optional associative array of configuration settings.
534 *
535 * @return JRouter|null
536 *
537 * @since 3.2
538 */
539 public static function getRouter($name = null, array $options = array())
540 {
541 if (!isset($name))
542 {
543 $app = JFactory::getApplication();
544 $name = $app->getName();
545 }
546
547 try
548 {
549 $router = JRouter::getInstance($name, $options);
550 }
551 catch (Exception $e)
552 {
553 return;
554 }
555
556 return $router;
557 }
558
559 /**
560 * Gets the name of the current template.
561 *
562 * @param boolean $params An optional associative array of configuration settings
563 *
564 * @return mixed System is the fallback.
565 *
566 * @since 3.2
567 */
568 public function getTemplate($params = false)
569 {
570 $template = new stdClass;
571
572 $template->template = 'system';
573 $template->params = new Registry;
574
575 if ($params)
576 {
577 return $template;
578 }
579
580 return $template->template;
581 }
582
583 /**
584 * Gets a user state.
585 *
586 * @param string $key The path of the state.
587 * @param mixed $default Optional default value, returned if the internal value is null.
588 *
589 * @return mixed The user state or null.
590 *
591 * @since 3.2
592 */
593 public function getUserState($key, $default = null)
594 {
595 $session = JFactory::getSession();
596 $registry = $session->get('registry');
597
598 if ($registry !== null)
599 {
600 return $registry->get($key, $default);
601 }
602
603 return $default;
604 }
605
606 /**
607 * Gets the value of a user state variable.
608 *
609 * @param string $key The key of the user state variable.
610 * @param string $request The name of the variable passed in a request.
611 * @param string $default The default value for the variable if not found. Optional.
612 * @param string $type Filter for the variable, for valid values see {@link JFilterInput::clean()}. Optional.
613 *
614 * @return mixed The request user state.
615 *
616 * @since 3.2
617 */
618 public function getUserStateFromRequest($key, $request, $default = null, $type = 'none')
619 {
620 $cur_state = $this->getUserState($key, $default);
621 $new_state = $this->input->get($request, null, $type);
622
623 if ($new_state === null)
624 {
625 return $cur_state;
626 }
627
628 // Save the new value only if it was set in this request.
629 $this->setUserState($key, $new_state);
630
631 return $new_state;
632 }
633
634 /**
635 * Initialise the application.
636 *
637 * @param array $options An optional associative array of configuration settings.
638 *
639 * @return void
640 *
641 * @since 3.2
642 */
643 protected function initialiseApp($options = array())
644 {
645 // Set the configuration in the API.
646 $this->config = JFactory::getConfig();
647
648 // Check that we were given a language in the array (since by default may be blank).
649 if (isset($options['language']))
650 {
651 $this->set('language', $options['language']);
652 }
653
654 // Build our language object
655 $lang = JLanguage::getInstance($this->get('language'), $this->get('debug_lang'));
656
657 // Load the language to the API
658 $this->loadLanguage($lang);
659
660 // Register the language object with JFactory
661 JFactory::$language = $this->getLanguage();
662
663 // Load the library language files
664 $this->loadLibraryLanguage();
665
666 // Set user specific editor.
667 $user = JFactory::getUser();
668 $editor = $user->getParam('editor', $this->get('editor'));
669
670 if (!JPluginHelper::isEnabled('editors', $editor))
671 {
672 $editor = $this->get('editor');
673
674 if (!JPluginHelper::isEnabled('editors', $editor))
675 {
676 $editor = 'none';
677 }
678 }
679
680 $this->set('editor', $editor);
681
682 // Trigger the onAfterInitialise event.
683 JPluginHelper::importPlugin('system');
684 $this->triggerEvent('onAfterInitialise');
685 }
686
687 /**
688 * Is admin interface?
689 *
690 * @return boolean True if this application is administrator.
691 *
692 * @since 3.2
693 * @deprecated Use isClient('administrator') instead.
694 */
695 public function isAdmin()
696 {
697 return $this->isClient('administrator');
698 }
699
700 /**
701 * Is site interface?
702 *
703 * @return boolean True if this application is site.
704 *
705 * @since 3.2
706 * @deprecated Use isClient('site') instead.
707 */
708 public function isSite()
709 {
710 return $this->isClient('site');
711 }
712
713 /**
714 * Checks if HTTPS is forced in the client configuration.
715 *
716 * @param integer $clientId An optional client id (defaults to current application client).
717 *
718 * @return boolean True if is forced for the client, false otherwise.
719 *
720 * @since 3.7.3
721 */
722 public function isHttpsForced($clientId = null)
723 {
724 $clientId = (int) ($clientId !== null ? $clientId : $this->getClientId());
725 $forceSsl = (int) $this->get('force_ssl');
726
727 if ($clientId === 0 && $forceSsl === 2)
728 {
729 return true;
730 }
731
732 if ($clientId === 1 && $forceSsl >= 1)
733 {
734 return true;
735 }
736
737 return false;
738 }
739
740 /**
741 * Check the client interface by name.
742 *
743 * @param string $identifier String identifier for the application interface
744 *
745 * @return boolean True if this application is of the given type client interface.
746 *
747 * @since 3.7.0
748 */
749 public function isClient($identifier)
750 {
751 return $this->getName() === $identifier;
752 }
753
754 /**
755 * Load the library language files for the application
756 *
757 * @return void
758 *
759 * @since 3.6.3
760 */
761 protected function loadLibraryLanguage()
762 {
763 $this->getLanguage()->load('lib_joomla', JPATH_ADMINISTRATOR);
764 }
765
766 /**
767 * Allows the application to load a custom or default session.
768 *
769 * The logic and options for creating this object are adequately generic for default cases
770 * but for many applications it will make sense to override this method and create a session,
771 * if required, based on more specific needs.
772 *
773 * @param JSession $session An optional session object. If omitted, the session is created.
774 *
775 * @return JApplicationCms This method is chainable.
776 *
777 * @since 3.2
778 */
779 public function loadSession(JSession $session = null)
780 {
781 if ($session !== null)
782 {
783 $this->session = $session;
784
785 return $this;
786 }
787
788 $this->registerEvent('onAfterSessionStart', array($this, 'afterSessionStart'));
789
790 /*
791 * Note: The below code CANNOT change from instantiating a session via JFactory until there is a proper dependency injection container supported
792 * by the application. The current default behaviours result in this method being called each time an application class is instantiated.
793 * https://github.com/joomla/joomla-cms/issues/12108 explains why things will crash and burn if you ever attempt to make this change
794 * without a proper dependency injection container.
795 */
796
797 $session = JFactory::getSession(
798 array(
799 'name' => JApplicationHelper::getHash($this->get('session_name', get_class($this))),
800 'expire' => $this->get('lifetime') ? $this->get('lifetime') * 60 : 900,
801 'force_ssl' => $this->isHttpsForced(),
802 )
803 );
804
805 $session->initialise($this->input, $this->dispatcher);
806
807 // TODO: At some point we need to get away from having session data always in the db.
808 $db = JFactory::getDbo();
809
810 // Remove expired sessions from the database.
811 $time = time();
812
813 if ($time % 2)
814 {
815 // The modulus introduces a little entropy, making the flushing less accurate
816 // but fires the query less than half the time.
817 $query = $db->getQuery(true)
818 ->delete($db->quoteName('#__session'))
819 ->where($db->quoteName('time') . ' < ' . $db->quote((int) ($time - $session->getExpire())));
820
821 $db->setQuery($query);
822 $db->execute();
823 }
824
825 // Get the session handler from the configuration.
826 $handler = $this->get('session_handler', 'none');
827
828 if (($handler !== 'database' && ($time % 2 || $session->isNew()))
829 || ($handler === 'database' && $session->isNew()))
830 {
831 $this->checkSession();
832 }
833
834 // Set the session object.
835 $this->session = $session;
836
837 return $this;
838 }
839
840 /**
841 * Login authentication function.
842 *
843 * Username and encoded password are passed the onUserLogin event which
844 * is responsible for the user validation. A successful validation updates
845 * the current session record with the user's details.
846 *
847 * Username and encoded password are sent as credentials (along with other
848 * possibilities) to each observer (authentication plugin) for user
849 * validation. Successful validation will update the current session with
850 * the user details.
851 *
852 * @param array $credentials Array('username' => string, 'password' => string)
853 * @param array $options Array('remember' => boolean)
854 *
855 * @return boolean|JException True on success, false if failed or silent handling is configured, or a JException object on authentication error.
856 *
857 * @since 3.2
858 */
859 public function login($credentials, $options = array())
860 {
861 // Get the global JAuthentication object.
862 $authenticate = JAuthentication::getInstance();
863 $response = $authenticate->authenticate($credentials, $options);
864
865 // Import the user plugin group.
866 JPluginHelper::importPlugin('user');
867
868 if ($response->status === JAuthentication::STATUS_SUCCESS)
869 {
870 /*
871 * Validate that the user should be able to login (different to being authenticated).
872 * This permits authentication plugins blocking the user.
873 */
874 $authorisations = $authenticate->authorise($response, $options);
875 $denied_states = JAuthentication::STATUS_EXPIRED | JAuthentication::STATUS_DENIED;
876
877 foreach ($authorisations as $authorisation)
878 {
879 if ((int) $authorisation->status & $denied_states)
880 {
881 // Trigger onUserAuthorisationFailure Event.
882 $this->triggerEvent('onUserAuthorisationFailure', array((array) $authorisation));
883
884 // If silent is set, just return false.
885 if (isset($options['silent']) && $options['silent'])
886 {
887 return false;
888 }
889
890 // Return the error.
891 switch ($authorisation->status)
892 {
893 case JAuthentication::STATUS_EXPIRED:
894 return JError::raiseWarning('102002', JText::_('JLIB_LOGIN_EXPIRED'));
895
896 case JAuthentication::STATUS_DENIED:
897 return JError::raiseWarning('102003', JText::_('JLIB_LOGIN_DENIED'));
898
899 default:
900 return JError::raiseWarning('102004', JText::_('JLIB_LOGIN_AUTHORISATION'));
901 }
902 }
903 }
904
905 // OK, the credentials are authenticated and user is authorised. Let's fire the onLogin event.
906 $results = $this->triggerEvent('onUserLogin', array((array) $response, $options));
907
908 /*
909 * If any of the user plugins did not successfully complete the login routine
910 * then the whole method fails.
911 *
912 * Any errors raised should be done in the plugin as this provides the ability
913 * to provide much more information about why the routine may have failed.
914 */
915 $user = JFactory::getUser();
916
917 if ($response->type === 'Cookie')
918 {
919 $user->set('cookieLogin', true);
920 }
921
922 if (in_array(false, $results, true) == false)
923 {
924 $options['user'] = $user;
925 $options['responseType'] = $response->type;
926
927 // The user is successfully logged in. Run the after login events
928 $this->triggerEvent('onUserAfterLogin', array($options));
929 }
930
931 return true;
932 }
933
934 // Trigger onUserLoginFailure Event.
935 $this->triggerEvent('onUserLoginFailure', array((array) $response));
936
937 // If silent is set, just return false.
938 if (isset($options['silent']) && $options['silent'])
939 {
940 return false;
941 }
942
943 // If status is success, any error will have been raised by the user plugin
944 if ($response->status !== JAuthentication::STATUS_SUCCESS)
945 {
946 JLog::add($response->error_message, JLog::WARNING, 'jerror');
947 }
948
949 return false;
950 }
951
952 /**
953 * Logout authentication function.
954 *
955 * Passed the current user information to the onUserLogout event and reverts the current
956 * session record back to 'anonymous' parameters.
957 * If any of the authentication plugins did not successfully complete
958 * the logout routine then the whole method fails. Any errors raised
959 * should be done in the plugin as this provides the ability to give
960 * much more information about why the routine may have failed.
961 *
962 * @param integer $userid The user to load - Can be an integer or string - If string, it is converted to ID automatically
963 * @param array $options Array('clientid' => array of client id's)
964 *
965 * @return boolean True on success
966 *
967 * @since 3.2
968 */
969 public function logout($userid = null, $options = array())
970 {
971 // Get a user object from the JApplication.
972 $user = JFactory::getUser($userid);
973
974 // Build the credentials array.
975 $parameters['username'] = $user->get('username');
976 $parameters['id'] = $user->get('id');
977
978 // Set clientid in the options array if it hasn't been set already and shared sessions are not enabled.
979 if (!$this->get('shared_session', '0') && !isset($options['clientid']))
980 {
981 $options['clientid'] = $this->getClientId();
982 }
983
984 // Import the user plugin group.
985 JPluginHelper::importPlugin('user');
986
987 // OK, the credentials are built. Lets fire the onLogout event.
988 $results = $this->triggerEvent('onUserLogout', array($parameters, $options));
989
990 // Check if any of the plugins failed. If none did, success.
991 if (!in_array(false, $results, true))
992 {
993 $options['username'] = $user->get('username');
994 $this->triggerEvent('onUserAfterLogout', array($options));
995
996 return true;
997 }
998
999 // Trigger onUserLoginFailure Event.
1000 $this->triggerEvent('onUserLogoutFailure', array($parameters));
1001
1002 return false;
1003 }
1004
1005 /**
1006 * Redirect to another URL.
1007 *
1008 * If the headers have not been sent the redirect will be accomplished using a "301 Moved Permanently"
1009 * or "303 See Other" code in the header pointing to the new location. If the headers have already been
1010 * sent this will be accomplished using a JavaScript statement.
1011 *
1012 * @param string $url The URL to redirect to. Can only be http/https URL
1013 * @param integer $status The HTTP 1.1 status code to be provided. 303 is assumed by default.
1014 *
1015 * @return void
1016 *
1017 * @since 3.2
1018 */
1019 public function redirect($url, $status = 303)
1020 {
1021 // Handle B/C by checking if a message was passed to the method, will be removed at 4.0
1022 if (func_num_args() > 1)
1023 {
1024 $args = func_get_args();
1025
1026 /*
1027 * Do some checks on the $args array, values below correspond to legacy redirect() method
1028 *
1029 * $args[0] = $url
1030 * $args[1] = Message to enqueue
1031 * $args[2] = Message type
1032 * $args[3] = $status (previously moved)
1033 */
1034 if (isset($args[1]) && !empty($args[1]) && (!is_bool($args[1]) && !is_int($args[1])))
1035 {
1036 // Log that passing the message to the function is deprecated
1037 JLog::add(
1038 'Passing a message and message type to JFactory::getApplication()->redirect() is deprecated. '
1039 . 'Please set your message via JFactory::getApplication()->enqueueMessage() prior to calling redirect().',
1040 JLog::WARNING,
1041 'deprecated'
1042 );
1043
1044 $message = $args[1];
1045
1046 // Set the message type if present
1047 if (isset($args[2]) && !empty($args[2]))
1048 {
1049 $type = $args[2];
1050 }
1051 else
1052 {
1053 $type = 'message';
1054 }
1055
1056 // Enqueue the message
1057 $this->enqueueMessage($message, $type);
1058
1059 // Reset the $moved variable
1060 $status = isset($args[3]) ? (boolean) $args[3] : false;
1061 }
1062 }
1063
1064 // Persist messages if they exist.
1065 if (count($this->_messageQueue))
1066 {
1067 $session = JFactory::getSession();
1068 $session->set('application.queue', $this->_messageQueue);
1069 }
1070
1071 // Hand over processing to the parent now
1072 parent::redirect($url, $status);
1073 }
1074
1075 /**
1076 * Rendering is the process of pushing the document buffers into the template
1077 * placeholders, retrieving data from the document and pushing it into
1078 * the application response buffer.
1079 *
1080 * @return void
1081 *
1082 * @since 3.2
1083 */
1084 protected function render()
1085 {
1086 // Setup the document options.
1087 $this->docOptions['template'] = $this->get('theme');
1088 $this->docOptions['file'] = $this->get('themeFile', 'index.php');
1089 $this->docOptions['params'] = $this->get('themeParams');
1090
1091 if ($this->get('themes.base'))
1092 {
1093 $this->docOptions['directory'] = $this->get('themes.base');
1094 }
1095 // Fall back to constants.
1096 else
1097 {
1098 $this->docOptions['directory'] = defined('JPATH_THEMES') ? JPATH_THEMES : (defined('JPATH_BASE') ? JPATH_BASE : __DIR__) . '/themes';
1099 }
1100
1101 // Parse the document.
1102 $this->document->parse($this->docOptions);
1103
1104 // Trigger the onBeforeRender event.
1105 JPluginHelper::importPlugin('system');
1106 $this->triggerEvent('onBeforeRender');
1107
1108 $caching = false;
1109
1110 if ($this->isClient('site') && $this->get('caching') && $this->get('caching', 2) == 2 && !JFactory::getUser()->get('id'))
1111 {
1112 $caching = true;
1113 }
1114
1115 // Render the document.
1116 $data = $this->document->render($caching, $this->docOptions);
1117
1118 // Set the application output data.
1119 $this->setBody($data);
1120
1121 // Trigger the onAfterRender event.
1122 $this->triggerEvent('onAfterRender');
1123
1124 // Mark afterRender in the profiler.
1125 JDEBUG ? $this->profiler->mark('afterRender') : null;
1126 }
1127
1128 /**
1129 * Route the application.
1130 *
1131 * Routing is the process of examining the request environment to determine which
1132 * component should receive the request. The component optional parameters
1133 * are then set in the request object to be processed when the application is being
1134 * dispatched.
1135 *
1136 * @return void
1137 *
1138 * @since 3.2
1139 */
1140 protected function route()
1141 {
1142 // Get the full request URI.
1143 $uri = clone JUri::getInstance();
1144
1145 $router = static::getRouter();
1146 $result = $router->parse($uri);
1147
1148 foreach ($result as $key => $value)
1149 {
1150 $this->input->def($key, $value);
1151 }
1152
1153 // Trigger the onAfterRoute event.
1154 JPluginHelper::importPlugin('system');
1155 $this->triggerEvent('onAfterRoute');
1156 }
1157
1158 /**
1159 * Sets the value of a user state variable.
1160 *
1161 * @param string $key The path of the state.
1162 * @param mixed $value The value of the variable.
1163 *
1164 * @return mixed The previous state, if one existed.
1165 *
1166 * @since 3.2
1167 */
1168 public function setUserState($key, $value)
1169 {
1170 $session = JFactory::getSession();
1171 $registry = $session->get('registry');
1172
1173 if ($registry !== null)
1174 {
1175 return $registry->set($key, $value);
1176 }
1177
1178 return;
1179 }
1180
1181 /**
1182 * Sends all headers prior to returning the string
1183 *
1184 * @param boolean $compress If true, compress the data
1185 *
1186 * @return string
1187 *
1188 * @since 3.2
1189 */
1190 public function toString($compress = false)
1191 {
1192 // Don't compress something if the server is going to do it anyway. Waste of time.
1193 if ($compress && !ini_get('zlib.output_compression') && ini_get('output_handler') !== 'ob_gzhandler')
1194 {
1195 $this->compress();
1196 }
1197
1198 if ($this->allowCache() === false)
1199 {
1200 $this->setHeader('Cache-Control', 'no-cache', false);
1201
1202 // HTTP 1.0
1203 $this->setHeader('Pragma', 'no-cache');
1204 }
1205
1206 $this->sendHeaders();
1207
1208 return $this->getBody();
1209 }
1210 }
1211