Squiz Matrix  4.12.2
 All Data Structures Namespaces Functions Variables Pages
mysource.inc
1 <?php
17 //require_once 'DB.php';
18 require_once SQ_LIB_PATH.'/MatrixDAL/MatrixDAL.inc';
19 
20 require_once SQ_INCLUDE_PATH.'/mysource_object.inc';
21 require_once SQ_INCLUDE_PATH.'/asset_manager.inc';
22 require_once SQ_INCLUDE_PATH.'/locale_manager.inc';
23 require_once SQ_INCLUDE_PATH.'/performance_manager.inc';
24 
25 // include the system version setttings
26 require_once SQ_INCLUDE_PATH.'/version.inc';
27 
39 {
45  public $db;
46 
53  protected $_db_conns = Array();
54 
61  protected $_db_stack = Array();
62 
63 
70  protected $_run_level_stack = Array();
71 
72 
79  protected $_context_stack = Array();
80 
86  public $backend;
87 
91  public $am;
92 
96  public $tm;
97 
101  public $pm;
102 
106  protected $ms;
107 
111  protected $mm;
112 
116  protected $hh;
117 
121  protected $wm;
122 
126  protected $fv;
127 
131  protected $em;
132 
136  protected $tag_manager;
137 
141  public $lm;
142 
146  protected $trigger_manager;
147 
151  public $log_manager;
152 
158  public $user = NULL;
159 
165  public $frontend_asset;
166 
172  protected $_user_is_root = FALSE;
173 
179  protected $_user_is_sys_admin = FALSE;
180 
186  protected $_user_is_public = TRUE;
187 
188 
194  protected $_msgs;
195 
196 
202  protected $_global_defines;
203 
204 
210  protected $_deja_vu;
211 
212 
217  function __construct()
218  {
219 
220  }//end constructor
221 
222 
223 
231  function __destruct()
232  {
233  if (!empty($this->lm) && array_get_index($this->lm->_tmp, 'strings_modified', FALSE)) {
234  $this->lm->locale_stack = Array();
235  // Attempt to store me so that I don't have to go through all that again.
236  $deja_vu = $this->getDejaVu();
237  if ($deja_vu) {
238  $deja_vu->remember(SQ_DEJA_VU_LOCALE, NULL, $this->lm);
239  }
240  }
241 
242  // force saving the session to memcache before respond to browser, otherwise, it would take forever, and concurrent session access issue happens.
243  if(SQ_CONF_SESSION_HANDLER === 'memcache')
244  session_write_close();
245 
246  }//end destructor
247 
248 
258  public function init()
259  {
260  $this->_processUserIP();
261 
262  $this->changeDatabaseConnection('db');
263  $this->changeContext(0);
264 
265  // initialise the asset manager as we are going to need it pretty much everywhere
266  $this->am = new Asset_Manager();
267 
268  // initialise the performance manager
269  $this->pm = new Performance_Manager();
270 
271 
272  // Same with the locale manager; translations will be all around us.
273  // Loading them is slow though, so first try deja_vu.
274  $deja_vu = $GLOBALS['SQ_SYSTEM']->getDejaVu();
275  if ($deja_vu) {
276  $this->lm = $deja_vu->recall(SQ_DEJA_VU_LOCALE);
277  }
278 
279  // If nothing, start from scratch.
280  if (empty($this->lm)) {
281  $this->lm = new Locale_Manager();
282  }
283 
284  $this->lm->setCurrentLocale(SQ_CONF_DEFAULT_BACKEND_LOCALE);
285 
286  if (!SQ_PHP_CLI && $GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS)) {
287  // we are accessing from the web, and the security subsystem is enabled in this run level,
288  // so we use cookies for security
289 
290  $proxy_conf_file = SQ_DATA_PATH.'/private/conf/proxy_authentication.inc';
291  if (file_exists($proxy_conf_file)) {
292  // proxy configuration exists, let's see if we need to do anything
293  require_once($proxy_conf_file);
294 
295  if (SQ_PROXY_URL_PORT_STRIP) {
296  $url_parts = parse_url($_SERVER['HTTP_HOST']);
297 
298  // remove port information from url and reconstruct it
299  if (isset($url_parts['port'])) {
300  unset($url_parts['port']);
301  }
302  $_SERVER['HTTP_HOST'] = implode('', $url_parts);
303  }
304  }
305 
306  // Set up the session handler
307  $session_handler = $this->getSessionHandlerClassName();
308  eval($session_handler.'::init();');
309 
310  if(SQ_CONF_SESSION_HANDLER === 'memcache') {
311  // memcache can only hold 30 days of cache, otherwise will fail silently
312  ini_set('session.gc_maxlifetime', SQ_CONF_SESSION_GC_MAXLIFETIME > 2592000 ? 2592000 : SQ_CONF_SESSION_GC_MAXLIFETIME);
313  }
314  else {
315  ini_set('session.gc_maxlifetime', SQ_CONF_SESSION_GC_MAXLIFETIME);
316  }
317 
318  $session_set = FALSE;
319  if (isset($_GET['SESSION_ID']) && (isset($_GET['SESSION_KEY']) || isset($_GET['SOAP_SESSION_KEY']))) {
320  // asset map or someone has sent session key through the GET
321  session_id($_GET['SESSION_ID']);
322  $session_set = TRUE;
323  }
324 
325  if (SQ_CONF_SESSION_HANDLER === 'memcache') {
326  $old_level = error_reporting(E_ERROR);
327  session_start();
328  error_reporting($old_level);
329  } else {
330  session_start();
331  }
332 
333  $restore_session = FALSE;
334  if (isset($_SESSION['activated']) === TRUE && $_SESSION['activated'] === 1) {
335  // This session is already activated.
336  // If we found the session is already activated.
337  session_regenerate_id(FALSE);
338  $new_session_id = session_id();
339  if (isset($_COOKIE['SQ_SYSTEM_SESSION'])) {
340  $new_session_id = $_COOKIE['SQ_SYSTEM_SESSION'];
341  }//end if
342  session_destroy();
343  $this->_clearSession();
344  session_id($new_session_id);
345  if (SQ_CONF_SESSION_HANDLER === 'memcache') {
346  $old_level = error_reporting(E_ERROR);
347  session_start();
348  error_reporting($old_level);
349  } else {
350  session_start();
351  }
352  $restore_session = TRUE;
353  }//end if
354 
355  $invalid_key = TRUE;
356  if ($restore_session === FALSE) {
357  if (isset($_GET['SOAP_SESSION_KEY']) === TRUE) {
358  $invalid_key = ($this->getUniqueSOAPSessionKey() != $_GET['SOAP_SESSION_KEY']);
359  if ($invalid_key === FALSE) {
360  $_SESSION['activated'] = 1;
361  define('SQ_SOAP_AUTH', '1');
362  }//end if
363  } else if (isset($_GET['SESSION_KEY']) === TRUE) {
364  $invalid_key = ($this->getUniqueSessionKey() != $_GET['SESSION_KEY']);
365  }//end if
366  }//end if
367 
368  if (!defined('SQ_SOAP_AUTH')) {
369  define('SQ_SOAP_AUTH', '0');
370  }//end if
371 
372  // check to see if the session key is the same as that used to login
373  if ($session_set && $invalid_key && !$restore_session) {
374  define('SQ_ROLLBACK_VIEW', '0');
375  define('SQ_TABLE_RUNNING_PREFIX', 'sq_');
376  $ms = $this->getMessagingService();
377  $log = $ms->newMessage(Array(), 'system.security.alert', Array());
378  $log->parameters['remote_addr'] = $_SERVER['REMOTE_ADDR'];
379  $log->parameters['sessionid'] = session_id();
380  $log->parameters['userid'] = $_SESSION['userid'];
381  $log->send();
382  exit();
383  }//end if
384 
385  // process rollback view mode global actions here so setting the rollback vars below
386  if (!empty($_REQUEST['SQ_ACTION']) && strpos($_REQUEST['SQ_ACTION'], 'rollback_view') === 0) {
387  $this->_processGlobalActions();
388  }
389 
390  // check if we are in rollback view mode
391  if (!empty($_SESSION['sq_rollback_view']) && (!isset($_REQUEST['SQ_ACTION']) || $_REQUEST['SQ_ACTION'] != 'logout')) {
392  // we are viewing the system at some time in the past
393  define('SQ_ROLLBACK_VIEW', '1');
394  define('SQ_TABLE_RUNNING_PREFIX', 'sq_rb_');
395 
396  // we need to let the database know what the current timestamp that we
397  // are using for rollback so that and stored procedures that require
398  // the rollback timestamp can acquire it
399  $then = $_SESSION['sq_rollback_view']['rollback_time'];
400 
401  $db = MatrixDAL::getDb();
402 
403  if (MatrixDAL::getDbType() == 'pgsql') {
404  $sql = 'SELECT sq_set_rollback_timestamp(:then)';
405  } else if (MatrixDAL::getDbType() == 'oci') {
406  $sql = 'DECLARE BEGIN sq_common_pkg.sq_set_rollback_timestamp(:then); END;';
407  }
408 
409  try {
410  $query = MatrixDAL::preparePdoQuery($sql);
411  MatrixDAL::bindValueToPdo($query, 'then', $then);
412  MatrixDAL::execPdoQuery($query);
413  } catch (DALException $e) {
414  throw new Exception('Unable to set rollback timestamp due to database error: '.$e->getMessage());
415  }
416 
417  } else {
418  define('SQ_ROLLBACK_VIEW', '0');
419  define('SQ_TABLE_RUNNING_PREFIX', 'sq_');
420  }
421 
422  $this->setupUser();
423  $this->_updateSessionCookie();
424  $_SESSION['user_last_access'] = time();
425 
426  // if we are using the 'a' querystring arg, remove any trailing exclamation marks
427  // that are used as termination characters in different kinds of content
428  if (isset($_REQUEST['a'])) {
429  $_REQUEST['a'] = rtrim($_REQUEST['a'], '$');
430  }
431 
432  } else {
433 
434  // we are either in CLI mode or the run level doesn't enable permissions
435  define('SQ_ROLLBACK_VIEW', '0');
436  define('SQ_TABLE_RUNNING_PREFIX', 'sq_');
437 
438  }//end if
439 
440  // In performance timing mode, set current user if required
441  if(defined('SQ_IN_PERFORMANCE_TIMING') && SQ_IN_PERFORMANCE_TIMING) {
442  if(isset($_REQUEST['performance_setting_user'])) {
443  // set user to either Public user or default user based on GET and SESSION settings
444  // the default user in SESSION must be in performance manager only with caution, we don't want to expose security hole
445  if($_REQUEST['performance_setting_user'] === 'Default' && ($GLOBALS['SQ_SYSTEM']->user instanceof Public_User) && isset($_SESSION['SQ_PERFORMANCE_SETTING']['default_user'])) {
446  $user = $GLOBALS['SQ_SYSTEM']->am->getAsset($_SESSION['SQ_PERFORMANCE_SETTING']['default_user']);
447  $this->setCurrentUser($user);
448  }
449  else if ($_REQUEST['performance_setting_user'] === 'Public' && !($GLOBALS['SQ_SYSTEM']->user instanceof Public_User)) {
450  $user = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('public_user');
451  $this->setCurrentUser($user);
452  }
453  }
454  }
455  // In result mode, print performance result
456  else if(defined('SQ_IN_PERFORMANCE_RESULT') && SQ_IN_PERFORMANCE_RESULT) {
457  // restore to default user
458  if(isset($_REQUEST['performance_setting_user']) ) {
459  if($GLOBALS['SQ_SYSTEM']->user instanceof Public_User && isset($_SESSION['SQ_PERFORMANCE_SETTING']['default_user'])) {
460  $user = $GLOBALS['SQ_SYSTEM']->am->getAsset($_SESSION['SQ_PERFORMANCE_SETTING']['default_user']);
461  $this->setCurrentUser($user);
462  }
463  }
464 
465  if(empty($GLOBALS['SQ_SYSTEM']->user) || !$GLOBALS['SQ_SYSTEM']->user->canAccessBackend()) exit(1);
466  $this->pm->printResults();
467  exit(1);
468  }
469 
470 
471  // process any global actions we have
472  if (!empty($_REQUEST['SQ_ACTION'])) {
473  $this->_processGlobalActions();
474  }
475 
476  // If this session is new, select an appropriate context
477  // and store it in the session so it sticks between pages
478  if (isset($_SESSION) === TRUE) {
479  $sandbox = array_get_index($_SESSION, SQ_SESSION_SANDBOX_INDEX, Array());
480 
481  // Get the backend override first, then if that's not set, then
482  // go to the user preference
483  $alternate_context_id = NULL;
484 
485  if ((SQ_IN_BACKEND === TRUE) || (SQ_IN_LIMBO === TRUE)) {
486  if ($this->userPublic() === FALSE) {
487  $alternate_context_id = array_get_index($sandbox, 'SQ_BACKEND_ALTERNATE_CONTEXT_ID');
488  }
489  }
490 
491  if ($alternate_context_id === NULL) {
492  $alternate_context_id = array_get_index($sandbox, 'SQ_ALTERNATE_CONTEXT_ID');
493  }
494 
495  if ($alternate_context_id !== NULL) {
496  // If the context no longer exists (eg. the current
497  // one was deleted through the backend interface),
498  // get Matrix to select a new one.
499  try {
500  $context_exists = MatrixDAL::executeAll('core', 'getContexts', Array('contextids' => Array($alternate_context_id)));
501  if (empty($context_exists) === TRUE) {
502  $alternate_context_id = NULL;
503  }
504  } catch (DALException $e) {
505  // Eep. This query hasn't even been baked yet.
506  // Set it to the default context since there's
507  // not much else that can be done here.
508  $alternate_context_id = 0;
509  }
510  }
511  } else if (SQ_PHP_CLI) {
512  $alternate_context_id = 0;
513  }//end if
514 
515  if ((isset($alternate_context_id) === FALSE) || ($alternate_context_id === NULL)) {
516  $alternate_context_id = $this->getAlternateContext();
517  }
518 
519  $this->changeContext($alternate_context_id);
520 
521  }//end init()
522 
523 
530  public function start()
531  {
532  // init perfromance timer
533  $this->pm->begin();
534 
535  // if the user is Up For Review, prompt the user to change password
536  $require_password_change = ($this->user && !($this->user instanceof Public_User) && $this->user->status == SQ_STATUS_LIVE_APPROVAL);
537 
538  if (SQ_IN_BACKEND || SQ_IN_LIMBO) {
539 
540  if ($require_password_change) {
541  $this->paintPasswordChange(translate('change_password'), translate('must_change_password_to_access_backend'));
542  exit();
543  }
544 
545  if ($this->user && !($this->user instanceof Public_User)) {
546  require_once SQ_INCLUDE_PATH.'/backend.inc';
547  $backend = new Backend();
548  $this->backend = &$backend;
549 
550  $current_url = current_url(FALSE, TRUE);
551  $designid = $GLOBALS['SQ_SYSTEM']->am->getDesignFromUrl($current_url);
552  $design_no_frame = NULL;
553  if (!empty($designid)) {
554  $design = $GLOBALS['SQ_SYSTEM']->am->getAsset($designid['designid']);
555  if ($design->type() === 'design_customisation') {
556  // Get the design from the design customisation.
557  $parent_designs = $GLOBALS['SQ_SYSTEM']->am->getParents($designid['designid'], 'design', TRUE);
558  $effective_designid = NULL;
559  if (count($parent_designs) > 0) {
560  $effective_designid = array_keys($parent_designs);
561  $effective_designid = $effective_designid[0];
562  }//end if
563  } else {
564  $effective_designid = $designid['designid'];
565  }//end else
566  $design_no_frame = $GLOBALS['SQ_SYSTEM']->am->getDesignHideFrameValues($effective_designid);
567  }//end if
568 
569  if ($design_no_frame) {
570  define('SQ_DESIGN_NO_FRAME', 1);
571  } else {
572  define('SQ_DESIGN_NO_FRAME', 0);
573  }//end else
574 
575  ob_start();
576  $backend->paint();
577  ob_end_flush();
578  } else {
579  $this->paintLogin(translate('login'), translate('must_login_to_access_backend'));
580  }
581 
582  } else if (SQ_IN_LOGIN) {
583  $this->paintLogin(translate('login'), translate('must_login_to_access_backend'));
584 
585  } else {
586  // we are on the frontend
587  if (!(SQ_IN_CRON || SQ_PHP_CLI) && defined('SQ_CONF_REDIRECT_URL_WITH_TRAILING_SLASH') && SQ_CONF_REDIRECT_URL_WITH_TRAILING_SLASH){
588  $host = (isset($_SERVER['HTTP_HOST'])) ? $_SERVER['HTTP_HOST'] : FALSE;
589  $uri = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : FALSE;
590  if ($host && $uri && substr($uri, -1) == '/') {
591  $uri = rtrim($uri, '/');
592  //do not redirect root URLs because browser may append a trailing slash
593  if (!empty($uri)){
594  $protocol = current_protocol().'://';
595  header('HTTP/1.1 301 Moved Permanently');
596  header('Location: '.$protocol.$host.$uri);
597  exit;
598  }
599  }
600  }
601 
602  // check for ./?a=xx type urls or normal
603  if (isset($_REQUEST['a'])) {
604  // requested asset is exclusively in the trash - don't display
605  if ($this->am->assetInTrash($_REQUEST['a'], TRUE)) {
606  $asset = NULL;
607  } else {
608  $asset = $this->am->getAsset($_REQUEST['a'], '', TRUE);
609  }
610 
611  //Feature #4759: Asset ID to URL remaps.
612  if (is_null($asset)){
613  $rm = $this->am->getSystemAsset('remap_manager');
614  $protocol = current_protocol();
615  if ($rm->loadRemapFromURL($protocol, '?a='.$_REQUEST['a'])) exit();
616  //Additionally Enhancement #4991
617  $substr = substr($_REQUEST['a'], 0, strpos($_REQUEST['a'], '?'));
618  if ($substr && $rm->loadRemapFromURL($protocol, '?a='.$substr)) exit();
619  }
620  } else {
621  $asset = $this->am->getAssetFromURL(NULL, NULL, TRUE, TRUE);
622  if ($asset != NULL) {
623  $valid_protocols = $asset->getValidProtocols();
624  if (empty($valid_protocols)) {
625  // if current protocol not valid and no other protocol exists for this URL, it's as if
626  // this asset has not been found at all. This would happen if it only had
627  // an HTTP URL but force-secure = true for the asset.
628  $asset = NULL;
629  }
630  }
631  }
632 
633  if (!is_null($asset) && !$asset->effectiveUnrestricted() && $require_password_change) {
634  $page_name = '"'.$asset->name.'"';
635  $this->paintPasswordChange(translate('change_password'), translate('must_change_password_to_access_asset', $page_name));
636  exit();
637  }
638 
639 
640  if (is_null($asset)) {
641 
642  $rm = $this->am->getSystemAsset('remap_manager');
643  if(!($rm->loadRemapFromCurrentURL())) {
644  $url = strip_url(current_url(FALSE, TRUE));
645  $protocol = current_protocol();
646 
647  // okay, the remap manager could not remap this url, so
648  // check to see if we can find a 404 page from a site matching this url
649  $root_url = $this->am->getRootURL($url);
650 
651  // if we can get a site from the url,
652  // try to delegate the 404 to its not found page
653  if (!empty($root_url)) {
654  $site = $this->am->getAsset($root_url['assetid']);
655  if (!is_null($site)) {
656  $page = $site->getSpecialPage('not_found');
657  if (!is_null($page)) {
658  $this->_paintNotFoundAsset($page, $site);
659  exit(1);
660  }
661  }
662  }
663 
664  // if we can't get a site from the url, or the
665  // site does not have a 404 page, then try asking
666  // the remap manager to remap to its 404 page
667  if (!$rm->paintPageNotFoundRemap()) {
668  header('HTTP/1.0 404 Not Found');
669 
670  $cm = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('cache_manager');
671  // Send Cacheable Header based on cache manager default setting
672  if (SQ_CONF_SEND_CACHEABLE_HEADER && SQ_CONF_SEND_404_CACHEABLE_HEADER && $cm->cacheableHeadersEnabledForCurrentProtocol()) {
673  header('Pragma: cache');
674 
675  $browser_cache_expiry = $cm->attr('browser_cache_expiry');
676  if (empty($browser_cache_expiry)) {
677  $browser_cache_expiry = $cm->attr('expiry');
678  }
679 
680  header('Cache-Control: max-age='.$browser_cache_expiry.', '.$cm->cacheControlLevel());
681  header('Expires: '.gmdate('D, d M Y H:i:s', time() + $browser_cache_expiry).' GMT');
682  }
683 
684  // last resort, display little green error
685  trigger_localised_error('SYS0218', E_USER_NOTICE, $protocol.'://'.$url);
686  }
687  }//end else
688 
689  exit(1);
690 
691  }//end if is_null(asset)
692 
693  // we need to check if the asset is being requested by Funnelback for Indexing Purpose
694  // if the condition matches then we need to send few extra headers along with regular contents
695  // Check the user agent first so we don't load funnelback manager unless we really need to.
696  if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Funnelback') !== FALSE) {
697  $fbm = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('funnelback_manager', TRUE);
698  if (!is_null($fbm)) {
699  $fb_user = $fbm->attr('user');
700  if ($GLOBALS['SQ_SYSTEM']->currentUserId() == $fb_user) {
701  $security_keys = $fbm->getAccessLocks($asset);
702  header('X-Funnelback-Locks: '.$security_keys);
703 
704  header('X-Funnelback-SQUIZASSETYPE: '.$asset->type());
705  header('X-Funnelback-last_modified: '.date('Y-m-d', $asset->updated).'T'.date('H:i:s', $asset->updated));
706  if ($asset instanceof File) {
707  header('X-Funnelback-file-title: '.$asset->attr('title'));
708  header('X-Funnelback-file-name: '.$asset->name);
709  }
710  if ($asset->created_userid != 0) {
711  $created_by = $GLOBALS['SQ_SYSTEM']->am->getAsset($asset->created_userid);
712  header('X-Funnelback-author-name: '.$created_by->name);
713  header('X-Funnelback-author-id: '.$created_by->id);
714  }
715  header('X-Funnelback-created: '.date('Y-m-d', $asset->created).'T'.date('H:i:s', $asset->created));
716  header('X-Funnelback-ancestors: '.implode(' ', array_keys($GLOBALS['SQ_SYSTEM']->am->getParents($asset->id))));
717 
718  // Add in the metadata
719  $mm = $GLOBALS['SQ_SYSTEM']->getMetadataManager();
720  require_once SQ_FUDGE_PATH.'/general/text.inc';
721 
722  $metadata = $mm->getMetadata($asset->id);
723  foreach ($metadata as $info) {
724  $name = $info[0]['name'];
725 
726  $value = $info[0]['value'];
727  $keywords = retrieve_keywords_replacements($value);
728  $replacements = Array();
729  foreach ($keywords as $keyword) {
730  $replacements[$keyword] = $asset->getKeywordReplacement($keyword);
731  }//end foreach
732  replace_keywords($value, $replacements);
733  $name = $fbm->encodeText(htmlspecialchars($name, ENT_COMPAT, SQ_CONF_DEFAULT_CHARACTER_SET));
734  $value = $fbm->encodeText(htmlspecialchars($value, ENT_COMPAT, SQ_CONF_DEFAULT_CHARACTER_SET));
735  header('X-Funnelback-metadata-'.$name.': '.$value);
736  }//end foreach
737  }//end if
738  }//end if
739  }//end if
740 
741  // In _performance view mode
742  if (SQ_IN_PERFORMANCE) {
743  if(empty($GLOBALS['SQ_SYSTEM']->user) || !$GLOBALS['SQ_SYSTEM']->user->canAccessBackend() || !$asset->writeAccess()) {
744  $this->paintLogin(translate('login'), translate('must_login_to_access_backend'));
745  } else {
746  $this->pm->printFrames($asset);
747  }
748  exit(1);
749  }
750 
751  // If we've loaded up an asset previously from the current url,
752  // check if it's a site asset.
753  // If it is, we don't need to work out the site again.
754  $site = NULL;
755  if (!is_null($asset)) {
756  $asset_type = $asset->type();
757  if ($asset_type == 'site' || $GLOBALS['SQ_SYSTEM']->am->isTypeDecendant($asset_type, 'site')) {
758  $site = $asset;
759  }
760  }
761 
762  // It's not a site asset or we don't know what the asset is?
763  if ($site === NULL) {
764  $asset_url = current_url(FALSE, TRUE);
765  // Muting errors by passing in the third parameter
766  $asset_lineage = $GLOBALS['SQ_SYSTEM']->am->getLineageFromURL(NULL, $asset_url, TRUE);
767  foreach ($asset_lineage as $asset_link) {
768  $asset_type = $asset_link['type_code'];
769  if ($asset_type == 'site' || $GLOBALS['SQ_SYSTEM']->am->isTypeDecendant($asset_type, 'site')) {
770  $site_assetid = $asset_link['assetid'];
771  if (!empty($site_assetid)) {
772  $site = $GLOBALS['SQ_SYSTEM']->am->getAsset($site_assetid);
773  break;
774  }//end if
775  }
776  }
777  }
778 
779  if (!is_null($site)) {
780  $GLOBALS['SQ_SYSTEM']->setGlobalDefine('CURRENT_SITE', $site);
781  }//end if
782 
783  // Are we an archived asset and we don't have the correct permission?
784  // (We need read permission, which in Archived mode actually means
785  // WRITE permission is required)
786  if (($asset->status == SQ_STATUS_ARCHIVED) && !$asset->readAccess()) {
787  // Don't get in the way of backend operations
788  if (!(SQ_IN_BACKEND || SQ_IN_LIMBO)) {
789  if ($GLOBALS['SQ_SYSTEM']->isGlobalDefineSet('CURRENT_SITE')) {
790  $site = $GLOBALS['SQ_SYSTEM']->getGlobalDefine('CURRENT_SITE');
791  } else {
792  $url = strip_url(current_url(FALSE, TRUE));
793  $root_url = $this->am->getRootURL($url);
794  if (!empty($root_url)) {
795  $site = $this->am->getAsset($root_url['assetid']);
796  }//end if
797  }//end if
798 
799  if (!is_null($site)) {
800  $page = $site->getSpecialPage('archive');
801  if (!is_null($page)) {
802  $this->_paintNotFoundAsset($page, $site);
803  exit(1);
804  }
805  }
806  }//end if
807  }//end if
808 
809  $this->frontend_asset = &$asset;
810  $this->broadcastTriggerEvent('trigger_event_asset_accessed', $asset);
811 
812  // See if caching is enabled for the asset
813  $cm = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('cache_manager');
814 
815 
816  // buffer the content and do keyword replacement
817  ob_start();
818  if (basename($_SERVER['PHP_SELF']) == SQ_CONF_RECACHE_SUFFIX) {
819 
820  // Gather assetids from cache table
821  $url = strip_url(current_url(TRUE, TRUE));
822  $cm->clearCachedAssetsByUrl($url);
823  $GLOBALS['SQ_SYSTEM']->broadcastTriggerEvent('trigger_event_asset_recached', $asset);
824 
825  // Finally recache
826  if (SQ_CONF_SEND_CACHEABLE_HEADER && $cm->cacheableHeadersEnabledForCurrentProtocol() && $cm->sendCacheableHeaders($asset->type(), $asset->id)) {
827  $this->_sendHTTPHeaders($asset);
828  } else {
829  $asset->printFrontend();
830  }
831  }//end if
832  // caching is enabled and asset is readable by public user, send HTTP headers
833  // however not send if user have just logged out (see bug #3766)
834  else if (basename($_SERVER['PHP_SELF']) != SQ_CONF_NOCACHE_SUFFIX &&
835  SQ_CONF_SEND_CACHEABLE_HEADER &&
836  empty($_POST) && $asset->status >= SQ_STATUS_LIVE &&
837  $asset->readAccess() &&
838  !(isset($_REQUEST['just_logged_out']) && $_REQUEST['just_logged_out']) &&
839  $cm->cacheableHeadersEnabledForCurrentProtocol() &&
840  $cm->sendCacheableHeaders($asset->type(), $asset->id)
841  ) {
842  $this->_sendHTTPHeaders($asset);
843 
844  } else {
845  $asset->printFrontend();
846  }
847  $content = ob_get_contents();
848  // now clear off our buffer but only if had anything in it
849  if (ob_get_length() !== FALSE) ob_end_clean();
850 
851  $this->replaceKeyword($content);
852 
853  // parse out self links if configured so
854  if (SQ_CONF_NO_ASSET_SELF_LINKS) {
855  $content = parse_self_links($content);
856  }
857 
858 
859  // Carryout highest level ./?a=xx URL translation
860  $this->_translateMatrixURL($content);
861 
862  // translate all urls to performance mode style url _performance and target=_top
863  $this->_translatePerformanceModeURL($content);
864 
865  // remove any sq_wysiwyg_* tags used as placeholders for plugins
866  // such as Embed Movie/Youtube
867  $content = preg_replace('|<sq_wysiwyg_([^ >]*)([^>]*)>(.*)</sq_wysiwyg_\1>|si', '$3', $content);
868 
869  echo $content;
870 
871  }//end else backend/limbo
872 
873  if (SQ_CONF_DEBUG & 4) {
874  // report all the assets with a reference count > 0
875  log_write($this->am->getForgottenItemsReport(), 'asset_cache', E_USER_NOTICE, FALSE);
876  }
877 
878  if (!defined('SQ_DESIGN_NO_FRAME')) {
879  define('SQ_DESIGN_NO_FRAME', 0);
880  }//end if
881 
882  $this->_setApacheNotes();
883  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($cm);
884 
885  // finalize perfromance timer
886  $this->pm->end();
887  }//end start()
888 
889 
896  protected function _setApacheNotes()
897  {
898  include SQ_DATA_PATH.'/private/conf/apache.inc';
899  foreach ($request_notes as $note_name => $note_info) {
900  // record request note for each type
901  $note_value = '';
902  switch ($note_info['type']) {
903  case 'user':
904  $note_value = $GLOBALS['SQ_SYSTEM']->user->getKeywordReplacement($note_info['keyword']);
905  break;
906  case 'asset':
907  if (isset($this->frontend_asset) && $this->frontend_asset->id) {
908  $note_value = $this->frontend_asset->getKeywordReplacement($note_info['keyword']);
909  }
910  break;
911  }
912  if ((trim($note_value) !== '') && ($note_value !== '%'.$note_info['keyword'].'%')) {
913  apache_note($note_name, $note_value);
914  }
915  }//end foreach
916 
917  }//end _setApacheNotes()
918 
919 
928  protected function _sendHTTPHeaders(&$asset)
929  {
930  $recache = basename($_SERVER['PHP_SELF']) == SQ_CONF_RECACHE_SUFFIX;
931  // Don't send cacheable headers when recacheing (see bug #3947)
932  if (!$recache) {
933  header('Pragma: cache');
934  }
935 
936  $contextid = $this->getContextId();
937  $cache_key = 'cache_info';
938  if ($contextid !== 0) {
939  $cache_key .= '.'.$contextid;
940  }
941 
942  // save/load the cache information of this asset
943  $cm = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('cache_manager');
944  $cache_info = $cm->loadFromCache($asset->id, $asset->type(), $cache_key);
945 
946  $cache_expiry = $cm->getExpiry($asset->type(), $asset->id);
947  $browser_cache_expiry = $cm->getBrowserCacheExpiry($asset->type(), $asset->id);
948  if (empty($browser_cache_expiry)) {
949  $browser_cache_expiry = $cache_expiry;
950  }
951  if (!$recache) {
952  header('Cache-Control: max-age='.$browser_cache_expiry.', '.$cm->cacheControlLevel());
953  }
954 
955  // not cached or has expired
956  if ($cache_info === FALSE) {
957  ob_start();
958  $loaded_assetids = $GLOBALS['SQ_SYSTEM']->am->_asset_cache->getAssetKeys();
959  $asset->printFrontend();
960  $all_assetids = $GLOBALS['SQ_SYSTEM']->am->_asset_cache->getAssetKeys();
961  $content = ob_get_contents();
962  ob_end_clean();
963 
964  // work out the correct last updated time of this asset
965  $assetids = array_diff($all_assetids, $loaded_assetids);
966 
967  // Remove any context info from asset cache key entries
968  foreach ($assetids as &$assetid) {
969  $assetid_bits = explode('\\', $assetid, 2);
970  if ($assetid_bits > 1) {
971  $assetid = $assetid_bits[1];
972  }
973  }
974 
975  $last_updated = $asset->getEffectiveLastUpdatedTime($assetids);
976 
977  // The Matrix Cache Expiry (a.k.a "Default Expiry") is used when saving to cache
978  $info = Array(
979  'last_modified' => $last_updated,
980  'expires' => (time() + $cache_expiry),
981  );
982  $cm->saveToCache($asset->id, $asset->type(), $cache_key, serialize($info));
983  // if cache_status is FALSE, then caching is probably disabled, expiry time = now
984 
985  // Calculate the "Browser Cache Expiry" value for use in HTTP headers
986  $expiry_time = time() + $browser_cache_expiry;
987 
988  if (!$recache) {
989  header('Expires: '.gmdate('D, d M Y H:i:s', $expiry_time).' GMT');
990  if (SQ_CONF_SEND_LAST_MODIFIED_HEADER ) {
991  header('Last-Modified: '.gmdate('D, d M Y H:i:s', $info['last_modified']).' GMT');
992  }
993  }
994  echo $content;
995 
996  // cached version found
997  } else {
998  $info = unserialize($cache_info);
999  $last_modified = gmdate('D, d M Y H:i:s', $info['last_modified']).' GMT';
1000  $headers = $this->_getHeaders();
1001  $not_modified = FALSE;
1002  // if the last modified time matches the If-Modified-Since header in HEAD request
1003  if (isset($headers['If-Modified-Since']) && ($last_modified == $headers['If-Modified-Since'])) {
1004  $not_modified =TRUE;
1005  }
1006 
1007  // If the Browser Cache Expiry is set, caclculate the expiry time from now.
1008  // Otherwise, use the expiry time as supplied by the Matrix cache.
1009  $browser_cache_expiry = $cm->getBrowserCacheExpiry($asset->type(), $asset->id);
1010  if (empty($browser_cache_expiry)) {
1011  header('Expires: '.gmdate('D, d M Y H:i:s', $info['expires']).' GMT');
1012  } else {
1013  header('Expires: '.gmdate('D, d M Y H:i:s', time() + $browser_cache_expiry).' GMT');
1014  }
1015 
1016  if (SQ_CONF_SEND_LAST_MODIFIED_HEADER) {
1017  header('Last-Modified: '.$last_modified);
1018  }
1019  if (SQ_CONF_SEND_NOT_MODIFIED_HEADER && $not_modified) {
1020  header('HTTP/1.1 304 Not Modified');
1021  exit(1);
1022  } else {
1023  // not sending 304 header, print the content
1024  $asset->printFrontend();
1025  }
1026  }
1027 
1028  $GLOBALS['SQ_SYSTEM']->am->forgetAsset($cm);
1029 
1030  }//end _sendHTTPHeaders()
1031 
1032 
1042  public function replaceKeyword(&$content, $additional_replacements=Array())
1043  {
1044  // Replace any global keywords first
1045  replace_global_keywords($content);
1046 
1047  if ($this->getGlobalDefine('SQ_REPLACE_MYSOURCE_LEVEL_KEYWORDS', TRUE)) {
1048  // Perform any additional replacements
1049  $keywords = retrieve_keywords_replacements($content);
1050  foreach ($keywords as $word) {
1051  if (!isset($additional_replacements[$word])) {
1052  $replacement = $this->frontend_asset->getKeywordReplacement($word);
1053  // still cannot replace it, blank it out
1054  if (($replacement == "%$word%") && (strpos('0123456789', $word[0]) === FALSE)) {
1055  $additional_replacements[$word] = '';
1056  } else {
1057  $additional_replacements[$word] = $replacement;
1058  }
1059  }
1060  }
1061  replace_keywords($content, $additional_replacements);
1062  }
1063 
1064  }//end replaceKeyword()
1065 
1066 
1067 //-- GLOBAL DEFINES --//
1068 
1069 
1079  public function setGlobalDefine($index, $value)
1080  {
1081  if (!empty($index)) {
1082  $this->_global_defines[$index] = $value;
1083  }
1084 
1085  }//end setGlobalDefine()
1086 
1087 
1096  public function unsetGlobalDefine($index)
1097  {
1098  unset($this->_global_defines[$index]);
1099 
1100  }//end unsetGlobalDefine()
1101 
1102 
1112  public function getGlobalDefine($index, $default=NULL)
1113  {
1114  if (!is_array($this->_global_defines)) return $default;
1115  return $this->isGlobalDefineSet($index) ? $this->_global_defines[$index] : $default;
1116 
1117  }//end getGlobalDefine()
1118 
1119 
1128  public function isGlobalDefineSet($index)
1129  {
1130  return (isset($this->_global_defines[$index])) ? TRUE : FALSE;
1131 
1132  }//end isGlobalDefineSet()
1133 
1134 
1135 //-- LOGGING USERS IN, OUT AND ABOUT --//
1136 
1137 
1147  public function paintLogin($heading, $msg)
1148  {
1149  // Attempt to perform an auth redirect. If there is one, then the script
1150  // will terminate inside the do_redirect() function and redirect. If
1151  // not, this function will return FALSE and we will fall through to the
1152  // login box.
1153  $this->authRedirect();
1154 
1155  $db = $this->db;
1156  header('HTTP/1.0 403 Forbidden');
1157 
1158  if (array_get_index($_REQUEST, 'SQ_BACKEND_PAGE') == 'header') {
1159  // we don't want to print a login box in the top frame, so reload the parent frame
1160  $redirect_location = replace_query_string_vars(Array('SQ_BACKEND_PAGE' => NULL));
1161  $redirect_location = strip_tags(addslashes($redirect_location));
1162  ?>
1163  <script type="text/javascript">
1164  window.parent.document.location = '<?php echo $redirect_location; ?>';
1165  </script>
1166  <?php
1167  return;
1168  }
1169  if ((current_protocol() == 'http') && (SQ_CONF_FORCE_SECURE_LOGIN_URLS != '')) {
1170  // check if we require secure login for this root URL
1171  $current_url = current_url(FALSE, TRUE);
1172  $root_urls = explode("\n", SQ_CONF_SYSTEM_ROOT_URLS);
1173  $current_root_url = '';
1174  foreach ($root_urls as $url) {
1175  if (0 === strpos($current_url, $url)) {
1176  if (strlen($url) > strlen($current_root_url)) {
1177  $current_root_url = $url;
1178  }
1179  }
1180  }
1181 
1182  if (in_array($current_root_url, explode("\n", SQ_CONF_FORCE_SECURE_LOGIN_URLS))) {
1183  // Redirect to HTTPS
1184  $_SESSION['SQ_RETURN_TO_HTTP'] = 1;
1185  do_redirect('https://'.current_url(FALSE)); // exits
1186  }
1187  }//end if http
1188  $GLOBALS['SQ_LOGIN_FORM'] = Array('paint' => 'login', 'heading' => $heading, 'msg' => $msg);
1189 
1190  // let's try do this via HTTP authentication
1191  if (SQ_CONF_ENABLE_HTTP_LOGIN && !empty($_GET['USE_HTTP_LOGIN']) && !isset($_SERVER['PHP_AUTH_USER'])) {
1192  // Blank out the session parameters regarding public user before sending the headers (see bug #4209)
1193  if ($this->userPublic($this->user)) {
1194  $_SESSION['user'] = NULL;
1195  $_SESSION['userid'] = NULL;
1196  $_SESSION['user_type_code'] = NULL;
1197  }
1198 
1199  header('WWW-Authenticate: Basic realm="MySource Matrix"');
1200  header('HTTP/1.0 401 Unauthorized');
1201  }
1202 
1203  if (empty($_GET['FORCE_BACKUP_LOGIN'])) {
1204 
1205  $current_asset = $this->am->getAssetFromURL(NULL, NULL, TRUE, TRUE);
1206  // if we can't find a current asset, use the root folder
1207  if (is_null($current_asset)) {
1208  $current_asset = $this->am->getSystemAsset('root_folder');
1209  if (is_null($current_asset)) {
1210  trigger_localised_error('SYS0248', E_USER_ERROR);
1211  }
1212  }
1213 
1214  $design_info = $this->am->getDesignFromURL(strip_url(current_url(FALSE, TRUE)), 'design::system::login');
1215 
1216  // we have found the design to use
1217  if ($design_info) {
1218  $design = $this->am->getAsset($design_info['designid'], $design_info['type_code']);
1219  } else {
1220  // OK, use the system login form
1221  $design = $this->am->getSystemAsset('login_design');
1222  }
1223 
1224  if (!is_null($design)) {
1225  $design->paint($current_asset);
1226  return;
1227  }
1228 
1229  }//end if
1230 
1231  // we can't find a design, resort to our backup
1232  if ($this->_msgs) echo implode('<br />', $this->_msgs);
1233  require_once SQ_INCLUDE_PATH.'/login.inc';
1234 
1235  }//end paintLogin()
1236 
1237 
1247  public function paintPasswordChange($heading, $msg)
1248  {
1249  $db = $this->db;
1250 
1251  $GLOBALS['SQ_PASSWORD_CHANGE_FORM'] = Array('paint' => 'password_change', 'heading' => $heading, 'msg' => $msg);
1252 
1253  if ((current_protocol() == 'http') && (SQ_CONF_FORCE_SECURE_LOGIN_URLS != '')) {
1254  // If we require secure login for this URL, we will also require it
1255  // for password changes.
1256  $current_url = current_url(FALSE, TRUE);
1257  $root_urls = explode("\n", SQ_CONF_SYSTEM_ROOT_URLS);
1258  $current_root_url = '';
1259  foreach ($root_urls as $url) {
1260  if (0 === strpos($current_url, $url)) {
1261  if (strlen($url) > strlen($current_root_url)) {
1262  $current_root_url = $url;
1263  }
1264  }
1265  }
1266 
1267  if (in_array($current_root_url, explode("\n", SQ_CONF_FORCE_SECURE_LOGIN_URLS))) {
1268  // Redirect to HTTPS
1269  $_SESSION['SQ_RETURN_TO_HTTP'] = 1;
1270  do_redirect('https://'.current_url(FALSE)); // exits
1271  }
1272  }//end if http
1273 
1274  if (empty($_GET['FORCE_BACKUP_PASSWORD_CHANGE'])) {
1275 
1276  $current_asset = $this->am->getAssetFromURL(NULL, NULL, TRUE, TRUE);
1277  // if we can't find a current asset, use the root folder
1278  if (is_null($current_asset)) {
1279  $current_asset = $this->am->getSystemAsset('root_folder');
1280  if (is_null($current_asset)) {
1281  trigger_localised_error('SYS0248', E_USER_ERROR);
1282  }
1283  }
1284 
1285  $design_info = $this->am->getDesignFromURL(strip_url(current_url(FALSE, TRUE)), 'design::system::password_change');
1286 
1287  // we have found the design to use
1288  if ($design_info) {
1289  $design = $this->am->getAsset($design_info['designid'], $design_info['type_code']);
1290  } else {
1291  // OK, use the system password change form
1292  $design = $this->am->getSystemAsset('password_change_design');
1293  }
1294 
1295  if (!is_null($design)) {
1296  $design->paint($current_asset);
1297  return;
1298  }
1299 
1300  }//end if
1301 
1302  // we can't find a design, resort to our backup
1303  if ($this->_msgs) echo implode('<br />', $this->_msgs);
1304  require_once SQ_INCLUDE_PATH.'/password_change.inc';
1305 
1306  }//end paintPasswordChange()
1307 
1308 
1315  public function setupUser()
1316  {
1317  if (!SQ_CONF_ALLOW_IP_CHANGE && isset($_SESSION['remote_addr']) && ($_SESSION['remote_addr'] != $_SERVER['REMOTE_ADDR'])) {
1318  // If not Using SOAP AUTH.
1319  if (!SQ_SOAP_AUTH) {
1320  if (isset($_SESSION['activated']) === FALSE || $_SESSION['activated'] != 1) {
1321  // IP Address has changed, and we are not authenticating via SOAP.
1322  $this->_logSecurityMsg('system.security.logout.ip_change', Array('old_ip' => $_SESSION['remote_addr'], 'new_ip' => $_SERVER['REMOTE_ADDR']));
1323  $this->_clearSession();
1324  $this->loginPublicUser();
1325  $this->generateLoginKey(TRUE);
1326  return;
1327  }//end if
1328  }//end if
1329  }//end if
1330 
1331  $this->user = NULL;
1332 
1333  if (!empty($_SESSION['userid']) && !empty($_SESSION['user']) && !empty($_SESSION['user_serialised_ts'])) {
1334  $userid = $_SESSION['userid'];
1335  // There is a serialised user object in the session; if it's not stale, unserialise and use it
1336  $user_info = $GLOBALS['SQ_SYSTEM']->am->getAssetInfo(Array($userid), $_SESSION['user_type_code'], TRUE, 'updated');
1337  $user_updated_date = array_get_index($user_info, $userid, FALSE);
1338  if ($user_updated_date && $_SESSION['user_serialised_ts'] > iso8601_ts($user_updated_date) || (!$user_updated_date && SQ_ROLLBACK_VIEW)) {
1339  $GLOBALS['SQ_SYSTEM']->am->includeAsset($_SESSION['user_type_code']);
1340  $this->user = unserialize($_SESSION['user']);
1341  $GLOBALS['SQ_SYSTEM']->am->rememberAsset($this->user);
1342  }
1343  }
1344 
1345  if (is_null($this->user) && !empty($_SESSION['userid'])) {
1346  // We failed to unserialise a user from the session, but there is nevertheless a userid
1347  // in the session, so the serialised version must have been stale. So, let's load the asset
1348  // from the DB using the userid and re-save to session.
1349  $this->user = $GLOBALS['SQ_SYSTEM']->am->getAsset($_SESSION['userid'], $_SESSION['user_type_code']);
1350  if (!is_null($this->user)) {
1351  $_SESSION['user'] = serialize($this->user);
1352  $_SESSION['user_serialised_ts'] = time();
1353  }
1354  }
1355 
1356  if (is_null($this->user) || $this->user instanceof Public_User) {
1357  // See if we can log in an http-authenticated user
1358  if (defined('SQ_CONF_ALLOW_HTTP_LOGIN') && SQ_CONF_ALLOW_HTTP_LOGIN) {
1359  $this->_loginHttpAuthenticatedUser();
1360  }
1361  }
1362 
1363  if (is_null($this->user)) {
1364  // Have a go at using HTTP credentials
1365  if (defined('SQ_CONF_ACCEPT_HTTP_CREDS') && SQ_CONF_ACCEPT_HTTP_CREDS) {
1366  $this->_authenticateHttpUser();
1367  }
1368  }
1369 
1370  if (is_null($this->user)) {
1371  // Must be the public user
1372  $this->loginPublicUser();
1373  }
1374 
1375  // Right, now we have a user of some sort; let's check their session preferences
1376  $security_actions = Array('login', 'logout', 'password_change', 'hipo', 'asset_map_request');
1377  $has_security_action = in_array(array_get_index($_REQUEST, 'SQ_ACTION'), $security_actions);
1378  if (array_get_index($_SESSION, 'user_last_access') && !$has_security_action) {
1379 
1380  $session_prefs = $this->getUserPrefs('user', 'SQ_USER_SESSION_PREFS', TRUE);
1381  $user_prefs = $this->_findValidSessionExpiry($session_prefs);
1382 
1383  // See if it's been too long since their last MatrixActivity(R)
1384  if (!empty($user_prefs['timeout'])) {
1385  if (time() - $_SESSION['user_last_access'] > $user_prefs['timeout']) {
1386  $this->_logSecurityMsg('system.security.logout.idle', Array('timeout' => $user_prefs['timeout']));
1387  $this->_clearSession();
1388  $this->loginPublicUser();
1389  $this->generateLoginKey(TRUE);
1390  return;
1391  }
1392  }
1393 
1394  // See if it's been too long since their last authentication (or beginning of public user's session).
1395  // To avoid demanding authentication at an inconvenient time (eg when the user is submitting a big form),
1396  // the timeout is rounded to a number of days. If the timeout was yesterday then we demand authentication
1397  // straight away. If the timeout is today then we only ask for authentication if the user has been inactive
1398  // for a certain time (Which probably means they are not editing).
1399  if (array_get_index($_SESSION, 'user_last_authentication')) {
1400  if (!empty($user_prefs['max_length'])) {
1401  $session_expiry_ts = $_SESSION['user_last_authentication'] + $user_prefs['max_length'];
1402  $today_start_ts = strtotime('00:00:01 today');
1403  $tomorrow_start_ts = strtotime('00:00:01 tomorrow');
1404  if ($session_expiry_ts < $today_start_ts) {
1405  // it expired yesterday so clear the session / force a login right now
1406  $this->_logSecurityMsg('system.security.logout.staleauth', Array('timeout' => $user_prefs['max_length']));
1407  $this->_clearSession();
1408  $this->loginPublicUser();
1409  $this->generateLoginKey(TRUE);
1410  } else if ($session_expiry_ts < $tomorrow_start_ts) {
1411  // it's meant to expire today...
1412  if (time() - $_SESSION['user_last_access'] > SQ_CONF_EDITING_TIME) {
1413  // because they haven't done anything for a while we assume it's safe to log them out
1414  // and ask for re-authentication
1415  $this->_logSecurityMsg('system.security.logout.staleauth', Array('timeout' => $user_prefs['max_length']));
1416  $this->_clearSession();
1417  $this->loginPublicUser();
1418  $this->generateLoginKey(TRUE);
1419  return;
1420  }
1421  }
1422  }
1423  }
1424  }//end if
1425 
1426  $this->_user_is_root = $_SESSION['user_is_root'];
1427  $this->_user_is_sys_admin = $_SESSION['user_is_sys_admin'];
1428  $this->_user_is_public = $_SESSION['user_is_public'];
1429 
1430  }//end setupUser()
1431 
1432 
1439  public function _processGlobalActions()
1440  {
1441  $db = MatrixDAL::getDb();
1442 
1443  switch ($_REQUEST['SQ_ACTION']) {
1444  case 'password_change' :
1445  $password_old = array_get_index($_POST, 'SQ_PASSWORD_CHANGE_OLD');
1446  $password_new = array_get_index($_POST, 'SQ_PASSWORD_CHANGE_NEW');
1447  $password_confirm = array_get_index($_POST, 'SQ_PASSWORD_CHANGE_CONFIRM');
1448 
1449  if (is_null($password_old)) return;
1450  if (is_null($password_new)) return;
1451  if (is_null($password_confirm)) return;
1452 
1453  if (!$this->user->comparePassword($password_old)) {
1454  $this->addMessage('Your old password is incorrect');
1455  return;
1456  }
1457 
1458  if ($password_old == $password_new) {
1459  $this->addMessage('The new password must be different from your old one');
1460  return;
1461  }
1462 
1463  if ($password_new != $password_confirm) {
1464  $this->addMessage('The new and confirmed passwords do not match');
1465  return;
1466  }
1467 
1468  include_once SQ_ATTRIBUTES_PATH.'/password/password.inc';
1469  $pass_attr = new Asset_Attribute_Password();
1470  if (!$pass_attr->validateValue($password_new)) {
1471  $this->addMessage('Bad password values');
1472  return;
1473  }
1474 
1475  if ($this->user && !($this->user instanceof Public_User)) {
1476  // logged in, and the password is correct. change the password
1477  if ($GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_PERMISSIONS)) {
1478  $run_level_changed = TRUE;
1479  $GLOBALS['SQ_SYSTEM']->setRunLevel($GLOBALS['SQ_SYSTEM']->getRunLevel() - SQ_SECURITY_PERMISSIONS);
1480  } else {
1481  $run_level_changed = FALSE;
1482  }
1483 
1484  $result = FALSE;
1485  if ($this->user->validatePassword($password_new, TRUE)) {
1486  $result = $this->user->setAttrValue('password', $password_new);
1487  $result = $result && $this->user->saveAttributes();
1488  }
1489 
1490  if ($run_level_changed) {
1491  $GLOBALS['SQ_SYSTEM']->restoreRunLevel();
1492  }
1493 
1494  if ($result && $this->user->status != SQ_STATUS_LIVE) {
1495  $available_statuses = $this->user->getAvailableStatii();
1496  if (!isset($available_statuses[SQ_STATUS_LIVE]) || !$this->user->processStatusChange(SQ_STATUS_LIVE)) {
1497  trigger_localised_error('SYS0304', E_USER_ERROR);
1498  return;
1499  }
1500  }
1501  }
1502 
1503  break;
1504 
1505  case 'login' :
1506  $login_username = array_get_index($_POST, 'SQ_LOGIN_USERNAME');
1507  $login_password = array_get_index($_POST, 'SQ_LOGIN_PASSWORD');
1508  $login_key = array_get_index($_POST, 'SQ_LOGIN_KEY');
1509  if (is_null($login_username) || is_null($login_password)) {
1510  return;
1511  }
1512 
1513  // if this person is already logged in, don't bother trying again because
1514  // we will only be giving them an incorrect login key error
1515  if (!is_null($this->user) && $login_username == $this->user->attr('username')) {
1516  if (SQ_IN_LOGIN) {
1517  $current_url = current_url(TRUE,TRUE);
1518  do_redirect($current_url);
1519  }
1520  return;
1521  }
1522  if (!is_null($login_key) && $this->loginKey() == $login_key) {
1523 
1524  // get a list of all the installed authentication systems
1525  $auth_folder = $this->am->getSystemAsset('authentication_folder');
1526  if (is_null($auth_folder)) {
1527  trigger_localised_error('SYS0249', E_USER_ERROR);
1528  return;
1529  }
1530  $auth_systems = $auth_folder->getAuthSystems();
1531  // try each auth system in order to see if we can load a user asset
1532  $user = NULL;
1533  $user_status = NULL;
1534  foreach ($auth_systems as $systemid) {
1535  $system = $this->am->getAsset($systemid);
1536  if (is_null($system)) continue;
1537 
1538  // if we are loging in as root user dont worry about other authentication system
1539  // roadmap issue #811 Logging in as root should not interrogate any ldap bridges
1540  if ($login_username === 'root' && $system->name != 'Default Authentication') continue;
1541 
1542  $user = $system->authenticateUser($login_username, $login_password);
1543  if (!is_null($user)) $user_status = $user->status;
1544  // check that the user exists AND that it can login
1545  // (ie. it is not yet live, or has been locked)
1546  if (!is_null($user) && $user->canLogin()) {
1547  return $this->loginUser($user);
1548  }
1549  }
1550 
1551  $failure_reason = translate('user_not_found');
1552  if (!is_null($user_status)) {
1553  if ($user_status & SQ_SC_STATUS_NOT_LIVE) {
1554  $failure_reason = translate('user_status_not_live');
1555  } else {
1556  $failure_reason = translate('user_asset_in_trash');
1557  }
1558  }
1559 
1560  $this->addMessage(translate('user_not_found'));
1561  // log failed attempt
1562  $this->_logSecurityMsg('system.security.login.failed', Array('login_name' => $login_username, 'failure_reason' => $failure_reason), TRUE);
1563 
1564  } else {
1565 
1566  // incorrect login key
1567  $this->addMessage(translate('login_key_incorrect'));
1568  }
1569 
1570  // deliberately don't have a break here so if the
1571  // login fails we make sure that we're logged out
1572  case 'logout' :
1573  $for_real = ($this->user && !($this->user instanceof Public_User));
1574  $old_user = &$this->user;
1575 
1576  // Log a slightly different system message if we logging out from secondary user session (after using "Login As...")
1577  if (isset($_SESSION['login_as_invoker_user_full_name'])) {
1578  $this->_logSecurityMsg('system.security.logout.secondary.manual', Array('secondary_user_name' => $_SESSION['login_as_invoker_user_full_name']));
1579  } else {
1580  $this->_logSecurityMsg('system.security.logout.manual');
1581  }
1582 
1583  // code below is added with respect to #5731 Login as and
1584  // stalled hippo. If we are switching back to the primary
1585  // user don't worry about chec king the stalled hippo jobs
1586  if (!isset($_SESSION['login_as_invoker_id']) && !isset($_SESSION['login_as_invoker_username'])) {
1587  $stalled_hipo_array = Array();
1588  $hh = $GLOBALS['SQ_SYSTEM']->getHipoHerder();
1589  $current_userid = $GLOBALS['SQ_SYSTEM']->currentUserId();
1590  // Only show hipo stalled job warning when it's really logging out from backend
1591  if ($for_real){
1592  $stalled_hipo_array = $hh->getJobsForUser($current_userid);
1593  }
1594  if (SQ_IN_BACKEND && !empty($stalled_hipo_array)) {
1595  $return_url = current_protocol().'://'.current_url(FALSE);
1596 
1597  // We need to handle the 'return to HTTP' stuff here in any case
1598  if (!$_SESSION['user_is_public'] && array_get_index($_SESSION, 'SQ_RETURN_TO_HTTP')) {
1599  unset($_SESSION['SQ_RETURN_TO_HTTP']);
1600  $return_url = 'http://'.current_url(FALSE);
1601  }
1602  $return_url = replace_query_string_vars(Array('SQ_ACTION' => NULL), $return_url);
1603  require_once dirname(__FILE__).'/hipo_reminder.inc';
1604  exit();
1605  }
1606  }
1607 
1608  if ($for_real) {
1609  $this->broadcastTriggerEvent('trigger_event_before_user_logout', $old_user, NULL);
1610  }
1611 
1612  // clear PHP authentication
1613  if (isset($_SERVER['PHP_AUTH_USER'])) { unset($_SERVER['PHP_AUTH_USER']); }
1614  if (isset($_SERVER['PHP_AUTH_PW'])) { unset ($_SERVER['PHP_AUTH_PW']); }
1615 
1616  // Restore a previous System Administrator account after user switching
1617  $invoker_user_id = NULL;
1618  if (isset($_SESSION['login_as_invoker_id']) && isset($_SESSION['login_as_invoker_username'])) {
1619  $invoker_user_id = $_SESSION['login_as_invoker_id'];
1620  $invoker_username = $_SESSION['login_as_invoker_username'];
1621  $this->_loginAsUser($invoker_username, $invoker_user_id, TRUE);
1622  } else {
1623  $this->_clearSession();
1624  if ($for_real) {
1625  $this->broadcastTriggerEvent('trigger_event_user_logout', $old_user, NULL);
1626  }
1627  $this->loginPublicUser();
1628  $this->generateLoginKey(TRUE);
1629 
1630  // Flag to know that logout request is made
1631  // $_REQUEST['SQ_ACTION'] cannot be used as its value is unset after once its used
1632  $_REQUEST['just_logged_out'] = 1;
1633  }
1634  break;
1635  case 'real_logout' :
1636  $for_real = ($this->user && !($this->user instanceof Public_User));
1637  $old_user = &$this->user;
1638  if ($for_real) {
1639  $this->broadcastTriggerEvent('trigger_event_before_user_logout', $old_user, NULL);
1640  }
1641  $this->_logSecurityMsg('system.security.logout.manual');
1642  $this->_clearSession();
1643  if ($for_real) {
1644  $this->broadcastTriggerEvent('trigger_event_user_logout', $old_user, NULL);
1645  }
1646  $this->loginPublicUser();
1647  $this->generateLoginKey(TRUE);
1648 
1649  break;
1650  case 'login_as' :
1651  $username = '';
1652  if (isset($_GET['user'])) {
1653  $username = trim($_GET['user']);
1654 
1655  /*
1656  * Don't allow the user to login again as themselves - that's a bit silly
1657  * Also don't allow the user to switch to "root" as no-one is worthy. Only an initial login
1658  * as "root" will provide access to this account
1659  */
1660 
1661  if (($username != 'root') && (($username != $this->user->attr('username')) && (strlen($username) > 0))) {
1662  $this->_loginAsUser($username);
1663  }
1664  }
1665  break;
1666  case 'send_security_key' :
1667  require_once SQ_FUDGE_PATH.'/general/general.inc';
1668  $vars = &$_SESSION['SQ_SYSTEM_SECURITY_KEY'];
1669 
1670  // always regenerating the security key to stop painting same text for same session
1671  $vars['key'] = generate_security_key(strlen($vars['key']), $vars['include_uppercase'], $vars['include_numbers']);
1672 
1673  security_key_image(
1674  $vars['key_len'],
1675  $vars['key'],
1676  $vars['width'],
1677  $vars['height'],
1678  $vars['bg_colour'],
1679  $vars['text_colour'],
1680  $vars['border_colour'],
1681  $vars['zoom'],
1682  $vars['use_colours'],
1683  $vars['use_font'],
1684  $vars['font'],
1685  $vars['font_size'],
1686  $vars['min_angle'],
1687  $vars['max_angle'],
1688  $vars['x_start'],
1689  $vars['min_dist'],
1690  $vars['max_dist'],
1691  $vars['ttf_width'],
1692  $vars['ttf_height'],
1693  $vars['use_arc'],
1694  $vars['arc_colour'],
1695  $vars['include_uppercase'],
1696  $vars['include_numbers']
1697  );
1698  exit(1);
1699  break;
1700  case 'attribute_image' :
1701  // grab the details we need to make this work
1702  $allowed_attributes = Array('email');
1703  if (isset($_GET['attribute']) && !empty($_GET['attribute'])) {
1704  $attribute = $_GET['attribute'];
1705  if (!in_array($attribute, $allowed_attributes)) {
1706  $attribute = '';
1707  }//end if
1708  }//end if
1709  if (isset($_GET['id']) && !empty($_GET['id'])) {
1710  $assetid = $_GET['id'];
1711  $asset = $GLOBALS['SQ_SYSTEM']->am->getAsset($assetid, '', TRUE);
1712  if (is_null($asset) || !empty($attribute)) {
1713  $text = $asset->attr($attribute);
1714  if (empty($text)) {
1715  $text = '';
1716  }//end if
1717  } else {
1718  $text = $asset->attr($attribute);
1719  }//end if
1720  }
1721 
1722  $this->_generateAttributeImage($text);
1723  exit(1);
1724  break;
1725  case 'rollback_view_start' :
1726  $_SESSION['sq_rollback_view'] = Array();
1727 
1728  if (isset($_GET['assetid']) && isset($_GET['version'])) {
1729  // work out when the version of the asset was created
1730  $assetid = $_GET['assetid'];
1731  $version = $_GET['version'];
1732  // note that we dont prep this query for rollback select
1733  // because we are selecting directly from the rollback table
1734  $then = NULL;
1735  try {
1736  $bind_vars = Array(
1737  'assetid' => $assetid,
1738  'version' => $version,
1739  );
1740  $then = MatrixDAL::executeOne('core', 'rollbackGetTimeStampFrom', $bind_vars);
1741  } catch (Exception $e) {
1742  throw new Exception('Could not find version '.$version.' of asset ID #'.$assetid.' in rollback system due to database error: '.$e->getMessage());
1743  }
1744  $_SESSION['sq_rollback_view'] = Array('rollback_time' => $then);
1745  }
1746 
1747  unset($_GET['assetid']);
1748  unset($_GET['version']);
1749  break;
1750 
1751  case 'rollback_view_stop' :
1752  if (isset($_SESSION['sq_rollback_view'])) {
1753  unset($_SESSION['sq_rollback_view']);
1754  }
1755  break;
1756 
1757  case 'hipo' :
1758  $hh = $this->getHipoHerder();
1759  if ($hh->processWeb()) $hh->paintWeb();
1760  exit(1);
1761  break;
1762 
1763  case 'set_design_name' :
1764  $_SESSION['SQ_DESIGN_NAME'] = (isset($_GET['SQ_DESIGN_NAME'])) ? $_GET['SQ_DESIGN_NAME'] : '';
1765  unset($_GET['SQ_DESIGN_NAME']);
1766  break;
1767 
1768  case 'clear_design_name' :
1769  if (isset($_SESSION['SQ_DESIGN_NAME'])) {
1770  unset($_SESSION['SQ_DESIGN_NAME']);
1771  }
1772  break;
1773 
1774  case 'set_context' :
1775  // if this is a valid context name, then use it, otherwise ignore
1776  // eg. SQ_CONTEXT_NAME=Default%20Context
1777  $context_name = (isset($_GET['SQ_CONTEXT_NAME'])) ? $_GET['SQ_CONTEXT_NAME'] : '';
1778  unset($_GET['SQ_CONTEXT_NAME']);
1779  $context_data = MatrixDAL::executeAll('core', 'getContextByName', Array('name' => Array($context_name)));
1780  if (empty($context_data) === FALSE) {
1781  $contextid = $context_data[0]['contextid'];
1782  $_SESSION[SQ_SESSION_SANDBOX_INDEX]['SQ_ALTERNATE_CONTEXT_ID'] = $contextid;
1783  }
1784  break;
1785 
1786  case 'set_backend_context' :
1787  // Only make this take effect while we are in the backend
1788  // and we are logged in as some sort of user
1789  if ((SQ_IN_BACKEND === TRUE) || (SQ_IN_LIMBO === TRUE)) {
1790  if ($this->userPublic() === FALSE) {
1791  $context_name = (isset($_GET['SQ_CONTEXT_NAME'])) ? $_GET['SQ_CONTEXT_NAME'] : '';
1792  unset($_GET['SQ_CONTEXT_NAME']);
1793  $context_data = MatrixDAL::executeAll('core', 'getContextByName', Array('name' => Array($context_name)));
1794  if (empty($context_data) === FALSE) {
1795  $contextid = $context_data[0]['contextid'];
1796  $_SESSION[SQ_SESSION_SANDBOX_INDEX]['SQ_BACKEND_ALTERNATE_CONTEXT_ID'] = $contextid;
1797  }
1798  }
1799  }
1800  break;
1801 
1802  case 'clear_context' :
1803  if ((isset($_SESSION[SQ_SESSION_SANDBOX_INDEX]) === TRUE) && (isset($_SESSION[SQ_SESSION_SANDBOX_INDEX]['SQ_ALTERNATE_CONTEXT_ID']) === TRUE)) {
1804  unset($_SESSION[SQ_SESSION_SANDBOX_INDEX]['SQ_ALTERNATE_CONTEXT_ID']);
1805  }
1806  break;
1807 
1808  case 'asset_map_request' :
1809  require_once SQ_LIB_PATH.'/asset_map/asset_map.inc';
1810  $asset_map = new Asset_Map();
1811  $asset_map->process($this->backend);
1812  exit(1);
1813  break;
1814 
1815  case 'get_soap_downloaded_file' :
1816  $filename = array_get_index($_GET, 'soap_filename', '');
1817  $sessionid = session_id();
1818  $full_file_path = SQ_DATA_PATH.'/private/soap_action_file_responses/'.$sessionid.'/'.$filename;
1819 
1820  if (!file_exists($full_file_path)) {
1821  trigger_localised_error('FVER0007', E_USER_WARNING, $filename);
1822  exit();
1823  }
1824 
1825  if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
1826  $finfo = finfo_open(FILEINFO_MIME_TYPE);
1827  $mime_type = finfo_file($finfo, $full_file_path);
1828  } else {
1829  $mime_type = mime_content_type($full_file_path);
1830  }
1831  header('Content-type: '.$mime_type);
1832  readfile($full_file_path);
1833 
1834  exit(1);
1835  break;
1836 
1837  case 'getToken' :
1838  echo (get_unique_token());
1839  exit(1);
1840  break;
1841  }//end switch
1842 
1843  // just so it isn't being used anywhere else
1844  $_REQUEST['SQ_ACTION'] = '';
1845 
1846  }//end _processGlobalActions()
1847 
1848 
1855  public function loginPublicUser()
1856  {
1857  if (empty($this->user) || !($this->user instanceof Public_User)) {
1858  $this->user = $this->getPublicUser();
1859  $_SESSION['user_is_root'] = $this->_user_is_root = FALSE;
1860  $_SESSION['user_is_sys_admin'] = $this->_user_is_sys_admin = FALSE;
1861  $_SESSION['user_is_public'] = $this->_user_is_public = TRUE;
1862  $_SESSION['userid'] = $this->user->id;
1863  $_SESSION['user_type_code'] = $this->user->type();
1864  $_SESSION['remote_addr'] = (isset($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : '';
1865  $_SESSION['user'] = serialize($this->user);
1866  $_SESSION['user_serialised_ts'] = time();
1867  $this->broadcastTriggerEvent('trigger_event_public_user_session_created', $this->user, NULL);
1868  }
1869 
1870  }//end loginPublicUser()
1871 
1872 
1885  private function _loginAsUser($username, $resuming_user_id_match='', $resuming_session=FALSE)
1886  {
1887  $logged_in = ($this->user && !($this->user instanceof Public_User));
1888 
1889  if ($logged_in && ($GLOBALS['SQ_SYSTEM']->userRoot() || $GLOBALS['SQ_SYSTEM']->userSystemAdmin() || $resuming_session)) {
1890  $old_user = $this->user;
1891  $invoking_user_id = $this->user->id;
1892  $invoking_username = $this->user->attr('username');
1893  $invoking_user_full_name = $this->user->name;
1894 
1895  // Be nice and store the user's navigation history so we make it easier when they resume
1896  $invoking_user_nav_history = NULL;
1897  if ($resuming_session) {
1898  if (isset($_SESSION['login_as_invoker_sq_nav_history'])) {
1899  $invoking_user_nav_history = $_SESSION['login_as_invoker_sq_nav_history'];
1900  }
1901  } else {
1902  if (isset($_SESSION['sq_nav_history'])) {
1903  $invoking_user_nav_history = $_SESSION['sq_nav_history'];
1904  }
1905  }
1906 
1907  // Log out the invoking user (System Admin)
1908  $this->broadcastTriggerEvent('trigger_event_before_user_logout', $this->user, NULL);
1909  $this->_logSecurityMsg('system.security.logout.manual');
1910 
1911  // Prepare a new session
1912  $this->_clearSession();
1913  $this->loginPublicUser();
1914  $this->generateLoginKey(TRUE);
1915 
1916  $this->broadcastTriggerEvent('trigger_event_user_logout', $old_user, NULL);
1917 
1918  // Switch to the secondary user without authenticating
1919  // Don't worry about the login key as we know we are performing the request ourselves
1920 
1921  // Get a list of all the installed authentication systems
1922  $auth_folder = $this->am->getSystemAsset('authentication_folder');
1923  if (is_null($auth_folder)) {
1924  trigger_localised_error('SYS0249', E_USER_ERROR);
1925  return;
1926  }
1927  $auth_systems = $auth_folder->getAuthSystems();
1928 
1929  // Try each auth system in order to see if we can load a user asset
1930  $secondary_user = NULL;
1931  $user_status = NULL;
1932  $failure_reason = translate('user_not_found');
1933 
1934  foreach ($auth_systems as $systemid) {
1935  $system = $this->am->getAsset($systemid);
1936  if (is_null($system)) continue;
1937 
1938  // Don't authenticate this time, but just locate the user and log in
1939  $secondary_user = $system->locateUser($username);
1940  if (!is_null($secondary_user)) {
1941  // Ensure that if we are resuming, that the username and ID match so we
1942  // are certain that we are logging back in to the correct account type
1943  if ($resuming_session && ($secondary_user->id != $resuming_user_id_match)) {
1944  // No go
1945  $this->_logSecurityMsg('system.security.login.secondary.failed', Array('login_name' => $old_user->attr('username'), 'secondary_login_name' => $username, 'failure_reason' => translate('user_switching_access_denied')), TRUE);
1946  return FALSE;
1947  }
1948 
1949  // user found with username and password supplied
1950  unset($_SESSION['user_login_attempts']);
1951 
1952  $user_status = $secondary_user->status;
1953 
1954  // check that the user exists AND that it can login
1955  // (ie. it is not yet live, or has been locked)
1956  if ($secondary_user->canLogin()) {
1957  // Store the details of the invoking user so we can switch back quickly
1958  if ($resuming_session) {
1959  // Alrighty, let's resume the user's previous account and their navigation history
1960  if (isset($invoking_user_nav_history)) {
1961  $_SESSION['sq_nav_history'] = $invoking_user_nav_history;
1962  }
1963  return $this->loginUser($secondary_user);
1964  } else {
1965  /*
1966  * Ok to be perfectly clear, we're NOT allowed to log in as the Root User, and
1967  * System Administrators are not allowed to log in as other System Administrators.
1968  *
1969  * When referring to System Administrators here we are interested in real admins, not the Root User
1970  * which just happens to be a System Administrator too.
1971  */
1972  $old_user_system_admin = (($old_user instanceof System_User) && !($old_user instanceof Root_User));
1973  $secondary_user_system_admin = (($secondary_user instanceof System_User) && !($secondary_user instanceof Root_User));
1974 
1975  $allowed_login = !(($secondary_user instanceof Root_User) || ($old_user_system_admin && $secondary_user_system_admin));
1976 
1977  if ($allowed_login) {
1978  $_SESSION['login_as_invoker_username'] = $invoking_username;
1979  $_SESSION['login_as_invoker_user_full_name'] = $invoking_user_full_name;
1980  $_SESSION['login_as_invoker_id'] = $invoking_user_id;
1981  if (isset($invoking_user_nav_history)) {
1982  $_SESSION['login_as_invoker_sq_nav_history'] = $invoking_user_nav_history;
1983  }
1984 
1985  // Ok all is good - login as the user we are after
1986  return $this->loginUser($secondary_user, $old_user->name);
1987  } else {
1988  $failure_reason = translate('user_switching_access_denied');
1989  $user_status = NULL;
1990  }
1991 
1992  }//end else
1993  }//end if (secondary user can login)
1994 
1995  }//end if (secondary user not null)
1996 
1997  }//end foreach (authentication systems)
1998 
1999  if (!is_null($user_status)) {
2000  if ($user_status & SQ_SC_STATUS_NOT_LIVE) {
2001  $failure_reason = translate('user_status_not_live');
2002  } else {
2003  $failure_reason = translate('user_asset_in_trash');
2004  }
2005  }
2006 
2007  $this->addMessage(translate('user_not_found'));
2008 
2009  // Log failed attempt
2010  $this->_logSecurityMsg('system.security.login.secondary.failed', Array('login_name' => $old_user->attr('username'), 'secondary_login_name' => $username, 'failure_reason' => $failure_reason), TRUE);
2011  }
2012 
2013  return FALSE;
2014 
2015  }//end _loginAsUser()
2016 
2017 
2024  protected function _loginHttpAuthenticatedUser()
2025  {
2026  if (array_get_index($_REQUEST, 'SQ_ACTION')) return;
2027  $http_username = array_get_index($_SERVER, SQ_CONF_HTTP_LOGIN_VAR);
2028  if (is_null($http_username) || ($http_username === '')) {
2029  return;
2030  }
2031 
2032  // get a list of all the installed authentication systems
2033  $auth_folder = $this->am->getSystemAsset('authentication_folder');
2034  if (is_null($auth_folder)) {
2035  trigger_localised_error('SYS0249', E_USER_ERROR);
2036  return;
2037  }
2038  $auth_systems = $auth_folder->getAuthSystems();
2039 
2040  // try each auth system in order to see if we can load a user asset
2041  $user = NULL;
2042  foreach ($auth_systems as $systemid) {
2043  $system = $this->am->getAsset($systemid);
2044  if (is_null($system)) continue;
2045  $user = $system->authenticateHttpUser($http_username);
2046  if (!is_null($user)) return $this->loginUser($user);
2047  }
2048 
2049  }//end _loginHttpAuthenticatedUser()
2050 
2051 
2058  protected function _authenticateHttpUser()
2059  {
2060  if (array_get_index($_REQUEST, 'SQ_ACTION')) return;
2061 
2062  if (!(array_get_index($_SERVER, 'PHP_AUTH_USER') && array_get_index($_SERVER, 'PHP_AUTH_PW'))) {
2063  return;
2064  }
2065  // get a list of all the installed authentication systems
2066  $auth_folder = $this->am->getSystemAsset('authentication_folder');
2067  if (is_null($auth_folder)) {
2068  trigger_localised_error('SYS0249', E_USER_ERROR);
2069  return;
2070  }
2071  $auth_systems = $auth_folder->getAuthSystems();
2072  // try each auth system in order to see if we can load a user asset
2073  $user = NULL;
2074  foreach ($auth_systems as $systemid) {
2075  $system = $this->am->getAsset($systemid);
2076  if (is_null($system)) continue;
2077  $user = $system->authenticateUser($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
2078  if (!is_null($user)) return $this->loginUser($user);
2079  }
2080 
2081  // clear the PHP_AUTH_USER / PASS if authentication failed
2082  unset($_SERVER['PHP_AUTH_USER']);
2083  unset($_SERVER['PHP_AUTH_PW']);
2084 
2085  return;
2086 
2087  }//end _authenticateHttpUser()
2088 
2089 
2101  public function loginUser(User $user, $invoking_user_full_name = '')
2102  {
2103  if (is_null($user)) return FALSE;
2104 
2105  if (!$this->setCurrentUser($user)) return FALSE;
2106 
2107  $old_session_id = session_id();
2108 
2109  // regenerate the session id to protect against session fixation attacks
2110  session_regenerate_id();
2111  // when logging in we wipe the previous session file
2112  $sess_filename = session_save_path().'/sess_'.$old_session_id;
2113  @unlink($sess_filename);
2114  $this->_updateSessionCookie();
2115 
2116  $_SESSION['userid'] = $user->id;
2117  $_SESSION['user_type_code'] = $user->type();
2118  $_SESSION['user'] = serialize($user);
2119  $_SESSION['user_serialised_ts'] = time();
2120  if (!$this->_user_is_public) {
2121  $_SESSION['remote_addr'] = $_SERVER['REMOTE_ADDR'];
2122  }
2123 
2124  // log a message so we know someone has logged in
2125  if (empty($invoking_user_full_name)) {
2126  $this->_logSecurityMsg('system.security.login');
2127  } else {
2128  $this->_logSecurityMsg('system.security.login.secondary', Array('secondary_user_name' => $invoking_user_full_name));
2129  }
2130 
2131  // Get login context if any. If the value returned is numeric, it's
2132  // a context ID. (Otherwise it's something like 'default', ie. don't
2133  // change it.) Save it to the session...
2134  $login_contextid = $this->getUserPrefs('user', 'SQ_USER_SET_CONTEXT_ON_LOGIN', TRUE);
2135 
2136  if (is_numeric($login_contextid) === TRUE) {
2137  $_SESSION[SQ_SESSION_SANDBOX_INDEX]['SQ_ALTERNATE_CONTEXT_ID'] = $login_contextid;
2138  $this->changeContext($login_contextid);
2139  }
2140 
2141  // generate a new login key so that when they try and login next
2142  // they have to re-enter the details
2143  $this->generateLoginKey(TRUE);
2144 
2145  $_SESSION['user_is_root'] = $this->_user_is_root;
2146  $_SESSION['user_is_sys_admin'] = $this->_user_is_sys_admin;
2147  $_SESSION['user_is_public'] = $this->_user_is_public;
2148  $_SESSION['user_last_access'] = time();
2149  $_SESSION['user_last_authentication'] = time();
2150 
2151  // Do pass any event data since we can get it from $_SESSION
2152  $event_data = Array('password' => array_get_index($_POST, 'SQ_LOGIN_PASSWORD'));
2153  $this->broadcastTriggerEvent('trigger_event_user_login', $user, $event_data);
2154 
2155  // check warranty (if a warranty key exists)
2156  $decoded_wkey = base64_decode(SQ_LICENCE_WARRANTY_KEY);
2157  if (SQ_IN_BACKEND && defined('SQ_LICENCE_WARRANTY_KEY') && ($decoded_wkey !== '') && (substr_replace($decoded_wkey, '', 6, 8) !== 'abcdef00000000')) {
2158  $wkey_snippet = substr($decoded_wkey, 0, 6);
2159  $ikey_snippet = substr(SQ_LICENCE_INSTALL_KEY, 0, 6);
2160 
2161  $expiry_date = substr($decoded_wkey, 14);
2162  $current_date = date('Ymd');
2163 
2164  $warranty_expired = ($current_date > $expiry_date) || ($wkey_snippet !== $ikey_snippet);
2165  } else {
2166  // no warranty, so don't show the warranty expiry message
2167  $warranty_expired = FALSE;
2168  }
2169 
2170  if ($this->userSystemAdmin($user) && $warranty_expired) {
2171  $return_url = current_protocol().'://'.current_url(FALSE);
2172 
2173  // We need to handle the 'return to HTTP' stuff here in any case
2174  if (!$_SESSION['user_is_public'] && array_get_index($_SESSION, 'SQ_RETURN_TO_HTTP')) {
2175  unset($_SESSION['SQ_RETURN_TO_HTTP']);
2176  $return_url = 'http://'.current_url(FALSE);
2177  }
2178  $return_url = replace_query_string_vars(Array('SQ_ACTION' => NULL), $return_url);
2179  require_once dirname(__FILE__).'/key_reminder.inc';
2180  exit();
2181  }
2182 
2183  if (!$_SESSION['user_is_public'] && array_get_index($_SESSION, 'SQ_RETURN_TO_HTTP')) {
2184  unset($_SESSION['SQ_RETURN_TO_HTTP']);
2185 
2186  // Strip login suffix if we are "in login" as we have already been authenticated
2187  do_redirect('http://'.current_url(FALSE, SQ_IN_LOGIN)); // exits
2188  }
2189 
2190  if (SQ_IN_LOGIN) {
2191  $current_url = current_url(TRUE, TRUE);
2192  do_redirect($current_url);
2193  }
2194 
2195  return TRUE;
2196 
2197  }//end loginUser()
2198 
2199 
2211  public function setCurrentUser(User $user)
2212  {
2213  if (!($user instanceof User)) {
2214  trigger_localised_error('SYS0171', E_USER_WARNING);
2215  return FALSE;
2216  }
2217 
2218  // they can only become current user if they can login
2219  if (!$user->canSetAsCurrentUser()) return FALSE;
2220 
2221  if (!is_null($this->user)) {
2222  if (!isset($this->_tmp['current_user_cache'])) {
2223  $this->_tmp['current_user_cache'] = Array();
2224  }
2225  array_push($this->_tmp['current_user_cache'], Array($this->user->id, $this->user->type()));
2226  }
2227 
2228  $this->user = &$user;
2229  $this->_user_is_root = $this->userRoot($user);
2230  $this->_user_is_sys_admin = $this->userSystemAdmin($user);
2231  $this->_user_is_public = $this->userPublic($user);
2232 
2233  return TRUE;
2234 
2235  }//end setCurrentUser()
2236 
2237 
2246  public function restoreCurrentUser()
2247  {
2248  if (empty($this->_tmp['current_user_cache'])) {
2249  $user = NULL;
2250  } else {
2251  list($userid, $type_code) = array_pop($this->_tmp['current_user_cache']);
2252  $user = $this->am->getAsset($userid, $type_code);
2253  }
2254 
2255  // They can only become current user if they can login
2256  if (is_null($user) || !$user->canSetAsCurrentUser()) {
2257  $this->user = $this->getPublicUser();
2258  $this->_user_is_root = FALSE;
2259  $this->_user_is_sys_admin = FALSE;
2260  $this->_user_is_public = TRUE;
2261 
2262  } else {
2263  $this->user = &$user;
2264  $this->_user_is_root = $this->userRoot($user);
2265  $this->_user_is_sys_admin = $this->userSystemAdmin($user);
2266  $this->_user_is_public = $this->userPublic($user);
2267  }
2268 
2269  return TRUE;
2270 
2271  }//end restoreCurrentUser()
2272 
2273 
2283  public function getUniqueSessionKey()
2284  {
2285  $key = $_SESSION['userid'].
2286  $_SESSION['user_type_code'];
2287 
2288  if (isset($_SESSION['SQ_LOGIN_KEY'])) {
2289  $key .= $_SESSION['SQ_LOGIN_KEY'];
2290  }
2291 
2292  if (!SQ_CONF_ALLOW_IP_CHANGE) {
2293  $key .= $_SESSION['remote_addr'];
2294  }
2295 
2296  return md5($key);
2297 
2298  }//end getUniqueSessionKey()
2299 
2300 
2310  public function getUniqueSOAPSessionKey()
2311  {
2312  $key = $_SESSION['userid'].
2313  $_SESSION['user_type_code'];
2314 
2315  if (isset($_SESSION['SQ_LOGIN_KEY'])) {
2316  $key .= $_SESSION['SQ_LOGIN_KEY'];
2317  }//end if
2318 
2319  return md5($key);
2320 
2321  }//end getUniqueSOAPSessionKey()
2322 
2323 
2333  public function generateLoginKey($force_new_key=FALSE)
2334  {
2335 
2336  require_once SQ_FUDGE_PATH.'/general/general.inc';
2337 
2338  if ($force_new_key || empty($_SESSION['SQ_LOGIN_KEY'])) {
2339  $_SESSION['SQ_LOGIN_KEY'] = random_password(20);
2340  }
2341 
2342  return $_SESSION['SQ_LOGIN_KEY'];
2343 
2344  }//end generateLoginKey()
2345 
2346 
2353  public function loginKey()
2354  {
2355  return (isset($_SESSION['SQ_LOGIN_KEY'])) ? $_SESSION['SQ_LOGIN_KEY'] : '';
2356 
2357  }//end loginKey()
2358 
2359 
2360  public function getSessionHandlerClassName()
2361  {
2362  if (SQ_CONF_SESSION_HANDLER === '') {
2363  $session_handler = 'Session_Handler_Default';
2364  } else {
2365  $session_handler = 'Session_Handler_'.ucwords(SQ_CONF_SESSION_HANDLER);
2366  }
2367 
2368  try {
2369  $this->am->includeAsset(strtolower($session_handler));
2370  } catch (Exception $e) {
2371  // Throwing a fatal may be a bit heavy-handed, but the alternative
2372  // is to throw a notice, and this breaks when other headers need
2373  // to be sent. Best to make them fix it first
2374  trigger_error('Cannot start MySource user session; session handler \''.SQ_CONF_SESSION_HANDLER.'\' does not exist', E_USER_ERROR);
2375  }
2376 
2377  return $session_handler;
2378 
2379  }//end getSessionHandlerClassName()
2380 
2381 
2390  protected function _updateSessionCookie()
2391  {
2392  $session_id = session_id();
2393  if (empty($session_id)) return FALSE;
2394 
2395  $session_prefs = $this->getUserPrefs('user', 'SQ_USER_SESSION_PREFS', TRUE);
2396  $user_prefs = $this->_findValidSessionExpiry($session_prefs);
2397 
2398  $time_to_live = $user_prefs['persist']; // 1 or 0
2399  if (!empty($time_to_live)) {
2400  $time_to_live = !empty($user_prefs['timeout']) ? ($user_prefs['timeout'] + 3600) : 0; // cookie should last 1 extra hour
2401  if (empty($time_to_live)) {
2402  // there is no max inactive time but we do want to persist across browser sessions...
2403  $time_to_live = 60 * 60 * 24 * 365; // give them a a year
2404  }
2405  }
2406  $session_params = session_get_cookie_params();
2407  $session_name = session_name();
2408  $header = "Set-Cookie: $session_name=$session_id; ";
2409  if (!empty($time_to_live)) {
2410  $header .= 'expires='.substr(date('r', time()+$time_to_live-((int)substr(date('O'), 0, 3)*60*60)), 0, -6).' GMT; ';
2411  }
2412  if (!empty($session_params['domain'])) {
2413  $domain = $session_params['domain'];
2414  if (defined('SQ_CONF_SYSTEM_PARENT_DOMAINS')) {
2415  $parent_domains = explode("\n", SQ_CONF_SYSTEM_PARENT_DOMAINS);
2416  if (is_array($parent_domains)) {
2417  foreach ($parent_domains as $parent_domain) {
2418  $parent_length = strlen($parent_domain);
2419  if ((strlen($domain) >= $parent_length) && (substr($domain, -$parent_length) == $parent_domain)) {
2420  // our current domain is a subdomain of this parent domain, so we will
2421  // actually set the cookie for the parent domain and its children
2422  $domain = '.'.$parent_domain;
2423  $session_params['path'] = '/';
2424  break;
2425  }
2426  }
2427  }
2428  }
2429  $header .= "domain=$domain; ";
2430  }
2431  if (!empty($session_params['path'])) {
2432  $header .= "path=$session_params[path]; ";
2433  }
2434 
2435  if (isset($session_params['httponly']) && $session_params['httponly']) {
2436  $header .= "HttpOnly; ";
2437  }
2438 
2439  if (isset($session_params['secure']) && $session_params['secure']) {
2440  $header .= "secure; ";
2441  }
2442 
2443  header($header);
2444 
2445  }//end _updateSessionCookie()
2446 
2447 
2456  public function _findValidSessionExpiry($session_prefs)
2457  {
2458  $prefs = Array(
2459  'persist' => '0',
2460  'timeout' => '0',
2461  'max_length' => '0',
2462  );
2463  $current_ip = (isset($_SERVER['REMOTE_ADDR'])) ? $_SERVER['REMOTE_ADDR'] : '';
2464  $patt = '/\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/';
2465  if (is_array($session_prefs)) {
2466  foreach ($session_prefs as $id => $sess_pref) {
2467  $temp = Array(
2468  'persist' => '0',
2469  'timeout' => '0',
2470  'max_length' => '0',
2471  );
2472  // Check that the current IP falls between the 'to' and 'from' field (also check an IP was passed)
2473  if (isset($sess_pref['ip_from']) && !empty($sess_pref['ip_from']) && preg_match($patt, $sess_pref['ip_from'])) {
2474  $current_ip_value = ip2long($current_ip);
2475  $ip_from_value = ip2long($sess_pref['ip_from']);
2476  if (($current_ip_value > -1) && ($current_ip_value != FALSE) && ($ip_from_value > -1) && ($ip_from_value != FALSE)) {
2477  if (!empty($current_ip) && ($current_ip == $sess_pref['ip_from'])) {
2478  // Match
2479  $prefs['persist'] = $sess_pref['persist'];
2480  $prefs['timeout'] = $sess_pref['timeout'];
2481  $prefs['max_length'] = $sess_pref['max_length'];
2482  break;
2483  } else if (!empty($current_ip) && ($current_ip_value > $ip_from_value)) {
2484  if (isset($sess_pref['ip_to']) && !empty($sess_pref['ip_to']) && preg_match($patt, $sess_pref['ip_to'])) {
2485  if ((ip2long($sess_pref['ip_to']) > -1) && (ip2long($sess_pref['ip_to']) != FALSE)) {
2486  if ($current_ip_value < ip2long($sess_pref['ip_to'])) {
2487  // Match
2488  $prefs['persist'] = $sess_pref['persist'];
2489  $prefs['timeout'] = $sess_pref['timeout'];
2490  $prefs['max_length'] = $sess_pref['max_length'];
2491  break;
2492  } else {
2493  // No Match
2494  continue;
2495  }//end if
2496  }//end if
2497  } else {
2498  // No Match
2499  continue;
2500  }//end if
2501  } else {
2502  // No Match
2503  continue;
2504  }//end if
2505  }//end if
2506  }//end if
2507 
2508  // No IP Restriction found, so continue
2509  $temp['persist'] = $sess_pref['persist'];
2510  $temp['timeout'] = $sess_pref['timeout'];
2511  $temp['max_length'] = $sess_pref['max_length'];
2512 
2513  // Look for the largest general result
2514  foreach ($temp as $key => $value) {
2515  if ($value > $prefs[$key]) $prefs[$key] = $value;
2516  }//end foreach
2517  }//end foreach
2518  }//end if
2519 
2520  // Return the results
2521  return $prefs;
2522 
2523  }//end _findValidSessionExpiry()
2524 
2525 
2532  protected function _clearSession()
2533  {
2534  foreach (array_keys($_SESSION) as $key) {
2535  if ($key == 'SQ_RETURN_TO_HTTP') continue;
2536  // do not reset the unsuccessful attempt, unset in &authenticateUser
2537  if ($key != 'user_login_attempts') {
2538  unset($_SESSION[$key]);
2539  }
2540  }
2541 
2542  }//end _clearSession()
2543 
2544 
2555  protected function _logSecurityMsg($code, $extra_replacements=Array(), $failed_login=FALSE)
2556  {
2557  if ($this->userPublic($this->user) && !$failed_login) {
2558  return;
2559  }
2560  $ms = $this->getMessagingService();
2561  $msg_reps = Array('user_name' => $this->user->name) + $extra_replacements;
2562  $log = $ms->newMessage(Array(), $code, $msg_reps);
2563  $log->parameters['remote_addr'] = $_SERVER['REMOTE_ADDR'];
2564  $log->parameters['sessionid'] = session_id();
2565  $log->send();
2566 
2567  }//end _logSecurityMsg()
2568 
2569 
2570 //-- INFO ABOUT CURRENT USER --//
2571 
2572 
2581  public function currentUser($user)
2582  {
2583  return ($this->user && $this->user->id == $user->id);
2584 
2585  }//end currentUser()
2586 
2587 
2594  public function currentUserId()
2595  {
2596  if ($this->user) {
2597  return $this->user->id;
2598  } else if (isset($_SESSION['userid'])) {
2599  return $_SESSION['userid'];
2600  } else {
2601  return 0;
2602  }
2603 
2604  }//end currentUserId()
2605 
2606 
2613  public function &getPublicUser()
2614  {
2615  $public_user = $this->am->getSystemAsset('public_user');
2616  return $public_user;
2617 
2618  }//end getPublicUser()
2619 
2620 
2629  public function userRoot($user=NULL)
2630  {
2631  if (is_null($user)) return $this->_user_is_root;
2632  if (!$user->id) return FALSE;
2633  if (get_class($user) == 'Root_User') return TRUE;
2634  return FALSE;
2635 
2636  }//end userRoot()
2637 
2638 
2651  public function userSystemAdmin($user=NULL)
2652  {
2653  if (is_null($user)) return $this->_user_is_sys_admin;
2654  if (!$user->id) return FALSE;
2655  if (!$user->canAccessBackend()) return FALSE;
2656 
2657  // now make sure that the user is in the system administrators group
2658  $sys_admin_group = $this->am->getSystemAsset('system_user_group');
2659  $sys_admins = $this->am->getLinks($sys_admin_group->id, SQ_LINK_TYPE_1);
2660  foreach ($sys_admins as $data) {
2661  if ($data['minorid'] == $user->id) return TRUE;
2662  }
2663  return FALSE;
2664 
2665  }//end userSystemAdmin()
2666 
2667 
2676  public function userPublic($user=NULL)
2677  {
2678  if (is_null($user)) return $this->_user_is_public;
2679  if (!$user->id) return FALSE;
2680  if (get_class($user) == 'Public_User') return TRUE;
2681  return FALSE;
2682 
2683  }//end userPublic()
2684 
2685 
2696  public function getUserPrefs($asset_type=NULL, $pref=NULL, $value_only=TRUE)
2697  {
2698  if (!is_file(SQ_DATA_PATH.'/private/conf/preferences.inc')) {
2699  return FALSE;
2700  }
2701 
2702  static $user_prefs = Array(); // per-script-execution cache
2703  if (empty($this->user->id)) {
2704  include SQ_DATA_PATH.'/private/conf/preferences.inc';
2705  if (is_null($pref)) {
2706  return $preferences;
2707  } else {
2708  if (!isset($preferences[$asset_type]) || !isset($preferences[$asset_type][$pref])) {
2709  return $preferences;
2710  }
2711  $pref_value = $preferences[$asset_type][$pref];
2712  if ($value_only) {
2713  return $pref_value['default'];
2714  } else {
2715  return $pref_value;
2716  }
2717  }
2718  }
2719 
2720  if (!isset($user_prefs[$this->user->id])) {
2721  $groups = $this->user->getGroups();
2722  include SQ_DATA_PATH.'/private/conf/preferences.inc';
2723  $global_prefs = $preferences;
2724  $final_prefs = Array();
2725  foreach ($groups as $groupid => $group_type_code) {
2726  $file_path = SQ_DATA_PATH.'/private/'.asset_data_path_suffix($group_type_code, $groupid).'/.preferences.inc';
2727  if (!is_file($file_path)) continue;
2728  include $file_path;
2729  foreach ($preferences as $type => $type_prefs) {
2730  if (!isset($final_prefs[$type])) {
2731  $final_prefs[$type] = $type_prefs;
2732  } else {
2733  $conflicting_prefs = array_intersect(array_keys($type_prefs), array_keys($final_prefs[$type]));
2734  if (empty($conflicting_prefs)) {
2735  $final_prefs[$type] += $type_prefs;
2736  } else {
2737  // a particular preference within this type has been defined twice, so we need the prefs
2738  // class to decide which value applies
2739  require_once SQ_SYSTEM_ROOT.'/'.$GLOBALS['SQ_SYSTEM']->am->getTypeInfo($type, 'dir').'/'.$type.'_prefs.inc'; // can this be more efficient?
2740  eval('$final_prefs[$type] = '.$type.'_Prefs::mergePrefs($type_prefs, $final_prefs[$type]);');
2741  }
2742  }
2743  if ($value_only) {
2744  foreach ($final_prefs[$type] as $pref_name => $pref_details) {
2745  if (is_array($pref_details) && isset($pref_details['default'])) {
2746  $final_prefs[$type][$pref_name] = $pref_details['default'];
2747  } else {
2748  $final_prefs[$type][$pref_name] = $pref_details;
2749  }
2750  }
2751  }
2752  }
2753  }
2754 
2755  // now final_prefs contains all the group prefs; pad it out with remaining global prefs
2756  foreach ($global_prefs as $type => $type_prefs) {
2757  foreach ($type_prefs as $pref_name => $pref_details) {
2758  if (!isset($final_prefs[$type][$pref_name])) {
2759  $final_prefs[$type][$pref_name] = $value_only ? $pref_details['default'] : $pref_details;
2760  }
2761  }
2762  }
2763 
2764  $user_prefs[$this->user->id] = $final_prefs;
2765  }//end if
2766  if (is_null($asset_type)) {
2767  return $user_prefs[$this->user->id];
2768  } else {
2769  if (is_null($pref) && isset($user_prefs[$this->user->id][$asset_type])) {
2770  return $user_prefs[$this->user->id][$asset_type];
2771  } else if (isset($user_prefs[$this->user->id][$asset_type][$pref])) {
2772  return $user_prefs[$this->user->id][$asset_type][$pref];
2773  } else {
2774  return FALSE;
2775  }
2776  }
2777 
2778  }//end getUserPrefs()
2779 
2780 
2781 //-- CONTEXT SET/GET --//
2782 
2783 
2790  public function getContextId()
2791  {
2792  $current_contextid = NULL;
2793  $stack_height = count($this->_context_stack);
2794  if ($stack_height > 0) {
2795  $current_contextid = $this->_context_stack[$stack_height - 1];
2796  }
2797  if (is_null($current_contextid) === FALSE) {
2798  $current_contextid = (int)$current_contextid;
2799  }
2800  return $current_contextid;
2801 
2802  }//end getContextId()
2803 
2804 
2812  public function changeContext($contextid)
2813  {
2814  array_push($this->_context_stack, $contextid);
2815 
2816  }//end changeContextId()
2817 
2818 
2824  public function restoreContext()
2825  {
2826  if (count($this->_context_stack) <= 0) {
2827  // Cannot restore the context as the context stack is empty
2828  trigger_localised_error('SYS0332', E_USER_ERROR);
2829  } else {
2830  array_pop($this->_context_stack);
2831  }
2832 
2833  }//end restoreContext()
2834 
2835 
2853  public function getAllContexts($force_reload=FALSE)
2854  {
2855  if (((boolean)$force_reload === TRUE) || (is_array($this->_tmp) === FALSE) || (array_key_exists('context_cache', $this->_tmp) === FALSE)) {
2856  $context_data = MatrixDAL::executeGroupedAssoc('core', 'getContexts', Array('contextids' => NULL));
2857  foreach ($context_data as $contextid => &$context_item) {
2858  $context_item = $context_item[0];
2859  if (empty($context_item["conditions"]) === FALSE) {
2860  $context_item['conditions'] = unserialize($context_item['conditions']);
2861  }
2862  }
2863  unset($context_item);
2864  $this->_tmp['context_cache'] = $context_data;
2865  }
2866 
2867  return $this->_tmp['context_cache'];
2868 
2869  }//end getContextInfo()
2870 
2871 
2881  public function insertContextData($contextid)
2882  {
2883  try {
2884  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
2885  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
2886  MatrixDAL::executeQuery('core', 'copyContextAttributeData', Array('contextid' => $contextid));
2887  MatrixDAL::executeQuery('core', 'copyContextUniqueAttributeData', Array('contextid' => $contextid));
2888  MatrixDAL::executeQuery('core', 'copyContextMetadataDefaults', Array('contextid' => $contextid));
2889  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
2890  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2891  } catch (DALException $e) {
2892  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2893  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2894  throw new Exception('Cannot insert default attribute data for context #'.$contextid.'; DB returned error "'.$e->getMessage().'"');
2895  }
2896 
2897  // Broadcast a 'context created' event so that assets can do something
2898  // with it if they want (such as create a new content file to go with it)
2899  // The broadcaster makes no difference since this will be called from a
2900  // config screen - so the currently logged in user will suffice
2901  $em = $this->getEventManager();
2902  $em->broadcastEvent($this->user, 'contextCreate', Array('contextid' => $contextid));
2903 
2904  return TRUE;
2905 
2906  }//end insertContextData()
2907 
2908 
2918  public function deleteContextData($contextid)
2919  {
2920  try {
2921  $GLOBALS['SQ_SYSTEM']->changeDatabaseConnection('db2');
2922  $GLOBALS['SQ_SYSTEM']->doTransaction('BEGIN');
2923  MatrixDAL::executeQuery('core', 'deleteContextAttributeData', Array('contextids' => Array($contextid)));
2924  MatrixDAL::executeQuery('core', 'deleteContextUniqueAttributeData', Array('contextids' => Array($contextid)));
2925  MatrixDAL::executeQuery('core', 'deleteContextMetadataDefaults', Array('contextids' => Array($contextid)));
2926  MatrixDAL::executeQuery('core', 'deleteContextMetadataValue', Array('contextids' => Array($contextid)));
2927  $GLOBALS['SQ_SYSTEM']->doTransaction('COMMIT');
2928  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2929  } catch (DALException $e) {
2930  $GLOBALS['SQ_SYSTEM']->doTransaction('ROLLBACK');
2931  $GLOBALS['SQ_SYSTEM']->restoreDatabaseConnection();
2932  throw new Exception('Cannot delete attribute data for context #'.$contextid.'; DB returned error "'.$e->getMessage().'"');
2933  }
2934 
2935  // Broadcast a 'context deleted' event so that assets can do something
2936  // with it if they want (such as cleaning up content files)
2937  // The broadcaster makes no difference since this will be called from a
2938  // config screen - so the currently logged in user will suffice
2939  $em = $this->getEventManager();
2940  $em->broadcastEvent($this->user, 'contextDelete', Array('contextid' => $contextid));
2941  return TRUE;
2942 
2943  }//end deleteContextData()
2944 
2945 
2956  public function getAlternateContext()
2957  {
2958  // Check first to see whether we have an overriding context name set,
2959  // that hasn't already been swallowed by either set_context or
2960  // set_backend_context global action
2961  $override_context_name = array_get_index($_GET, 'SQ_CONTEXT_NAME', NULL);
2962  if ($override_context_name !== NULL) {
2963  $context_name = (isset($_GET['SQ_CONTEXT_NAME'])) ? $_GET['SQ_CONTEXT_NAME'] : '';
2964  unset($_GET['SQ_CONTEXT_NAME']);
2965  $context_data = MatrixDAL::executeAll('core', 'getContextByName', Array('name' => Array($context_name)));
2966  if (empty($context_data) === FALSE) {
2967  $contextid = $context_data[0]['contextid'];
2968  return $contextid;
2969  }
2970  }
2971 
2972  // Get all contexts, except the default
2973  $contexts = $this->getAllContexts();
2974  unset($contexts[0]);
2975 
2976  // If the system doesn't have any contexts, just return.
2977  if (empty($contexts)) {
2978  $contextid = 0;
2979  return $contextid;
2980  }
2981 
2982  uasort($contexts, create_function('$a,$b', 'return $a["sort_order"] - $b["sort_order"];'));
2983 
2984  // Get the base context ID relating to the current URL
2985  // However, if it's stale and the context has since been deleted,
2986  // use the default context.
2987  $root_url_info = $GLOBALS['SQ_SYSTEM']->am->getRootURL();
2988  if (array_key_exists($root_url_info['base_contextid'], $contexts) === TRUE) {
2989  $use_contextid = $root_url_info['base_contextid'];
2990  } else {
2991  $use_contextid = 0;
2992  }
2993 
2994  foreach ($contexts as $contextid => $context) {
2995  // If set to "all", change to the number of conditions we have
2996  // Then calculate the maximum number of fails we are allowed
2997  if ($context['at_least'] === NULL) {
2998  $context['at_least'] = max(1, count($context['conditions']));
2999  }
3000  $maximum_failures = count($context['conditions']) - $context['at_least'];
3001 
3002  if ($maximum_failures < 0) {
3003  continue;
3004  }
3005 
3006  $total_failures = 0;
3007 
3008  foreach ($context['conditions'] as $condition) {
3009  $condition_type = $condition['type'];
3010  $condition_options = $condition['options'];
3011 
3012  $pass = FALSE;
3013 
3014  switch ($condition_type) {
3015  case 'accept-language':
3016  $language = $condition_options['language'];
3017  $language = str_replace('_', '-', strtolower($language));
3018  $min_q = $condition_options['min_q'];
3019  $header = array_get_index($_SERVER, 'HTTP_ACCEPT_LANGUAGE');
3020 
3021  if ($header !== NULL) {
3022  $header_items = explode(',', $header);
3023  foreach ($header_items as $header_item) {
3024  $item_bits = explode(';', trim($header_item));
3025  if (count($item_bits) === 1) {
3026  $item_bits[1] = 'q=1.0';
3027  }
3028  list($header_language, $header_q_value) = $item_bits;
3029  $header_language = trim($header_language);
3030  $header_q_value = (float)str_replace('q=', '', trim($header_q_value));
3031 
3032  // ??? List of Matrix's languages only handle language and not locale
3033  // (eg. en vs. en-au). Currently handled by taking start of locale
3034  if (strpos($header_language, $language) === 0) {
3035  if ($header_q_value >= $min_q) {
3036  $pass = TRUE;
3037  break;
3038  }
3039  }
3040  }
3041  }
3042  break;
3043 
3044  case 'get':
3045  case 'post':
3046  if ($condition_type == 'get') {
3047  $var_value = array_get_index($_GET, $condition_options['var_name']);
3048  } else {
3049  $var_value = array_get_index($_POST, $condition_options['var_name']);
3050  }
3051 
3052  $matched = FALSE;
3053  $match_type = $condition_options['match_type'];
3054  $match_value = $condition_options['match_value'];
3055  $case_sensitive = array_get_index($condition_options, 'case_sensitive', 1);
3056 
3057  if ($match_type === 'exists') {
3058  $matched = ($var_value === NULL) ? FALSE : TRUE;
3059  } else {
3060  if ($var_value !== NULL) {
3061  if ($case_sensitive === 0) {
3062  $var_value = strtolower($var_value);
3063  $match_value = strtolower($match_value);
3064  }
3065 
3066  switch ($match_type) {
3067  case 'exact':
3068  if ($var_value === $match_value) {
3069  $matched = TRUE;
3070  }
3071  break;
3072 
3073  case 'begins':
3074  if (strpos($var_value, $match_value) === 0) {
3075  $matched = TRUE;
3076  }
3077  break;
3078 
3079  case 'ends':
3080  if (strpos($var_value, $match_value) === strlen($var_value) - strlen($match_value)) {
3081  $matched = TRUE;
3082  }
3083  break;
3084 
3085  case 'contains':
3086  if (strpos($var_value, $match_value) !== FALSE) {
3087  $matched = TRUE;
3088  }
3089  break;
3090  }//end switch on match type
3091 
3092  }//end if header is not null
3093 
3094  }//end if match type is "variable exists"
3095 
3096  if ($condition_options['match_logic'] === 0) {
3097  $pass = !$matched;
3098  } else {
3099  $pass = $matched;
3100  }
3101 
3102  break;
3103 
3104  case 'header':
3105  $server_index = 'HTTP_'.str_replace('-', '_', strtoupper($condition_options['header_name']));
3106  $header_value = array_get_index($_SERVER, $server_index);
3107 
3108  $matched = FALSE;
3109  $match_type = $condition_options['match_type'];
3110  $match_value = $condition_options['match_value'];
3111  $case_sensitive = array_get_index($condition_options, 'case_sensitive', 1);
3112 
3113  if ($match_type === 'exists') {
3114  $matched = ($header_value === NULL) ? FALSE : TRUE;
3115  } else {
3116  if ($header_value !== NULL) {
3117  if ($case_sensitive === 0) {
3118  $header_value = strtolower($header_value);
3119  $match_value = strtolower($match_value);
3120  }
3121 
3122  switch ($match_type) {
3123  case 'exact':
3124  if ($header_value === $match_value) {
3125  $matched = TRUE;
3126  }
3127  break;
3128 
3129  case 'begins':
3130  if (strpos($header_value, $match_value) === 0) {
3131  $matched = TRUE;
3132  }
3133  break;
3134 
3135  case 'ends':
3136  if (strpos($header_value, $match_value) === strlen($header_value) - strlen($match_value)) {
3137  $matched = TRUE;
3138  }
3139  break;
3140 
3141  case 'contains':
3142  if (strpos($header_value, $match_value) !== FALSE) {
3143  $matched = TRUE;
3144  }
3145  break;
3146  }//end switch on match type
3147 
3148  }//end if header is not null
3149 
3150  }//end if match type is "header exists"
3151 
3152  if ($condition_options['match_logic'] === 0) {
3153  $pass = !$matched;
3154  } else {
3155  $pass = $matched;
3156  }
3157 
3158  break;
3159 
3160  case 'url':
3161  $current_url = current_url(FALSE, TRUE);
3162 
3163  $matched = FALSE;
3164  $match_type = $condition_options['match_type'];
3165  $match_value = $condition_options['match_value'];
3166 
3167  switch ($match_type) {
3168  case 'exact':
3169  if ($current_url === $match_value) {
3170  $matched = TRUE;
3171  }
3172  break;
3173 
3174  case 'begins':
3175  if (strpos($current_url, $match_value) === 0) {
3176  $matched = TRUE;
3177  }
3178  break;
3179 
3180  case 'ends':
3181  if (strpos($current_url, $match_value) === strlen($current_url) - strlen($match_value)) {
3182  $matched = TRUE;
3183  }
3184  break;
3185 
3186  case 'contains':
3187  if (strpos($current_url, $match_value) !== FALSE) {
3188  $matched = TRUE;
3189  }
3190  break;
3191  }//end switch on match type
3192 
3193  if ($condition_options['match_logic'] === 0) {
3194  $pass = !$matched;
3195  } else {
3196  $pass = $matched;
3197  }
3198 
3199  break;
3200 
3201  case 'port':
3202  // If we do not have a server port, assume port 80
3203  $current_port = (int)array_get_index($_SERVER, 'SERVER_PORT', 80);
3204  $match_type = $condition_options['match_type'];
3205  $match_value = (int)$condition_options['match_value'];
3206 
3207  if ($match_type === 'is') {
3208  // Secure
3209  $pass = ($current_port === $match_value) ? TRUE : FALSE;
3210  } else {
3211  // Not secure
3212  $pass = ($current_port !== $match_value) ? TRUE : FALSE;
3213  }
3214 
3215  break;
3216 
3217  case 'secure':
3218  $current_protocol = current_protocol();
3219  $match_value = (int)$condition_options['match_value'];
3220 
3221  if ($match_value === 1) {
3222  // Secure
3223  $pass = ($current_protocol === 'https') ? TRUE : FALSE;
3224  } else {
3225  // Not secure
3226  $pass = ($current_protocol === 'http') ? TRUE : FALSE;
3227  }
3228 
3229  break;
3230 
3231  }//end switch on condition type
3232 
3233  if ($pass === FALSE) {
3234  $total_failures++;
3235 
3236  // Exceeded maximum number of failures,
3237  // so stop processing this context
3238  if ($total_failures > $maximum_failures) {
3239  break;
3240  }
3241  }
3242 
3243  }//end foreach condition
3244 
3245  // We survived! This is the alternate context
3246  // we want.
3247  if ($total_failures <= $maximum_failures) {
3248  $use_contextid = $contextid;
3249  break;
3250  }
3251 
3252  }//end foreach context
3253 
3254  return $use_contextid;
3255 
3256  }//end getAlternateContext()
3257 
3258 
3259 //-- GET MANAGERS --//
3260 
3261 
3270  public function &getMessagingService()
3271  {
3272  if (!isset($this->ms) || (get_class($this->ms) != 'Messaging_Service')) {
3273  require_once SQ_INCLUDE_PATH.'/messaging_service.inc';
3274  $this->ms = new Messaging_Service();
3275  }
3276  return $this->ms;
3277 
3278  }//end getMessagingService()
3279 
3280 
3289  public function &getFileVersioning()
3290  {
3291  if (!isset($this->fv) || (get_class($this->fv) != 'File_Versioning')) {
3292  require_once SQ_LIB_PATH.'/file_versioning/file_versioning.inc';
3293  $this->fv = new File_Versioning(SQ_DATA_PATH.'/file_repository');
3294  }
3295  return $this->fv;
3296 
3297  }//end getFileVersioning()
3298 
3299 
3308  public function &getHipoHerder()
3309  {
3310  if (!isset($this->hh) || (get_class($this->hh) != 'HIPO_Herder')) {
3311  require_once SQ_SYSTEM_ROOT.'/core/hipo/hipo_herder.inc';
3312  $this->hh = new HIPO_Herder();
3313  }
3314  return $this->hh;
3315 
3316  }//end getHipoHerder()
3317 
3318 
3327  public function &getMetadataManager()
3328  {
3329  if (!isset($this->mm) || (get_class($this->mm) != 'Metadata_Manager')) {
3330  require_once SQ_INCLUDE_PATH.'/metadata_manager.inc';
3331  $this->mm = new Metadata_Manager();
3332  }
3333  return $this->mm;
3334 
3335  }//end getMetadataManager()
3336 
3337 
3347  {
3348  if (!isset($this->wm) || (get_class($this->wm) != 'Workflow_Manager')) {
3349  require_once SQ_INCLUDE_PATH.'/workflow_manager.inc';
3350  $this->wm = new Workflow_Manager();
3351  }
3352  return $this->wm;
3353 
3354  }//end getWorkflowManager()
3355 
3356 
3365  function &getEventManager()
3366  {
3367  if (!isset($this->em) || (get_class($this->em) != 'Event_Manager')) {
3368  require_once SQ_INCLUDE_PATH.'/event_manager.inc';
3369  $this->em = new Event_Manager();
3370  }
3371  return $this->em;
3372 
3373  }//end getEventManager()
3374 
3375 
3384  function &getTagManager()
3385  {
3386  $am = $this->am;
3387  if ((!isset($this->tag_manager) || (get_class($this->tag_manager) != 'Tag_Manager')) && $am->isSystemAssetType('tag_manager')) {
3388  $this->tag_manager = $am->getSystemAsset('tag_manager');
3389  }
3390  return $this->tag_manager;
3391 
3392  }//end getTagManager()
3393 
3394 
3403  function &getTriggerManager()
3404  {
3405  $am = $this->am;
3406  if ((!isset($this->trigger_manager) || ($am->installed('trigger_manager') && !($this->trigger_manager instanceof Trigger_Manager))) && $am->isSystemAssetType('trigger_manager')) {
3407  $this->trigger_manager = $am->getSystemAsset('trigger_manager');
3408  }
3409 
3410  return $this->trigger_manager;
3411 
3412  }//end getTriggerManager()
3413 
3414 
3421  function &getLogManager()
3422  {
3423  $am = $this->am;
3424  if ((!isset($this->log_manager) || (get_class($this->log_manager) != 'Log_Manager')) && $am->isSystemAssetType('log_manager')) {
3425  $this->log_manager = $am->getSystemAsset('log_manager');
3426  }
3427 
3428  // this is a bypass to allow logging while Log Manager asset has not been installed yet
3429  // necessary during installation, because we want ALL the log entries to be handled by the log manager,
3430  // not only those that appeared after it was installed.
3431  if (is_null($this->log_manager)) {
3432  return new Log_Manager();
3433  }
3434 
3435  return $this->log_manager;
3436 
3437  }//end getLogManager()
3438 
3439 
3448  function getDejaVu()
3449  {
3450  if (!isset($this->_deja_vu) || (get_class($this->_deja_vu) != 'Deja_Vu')) {
3451  require_once SQ_INCLUDE_PATH.'/deja_vu.inc';
3452  $this->_deja_vu = new Deja_Vu();
3453  }
3454 
3455  if ($this->_deja_vu->enabled()) {
3456  return $this->_deja_vu;
3457  } else {
3458  return NULL;
3459  }
3460 
3461  }//end getDejaVu()
3462 
3463 
3464 //-- RUN LEVELS --//
3465 
3466 
3477  function setRunLevel($run_level)
3478  {
3479  $this->_run_level_stack[] = $run_level;
3480 
3481  }//end setRunLevel()
3482 
3483 
3490  function restoreRunLevel()
3491  {
3492  if (empty($this->_run_level_stack)) {
3493  // there is no run level to restore
3494  trigger_localised_error('SYS0110', E_USER_ERROR);
3495  }
3496 
3497  array_pop($this->_run_level_stack);
3498 
3499  }//end restoreRunLevel()
3500 
3501 
3510  function getRunLevel()
3511  {
3512  if (empty($this->_run_level_stack)) return NULL;
3513 
3514  end($this->_run_level_stack);
3515  $current_run_level = current($this->_run_level_stack);
3516  reset($this->_run_level_stack);
3517 
3518  return $current_run_level;
3519 
3520  }//end getRunLevel()
3521 
3522 
3532  function runLevelEnables($security_system)
3533  {
3534  $current_run_level = $this->getRunLevel();
3535  if (is_null($current_run_level)) {
3536  // there is no run level to check
3537  trigger_localised_error('SYS0102', E_USER_ERROR);
3538  }
3539 
3540  return ($security_system & $current_run_level);
3541 
3542  }//end runLevelEnables()
3543 
3544 
3545 //-- DATABASE --//
3546 
3547 
3557  function changeDatabaseConnection($conn, $oci_force_new = FALSE)
3558  {
3559  $dsn = NULL;
3560  $db_conf = require(SQ_DATA_PATH.'/private/conf/db.inc');
3561 
3562  // If attempting to connect to empty dbcache entry
3563  if ($conn === 'dbcache') {
3564  if (empty($db_conf[$conn]) && empty($db_conf['db2'])) {
3565  throw new Exception ('Unable to connect to connection "'.$conn.'". No connection with that name is defined in the database config, and unable to fall back to "db2" connection.');
3566  } else if (empty($db_conf[$conn])) {
3567  // no dbcache, but db2 exists, use that instead
3568  $dsn_array = $db_conf['db2'];
3569  } else {
3570  // dbcache exists
3571  $dsn_array = $db_conf[$conn];
3572  }
3573  } else if ($conn === 'dbsearch') {
3574  if (empty($db_conf[$conn]) && empty($db_conf['db'])) {
3575  throw new Exception ('Unable to connect to connection "'.$conn.'". No connection with that name is defined in the database config, and unable to fall back to "db" connection.');
3576  } else if (empty($db_conf[$conn])) {
3577  // no dbsearch, but db exists, use that instead
3578  $dsn_array = $db_conf['db'];
3579  } else {
3580  // dbsearch exists
3581  $dsn_array = $db_conf[$conn];
3582  }
3583  } else {
3584  // other connection (db, db2, db3)
3585  if (empty($db_conf[$conn])) {
3586  throw new Exception ('Unable to connect to connection "'.$conn.'". No connection with that name is defined in the database config.');
3587  } else {
3588  // When in backend or CLI, replace connections to DB with DB2
3589  // to avoid concurrency issues.
3590  if ($conn === 'db' && (SQ_IN_BACKEND || SQ_IN_LIMBO || SQ_PHP_CLI)) {
3591  $dsn_array = $db_conf['db2'];
3592  } else {
3593  $dsn_array = $db_conf[$conn];
3594  }
3595  }
3596  }
3597 
3598  if (empty($this->_db_conns[$conn])) {
3599  try {
3600  // TODO: parse DSN
3601  $this->_db_conns[$conn] = MatrixDAL::dbConnect($dsn_array, $conn, $oci_force_new);
3602  $db = MatrixDAL::getDb($conn);
3603 
3604  if ($dsn_array['type'] === 'oci') {
3605  // We cannot specify any functions upon connect for OCI8
3606  // functions - we must specify them in functions like
3607  // oci_execute()...
3608  ;
3609  } else {
3610  // Turn off auto-commit, and ensure column names are
3611  // returned in lowercase (important for Oracle)
3612  if ($dsn_array['type'] === 'oci') {
3613  $db->setAttribute(PDO::ATTR_AUTOCOMMIT, FALSE);
3614  }
3615 
3616  // Ensure that no conversion is done to empty strings and NULLs
3617  $db->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL);
3618 
3619  // Return field names in lowercase
3620  $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
3621 
3622  // String-ify all fetches
3623  $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, TRUE);
3624  }
3625 
3626  } catch (Exception $e) {
3627  header('HTTP/1.0 500 Internal Server Error');
3628  throw new Exception('Could not create database connection: '.$e->getMessage());
3629  }
3630  }//end if
3631 
3632  MatrixDAL::changeDb($conn);
3633 
3634  }//end changeDatabaseConnection()
3635 
3636 
3645  function restoreDatabaseConnection($force_reconnect=FALSE)
3646  {
3648  if ($force_reconnect) {
3649  // need to clear this otherwise will not get new db connection
3650  // from DAL
3651  array_pop($this->_db_conns);
3652  }
3653 
3654  }//end restoreDatabaseConnection()
3655 
3656 
3668  function doTransaction($type)
3669  {
3670  if (!isset($this->tm) || get_class($this->tm) != 'Transaction_Manager') {
3671  require_once SQ_INCLUDE_PATH.'/transaction_manager.inc';
3672  $this->tm = new Transaction_Manager();
3673  }
3674  // Use this when there is a transaction problem to create a log and then run this from the cmd line
3675  // and look for the fn with the odd number of calls
3676  // cat trans.log | awk '{ print $4 }' | sort | uniq -c # number of calls in a fn
3677  // cat trans.log | awk '{ print $6 }' | sort | uniq -c # total number of each type of call
3678  // $bt = debug_backtrace();
3679  // if (isset($bt[1])) {
3680  // error_log (MatrixDAL::getCurrentDbId().' TRANS : '.$bt[1]['class'].'::'.$bt[1]['function'].':'.$bt[0]['line'].' -- '.$type."\n", 3, SQ_SYSTEM_ROOT.'/data/private/logs/trans.log');
3681  // } else {
3682  // error_log (MatrixDAL::getCurrentDbId().' TRANS : '.$bt[0]['file'].'::'.$bt[0]['line'].' -- '.$type."\n", 3, SQ_SYSTEM_ROOT.'/data/private/logs/trans.log');
3683  // }
3684 
3685  $current_db = MatrixDAL::getCurrentDbId();
3686 
3687  switch (strtolower($type)) {
3688  case 'begin':
3689  return $this->tm->begin($current_db);
3690  case 'commit':
3691  return $this->tm->commit($current_db);
3692  case 'rollback':
3693  return $this->tm->rollback($current_db);
3694  }
3695 
3696  }//end doTransaction()
3697 
3698 
3699 //-- SYSTEM ROLLBACK --//
3700 
3701 
3710  {
3711  require_once SQ_FUDGE_PATH.'/general/datetime.inc';
3712  $then = strtotime($_SESSION['sq_rollback_view']['rollback_time']);
3713  $time_string = readable_datetime($then).' ('.easy_time_total(time() - $then, TRUE).' ago)';
3714  $warning_message = translate('rollback_mode_warning_message', $time_string);
3715  $exit_url = $_SERVER['PHP_SELF'].'?SQ_ACTION=rollback_view_stop';
3716 
3717  ?>
3718  <div align="center">
3719  <style>
3720  .sq-rollback-warning {
3721  color: #FF0000;
3722  font-family: Verdana, Arial, Helvetica, sans-serif;
3723  font-weight: bold;
3724  text-decoration: none;
3725  font-size: 11px;
3726  }
3727  </style>
3728  <br/>
3729  <table border="0" cellspacing="3" cellpadding="2" width="95%" bgcolor="#FF0000">
3730  <tr>
3731  <td bgcolor="#FFE5E8">
3732  <table border="0" cellspacing="2" cellpadding="2" width="100%">
3733  <tr>
3734  <td class="sq-rollback-warning" align="right"><?php echo translate('exit_rollback_view'); ?><?php sq_print_icon(sq_web_path('lib').'/web/images/icons/delete.png', '16', '16', translate('exit_rollback_view'), NULL, 'onclick="window.top.location=\''.$exit_url.'\'; return false;" style="cursor: pointer;"'); ?>
3735  </td>
3736  </tr>
3737  <tr>
3738  <td class="sq-rollback-warning" align="center"><u><?php echo translate('in_rollback_mode'); ?></u>
3739  </td>
3740  </tr>
3741  <tr>
3742  <td class="sq-rollback-warning"><?php echo $warning_message; ?>
3743  </td>
3744  </tr>
3745  </table>
3746  </td>
3747  </tr>
3748  </table>
3749  <br/>
3750  </div>
3751  <?php
3752 
3753  }//end printRollbackWarning()
3754 
3755 
3774  function constructRollbackWhereClause($where='', $table_alias=NULL, $prefix='WHERE', $is_table_alias=TRUE)
3775  {
3776  // trim off a current prefix keyword if it exists
3777  $where = trim($where);
3778  if (strtoupper(substr($where, 0, strlen($prefix))) == strtoupper($prefix)) {
3779  $where = substr($where, strlen($prefix));
3780  }
3781 
3782  if (!SQ_ROLLBACK_VIEW) {
3783  // we are not in rollback view so we dont need anything special in the where clause
3784  if (empty($where)) {
3785  return '';
3786  } else {
3787  return ' '.$prefix.' '.$where;
3788  }
3789  }
3790 
3791  if (!is_null($table_alias)) {
3792  if ($is_table_alias) $table_alias .= '.';
3793  } else {
3794  $table_alias = '';
3795  }
3796 
3797  $then = $_SESSION['sq_rollback_view']['rollback_time'];
3798 
3799  if (!empty($where)) $where .= ' AND ';
3800  $where .= $table_alias.'sq_eff_from <= \''.$then.'\'
3801  AND ('.$table_alias.'sq_eff_to IS NULL
3802  OR '.$table_alias.'sq_eff_to > \''.$then.'\')';
3803 
3804  return ' '.$prefix.' '.$where;
3805 
3806  }//end constructRollbackWhereClause()
3807 
3808 
3809 //-- LOCKING --//
3810 
3811 
3826  function acquireLock($lockid, $source_lockid='', $expires=0)
3827  {
3828  $class_name = 'locking_method_'.SQ_CONF_LOCKING_METHOD;
3829  $this->am->includeAsset($class_name);
3830 
3831  try {
3832  $ok = eval('return '.$class_name.'::acquireLock($lockid, $source_lockid, $expires);');
3833  } catch (Exception $e) {
3834  return $e->getMessage();
3835  }
3836 
3837  return $ok;
3838 
3839  }//end acquireLock()
3840 
3841 
3850  function releaseLock($lockid)
3851  {
3852  $class_name = 'locking_method_'.SQ_CONF_LOCKING_METHOD;
3853  $this->am->includeAsset($class_name);
3854 
3855  try {
3856  $ok = eval('return '.$class_name.'::releaseLock($lockid);');
3857  } catch (Exception $e) {
3858  return $e->getMessage();
3859  }
3860 
3861  return $ok;
3862 
3863  }//end releaseLock()
3864 
3865 
3880  function updateLock($lockid, $expires=0)
3881  {
3882  $class_name = 'locking_method_'.SQ_CONF_LOCKING_METHOD;
3883  $this->am->includeAsset($class_name);
3884 
3885  try {
3886  $ok = eval('return '.$class_name.'::updateLock($lockid, $expires);');
3887  } catch (Exception $e) {
3888  return $e->getMessage();
3889  }
3890 
3891  return $ok;
3892 
3893  }//end updateLock()
3894 
3895 
3907  function getLockInfo($lockid, $full_chain=FALSE, $check_expires=TRUE, $allow_only_one=TRUE)
3908  {
3909  $class_name = 'locking_method_'.SQ_CONF_LOCKING_METHOD;
3910  $this->am->includeAsset($class_name);
3911 
3912  try {
3913  $lock_info = eval('return '.$class_name.'::getLockInfo($lockid, $full_chain, $check_expires, $allow_only_one);');
3914  } catch (Exception $e) {
3915  return $e->getMessage();
3916  }
3917 
3918  return $lock_info;
3919 
3920  }//end getLockInfo()
3921 
3922 
3923 //-- SYSTEM MESSAGES --//
3924 
3925 
3934  function addMessage($msg)
3935  {
3936  $this->_msgs[] = $msg;
3937 
3938  }//end addMessage()
3939 
3940 
3947  function messages()
3948  {
3949  return (empty($this->_msgs)) ? Array() : $this->_msgs;
3950 
3951  }//end messages()
3952 
3953 
3954 //-- DATE/TIME --//
3955 
3956 
3965  function datetime($timestamp=NULL)
3966  {
3967  return (is_null($timestamp)) ? date('d/m/Y H:i:s') : date('d/m/Y H:i:s', $timestamp);
3968 
3969  }//end datetime()
3970 
3971 
3980  function date($timestamp=NULL)
3981  {
3982  return (is_null($timestamp)) ? date('d/m/Y') : date('d/m/Y', $timestamp);
3983 
3984  }//end date()
3985 
3986 
3995  function time($timestamp=NULL)
3996  {
3997  return (is_null($timestamp)) ? date('H:i:s') : date('H:i:s', $timestamp);
3998 
3999  }//end time()
4000 
4001 
4002 //-- PACKAGES --//
4003 
4004 
4015  function getInstalledPackages()
4016  {
4017  try {
4018  $sql = 'SELECT code_name, version, name, description FROM sq_pkg';
4019  $packages = MatrixDAL::executeSqlAssoc($sql);
4020  } catch (DALException $e) {
4021  throw new Exception ('Could not get installed packages list due to database error: '.$e->getMessage());
4022  }
4023 
4024  return $packages;
4025 
4026  }//end getInstalledPackages()
4027 
4028 
4029 //-- TRIGGERS --//
4030 
4031 
4042  function broadcastTriggerEvent($event_name, &$broadcaster, $parameters=NULL)
4043  {
4044  if (!$GLOBALS['SQ_SYSTEM']->runLevelEnables(SQ_SECURITY_TRIGGERS)) {
4045  return TRUE;
4046  }
4047  $tm = $this->getTriggerManager();
4048  if (!is_null($tm)) {
4049  return $tm->broadcastEvent($event_name, $broadcaster, $parameters);
4050  } else {
4051  return TRUE;
4052  }
4053 
4054  }//end broadcastTriggerEvent()
4055 
4056 
4063  function _getHeaders()
4064  {
4065  if (function_exists('apache_request_headers')) {
4066  // If the apache_request_headers function exists, we are
4067  // in an Apache module
4068  return apache_request_headers();
4069  } else if (isset($_ENV)) {
4070  // Otherwise, we are probably in CGI, where request headers are
4071  // dumped into $_ENV, so return that instead
4072  return $_ENV;
4073  }
4074 
4075  }//end _getHeaders()
4076 
4077 
4091  protected function _paintNotFoundAsset(Asset $asset, Site $site)
4092  {
4093  header('HTTP/1.0 404 Not Found');
4094 
4095  // Send Cacheable Header based on Not Found page asset type
4096  if (SQ_CONF_SEND_CACHEABLE_HEADER && SQ_CONF_SEND_404_CACHEABLE_HEADER) {
4097  $cm = $GLOBALS['SQ_SYSTEM']->am->getSystemAsset('cache_manager');
4098  header('Pragma: cache');
4099 
4100  $browser_cache_expiry = $cm->getBrowserCacheExpiry($asset->type(), $asset->id);
4101  if (empty($browser_cache_expiry)) {
4102  $browser_cache_expiry = $cm->getExpiry($asset->type(), $asset->id);
4103  }
4104 
4105  header('Cache-Control: max-age='.$browser_cache_expiry.', '.$cm->cacheControlLevel());
4106  header('Expires: '.gmdate('D, d M Y H:i:s', time() + $browser_cache_expiry).' GMT');
4107  }
4108 
4109  $old_current_asset = $GLOBALS['SQ_SYSTEM']->getGlobalDefine('CURRENT_ASSET', NULL);
4110  $GLOBALS['SQ_SYSTEM']->setGlobalDefine('CURRENT_ASSET', $asset);
4111 
4112  // If "Not found" page has overriding design and/or paint layout
4113  $override_design = $site->getSpecialPage('not_found_design');
4114  $override_layout = $site->getSpecialPage('not_found_layout');
4115  ob_start();
4116  $site->paintAsset($asset, $override_design, $override_layout);
4117  $content = ob_get_clean();
4118 
4119  $this->replaceKeyword($content);
4120 
4121  if (is_null($old_current_asset)) {
4122  $GLOBALS['SQ_SYSTEM']->unsetGlobalDefine('CURRENT_ASSET');
4123  } else {
4124  $GLOBALS['SQ_SYSTEM']->setGlobalDefine('CURRENT_ASSET', $old_current_asset);
4125  }//end if
4126 
4127  echo $content;
4128 
4129  }//end _paintNotFoundAsset()
4130 
4131 
4144  function authRedirect()
4145  {
4146  $db = $GLOBALS['SQ_SYSTEM']->db;
4147 
4148  $current_url = current_url(FALSE, FALSE);
4149  $current_protocol = current_protocol();
4150  $root_url = $GLOBALS['SQ_SYSTEM']->am->getRootURL();
4151 
4152  $bind_vars = Array(
4153  'urlid' => $root_url['urlid'],
4154  );
4155  $result = MatrixDAL::executeAll('core', 'getAuthRedirect', $bind_vars);
4156 
4157  if (empty($result)) return FALSE;
4158  $new_root_url = $result[0];
4159  unset($result);
4160 
4161  // If the protocol is valid, then use the same protocol, otherwise
4162  // use the one that is available
4163  if ($new_root_url[$current_protocol]) {
4164  $protocol = $current_protocol;
4165  } else {
4166  // If current protocol not valid, then only one must be selected...
4167  // and it's the wrong one
4168  $protocol = $new_root_url['https' ] ? 'https' : 'http';
4169  }
4170 
4171  // build the new URL, and redirect - this exits the script
4172  $url = $protocol.'://'.preg_replace('|^'.addslashes($root_url['url']).'|', $new_root_url['url'], $current_url);
4173 
4174  // Before redirecting, blanking out the public user, see ya on the flip side!
4175  if ($this->userPublic($this->user)) {
4176  $_SESSION['user'] = NULL;
4177  $_SESSION['userid'] = NULL;
4178  $_SESSION['user_type_code'] = NULL;
4179  }
4180 
4181  do_redirect($url);
4182 
4183  // Fall-through that should never be executed
4184  return FALSE;
4185 
4186  }//end authRedirect()
4187 
4188 
4197  public function _generateAttributeImage($text)
4198  {
4199  // Default settings
4200  putenv('GDFONTPATH=' . realpath(SQ_LIB_PATH.'/fonts'));
4201  $default_fonts = Array( 1,2,3,4,5 );
4202  $backup_font = 3;
4203 
4204  // Load the user prefs
4205  $image_prefs = $this->getUserPrefs('user', 'SQ_USER_ATTRIBUTE_IMAGE', TRUE);
4206  $font = $image_prefs['face'];
4207  $size = $image_prefs['size'];
4208  $hpad = $image_prefs['hpad'];
4209  $vpad = $image_prefs['vpad'];
4210  $colour1 = ltrim($image_prefs['bgcol'], '#');
4211  $colour2 = ltrim($image_prefs['fgcol'], '#');
4212 
4213  // Calculate the sizes etc.
4214  $width = ((in_array($font, $default_fonts)) ? (imagefontwidth($font) * strlen($text)) : ($size * strlen($text))) + ($hpad * 2);
4215  $height = ((in_array($font, $default_fonts)) ? imagefontheight($font) : ($size + $size/2)) + ($vpad * 2);
4216  $hh = $height / 2;
4217  $x = 0 + $hpad;
4218  $y = (in_array($font, $default_fonts)) ? (0 + $vpad) : (round($height - ($size / 2), 0) - $vpad);
4219 
4220  // Create the image
4221  if (!$image = imagecreatetruecolor($width, $height)) {
4222  trigger_error('Cannot Initialize new GD image stream', E_USER_WARNING);
4223  exit();
4224  }
4225 
4226  // Work out the colours
4227  list($r1,$g1,$b1) = str_split($colour1, 2);
4228  list($r2,$g2,$b2) = str_split($colour2, 2);
4229  $bg_colour = imagecolorallocate($image, hexdec($r1), hexdec($g1), hexdec($b1));
4230  $fg_colour = imagecolorallocate($image, hexdec($r2), hexdec($g2), hexdec($b2));
4231 
4232  // Colour the background
4233  imagefill($image, 0,0, $bg_colour);
4234 
4235  // Write the text on the image
4236  if (in_array($font, $default_fonts)) {
4237  $text_created = imagestring($image, $font, $x, $y, $text, $fg_colour);
4238  } else {
4239  $text_created = @imagettftext($image, $size, 0, $x, $y, $fg_colour, $font, $text);
4240  }//end if
4241 
4242  // Getting image sizes correct the first time, is kind of tricky
4243  // So creating the image putting the text on it, then resizing the image, seems to work
4244  if ($text_created === FALSE) {
4245  // An error occured with drawing the text, try using the default font
4246  $width = (imagefontwidth * strlen($text)) + ($hpad * 2);
4247  $height = imagefontheight + ($vpad * 2);
4248  if (!$image = imagecreatetruecolor($width, $height)) {
4249  trigger_error('Cannot Initialize new GD image stream', E_USER_WARNING);
4250  exit();
4251  }
4252  imagefill($image, 0,0, $bg_colour);
4253  $text_created = imagestring($image, $backup_font, $x, $y, $text, $fg_colour);
4254  } else if (isset($text_created[2]) && !empty($text_created[2]) && $text_created[2] != $width) {
4255  $width = $text_created[2]+1;
4256  $height = $text_created[3];
4257  if (!$image = imagecreatetruecolor($width, $height)) {
4258  trigger_error('Cannot Initialize new GD image stream', E_USER_WARNING);
4259  exit();
4260  }
4261  $bg_colour = imagecolorallocate($image, hexdec($r1), hexdec($g1), hexdec($b1));
4262  $fg_colour = imagecolorallocate($image, hexdec($r2), hexdec($g2), hexdec($b2));
4263  imagefill($image, 0,0, $bg_colour);
4264  $text_created = @imagettftext($image, $size, 0, $x, $y, $fg_colour, $font, $text);
4265  }//end if
4266  header('Content-type: image/png');
4267  imagepng($image);
4268 
4269  }//end _generateAttributeImage()
4270 
4271 
4280  function _translateMatrixURL(&$content)
4281  {
4282  // Get all the matrix ./a=xx links in the content
4283  preg_match_all('!<[^>]*'.'(?:(?:href)|(?:src))\s*=\s*'.'(?:"|\')'.'\./\?a=([0-9]+(?:\:[0-9a-z]+\$?)?)'.'[^:"\']*?'.'(?:"|\')'.'[^>]*>!msi', $content, $matches);
4284  if (empty($matches[1])) return;
4285 
4286  foreach($matches[1] as $key => $assetid) {
4287  $matches[1][$key] = rtrim($assetid, '$');
4288  }
4289  $assets_url = $GLOBALS['SQ_SYSTEM']->am->getAssetURL($matches[1]);
4290 
4291  // Replace each ./a=xx link by its respective full URL
4292  foreach($matches[1] as $index => $assetid) {
4293  if (isset($assets_url[$assetid]) && !empty($assets_url[$assetid])) {
4294  $replacement = preg_replace('!\./\?a=[0-9]+(?:\:[0-9a-z]+\$?)?!i', str_replace('$', '\$', $assets_url[$assetid]), $matches[0][$index]);
4295  $content = str_replace($matches[0][$index], $replacement, $content);
4296  }
4297  }
4298  }//end _translateMatrixURL()
4299 
4300 
4309  function _translatePerformanceModeURL(&$content)
4310  {
4311  if(!defined('SQ_IN_PERFORMANCE_TIMING')) return;
4312 
4313  // Get all links in the content
4314  preg_match_all('/<a\s[^>]*href=(\"??)([^\" >]*?)\\1[^>]*>(.*)<\/a>/siU', $content, $matches);
4315 
4316  // Replace hyper links with _performance style url
4317  foreach($matches[0] as $index => $url) {
4318  $src = $matches[2][$index];
4319  if(empty($src)) continue;
4320 
4321  // remove trailing slash
4322  if(substr($src, -1) == '/') {
4323  $src = rtrim($src, '/');
4324  }
4325  // add _performance suffix
4326  if(strpos($src, '?') !== FALSE) {
4327  $src = preg_replace('/\?/', '/'.SQ_CONF_PERFORMANCE_SUFFIX.'/?', $src);
4328  }
4329  else {
4330  $src = $src.'/'.SQ_CONF_PERFORMANCE_SUFFIX;
4331  }
4332 
4333  // only replace the first link found
4334  $pos = strpos($url, $matches[2][$index]);
4335  if($pos !== FALSE)
4336  $new_url = substr_replace($url, $src, $pos, strlen($matches[2][$index]));
4337 
4338  // remove target attribute
4339  $new_url = preg_replace('/target[ ]*=[ ]*[^a-zA-Z_"\']*/i', '', $new_url);
4340 
4341  // add new target attribute
4342  $new_url = preg_replace('/^<a/i', '<a target="_top"', $new_url);
4343 
4344  $content = str_replace($url, $new_url, $content);
4345  }
4346 
4347 
4348  // Get all forms in the content
4349  preg_match_all('/<form\s[^>]*action=(\"??)([^\" >]*?)\\1[^>]*>/siU', $content, $matches);
4350 
4351  // Add onsubmit event for all forms. We have to refresh the performance result frame in this way(using js addEvent could cause too much issues)
4352  foreach($matches[0] as $index => $url) {
4353  $new_url = $url;
4354  if(preg_match('/onsubmit/i', $new_url)) {
4355  // existing submit attribute
4356  $new_url = preg_replace('/onsubmit[ ]*=[ ]*["\']+([^"\']*)["\']+/i', 'onsubmit="parent.result_frame.document.location.reload(); \\1"', $new_url);
4357  }
4358  else {
4359  // add new submit event
4360  $new_url = preg_replace('/^<form/i', '<form onsubmit="parent.result_frame.document.location.reload();"', $new_url);
4361  }
4362 
4363 
4364  $content = str_replace($url, $new_url, $content);
4365  }
4366 
4367  }//end _translatePerformanceModeURL()
4368 
4369 
4379  private function _processUserIP()
4380  {
4381  if(!defined('SQ_CONF_USE_HTTP_X_FORWARDED_FOR') || !defined('SQ_CONF_FORWARDED_IP_PROXY_LIST')) return;
4382  if (SQ_CONF_USE_HTTP_X_FORWARDED_FOR && SQ_CONF_FORWARDED_IP_PROXY_LIST != ''){
4383  $safe_list = explode("\n", SQ_CONF_FORWARDED_IP_PROXY_LIST);
4384  if (array_key_exists('REMOTE_ADDR', $_SERVER) && in_array($_SERVER['REMOTE_ADDR'], $safe_list)) {
4385  // if we have an X-Forwarded-For set
4386  if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
4387  $forwards = explode(', ', $_SERVER['HTTP_X_FORWARDED_FOR']);
4388  // right most is more recent $forwards[$n]
4389  // starting from right, find the first IP which is not in SQ_CONF_FORWARDED_IP_PROXY_LIST
4390  for ($n=count($forwards)-1; $n > 0; $n--) {
4391  if (!in_array($forwards[$n], $safe_list)) {
4392  break;
4393  }
4394  }
4395  $last_forward = trim($forwards[$n]);
4396  //change the remote addr
4397  $_SERVER['REMOTE_ADDR'] = $last_forward;
4398  }
4399  }
4400  }
4401 
4402  }//end _processUserIP()
4403 
4404 
4405 }//end class
4406 ?>