1
0
mirror of https://github.com/e107inc/e107.git synced 2025-01-16 20:28:28 +01:00
php-e107/e107_handlers/application.php
2024-06-27 17:07:41 -07:00

5387 lines
128 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*
* e107 website system
*
* Copyright (C) 2008-2012 e107 Inc (e107.org)
* Released under the terms and conditions of the
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
*
*/
/**
* Class e_url
* New v2.1.6
*/
class e_url
{
private static $_instance;
private $_request = null;
private $_config = array();
private $_include = null;
private $_rootnamespace = null;
private $_alias = array();
private $_legacy = array();
private $_legacyAliases = array();
/**
* e_url constructor.
*/
function __construct()
{
$this->_request = (e_HTTP === '/') ? ltrim(e_REQUEST_URI,'/') : str_replace(e_HTTP,'', e_REQUEST_URI) ;
$this->_config = e107::getUrlConfig();
$this->_alias = e107::getPref('e_url_alias');
$this->_rootnamespace = e107::getPref('url_main_module');
$this->_legacy = e107::getPref('url_config');
$this->_legacyAliases = e107::getPref('url_aliases');
$this->setRootNamespace();
}
/**
* Detect older e_url system.
* @return bool
*/
private function isLegacy()
{
$arr = (!empty($this->_legacyAliases[e_LAN])) ? array_merge($this->_legacy,$this->_legacyAliases[e_LAN]) : $this->_legacy;
$list = array_keys($arr);
foreach($list as $leg)
{
if(strpos($this->_request,$leg.'/') === 0 || $this->_request === $leg)
{
return true;
}
}
return false;
}
/**
* @return string|null
*/
public function getInclude()
{
return $this->_include;
}
/**
* @return void
*/
private function setRootNamespace()
{
$plugs = array_keys($this->_config);
if(!empty($this->_rootnamespace) && in_array($this->_rootnamespace,$plugs)) // Move rootnamespace check to the end of the list.
{
$v = $this->_config[$this->_rootnamespace];
unset($this->_config[$this->_rootnamespace]);
$this->_config[$this->_rootnamespace] = $v;
}
}
/**
* @return bool|void
*/
public function run()
{
$pref = e107::getPref();
$tp = e107::getParser();
if(empty($this->_request)) // likely 'index.php' ie. the home page.
{
$this->_request = e107::getFrontPage();
e107::canonical('_SITEURL_');
}
if(empty($this->_config) /*|| empty($this->_request) || $this->_request === 'index.php'*/ || $this->isLegacy() === true)
{
return false;
}
$replaceAlias = array('{alias}\/?','{alias}/?','{alias}\/','{alias}/',);
foreach($this->_config as $plug=>$cfg)
{
if(empty($pref['e_url_list'][$plug])) // disabled.
{
e107::getDebug()->log('e_URL for <b>'.$plug.'</b> is disabled.');
continue;
}
foreach($cfg as $k=>$v)
{
if(empty($v['regex']))
{
// e107::getMessage()->addDebug("Skipping empty regex: <b>".$k."</b>");
continue;
}
if((empty($v['domain']) && (empty($this->_request) || $this->_request === 'index.php')) || (isset($v['domain']) && ($v['domain'] !== $_SERVER['HTTP_HOST'])))
{
continue;
}
if(!empty($v['alias']))
{
$alias = (!empty($this->_alias[e_LAN][$plug][$k])) ? $this->_alias[e_LAN][$plug][$k] : $v['alias'];
// e107::getMessage()->addDebug("e_url alias found: <b>".$alias."</b>");
if(!empty($this->_rootnamespace) && $this->_rootnamespace === $plug)
{
$v['regex'] = str_replace($replaceAlias, '', $v['regex']);
}
else
{
$v['regex'] = str_replace('{alias}', $alias, $v['regex']);
}
}
$regex = '#'.$v['regex'].'#';
if(empty($v['redirect']))
{
continue;
}
$newLocation = preg_replace($regex, $v['redirect'], $this->_request);
if($newLocation != $this->_request)
{
$redirect = e107::getParser()->replaceConstants($newLocation);
list($file,$query) = explode("?", $redirect,2);
$get = array();
if(!empty($query))
{
// issue #3171 fix double ampersand in case of wrong query definition
$query = str_replace('&&', '&', $query);
parse_str($query,$get);
}
foreach($get as $gk=>$gv)
{
$_GET[$gk] = $gv;
}
e107::getDebug()->log('e_URL in <b>'.$plug.'</b> with key: <b>'.$k.'</b> matched <b>'.$v['regex'].'</b> and included: <b>'.$file.'</b> with $_GET: '.print_a($_GET,true),1);
if(file_exists($file))
{
define('e_CURRENT_PLUGIN', $plug);
define('e_QUERY', str_replace('&&', '&', $query)); // do not add to e107_class.php
define('e_URL_LEGACY', $redirect);
if(!defined('e_PAGE'))
{
define('e_PAGE', basename($file));
}
if(!e107::route()) // subject to removal at any time.
{
e107::route($plug.'/'.$k);
}
$fpUrl = str_replace(SITEURL, '', rtrim(e_REQUEST_URL, '?/'));
$fpPref = e107::getFrontpage();
if($fpUrl === $fpPref)
{
}
unset($fpUrl, $fpPref);
$this->_include= $file;
return true;
// exit;
}
elseif(getperms('0'))
{
echo "<div class='alert alert-warning'>";
echo "<h3>SEF Debug Info</h3>";
echo "File missing: ".$file;
echo "<br />Matched key: <b>".$k."</b>";
print_a($v);
echo "</div>";
}
}
}
}
}
/**
* Singleton implementation
* @return e_url
*/
public static function instance()
{
if(null == self::$_instance)
{
self::$_instance = new self();
}
return self::$_instance;
}
}
/**
* e107 Front controller
*/
class eFront
{
/**
* Singleton instance
* @var eFront
*/
private static $_instance;
/**
* @var eDispatcher
*/
protected $_dispatcher;
/**
* @var eRequest
*/
protected $_request;
/**
* @var eRouter
*/
protected $_router;
protected $_response;
/**
* @var string path to file to include - the old deprecated way of delivering content
*/
protected static $_legacy = '';
/**
* Constructor
*/
private function __construct()
{
}
/**
* Cloning not allowed
*
*/
private function __clone()
{
}
/**
* Singleton implementation
* @return eFront
*/
public static function instance()
{
if(null == self::$_instance)
{
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Dispatch
*/
public function dispatch(eRequest $request = null, eResponse $response = null, eDispatcher $dispatcher = null)
{
if(null === $request)
{
if(null === $this->getRequest())
{
$request = new eRequest();
$this->setRequest($request);
}
else $request = $this->getRequest();
}
elseif(null === $this->getRequest()) $this->setRequest($request);
if(null === $response)
{
if(null === $this->getResponse())
{
$response = new eResponse();
$this->setResponse($response);
}
else $response = $this->getResponse();
}
elseif(null === $this->getRequest()) $this->setRequest($request);
if(null === $dispatcher)
{
if(null === $this->getDispatcher())
{
$dispatcher = new eDispatcher();
$this->setDispatcher($dispatcher);
}
else $dispatcher = $this->getDispatcher();
}
elseif(null === $this->getDispatcher()) $this->setDispatcher($dispatcher);
// set dispatched status true, required for checkLegacy()
$request->setDispatched(true);
$router = $this->getRouter();
// If current request not already routed outside the dispatch method, route it
if(!$request->routed) $router->route($request);
$c = 0;
// dispatch loop
do
{
$c++;
if($c > 100)
{
throw new eException("Too much dispatch loops", 1);
}
// dispatched status true on first loop
$router->checkLegacy($request);
// dispatched by default - don't allow legacy to alter dispatch status
$request->setDispatched(true);
// legacy mod - return control to the bootstrap
if(self::isLegacy())
{
return;
}
// for the good players - dispatch loop - no more BC!
try
{
$dispatcher->dispatch($request, $response);
}
catch(eException $e)
{
echo $request->getRoute().' - '.$e->getMessage();
exit;
}
} while (!$request->isDispatched());
}
/**
* Init all objects required for request dispatching
* @return eFront
*/
public function init()
{
$request = new eRequest();
$this->setRequest($request);
$dispatcher = new eDispatcher();
$this->setDispatcher($dispatcher);
$router = new eRouter();
$this->setRouter($router);
/** @var eResponse $response */
$response = e107::getSingleton('eResponse');
$this->setResponse($response);
return $this;
}
/**
* Dispatch
* @param string|eRequest $route
*/
public function run($route = null)
{
if($route)
{
if(is_object($route) && ($route instanceof eRequest)) $this->setRequest($route);
elseif(null !== $route && null !== $this->getRequest()) $this->getRequest()->setRoute($route);
}
try
{
$this->dispatch();
}
catch(eException $e)
{
echo $e->getMessage();
exit;
}
}
/**
* Application instance (e107 class)
* @return e107
*/
public static function app()
{
return e107::getInstance();
}
/**
* Get dispatcher instance
* @return eDispatcher
*/
public function getDispatcher()
{
return $this->_dispatcher;
}
/**
* Set dispatcher
* @param eDispatcher $dispatcher
* @return eFront
*/
public function setDispatcher(eDispatcher $dispatcher)
{
$this->_dispatcher = $dispatcher;
return $this;
}
/**
* Get request instance
* @return eRequest
*/
public function getRequest()
{
return $this->_request;
}
/**
* Set request
* @param eRequest $request
* @return eFront
*/
public function setRequest(eRequest $request)
{
$this->_request = $request;
return $this;
}
/**
* Get response instance
* @return eResponse
*/
public function getResponse()
{
return $this->_response;
}
/**
* Set response
* @param eResponse $response
* @return eFront
*/
public function setResponse(eResponse $response)
{
$this->_response = $response;
return $this;
}
/**
* Get router instance
* @return eRouter
*/
public function getRouter()
{
return $this->_router;
}
/**
* Set router instance
* @return eFront
*/
public function setRouter(eRouter $router)
{
$this->_router = $router;
return $this;
}
/**
* Set/get legacy status of the current request
* @param boolean $status
* @return boolean
*/
public static function isLegacy($status = null)
{
if(null !== $status)
{
if(!empty($status[0]) && ($status[0] === '{'))
{
$status = e107::getParser()->replaceConstants($status);
}
self::$_legacy = $status;
}
return self::$_legacy;
}
}
/**
* e107 Dispatcher
* It decides how to dispatch the request.
*/
class eDispatcher
{
protected static $_configObjects = array();
/**
* @param eRequest|null $request
* @param eResponse|null $response
* @return void
* @throws eException
*/
public function dispatch(eRequest $request = null, eResponse $response = null)
{
$controllerName = $request->getControllerName();
$moduleName = $request->getModuleName();
$className = $this->isDispatchable($request, false);
// dispatch based on rule settings
if(!$className)
{
if($controllerName == 'index') // v2.x upgrade has not been run yet.
{
e107::getRedirect()->redirect(e_ADMIN."admin.php");
}
throw new eException("Invalid controller '".$controllerName."'");
}
$controller = new $className($request, $response);
if(!($controller instanceof eController))
{
throw new eException("Controller $controller is not an instance of eController");
}
$actionName = $request->getActionMethodName();
ob_start();
$controller->dispatch($actionName);
$content = ob_get_clean();
$response->appendBody($content);
unset($controller);
}
/**
* Get path to the e_url handler
* @param string $module
* @param string $location plugin|core|override
* @param boolean $sc
* @return string path
*/
public static function getConfigPath($module, $location, $sc = false)
{
$tmp = explode('/', $location);
$custom = '';
$location = $tmp[0];
if(isset($tmp[1]) && !empty($tmp[1]))
{
$custom = $tmp[1].'_';
}
unset($tmp);
if($module !== '*') $module .= '/';
switch ($location)
{
case 'plugin':
//if($custom) $custom = 'url/'.$custom;
if(!defined('e_CURRENT_PLUGIN'))
{
define('e_CURRENT_PLUGIN', rtrim($module,'/')); // TODO Move to a better location.
}
return $sc ? '{e_PLUGIN}'.$module.'url/'.$custom.'url.php' : e_PLUGIN.$module.'url/'.$custom.'url.php';
break;
case 'core':
if($module === '*') return $sc ? '{e_CORE}url/' : e_CORE.'url/';
return $sc ? '{e_CORE}url/'.$module.$custom.'url.php' : e_CORE.'url/'.$module.$custom.'url.php';
break;
case 'override':
if($module === '*') return $sc ? '{e_CORE}override/url/' : e_CORE.'override/url/' ;
return $sc ? '{e_CORE}override/url/'.$module.$custom.'url.php' : e_CORE.'override/url/'.$module.$custom.'url.php' ;
break;
default:
return null;
break;
}
}
/**
* Get path to url configuration subfolders
* @param string $module
* @param string $location plugin|core|override
* @param boolean $sc
* @return string path
*/
public static function getConfigLocationPath($module, $location, $sc = false)
{
switch ($location)
{
case 'plugin':
return $sc ? '{e_PLUGIN}'.$module.'/url/' : e_PLUGIN.$module.'/url/';
break;
case 'core':
return $sc ? '{e_CORE}url/'.$module.'/' : e_CORE.'url/'.$module.'/';
break;
case 'override':
return $sc ? '{e_CORE}override/url/'.$module.'/' : e_CORE.'override/url/'.$module.'/';
break;
default:
return null;
break;
}
}
/**
* Get dispatch system path
* @param string $location plugin|core|override
* @param string $plugin required only when $location is plugin
* @param boolean $sc
* @return string path
*/
public static function getDispatchLocationPath($location, $plugin = null, $sc = false)
{
switch ($location)
{
case 'plugin':
if(!$plugin) return null;
return $sc ? '{e_PLUGIN}'.$plugin.'/controllers/' : e_PLUGIN.$plugin.'/controllers/';
break;
case 'core':
return $sc ? '{e_CORE}controllers/' : e_CORE.'controllers/';
break;
case 'override':
return $sc ? '{e_CORE}override/controllers/' : e_CORE.'override/controllers/';
break;
default:
return null;
break;
}
}
/**
* Get full dispatch system path
* @param string $module
* @param string $location plugin|core|override
* @param boolean $sc
* @return string path
*/
public static function getDispatchPath($module, $location, $sc = false)
{
switch ($location)
{
case 'plugin':
return $sc ? '{e_PLUGIN}'.$module.'/controllers/' : e_PLUGIN.$module.'/controllers/';
break;
case 'core':
return $sc ? '{e_CORE}controllers/'.$module.'/' : e_CORE.'controllers/'.$module.'/';
break;
case 'override':
return $sc ? '{e_CORE}override/controllers/'.$module.'/' : e_CORE.'override/controllers/'.$module.'/';
break;
default:
return null;
break;
}
}
/**
* Get include path to a given module/controller
*
* @param string $module valid module name
* @param string $controller controller name
* @param string $location core|plugin|override
* @param boolean $sc return relative (false) OR shortcode (true) path
* @return string controller path
*/
public static function getControllerPath($module, $controller, $location = null, $sc = false)
{
if(null === $location) $location = self::getDispatchLocation($module);
return ($location ? self::getDispatchPath($module, $location, $sc).$controller.'.php': null);
}
/**
* Get class name of a given module/controller
*
* @param string $module valid module name
* @param string $controllerName controller name
* @param string $location core|plugin|override
* @return string controller path
*/
public static function getControllerClass($module, $controllerName, $location = null)
{
if(null === $location) $location = self::getDispatchLocation($module);
return ($location ? $location.'_'.$module.'_'.$controllerName.'_controller' : null);
}
/**
* Get controller object
*
* @param eRequest $request
* @param boolean $checkOverride whether to check the override location
* @return eController null if not dispatchable
*/
public function getController(eRequest $request, $checkOverride = true)
{
$class_name = $this->isDispatchable($request, true, $checkOverride);
if(!$class_name) return null;
return new $class_name();
}
/**
* Check if given module/controller is dispatchable
* @param string $module valid module name
* @param string $controllerName controller name
* @param string $location core|plugin|override
* @param boolean $checkOverride whether to check the override location
* @return string class name OR false if not dispatchable
*/
public function isDispatchableModule($module, $controllerName, $location, $checkOverride = false)
{
if($checkOverride || $location == 'override')
{
$path = self::getControllerPath($module, $controllerName, 'override', false);
$class_name = self::getControllerClass($module, $controllerName, 'override');
if($class_name && !class_exists($class_name, false) && is_readable($path)) include_once($path);
if($class_name && class_exists($class_name, false)) return $class_name;
}
// fallback to original dispatch location if any
if($location === 'override')
{
// check for real location
if(($location = eDispatcher::getModuleRealLocation($module)) === null) return false;
}
if($location !== 'override')
{
$path = self::getControllerPath($module, $controllerName, $location, false);
$class_name = self::getControllerClass($module, $controllerName, $location);
if(!$class_name) return false;
if(!class_exists($class_name, false) && is_readable($path)) include_once($path);
if(class_exists($class_name, false)) return $class_name;
}
return false;
}
/**
* Automated version of self::isDispatchableModule()
* @param eRequest $request
* @param boolean $checkReflection deep check - proper subclassing, action
* @param boolean $checkOverride try override controller folder first
* @return false|string class name OR false if not dispatchable
*/
public function isDispatchable(eRequest $request, $checkReflection = false, $checkOverride = true)
{
$location = self::getDispatchLocation($request->getModuleName());
$controllerName = $request->getControllerName();
$moduleName = $request->getModuleName();
$className = false;
// dispatch based on url_config preference value, if config location is override and there is no
// override controller, additional check against real controller location will be made
if($location)
{
$className = $this->isDispatchableModule($moduleName, $controllerName, $location, $checkOverride);
}
//else
//{
# Disable plugin check for routes with no config info - prevent calling of non-installed plugins
# We may allow this for plugins which don't have plugin.xml in the future
// $className = $this->isDispatchableModule($moduleName, $controllerName, 'plugin', $checkOverride);
// if(!$className)
//$className = $this->isDispatchableModule($moduleName, $controllerName, 'core', $checkOverride);
//}
if(empty($className)) return false;
elseif(!$checkReflection) return $className;
$rfl = new ReflectionClass($className);
$method = $request->getActionMethodName();
if($rfl->isSubclassOf('eController') && $rfl->hasMethod($method) && $rfl->getMethod($method)->isPublic() && !$rfl->getMethod($method)->isStatic())
return $className;
return false;
}
/**
* Get class name of a given module config
*
* @param string $module valid module name
* @param string $location core|plugin|override[/custom]
* @return string controller path
*/
public static function getConfigClassName($module, $location)
{
$tmp = explode('/', $location);
$custom = '';
$location = $tmp[0];
if(isset($tmp[1]) && !empty($tmp[1]))
{
$custom = $tmp[1].'_';
}
unset($tmp);
$module .= '_';
// we need to prepend location to avoid namespace colisions
return $location.'_'.$module.$custom.'url';
}
/**
* Get config object for a module
*
* @param string $module valid module name
* @param string $location core|plugin|override[/custom]
* @return eUrlConfig
*/
public static function getConfigObject($module, $location = null)
{
if(null === $location)
{
$location = self::getModuleConfigLocation($module);
if(!$location) return null;
}
$reg = $module.'/'.$location;
if(isset(self::$_configObjects[$reg])) return self::$_configObjects[$reg];
$className = self::getConfigClassName($module, $location);
if(!class_exists($className, false))
{
$path = self::getConfigPath($module, $location, false);
if(!is_readable($path)) return null;
include_once($path);
if(!class_exists($className, false)) return null;
}
/** @var eUrlConfig $obj */
$obj = new $className();
$obj->init();
self::$_configObjects[$reg] = $obj;
$obj = null;
return self::$_configObjects[$reg];
}
/**
* Auto discover module location from stored in core prefs data
* @param string $module
* @return mixed
*/
public static function getModuleConfigLocation($module)
{
//retrieve from config prefs
return e107::findPref('url_config/'.$module, '');
}
/**
* Auto discover module location from stored in core prefs data
* @param string $module
* @return mixed|null|string
*/
public static function getDispatchLocation($module)
{
//retrieve from prefs
$location = self::getModuleConfigLocation($module);
if(!$location) return null;
if(($pos = strpos($location, '/'))) //can't be 0
{
return substr($location, 0, $pos);
}
return $location;
}
/**
* Auto discover module real location (and not currently set from url adminsitration) from stored in core prefs data
* @param string $module
*/
public static function getModuleRealLocation($module)
{
//retrieve from prefs
$searchArray = e107::findPref('url_modules');
if(!$searchArray) return null;
$search = array('core', 'plugin', 'override');
foreach ($search as $location)
{
$_searchArray = vartrue($searchArray[$location], array());
if(in_array($module, $_searchArray)) return $location;
}
return null;
}
}
/**
* URL manager - parse and create URLs based on rules set
* Inspired by Yii Framework UrlManager <www.yiiframework.com>
*/
class eRouter
{
/**
* Configuration array containing all available syste routes and route object configuration values
* @var array
*/
protected $_rules = array();
/**
* List of all system wide available aliases
* This includes multi-lingual configurations as well
* @var array
*/
protected $_aliases = array();
/**
* Cache for rule objects
* @var array
*/
protected $_parsedRules = array(); // array of rule objects
/**
* Global config values per rule set
* @var array
*/
protected $_globalConfig = array();
/**
* Module name which is used for site main namespace
* Example mysite.com/news/News Item => converted to mysite.com/News Item
* NOTE: Could be moved to rules config
*
* @var string
*/
protected $_mainNsModule = '';
/**
* Default URL suffix - to be added to end of all urls (e.g. '.html')
* This value can be overridden per rule item
* NOTE could be moved to rules config only
* @var string
*/
public $urlSuffix = '';
/**
* @var string GET variable name for route. Defaults to 'route'.
*/
public $routeVar = 'route';
/**
* @var string
*/
const FORMAT_GET = 'get';
/**
* @var string
*/
const FORMAT_PATH = 'path';
/**
* @var string
*/
private $_urlFormat = self::FORMAT_PATH;
/**
* Not found route
* @var string
*/
public $notFoundRoute = 'system/error/notfound';
protected $_defaultAssembleOptions = array('full' => false, 'amp' => '&amp;', 'equal' => '=', 'encode' => true);
/**
* Not found URL - used when system route not found and 'url_error_redirect' core pref is true
* TODO - user friendly URL ('/system/404') when system config is ready ('/system/404')
* @var string
*/
public $notFoundUrl = 'system/error/404?type=routeError';
public function __construct()
{
$this->_init();
}
/**
* Init object
* @return void
*/
protected function _init()
{
// Gather all rules, add-on info, cache, module for main namespace etc
$this->loadConfig()
->setAliases();
// we need config first as setter does some checks if module can be set as main
$this->setMainModule(e107::getPref('url_main_module', ''));
}
/**
* Set module for default namespace
* @param string $module
* @return eRouter
*/
public function setMainModule($module)
{
if(!$module || !$this->isModule($module) || !$this->getConfigValue($module, 'allowMain')) return $this;
$this->_mainNsModule = $module;
return $this;
}
/**
* Get main url namespace module
* @return string
*/
public function getMainModule()
{
return $this->_mainNsModule;
}
/**
* Check if given module is the main module
* @param string $module
* @return boolean
*/
public function isMainModule($module)
{
return ($this->_mainNsModule === $module);
}
/**
* @return string get|path
*/
public function getUrlFormat()
{
return $this->_urlFormat;
}
/**
* Load config and url rules, if not available - build it on the fly
* @return eRouter
*/
public function loadConfig($forceRebuild = false)
{
if(!is_readable(e_CACHE_URL . 'config.php') || $forceRebuild == true)
{
$config = $this->buildGlobalConfig();
}
else
{
$config = include(e_CACHE_URL . 'config.php');
}
if(empty($config))
{
trigger_error('URL Config is empty', E_USER_NOTICE);
$config = array();
}
$rules = array();
foreach ($config as $module => $c)
{
$rules[$module] = $c['rules'];
unset($config[$module]['rules']);
$config[$module] = $config[$module]['config'];
}
$this->_globalConfig = $config;
$this->setRuleSets($rules);
return $this;
}
/**
* @return void
*/
public static function clearCache()
{
if(file_exists(e_CACHE_URL.'config.php'))
{
@unlink(e_CACHE_URL.'config.php');
}
}
/**
* Build unified config.php
*/
public function buildGlobalConfig($save = true)
{
$active = e107::getPref('url_config', array());
if(empty($active))
{
trigger_error('url_config pref is empty', E_USER_NOTICE);
}
$config = array();
foreach ($active as $module => $location)
{
$_config = array();
$obj = eDispatcher::getConfigObject($module, $location);
$path = eDispatcher::getConfigPath($module, $location, true);
if(null !== $obj)
{
$_config = $obj->config();
$_config['config']['configPath'] = $path;
$_config['config']['configClass'] = eDispatcher::getConfigClassName($module, $location);
}
if(!isset($_config['config'])) $_config['config'] = array();
$_config['config']['location'] = $location;
if(!isset($_config['config']['format']) || !in_array($_config['config']['format'], array(self::FORMAT_GET, self::FORMAT_PATH)))
{
$_config['config']['format'] = $this->getUrlFormat();
}
if(!isset($_config['rules'])) $_config['rules'] = array();
foreach ($_config['rules'] as $pattern => $rule)
{
if(!is_array($rule))
{
$_config['rules'][$pattern] = array($rule);
}
}
$config[$module] = $_config;
}
if($save)
{
$fileContent = '<?php'."\n### Auto-generated - DO NOT EDIT ### \nreturn ";
$fileContent .= trim(var_export($config, true)).';';
file_put_contents(e_CACHE_URL.'config.php', $fileContent);
}
return $config;
}
/**
* Retrieve config array from a given system path
* @param string $path
* @param string $location core|plugin|override
*/
public static function adminReadConfigs($path, $location = null)
{
$file = e107::getFile(false);
$ret = array();
$file->mode = 'fname';
$files = $file->setFileInfo('fname')
->get_files($path, '^([a-z_]{1,}_)?url\.php$');
foreach ($files as $file)
{
if(null === $location)
{
$c = eRouter::file2config($file, $location);
if($c) $ret[] = $c;
continue;
}
$ret[] = eRouter::file2config($file, $location);
}
return $ret;
}
/**
* Convert filename to configuration string
* @param string $filename
* @param string $location core|plugin|override
*/
public static function file2config($filename, $location = '')
{
if($filename == 'url.php') return $location;
if($location) $location .= '/';
return $location.substr($filename, 0, strrpos($filename, '_'));
}
/**
* Detect all available system url modules, used as a map on administration configuration path
* and required (same structure) {@link from eDispatcher::adminBuildConfig())
* This is a very liberal detection, as it doesn't require config file.
* It goes through both config and dispatch locations and registers directory tree as modules
* The only exception are plugins - if plugin requires install (plugin.xml) and it is not installed,
* it won't be registered
* Another important thing is - core has always higher priority, as plugins are not allowed to
* directly override core modules. At this moment, core modules could be overloaded only via override configs (e107_core/override/url/)
* and controllers (e107_core/override/controllers)
* This array is stored as url_modules core preference
*
* @param string $type possible values are all|plugin|core|override
* @return array available system url modules stored as url_modules core preference
*/
public static function adminReadModules($type = 'all')
{
$f = e107::getFile();
$ret = array('core' => array(), 'plugin' => array(), 'override' => array());
$plugins = array();
if($type == 'all' || $type = 'core')
{
$location = eDispatcher::getDispatchLocationPath('core');
// search for controllers first
$ret['core'] = $f->get_dirs($location);
// merge with configs
$configArray = $f->get_dirs(eDispatcher::getConfigPath('*', 'core'));
foreach ($configArray as $config)
{
if(!in_array($config, $ret['core']))
{
$ret['core'][] = $config;
}
}
sort($ret['core']);
}
if($type == 'all' || $type = 'plugin')
{
$plugins = $f->get_dirs(e_PLUGIN);
foreach ($plugins as $plugin)
{
// DON'T ALLOW PLUGINS TO OVERRIDE CORE!!!
// This will be possible in the future under some other, more controllable form
if(in_array($plugin, $ret['core'])) continue;
$location = eDispatcher::getDispatchLocationPath('plugin', $plugin);
$config = eDispatcher::getConfigPath($plugin, 'plugin');
if(e107::isInstalled($plugin))
{
if(is_dir($location) || is_readable($config))
{
$ret['plugin'][] = $plugin;
}
continue;
}
// Register only those who don't need install and may be dispatchable
if((!is_readable(e_PLUGIN.$plugin.'/plugin.php') && !is_readable(e_PLUGIN.$plugin.'/plugin.xml')))
{
if(is_dir($location) || is_readable($config))
{
$ret['plugin'][] = $plugin;
}
}
}
sort($ret['plugin']);
}
if($type == 'all' || $type = 'override')
{
// search for controllers first
$location = eDispatcher::getDispatchLocationPath('override');
$ret['override'] = $f->get_dirs($location);
// merge with configs
$configArray = $f->get_dirs(eDispatcher::getConfigPath('*', 'override'));
foreach ($configArray as $config)
{
if(!in_array($config, $ret['override']))
{
$ret['override'][] = $config;
}
}
sort($ret['override']);
// remove not installed plugin locations, possible only for 'all' type
if($type == 'all')
{
foreach ($ret['override'] as $i => $l)
{
// it's a plugin override, but not listed in current plugin array - remove
if(in_array($l, $plugins) && !in_array($l, $ret['plugin']))
{
unset($ret['override'][$i]);
}
}
}
}
return $ret;
}
/**
* Rebuild configuration array, stored as url_config core preference
* More strict detection compared to {@link eDispatcher::adminReadModules()}
* Current flat array containing config locations per module are rebuilt so that new
* modules are registered, missing modules - removed. Additionally fallback to the default location
* is done if current user defined location is not readable
* @see eDispatcher::adminReadModules()
* @param array current configuration array (url_config core preference like)
* @param array available URL modules as detected by {@link eDispatcher::adminReadModules()} and stored as url_modules core preference value
* @return array new url_config array
*/
public static function adminBuildConfig($current, $adminReadModules = null)
{
if(null === $adminReadModules) $adminReadModules = self::adminReadModules();
$ret = array();
$all = array_unique(array_merge($adminReadModules['core'], $adminReadModules['plugin'], $adminReadModules['override']));
foreach ($all as $module)
{
if(isset($current[$module]))
{
// current contains custom (readable) config location e.g. news => core/rewrite
if(strpos($current[$module], '/') !== false && is_readable(eDispatcher::getConfigPath($module, $current[$module])))
{
$ret[$module] = $current[$module];
continue;
}
// in all other cases additional re-check will be made - see below
}
if(in_array($module, $adminReadModules['override']))
{
// core check
if(in_array($module, $adminReadModules['core']))
{
$mustHave = is_readable(eDispatcher::getConfigPath($module, 'core'));
$has = is_readable(eDispatcher::getConfigPath($module, 'override'));
// No matter if it must have, it has e_url config
if($has) $ret[$module] = 'override';
// It must have but it doesn't have e_url config, fallback
elseif($mustHave && !$has) $ret[$module] = 'core';
// Rest is always core as controller override is done on run time
else $ret[$module] = 'core';
}
// plugin check
elseif(in_array($module, $adminReadModules['plugin']))
{
$mustHave = is_readable(eDispatcher::getConfigPath($module, 'plugin'));
$has = is_readable(eDispatcher::getConfigPath($module, 'override'));
// No matter if it must have, it has e_url config
if($has) $ret[$module] = 'override';
// It must have but it doesn't have e_url config, fallback
elseif($mustHave && !$has) $ret[$module] = 'plugin';
// Rest is always plugin as config is most important, controller override check is done on run time
else $ret[$module] = 'plugin';
}
// standalone override module
else
{
$ret[$module] = 'override';
}
}
// default core location
elseif(in_array($module, $adminReadModules['core']))
{
$ret[$module] = 'core';
}
// default plugin location
elseif(in_array($module, $adminReadModules['plugin']))
{
$ret[$module] = 'plugin';
}
}
return $ret;
}
/**
* Detect available config locations (readable check), based on available url_modules {@link eDispatcher::adminReadModules()} core preference arrays
* Used to rebuild url_locations core preference value
* @see eDispatcher::adminBuildConfig()
* @see eDispatcher::adminReadModules()
* @param array $available {@link eDispatcher::adminReadModules()} stored as url_modules core preference
* @return array available config locations, stored as url_locations core preference
*/
public static function adminBuildLocations($available = null)
{
$ret = array();
if(null === $available) $available = self::adminReadModules();
$fl = e107::getFile();
// Core
foreach ($available['core'] as $module)
{
// Default module
$ret[$module] = array('core');
// read sub-locations
$path = eDispatcher::getConfigLocationPath($module, 'core');
//$sub = $fl->get_dirs($path);
$sub = eRouter::adminReadConfigs($path);
if($sub)
{
foreach ($sub as $moduleSub)
{
// auto-override: override available (controller or url config), check for config
if(in_array($module, $available['override']) && is_readable(eDispatcher::getConfigPath($module, 'override/'.$moduleSub)))
{
$ret[$module][] = 'override/'.$moduleSub;
}
// no override available, register the core location
elseif(is_readable(eDispatcher::getConfigPath($module, 'core/'.$moduleSub)))
{
$ret[$module][] = 'core/'.$moduleSub;
}
}
}
}
// Plugins
foreach ($available['plugin'] as $module)
{
// Default module
$ret[$module] = array('plugin');
// read sub-locations
$path = eDispatcher::getConfigLocationPath($module, 'plugin');
//$sub = $fl->get_dirs($path);
$sub = eRouter::adminReadConfigs($path);
if($sub)
{
foreach ($sub as $moduleSub)
{
// auto-override: override available (controller or url config), check for config
if(in_array($module, $available['override']) && is_readable(eDispatcher::getConfigPath($module, 'override/'.$moduleSub)))
{
$ret[$module][] = 'override/'.$moduleSub;
}
// no override available, register the core location
elseif(is_readable(eDispatcher::getConfigPath($module, 'plugin/'.$moduleSub)))
{
$ret[$module][] = 'plugin/'.$moduleSub;
}
}
}
}
// Go through all overrides, register those who don't belong to core & plugins as standalone core modules
foreach ($available['override'] as $module)
{
// either it is a core/plugin module or e_url.php is not readable - continue
if(in_array($module, $available['core']) || in_array($module, $available['plugin']))
{
continue;
}
// Default module
$ret[$module] = array('override');
// read sub-locations
$path = eDispatcher::getConfigLocationPath($module, 'override');
//$sub = $fl->get_dirs($path);
$sub = eRouter::adminReadConfigs($path);
if($sub)
{
foreach ($sub as $moduleSub)
{
if(is_readable(eDispatcher::getConfigPath($module, 'override/'.$moduleSub)))
{
$ret[$module][] = 'override/'.$moduleSub;
}
}
}
}
return $ret;
}
/**
* Match current aliases against currently available module and languages
* @param array $currentAliases url_aliases core preference
* @param array $currentConfig url_config core preference
* @return array cleaned aliases
*/
public static function adminSyncAliases($currentAliases, $currentConfig)
{
if(empty($currentAliases)) return array();
$modules = array_keys($currentConfig);
// remove non existing languages
$lng = e107::getLanguage();
$lanList = $lng->installed();
if(is_array($currentAliases))
{
foreach ($currentAliases as $lanCode => $aliases)
{
$lanName = $lng->convert($lanCode);
if(!$lanName || !in_array($lanName, $lanList))
{
unset($currentAliases[$lanCode]);
continue;
}
// remove non-existing modules
foreach ($aliases as $alias => $module)
{
if(!isset($currentConfig[$module])) unset($currentAliases[$lanCode][$alias]);
}
}
}
return $currentAliases;
}
/**
* Retrieve global configuration array for a single or all modules
* @param string $module system module
* @return array configuration
*/
public function getConfig($module = null)
{
if(null === $module) return $this->_globalConfig;
return isset($this->_globalConfig[$module]) ? $this->_globalConfig[$module] : array();
}
/**
* Retrieve single value from a module global configuration array
* @param string $module system module
* @return array configuration
*/
public function getConfigValue($module, $key, $default = null)
{
return isset($this->_globalConfig[$module]) && isset($this->_globalConfig[$module][$key]) ? $this->_globalConfig[$module][$key] : $default;
}
/**
* Get system name of a module by its alias
* Returns null if $alias is not an existing alias
* @param string $alias
* @param string $lan optional language alias check. Example $lan = 'bg' (search for Bulgarian aliases)
* @return string module
*/
public function getModuleFromAlias($alias, $lan = null)
{
if($lan) return e107::findPref('url_aliases/'.$lan.'/'.$alias, null);
return (isset($this->_aliases[$alias]) ? $this->_aliases[$alias] : null);
}
/**
* Get alias name for a module
* Returns null if module doesn't have an alias
* @param string $module
* @param string $lan optional language alias check. Example $lan = 'bg' (search for Bulgarian aliases)
* @return string alias
*/
public function getAliasFromModule($module, $lan = null)
{
if($lan)
{
$aliases = e107::findPref('url_aliases/'.$lan, array());
return (in_array($module, $aliases) ? array_search($module, $aliases) : null);
}
return (in_array($module, $this->_aliases) ? array_search($module, $this->_aliases) : null);
}
/**
* Check if alias exists
* @param string $alias
* @param string $lan optional language alias. Example $lan = 'bg' (search for Bulgarian aliases)
* @return boolean
*/
public function isAlias($alias, $lan = null)
{
if($lan)
{
$aliases = e107::findPref('url_aliases/'.$lan, array());
return isset($aliases[$alias]);
}
return isset($this->_aliases[$alias]);
}
/**
* Check if there is an alias for provided module
* @param string $module
* @param string $lan optional language alias check. Example $lan = 'bg' (search for Bulgarian aliases)
* @return boolean
*/
public function hasAlias($module, $lan = null)
{
if($lan)
{
$aliases = e107::findPref('url_aliases/'.$lan, array());
return in_array($module, $aliases);
}
return in_array($module, $this->_aliases);
}
/**
* Get all available module aliases
* @param string|null $lanCode optional language alias check. Example $lan = 'bg' (search for Bulgarian aliases)
* @return array
*/
public function getAliases($lanCode = null)
{
if($lanCode)
{
return e107::findPref('url_aliases/'.$lanCode, array());
}
return $this->_aliases;
}
/**
* Set module aliases
* @param array $aliases
* @return eRouter
*/
public function setAliases($aliases = null)
{
if(null === $aliases)
{
$lanCode = e107::getLanguage()->convert(e_LANGUAGE);
$aliases = e107::findPref('url_aliases/'.$lanCode, array());
}
$this->_aliases = $aliases;
return $this;
}
/**
* Check if provided module is present in the rules config
* @param string module
* @return boolean
*/
public function isModule($module)
{
return isset($this->_globalConfig[$module]);
}
/**
* Check if the passed value is valid module or module alias, returns system module
* or null on failure
* @param string $module
* @param boolean $strict check for existence if true
* @return string module
*/
public function retrieveModule($module, $strict = true)
{
if($this->isAlias($module))
$module = $this->getModuleFromAlias($module);
if($strict && (!$module || !$this->isModule($module)))
return null;
return $module;
}
/**
* Set rule config for this instance
* @param array $rules
* @return void
*/
public function setRuleSets($rules)
{
$this->_rules = $rules;
}
/**
* Retrieve rule set for a module
* @param string $module
*/
public function getRuleSet($module)
{
return (isset($this->_rules[$module]) ? $this->_rules[$module] : array());
}
/**
* Get all rule sets
*/
public function getRuleSets()
{
return $this->_rules;
}
/**
* Retrive array of eUrlRule objects for given module
*/
public function getRules($module)
{
return $this->_processRules($module);
}
/**
* Process rule set array, create rule objects
* TODO - rule cache
* @param string $module
* @return array processed rule set
*/
protected function _processRules($module)
{
if(!$this->isModule($module)) return array();
if(!isset($this->_parsedRules[$module]))
{
$rules = $this->getRuleSet($module);
$config = $this->getConfig($module);
$this->_parsedRules[$module] = array();
$map = array('urlSuffix' => 'urlSuffix', 'legacy' => 'legacy', 'legacyQuery' => 'legacyQuery', 'mapVars' => 'mapVars', 'allowVars' => 'allowVars', 'matchValue' => 'matchValue');
foreach ($rules as $pattern => $set)
{
foreach ($map as $key => $value)
{
if(!isset($set[$value]) && isset($config[$key]))
{
$set[$value] = $config[$key];
}
}
$this->_parsedRules[$module][$pattern] = $this->createRule($set, $pattern);
}
}
return $this->_parsedRules[$module];
}
/**
* Create rule object
*
* @param string $route
* @param string|array $pattern
* @param boolean $cache
* @return eUrlRule
*/
protected function createRule($route, $pattern = null, $cache = false)
{
return new eUrlRule($route, $pattern, $cache);
}
/**
* @param $label
* @param $val
* @param $line
* @return false|void
*/
private function _debug($label, $val=null, $line=null)
{
if(!deftrue('e_DEBUG_SEF'))
{
return false;
}
e107::getDebug()->log("<h3>SEF: ".$label . " <small>".basename(__FILE__)." (".$line.")</small></h3>".print_a($val,true));
}
/**
* Route current request
* @param eRequest $request
* @param bool $checkOnly
* @return boolean
*/
public function route(eRequest $request, $checkOnly = false)
{
$request->routed = false;
if(isset($_GET[$this->routeVar]))
{
$rawPathInfo = $_GET[$this->routeVar];
unset($_GET[$this->routeVar]);
$this->_urlFormat = self::FORMAT_GET;
}
else
{
$rawPathInfo = rawurldecode($request->getPathInfo());
//$this->_urlFormat = self::FORMAT_PATH;
}
// Ignore social trackers when determining route.
$get = eHelper::removeTrackers($_GET);
// Route to front page - index/index/index route
if(!$rawPathInfo && (!$this->getMainModule() || empty($get)))
{
// front page settings will be detected and front page will be rendered
$request->setRoute('index/index/index');
$request->addRouteHistory($rawPathInfo);
$request->routed = true;
return true;
}
// max number of parts is actually 4 - module/controller/action/[additional/pathinfo/vars], here for reference only
$parts = $rawPathInfo ? explode('/', $rawPathInfo, 4) : array();
$this->_debug('parts',$parts, __LINE__);
// find module - check aliases
$module = $this->retrieveModule($parts[0]);
$mainSwitch = false;
// no module found, switch to Main module (pref) if available
if(null === $module && $this->getMainModule() && $this->isModule($this->getMainModule()))
{
$module = $this->getMainModule();
$rawPathInfo = $module.'/'.$rawPathInfo;
array_unshift($parts, $module);
$mainSwitch = true;
}
$request->routePathInfo = $rawPathInfo;
$this->_debug('module',$module, __LINE__);
$this->_debug('rawPathInfo',$rawPathInfo, __LINE__);
// valid module
if(null !== $module)
{
// we have valid module
$config = $this->getConfig($module);
$this->_debug('config',$module, __LINE__);
// set legacy state
eFront::isLegacy(varset($config['legacy']));
// Don't allow single entry if required by module config
if(!empty($config['noSingleEntry']))
{
$request->routed = true;
if(!eFront::isLegacy())
{
$request->setRoute($this->notFoundRoute);
return false;
}
// legacy entry point - include it later in the bootstrap, legacy query string will be set to current
$request->addRouteHistory($rawPathInfo);
return true;
}
// URL format - the one set by current config overrides the auto-detection
$format = isset($config['format']) && $config['format'] ? $config['format'] : $this->getUrlFormat();
//remove leading module, unnecessary overhead while matching
array_shift($parts);
$rawPathInfo = $parts ? implode('/', $parts) : '';
$pathInfo = $this->removeUrlSuffix($rawPathInfo, $this->urlSuffix);
// retrieve rules if any and if needed
$rules = $format == self::FORMAT_PATH ? $this->getRules($module) : array();
// Further parsing may still be needed
if(empty($rawPathInfo))
{
$rawPathInfo = $pathInfo;
}
// parse callback
if(!empty($config['selfParse']))
{
// controller/action[/additional/parms]
if(!empty($config['urlSuffix'])) $rawPathInfo = $this->removeUrlSuffix($rawPathInfo, $config['urlSuffix']);
$route = $this->configCallback($module, 'parse', array($rawPathInfo, $_GET, $request, $this, $config), $config['location']);
}
// default module route
elseif($format == self::FORMAT_GET || !$rules)
{
$route = $pathInfo;
}
// rules available - try to match an Url Rule
elseif($rules)
{
// $this->_debug('rules',$rules, __LINE__);
foreach ($rules as $rule)
{
$route = $rule->parseUrl($this, $request, $pathInfo, $rawPathInfo);
if($route !== false)
{
eFront::isLegacy($rule->legacy); // legacy include override
$this->_debug('rule->legacy',$rule->legacy, __LINE__);
$this->_debug('rule->parseCallback',$rule->parseCallback, __LINE__);
if($rule->parseCallback)
{
$this->configCallback($module, $rule->parseCallback, array($request), $config['location']);
}
// parse legacy query string if any
$this->_debug('rule->legacyQuery',$rule->legacyQuery, __LINE__);
if(null !== $rule->legacyQuery)
{
$obj = eDispatcher::getConfigObject($module, $config['location']);
// eUrlConfig::legacyQueryString set as legacy string by default in eUrlConfig::legacy() method
$vars = new e_vars($request->getRequestParams());
$vars->module = $module;
$vars->controller = $request->getController();
$vars->action = $request->getAction();
if($rule->allowVars)
{
foreach ($rule->allowVars as $key)
{
if(isset($_GET[$key]) && !$request->isRequestParam($key))
{
// sanitize
$vars->$key = preg_replace('/[^\w\-]/', '', $_GET[$key]);
}
}
}
$obj->legacyQueryString = e107::getParser()->simpleParse($rule->legacyQuery, $vars, '0');
$this->_debug('obj->legacyQueryString',$obj->legacyQueryString, __LINE__);
unset($vars, $obj);
}
break;
}
}
}
// append module to be registered in the request object
if(false !== $route)
{
// don't modify if true - request directly modified by config callback
if(!$request->routed)
{
if(eFront::isLegacy()) $this->configCallback($module, 'legacy', array($route, $request), $config['location']);
$route = $module.'/'.$route;
}
}
// No route found, we didn't switched to main module auto-magically
elseif(!$mainSwitch && vartrue($config['errorRoute']))
{
$route = !$checkOnly ? $module.'/'.$config['errorRoute'] : false;
}
}
// final fallback
if(!vartrue($route))
{
if($request->routed)
{
$route = $request->getRoute();
}
if(!$route)
{
$route = $this->notFoundRoute;
eFront::isLegacy(''); // reset legacy - not found route isn't legacy call
$request->routed = true;
if($checkOnly) return false;
## Global redirect on error option
if(e107::getPref('url_error_redirect', false) && $this->notFoundUrl)
{
$redirect = $this->assemble($this->notFoundUrl, '', 'encode=0&full=1');
//echo $redirect; exit;
e107::getRedirect()->redirect($redirect, true, 404);
}
}
}
$this->_debug('route',$route, __LINE__);
$request->setRoute($route);
$request->addRouteHistory($route);
$request->routed = true;
return true;
}
/**
* And more BC
* Checks and does some addtional logic if registered module is of type legacy
* @param eRequest $request
* @return void
*/
public function checkLegacy(eRequest $request)
{
$module = $request->getModule();
// forward from controller to a legacy module - bad stuff
if(!$request->isDispatched() && $this->getConfigValue($module, 'legacy'))
{
eFront::isLegacy($this->getConfigValue($module, 'legacy'));
$url = $this->assemble($request->getRoute(), $request->getRequestParams());
$request->setRequestInfo($url)->setPathInfo(null)->setRoute(null);
$_GET = $request->getRequestParams();
$_SERVER['QUERY_STRING'] = http_build_query($request->getRequestParams(), null, '&');
// Infinite loop impossible, as dispatcher will break because of the registered legacy path
$this->route($request);
}
}
/**
* Convenient way to call config methods
*/
public function configCallback($module, $callBack, $params, $location)
{
if(null == $location) $location = eDispatcher::getModuleConfigLocation($module);
if(!$module || !($obj = eDispatcher::getConfigObject($module, $location))) return false;
return call_user_func_array(array($obj, $callBack), $params);
}
/**
* Convert assembled url to shortcode
*
* @param string $route
* @param array $params
* @param array $options {@see eRouter::$_defaultAssembleOptions}
*/
public function assembleSc($route, $params = array(), $options = array())
{
//if(is_string($options)) parse_str($options, $options);
$url = $this->assemble($route, $params, $options);
return e107::getParser()->createConstants($url, 'mix');
}
/**
* Assemble system URL
* Examples:
* <?php
* $router->assemble('/'); // index page URL e.g. / or /site_folder/
* $router->assemble('news/view/item?id=1'); // depends on current news config, possible return value is /news/1
* $router->assemble('*', 'id=1'); // use current request info - /module/controller/action?id=1
* $router->assemble('* /* /newaction'); // (NO EMPTY SPACES) change only current action - /module/controller/newaction
* $newsItem = array('news_id' => 1, 'news_sef' => 'My Title', ...); // as retrieved from DB
* $router->assemble('news/view/item', $newsItem); // All unused key=>values will be removed and NOT appended as GET vars
*
* @param string $route
* @param array $params
* @param array $options {@see eRouter::$_defaultAssembleOptions}
*/
public function assemble($route, $params = array(), $options = array())
{
// TODO - url options
$request = eFront::instance()->getRequest();
if(is_string($options)) parse_str($options, $options);
$options = array_merge($this->_defaultAssembleOptions, $options);
$base = ($options['full'] ? SITEURLBASE : '').$request->getBasePath();
$anc = '';
if(is_string($params)) parse_str($params, $params);
if(isset($params['#']))
{
$anc = '#'.$params['#'];
unset($params['#']);
}
// Config independent - Deny parameter keys, useful for directly denying sensitive data e.g. password db fields
if(isset($options['deny']))
{
$list = array_map('trim', explode(',', $options['deny']));
foreach ($list as $value)
{
unset($params[$value]);
}
unset($list);
}
// Config independent - allow parameter keys, useful to directly allow data (and not to rely on config allowVars) e.g. when retrieved from db
if(isset($options['allow']))
{
$list = array_map('trim', explode(',', $options['allow']));
$_params = $params;
$params = array();
foreach ($list as $value)
{
if(isset($_params[$value])) $params[$value] = $_params[$value];
}
unset($list, $_params);
}
# Optional convenient masks for creating system URL's
if($route === '/' || empty($route))
{
if($params)
{
$params = $this->createPathInfo($params, $options);
return $base.'?'.$params;
}
return $base;
}
elseif(strpos($route, '?') !== false)
{
$tmp = explode('?', $route, 2);
$route = $tmp[0];
parse_str($tmp[1], $params);
unset($tmp);
}
if($route === '*')
{
$route = $route = explode('/', $request->getRoute());
}
elseif(strpos($route, '*') !== false)
{
$route = explode('/', $route, 3);
if($route[0] === '*') $route[0] = $request->getModule();
if(isset($route[1]) && $route[1] === '*') $route[1] = $request->getController();
}
else
{
$route = explode('/', $route, 3);
}
// we don't know anything about this route, just build it blind
if(!$this->isModule($route[0]))
{
if($params)
{
$params = $this->createPathInfo($params, $options);
return $base.implode('/', $route).'?'.$params;
}
return $base.implode('/', $route);
}
# fill in index when needed - XXX not needed, may be removed soon
switch (count($route))
{
case 1:
$route[1] = 'index';
$route[2] = 'index';
break;
case 2:
$route[2] = 'index';
break;
}
# aliases
$module = $route[0];
$config = $this->getConfig($module);
$alias = $this->hasAlias($module, vartrue($options['lan'], null)) ? $this->getAliasFromModule($module, vartrue($options['lan'], null)) : $module;
$route[0] = $alias;
if($options['encode']) $alias = rawurlencode($alias);
$format = isset($config['format']) && $config['format'] ? $config['format'] : self::FORMAT_GET;
$urlSuffix = '';
// Fix base url for legacy links
if(!empty($config['noSingleEntry'])) $base = $options['full'] ? SITEURL : e_HTTP;
elseif(self::FORMAT_GET !== $config['format'])
{
$urlSuffix = $this->urlSuffix;
if(isset($config['urlSuffix'])) $urlSuffix = $config['urlSuffix'];
}
// Create by config callback
if(!empty($config['selfCreate']))
{
$tmp = $this->configCallback($module, 'create', array(array($route[1], $route[2]), $params, $options), $config['location']);
if(empty($tmp)) return '#not-found';
if(is_array($tmp))
{
$route = $tmp[0];
$params = $tmp[1];
if($options['encode']) $route = array_map('rawurlencode', $route);
$route = implode('/', $route);
if(!$route)
{
$urlSuffix = '';
if(!$this->isMainModule($module)) $route = $alias;
}
elseif (!$this->isMainModule($module))
{
$route = $alias.'/'.$route;
}
}
else
{
// relative url returned
return $base.$tmp.$anc;
}
unset($tmp);
if($format === self::FORMAT_GET)
{
$params[$this->routeVar] = $route;
$route = '';
}
if($params)
{
$params = $this->createPathInfo($params, $options);
return $base.$route.$urlSuffix.'?'.$params.$anc;
}
return $base.$route.$urlSuffix.$anc;
}
// System URL create routine
$rules = $this->getRules($module);
if($format !== self::FORMAT_GET && !empty($rules))
{
foreach ($rules as $k => $rule)
{
if (($url = $rule->createUrl($this, array($route[1], $route[2]), $params, $options)) !== false)
{
return $base.rtrim(($this->isMainModule($module) ? '' : $alias.'/').$url, '/').$anc;
}
}
}
// default - module/controller/action
if($this->isMainModule($module)) unset($route[0]);
if($route[2] == 'index')
{
unset($route[2]);
if($route[1] == 'index') unset($route[1]);
}
# Modify params if required
if($params)
{
if(!empty($config['mapVars']))
{
foreach ($config['mapVars'] as $srcKey => $dstKey)
{
if (isset($params[$srcKey]))
{
$params[$dstKey] = $params[$srcKey];
unset($params[$srcKey]);
}
else
{
trigger_error("Missing ".$srcKey." during URL creation in ".$module, E_USER_NOTICE);
}
}
}
// false means - no vars are allowed, nothing to preserve here
if(varset($config['allowVars']) === false) $params = array();
// default empty array value - try to guess what's allowed - mapVars is the best possible candidate
elseif(empty($config['allowVars']) && !empty($config['mapVars'])) $params = array_unique(array_values($config['mapVars']));
// disallow everything but valid URL parameters
if(!empty($config['allowVars']))
{
$copy = $params;
$params = array();
foreach ($config['allowVars'] as $key)
{
if(isset($copy[$key]))
{
$params[$key] = $copy[$key];
}
else
{
trigger_error("Missing ".$key." during URL creation in ".$module, E_USER_NOTICE);
}
}
unset($copy);
}
if($format === self::FORMAT_GET)
{
$urlSuffix = '';
$copy = $params;
$params = array();
$params[$this->routeVar] = implode('/', $route);
foreach ($copy as $key => $value)
{
$params[$key] = $value;
}
unset($copy);
$route = array();
}
$params = $this->createPathInfo($params, $options);
$route = implode('/', $route);
if(!$route || $route == $alias) $urlSuffix = '';
return $base.$route.$urlSuffix.'?'.$params.$anc;
}
$route = implode('/', $route);
if(!$route || $route == $alias) $urlSuffix = '';
return $format === self::FORMAT_GET ? $base.'?'.$this->routeVar.'='.$route.$anc : $base.$route.$urlSuffix.$anc;
}
/**
* Alias of assemble()
*/
public function url($route, $params = array())
{
return $this->assemble($route, $params);
}
/**
* Creates a path info based on the given parameters.
* XXX - maybe we can switch to http_build_query(), should be able to do everything we need in a much better way
*
* @param array $params list of GET parameters
* @param array $options rawurlencode, equal, encode and amp settings
* @param string $key this is used internally for recursive calls
*
* @return string the created path info
*/
public function createPathInfo($params, $options, $key = null)
{
$pairs = array();
$equal = $options['equal'];
$encode = $options['encode'];
$ampersand = !$encode && $options['amp'] == '&amp;' ? '&' : $options['amp'];
foreach ($params as $k => $v)
{
if (null !== $key) $k = $key.'['.rawurlencode($k).']';
if (is_array($v)) $pairs[] = $this->createPathInfo($v, $options, $k);
else
{
if(null === $v)
{
if($encode)
{
$k = null !== $key ? $k : rawurlencode($k);
}
$pairs[] = $k;
continue;
}
if($encode)
{
$k = null !== $key ? $k : rawurlencode($k);
$v = rawurlencode($v);
}
$pairs[] = $k.$equal.$v;
}
}
return implode($ampersand, $pairs);
}
/**
* Parses a path info into URL segments
* Be sure to not use non-unique chars for equal and ampersand signs, or you'll break your URLs
*
* @param string $pathInfo path info
* @param string $equal
* @param string $ampersand
* @return array|void
*/
public function parsePathInfo($pathInfo, $equal = '/', $ampersand = '/')
{
if ('' === $pathInfo) return;
if ($equal != $ampersand) $pathInfo = str_replace($equal, $ampersand, $pathInfo);
// $segs = explode($ampersand, $pathInfo.$ampersand);
$segs = explode('/', $pathInfo);
$ret = array();
for ($i = 0, $n = count($segs); $i < $n - 1; $i += 2)
{
$key = $segs[$i];
if ('' === $key) continue;
$value = $segs[$i + 1];
// array support
if (($pos = strpos($key, '[')) !== false && ($pos2 = strpos($key, ']', $pos + 1)) !== false)
{
$name = substr($key, 0, $pos);
// numerical array
if ($pos2 === $pos + 1)
$ret[$name][] = $value;
// associative array
else
{
$key = substr($key, $pos + 1, $pos2 - $pos - 1);
$ret[$name][$key] = $value;
}
}
else
{
$ret[$key] = $value;
}
}
return $ret;
}
/**
* Removes the URL suffix from path info.
* @param string $pathInfo path info part in the URL
* @param string $urlSuffix the URL suffix to be removed
*
* @return string path info with URL suffix removed.
*/
public function removeUrlSuffix($pathInfo, $urlSuffix)
{
if ('' !== $urlSuffix && substr($pathInfo, -strlen($urlSuffix)) === $urlSuffix) return substr($pathInfo, 0, -strlen($urlSuffix));
else return $pathInfo;
}
}
/**
*
*/
class eException extends Exception
{
}
/**
* Based on Yii Framework UrlRule handler <www.yiiframework.com>
*/
class eUrlRule
{
/**
*
* For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
* Defaults to null, meaning using the value of {@link cl_shop_core_url::urlSuffix}.
*
* @var string the URL suffix used for this rule.
*/
public $urlSuffix;
/**
* When this rule is used to parse the incoming request, the values declared in this property
* will be injected into $_GET.
*
* @var array the default GET parameters (name=>value) that this rule provides.
*/
public $defaultParams = array();
/**
* @var string module/controller/action
*/
public $route;
/**
* @var array the mapping from route param name to token name (e.g. _r1=><1>)
*/
public $references = array();
/**
* @var string the pattern used to match route
*/
public $routePattern;
/**
* @var string regular expression used to parse a URL
*/
public $pattern;
/**
* @var string template used to construct a URL
*/
public $template;
/**
* @var array list of parameters (name=>regular expression)
*/
public $params = array();
/**
* @var boolean whether the URL allows additional parameters at the end of the path info.
*/
public $append;
/**
* @var array list of SourceKey=>DestinationKey associations
*/
public $mapVars = array();
/**
* Numerical array of allowed parameter keys. If set, everything else will be wiped out from the passed parameter array
* @var array
*/
public $allowVars = array();
/**
* Should be values matched vs route patterns when assembling URLs
* Warning SLOW when true!!!
* @var mixed true or 1 for preg_match (extremely slower), or 'empty' for only empty check (better)
*/
public $matchValue;
/**
* Method member of module config object, to be called after successful request parsing
* @var string
*/
public $parseCallback;
/**
* Shortcode path to the old entry point e.g. '{e_BASE}news.php'
* @var string
*/
public $legacy;
/**
* Template used for automated recognition of legacy QueryString (parsed via simpleParser with values of retrieved requestParameters)
* @var string
*/
public $legacyQuery;
/**
* Core regex templates
* Example usage - route <var:{number}> will result in
* @var array
*/
public $regexTemplates = array(
'az' => '[A-Za-z]+', // NOTE - it won't match non-latin word characters!
'alphanum' => '[\w\pL]+',
'sefsecure' => '[\w\pL\s\-+.,]+',
'secure' => '[^\/\'"\\<%]+',
'number' => '[\d]+',
'username' => '[\w\pL.\-\s!,]+', // TODO - should equal to username pattern, sync it
'azOptional' => '[A-Za-z]{0,}',
'alphanumOptional' => '[\w\pL]{0,}',
'sefsecureOptional' => '[\w\pL\s\-+.,]{0,}',
'secureOptional' => '[^\/\'"\\<%]{0,}',
'numberOptional' => '[\d]{0,}',
'usernameOptional' => '[\w\pL.\-\s!,]{0,}', // TODO - should equal to username pattern, sync it
);
/**
* User defined regex templates
* @var array
*/
public $varTemplates = array();
/**
* All regex templates
* @var e_var
*/
protected $_regexTemplates;
/**
* Constructor.
* @param string $route the route of the URL (controller/action)
* @param string $pattern the pattern for matching the URL
*/
public function __construct($route, $pattern, $fromCache = false)
{
if (is_array($route))
{
if ($fromCache && !$pattern)
{
$this->setData($route);
$this->_regexTemplates = new e_vars($this->regexTemplates);
return;
}
$this->setData($route);
if($this->defaultParams && is_string($this->defaultParams))
{
parse_str($this->defaultParams, $this->defaultParams);
}
$route = $this->route = $route[0];
}
else $this->route = $route;
$tr2['/'] = $tr['/'] = '\\/';
if (strpos($route, '<') !== false && preg_match_all('/<(\w+)>/', $route, $matches2))
{
foreach ($matches2[1] as $name) $this->references[$name] = "<$name>";
}
if($this->varTemplates)
{
// don't override core regex templates
$this->regexTemplates = array_merge($this->varTemplates, $this->regexTemplates);
$this->varTemplates = array();
}
$this->_regexTemplates = new e_vars($this->regexTemplates);
if (preg_match_all('/<(\w+):?(.*?)?>/', $pattern, $matches))
{
$tokens = array_combine($matches[1], $matches[2]);
$tp = e107::getParser();
foreach ($tokens as $name => $value)
{
if ($value === '') $value = '[^\/]+';
elseif($value[0] == '{')
{
$value = $tp->simpleParse($value, $this->_regexTemplates, '[^\/]+');
}
$tr["<$name>"] = "(?P<$name>$value)";
if (isset($this->references[$name])) $tr2["<$name>"] = $tr["<$name>"];
else $this->params[$name] = $value;
}
}
$p = rtrim($pattern, '*');
$this->append = $p !== $pattern;
$p = trim($p, '/');
$this->template = preg_replace('/<(\w+):?.*?>/', '<$1>', $p);
$this->pattern = '/^'.strtr($this->template, $tr).'\/?';
if ($this->append) $this->pattern .= '/u';
else $this->pattern .= '$/u';
if ($this->references !== array()) $this->routePattern = '/^'.strtr($this->route, $tr2).'$/u';
}
/**
* @return array
*/
public function getData()
{
$vars = array_keys(get_class_vars(__CLASS__));
$data = array();
foreach ($vars as $prop)
{
$data[$prop] = $this->$prop;
}
return $data;
}
/**
* @param $data
* @return void
*/
protected function setData($data)
{
if (!is_array($data)) return;
$vars = array_keys(get_class_vars(__CLASS__));
foreach ($vars as $prop)
{
if (!isset($data[$prop])) continue;
$this->$prop = $data[$prop];
}
}
/**
* Creates a URL based on this rule.
* TODO - more clear logic and flexibility by building the query string
*
* @param eRouter $manager the router/manager
* @param string $route the route
* @param array $params list of parameters
* @param array $options
* @return false|string|null the constructed URL or false on error
*/
public function createUrl($manager, $route, $params, $options)
{
$case = 'i';
$ampersand = $options['amp'];
$encode = vartrue($options['encode']);
if(is_array($route)) $route = implode('/', $route);
$tr = array();
if ($route !== $this->route)
{
if ($this->routePattern !== null && preg_match($this->routePattern.$case, $route, $matches))
{
foreach ($this->references as $key => $name) $tr[$name] = $matches[$key];
}
else return false;
}
// map vars first
foreach ($this->mapVars as $srcKey => $dstKey)
{
if (isset($params[$srcKey])/* && !isset($params[$dstKey])*/)
{
$params[$dstKey] = $params[$srcKey];
unset($params[$srcKey]);
}
}
// false means - no vars are allowed, preserve only route vars
if($this->allowVars === false) $this->allowVars = array_keys($this->params);
// empty array (default) - everything is allowed
// disallow everything but valid URL parameters
if(!empty($this->allowVars))
{
$copy = $params;
$params = array();
$this->allowVars = array_unique(array_merge($this->allowVars, array_keys($this->params)));
foreach ($this->allowVars as $key)
{
if(isset($copy[$key])) $params[$key] = $copy[$key];
}
unset($copy);
}
foreach ($this->defaultParams as $key => $value)
{
if (isset($params[$key]))
{
if ($params[$key] == $value) unset($params[$key]);
else return false;
}
}
foreach ($this->params as $key => $value) if (!isset($params[$key])) return false;
if($this->matchValue)
{
if('empty' !== $this->matchValue)
{
foreach($this->params as $key=>$value)
{
if(!preg_match('/'.$value.'/'.$case,$params[$key]))
return false;
}
}
else
{
foreach($this->params as $key=>$value)
{
if(empty($params[$key]) )
return false;
}
}
}
$tp = e107::getParser();
$urlFormat = e107::getConfig()->get('url_sef_translate');
foreach ($this->params as $key => $value)
{
// FIX - non-latin URLs proper encoded
$tr["<$key>"] = rawurlencode($params[$key]); //todo transliterate non-latin
// $tr["<$key>"] = eHelper::title2sef($tp->toASCII($params[$key]), $urlFormat); // enabled to test.
unset($params[$key]);
}
$suffix = $this->urlSuffix === null ? $manager->urlSuffix : $this->urlSuffix;
// XXX TODO Find better place for this check which will affect all types of SEF URL configurations. (@see news/sef_noid_url.php for duplicate)
if($urlFormat == 'dashl' || $urlFormat == 'underscorel' || $urlFormat == 'plusl') // convert template to lowercase when using lowercase SEF URL format.
{
$this->template = strtolower($this->template);
}
$url = strtr($this->template, $tr);
// Work-around fix for lowercase username
if($urlFormat == 'dashl' && $this->route == 'profile/view')
{
$url = str_replace('%20','-', strtolower($url));
}
if(empty($params))
{
return $url !== '' ? $url.$suffix : $url;
}
// apppend not supported, maybe in the future...?
if ($this->append) $url .= '/'.$manager->createPathInfo($params, '/', '/').$suffix;
else
{
if ($url !== '') $url = $url.$suffix;
$options['equal'] = '=';
$url .= '?'.$manager->createPathInfo($params, $options);
}
return rtrim($url, '/');
}
/**
* Parases a URL based on this rule.
* @param eRouter $manager the router/URL manager
* @param eRequest $request the request object
* @param string $pathInfo path info part of the URL
* @param string $rawPathInfo path info that contains the potential URL suffix
* @return mixed the route that consists of the controller ID and action ID or false on error
*/
public function parseUrl($manager, $request, $pathInfo, $rawPathInfo)
{
$case = 'i'; # 'i' = insensitive
if ($this->urlSuffix !== null) $pathInfo = $manager->removeUrlSuffix($rawPathInfo, $this->urlSuffix);
$pathInfo = rtrim($pathInfo, '/').'/';
// pathInfo is decoded, pattern could be encoded - required for proper url assemble (e.g. cyrillic chars)
if (preg_match(rawurldecode($this->pattern).$case, $pathInfo, $matches))
{
foreach ($this->defaultParams as $name => $value)
{
//if (!isset($_GET[$name])) $_REQUEST[$name] = $_GET[$name] = $value;
if (!$request->isRequestParam($name)) $request->setRequestParam($name, $value);
}
$tr = array();
foreach ($matches as $key => $value)
{
if (isset($this->references[$key])) $tr[$this->references[$key]] = $value;
elseif (isset($this->params[$key]))
{
//$_REQUEST[$key] = $_GET[$key] = $value;
$request->setRequestParam($key, $value);
}
}
if ($pathInfo !== $matches[0]) # Additional GET params exist
{
$manager->parsePathInfo($request, ltrim(substr($pathInfo, strlen($matches[0])), '/'));
}
return (null !== $this->routePattern ? strtr($this->route, $tr) : $this->route);
}
else return false;
}
}
/**
*
*/
abstract class eUrlConfig
{
/**
* Registered by parse method legacy query string
*/
public $legacyQueryString = null;
/**
* User defined initialization
*/
public function init() {}
/**
* Retrieve module config options (including url rules if any)
* Return array is called once and cached, so runtime changes are not an option
* @return array
*/
abstract public function config();
/**
* Create URL callback, called only when config option selfParse is set to true
* Expected return array format:
* <code>
* array(
* array(part1, part2, part3),
* array(parm1 => val1, parm2 => val2),
* );
* </code>
* @param array $route parts
* @param array $params
* @return array|string numerical of type (routeParts, GET Params)| string route or false on error
*/
public function create($route, $params = array(), $options = array()) {}
/**
* Parse URL callback, called only when config option selfCreate is set to true
* TODO - register variable eURLConfig::currentConfig while initializing the object, remove from method arguments
* @param string $pathInfo
* @param array $params request parameters
* @param eRequest|null $request
* @param eRouter|null $router
* @param array $config
* @return string route or false on error
*/
public function parse($pathInfo, $params = array(), eRequest $request = null, eRouter $router = null, $config = array())
{
return false;
}
/**
* Legacy callback, used called when config option legacy is not empty
* By default it sets legacy query string to $legacyQueryString value (normaly assigned inside of the parse method)
* @param string $resolvedRoute
* @param eRequest $request
* @param string $callType 'route' - called once, when parsing the request, 'dispatch' - called inside the dispatch loop (in case of controller _forward)
* @param void
*/
public function legacy($resolvedRoute, eRequest $request, $callType = 'route')
{
if($this->legacyQueryString !== null)
{
$request->setLegacyQstring($this->legacyQueryString);
$request->setLegacyPage();
}
}
/**
* Developed mainly for legacy modules.
* It should be manually triggered inside of old entry point. The idea is
* to avoid multiple URL addresses having same content (bad SEO practice)
* FIXME - under construction
*/
public function forward() {}
/**
* Admin interface callback, returns array with all required from administration data
* Return array structure:
* <code>
* <?php
* return array(
* 'labels' => array(
* 'name' => 'Module name',
* 'label' => 'Profile Label',
* 'description' => 'Additional profile info, exmples etc.',
* ),
* 'form' => array(), // awaiting future development
* 'callbacks' => array(), // awaiting future development
* );
* </code>
*/
public function admin() { return array(); }
/**
* Admin submit hook
* FIXME - under construction
*/
public function submit() {}
/**
* Admin interface help messages, labels and titles
* FIXME - under construction
*/
public function help() {}
}
/**
* Controller base class, actions are extending it
*
*/
class eController
{
protected $_request;
protected $_response;
/**
* @param eRequest $request
* @param eResponse|null $response
*/
public function __construct(eRequest $request, eResponse $response = null)
{
$this->setRequest($request)
->setResponse($response)
->init();
}
/**
* Custom init, always called in the constructor, no matter what is the request dispatch status
*/
public function init() {}
/**
* Custom shutdown, always called after the controller dispatch, no matter what is the request dispatch status
*/
public function shutdown() {}
/**
* Pre-action callback, fired only if dispatch status is still true and action method is found
*/
public function preAction() {}
/**
* Post-action callback, fired only if dispatch status is still true and action method is found
*/
public function postAction() {}
/**
* @param eRequest $request
* @return eController
*/
public function setRequest($request)
{
$this->_request = $request;
return $this;
}
/**
* @return eRequest
*/
public function getRequest()
{
return $this->_request;
}
/**
* @param eResponse $response
* @return eController
*/
public function setResponse($response)
{
$this->_response = $response;
return $this;
}
/**
* @return eResponse
*/
public function getResponse()
{
return $this->_response;
}
/**
* @param $content
* @return $this
*/
public function addBody($content)
{
$this->getResponse()->appendBody($content);
return $this;
}
/**
* @param $description
* @return $this
*/
public function addMetaDescription($description)
{
$this->getResponse()->addMetaDescription($description);
return $this;
}
/**
* Add document title
* @param string $title
* @param boolean $meta auto-add it as meta-title
* @return eController
*/
public function addTitle($title, $meta = true)
{
$this->getResponse()->appendTitle($title);
if($meta) $this->addMetaTitle(strip_tags($title));
return $this;
}
/**
* @param $title
* @return $this
*/
public function addMetaTitle($title)
{
$this->getResponse()->addMetaTitle($title);
return $this;
}
/**
* @param $actionMethodName
* @return void
* @throws eException
*/
public function dispatch($actionMethodName)
{
$request = $this->getRequest();
$content = '';
// init() could modify the dispatch status
if($request->isDispatched())
{
if(method_exists($this, $actionMethodName))
{
$this->preAction();
// TODO request userParams() to store private data - check for noPopulate param here
if($request->isDispatched())
{
$request->populateRequestParams();
// allow return output
$content = $this->$actionMethodName();
if(!empty($content)) $this->addBody($content);
if($request->isDispatched())
{
$this->postAction();
}
}
}
else
{
//TODO not found method by controller or default one
$action = substr($actionMethodName, 6);
throw new eException('Action "'.$action.'" does not exist');
}
}
$this->shutdown();
}
/**
* @param eRequest|null $request
* @param eResponse|null $response
* @return eResponse
* @throws eException
*/
public function run(eRequest $request = null, eResponse $response = null)
{
if(null === $request) $request = $this->getRequest();
else $this->setRequest($request);
if(null === $response) $response = $this->getResponse();
else $this->setResponse($response);
$action = $request->getActionMethodName();
$request->setDispatched(true);
$this->dispatch($action);
return $this->getResponse();
}
/**
* @param $url
* @param $createURL
* @param $code
* @return void
*/
protected function _redirect($url, $createURL = false, $code = null)
{
$redirect = e107::getRedirect();
if($createURL)
{
$url = eFront::instance()->getRouter()->assemble($url, '', 'encode=0');
}
if(strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0)
{
$url = $url[0] == '/' ? SITEURLBASE.$url : SITEURL.$url;
}
$redirect->redirect($url, true, $code);
}
/**
* System forward
* @param string $route
* @param array $params
*/
protected function _forward($route, $params = array())
{
$request = $this->getRequest();
if(is_string($params))
{
parse_str($params, $params);
}
$oldRoute = $request->getRoute();
$route = explode('/', trim($route, '/'));
switch (count($route)) {
case 3:
if($route[0] !== '*') $request->setModule($route[0]);
if($route[1] !== '*') $request->setController($route[1]);
$request->setAction($route[2]);
break;
case 2:
if($route[1] !== '*') $request->setController($route[0]);
$request->setAction($route[1]);
break;
case 1:
$request->setAction($route[0]);
break;
default:
return;
break;
}
$request->addRouteHistory($oldRoute);
if(false !== $params) $request->setRequestParams($params);
$request->setDispatched(false);
}
/**
* @param string $methodName
* @param array $args
* @return void
* @throws eException
*/
public function __call($methodName, $args)
{
if (strpos($methodName, 'action') === 0)
{
$action = substr($methodName, 6);
throw new eException('Action "'.$action.'" does not exist', 2404);
}
throw new eException('Method "'.$methodName.'" does not exist', 3404);
}
}
/**
* @package e107
* @subpackage e107_handlers
* @version $Id$
*
* Base front-end controller
*/
class eControllerFront extends eController
{
/**
* Plugin name - used to check if plugin is installed
* Set this only if plugin requires installation
* @var string
*/
protected $plugin = null;
/**
* Default controller access
* @var integer
*/
protected $userclass = e_UC_PUBLIC;
/**
* Generic 404 page URL (redirect), SITEURL will be added
* @var string
*/
protected $e404 = '404.html';
/**
* Generic 403 page URL (redirect), SITEURL will be added
* @var string
*/
protected $e403 = '403.html';
/**
* Generic 404 route URL (forward)
* @var string
*/
protected $e404route = 'index/not-found';
/**
* Generic 403 route URL (forward)
* @var string
*/
protected $e403route = 'index/access-denied';
/**
* View renderer objects
* @var array
*/
protected $_validator;
/**
* Per action access
* Format 'action' => userclass
* @var array
*/
protected $access = array();
/**
* User input filter (_GET)
* Format 'action' => array(var => validationArray)
* @var array
*/
protected $filter = array();
/**
* Base constructor - set 404/403 locations
*/
public function __construct(eRequest $request, eResponse $response = null)
{
parent::__construct($request, $response);
$this->_init();
}
/**
* Base init, called after the public init() - handle access restrictions
* The base init() method is able to change controller variables on the fly (e.g. access, filters, etc)
*/
final protected function _init()
{
// plugin check
if(null !== $this->plugin)
{
if(!e107::isInstalled($this->plugin))
{
$this->forward403();
return;
}
}
// global controller restriction
if(!e107::getUser()->checkClass($this->userclass, false))
{
$this->forward403();
return;
}
// by action access
if(!$this->checkActionPermissions()) exit;
// _GET input validation
$this->validateInput();
// Set Render mode to module-controller-action, override possible within the action
$this->getResponse()->setRenderMod(str_replace('/', '-', $this->getRequest()->getRoute()));
}
/**
* Check persmission for current action
* @return boolean
*/
protected function checkActionPermissions()
{
// per action restrictions
$action = $this->getRequest()->getAction();
if(isset($this->access[$action]) && !e107::getUser()->checkClass($this->access[$action], false))
{
$this->forward403();
return false;
}
return true;
}
/**
* @return void
*/
public function redirect404()
{
e107::getRedirect()->redirect(SITEURL.$this->e404);
}
/**
* @return void
*/
public function redirect403()
{
e107::getRedirect()->redirect(SITEURL.$this->e403);
}
/**
* @return void
*/
public function forward404()
{
$this->_forward($this->e404route);
}
/**
* @return void
*/
public function forward403()
{
$this->_forward($this->e403route);
}
/**
* Controller validator object
* @return e_validator
*/
public function getValidator()
{
if(null === $this->_validator)
{
$this->_validator = new e_validator('controller');
}
return $this->_validator;
}
/**
* Register request parameters based on current $filter data (_GET only)
* Additional security layer
*/
public function validateInput()
{
$validator = $this->getValidator();
$request = $this->getRequest();
if(empty($this->filter) || !isset($this->filter[$request->getAction()])) return;
$validator->setRules($this->filter[$request->getAction()])
->validate($_GET);
$validData = $validator->getValidData();
foreach ($validData as $key => $value)
{
if(!$request->isRequestParam($key)) $request->setRequestParam($key, $value);
}
$validator->clearValidateMessages();
}
/**
* System error message proxy
* @param string $message
* @param boolean $session
*/
public function messageError($message, $session = false)
{
return e107::getMessage()->addError($message, 'default', $session);
}
/**
* System success message proxy
* @param string $message
* @param boolean $session
*/
public function messageSuccess($message, $session = false)
{
return e107::getMessage()->addSuccess($message, 'default', $session);
}
/**
* System warning message proxy
* @param string $message
* @param boolean $session
*/
public function messageWarning($message, $session = false)
{
return e107::getMessage()->addWarning($message, 'default', $session);
}
/**
* System debug message proxy
* @param string $message
* @param boolean $session
*/
public function messageDebug($message, $session = false)
{
return e107::getMessage()->addDebug($message, 'default', $session);
}
}
/**
* Request handler
*
*/
class eRequest
{
/**
* @var string
*/
protected $_module;
/**
* @var string
*/
protected $_controller;
/**
* @var string
*/
protected $_action;
/**
* Request status
* @var boolean
*/
protected $_dispatched = false;
/**
* @var array
*/
protected $_requestParams = array();
/**
* @var string
*/
protected $_basePath;
/**
* @var string
*/
protected $_pathInfo;
/**
* @var string
*/
protected $_requestInfo;
/**
* Pathinfo string used for initial system routing
*/
public $routePathInfo;
/**
* @var array
*/
protected $_routeHistory = array();
/**
* @var boolean if request is already routed - generally set by callbacks to notify router about route changes
*/
public $routed = false;
/**
* Name of the bootstrap file
* @var string
*/
public $singleEntry = 'index.php';
/**
* Request constructor
*/
public function __construct($route = null)
{
if(null !== $route)
{
$this->setRoute($route);
$this->routed = true;
}
}
/**
* Get system base path
* @return string
*/
public function getBasePath()
{
if(null == $this->_basePath)
{
$this->_basePath = e_HTTP;
if(!e107::getPref('url_disable_pathinfo')) $this->_basePath .= $this->singleEntry.'/';
}
return $this->_basePath;
}
/**
* Set system base path
* @param string $basePath
* @return eRequest
*/
public function setBasePath($basePath)
{
$this->_basePath = $basePath;
return $this;
}
/**
* Get path info
* If not set, it'll be auto-retrieved
* @return string path info
*/
public function getPathInfo()
{
if(null == $this->_pathInfo)
{
if($this->getBasePath() == $this->getRequestInfo())
$this->_pathInfo = ''; // map to indexRoute
else
$this->_pathInfo = substr($this->getRequestInfo(), strlen($this->getBasePath()));
if($this->_pathInfo && trim($this->_pathInfo, '/') == trim($this->singleEntry, '/')) $this->_pathInfo = '';
}
return $this->_pathInfo;
}
/**
* Override path info
* @param string $pathInfo
* @return eRequest
*/
public function setPathInfo($pathInfo)
{
$this->_pathInfo = $pathInfo;
return $this;
}
/**
* @return string request info
*/
public function getRequestInfo()
{
if(null === $this->_requestInfo)
{
$this->_requestInfo = e_REQUEST_HTTP;
}
return $this->_requestInfo;
}
/**
* Override request info
* @param string $requestInfo
* @return eRequest
*/
public function setRequestInfo($requestInfo)
{
$this->_requestInfo = $requestInfo;
return $this;
}
/**
* Quick front page check
*/
public static function isFrontPage($entryScript = 'index.php', $currentPathInfo = e_REQUEST_HTTP)
{
$basePath = e_HTTP;
if(!e107::getPref('url_disable_pathinfo')) $basePath .= $entryScript.'/';
return ($basePath == $currentPathInfo);
}
/**
* Get current controller string
* @return string
*/
public function getController()
{
return $this->_controller;
}
/**
* Get current controller name
* Example: requested controller-name or 'controller name' -> converted to controller_name
* @return string
*/
public function getControllerName()
{
return eHelper::underscore($this->_controller);
}
/**
* Set current controller name
* Example: controller_name OR 'controller name' -> converted to controller-name
* Always sanitized
* @param string $controller
* @return eRequest
*/
public function setController($controller)
{
$this->_controller = strtolower(eHelper::dasherize($this->sanitize($controller)));
return $this;
}
/**
* Get current module string
* @return string
*/
public function getModule()
{
return $this->_module;
}
/**
* Get current module name
* Example: module-name OR 'module name' -> converted to module_name
* @return string
*/
public function getModuleName()
{
return eHelper::underscore($this->_module);
}
/**
* Set current module name
* Example: module_name OR 'module name' -> converted to module-name
* Always sanitized
* @param string $module
* @return eRequest
*/
public function setModule($module)
{
$this->_module = strtolower(eHelper::dasherize($this->sanitize($module)));
return $this;
}
/**
* Get current action string
* @return string
*/
public function getAction()
{
return $this->_action;
}
/**
* Get current action name
* Example: action-name OR 'action name' OR action_name -> converted to ActionName
* @return string
*/
public function getActionName()
{
return eHelper::camelize($this->_action, true);
}
/**
* Get current action method name
* Example: action-name OR 'action name' OR action_name -> converted to actionActionName
* @return string
*/
public function getActionMethodName()
{
return 'action'.eHelper::camelize($this->_action, true);
}
/**
* Set current action name
* Example: action_name OR 'action name' OR Action_Name OR 'Action Name' -> converted to ation-name
* Always sanitized
* @param string $action
* @return eRequest
*/
public function setAction($action)
{
$this->_action = strtolower(eHelper::dasherize($this->sanitize($action)));
return $this;
}
/**
* Get current route string/array -> module/controller/action
* @param boolean $array
* @return string|array route
*/
public function getRoute($array = false)
{
if(!$this->getModule())
{
$route = array('index', 'index', 'index');
}
else
{
$route = array(
$this->getModule(),
$this->getController() ? $this->getController() : 'index',
$this->getAction() ? $this->getAction() : 'index',
);
}
return ($array ? $route : implode('/', $route));
}
/**
* Set current route
* @param string $route module/controller/action
* @return eRequest
*/
public function setRoute($route)
{
if(null === $route)
{
$this->_module = null;
$this->_controller = null;
$this->_action = null;
}
return $this->initFromRoute($route);
}
/**
* System routing track, used in controllers forwarder
* @param string $route
* @return eRequest
*/
public function addRouteHistory($route)
{
$this->_routeHistory[] = $route;
return $this;
}
/**
* Retrieve route from history track
* Based on $source we can retrieve
* - array of all history records
* - 'first' route record
* - 'last' route record
* - history record by its index number
* @param mixed $source
* @return string|array
*/
public function getRouteHistory($source = null)
{
if(null === $source) return $this->_routeHistory;
if(!$this->_routeHistory) return null;
elseif('last' === $source)
{
return $this->_routeHistory[count($this->_routeHistory) -1];
}
elseif('first' === $source)
{
return $this->_routeHistory[0];
}
elseif(is_int($source))
{
return isset($this->_routeHistory[$source]) ? $this->_routeHistory[$source] : null;
}
return null;
}
/**
* Search route history for the given $route
*
* @param string $route
* @return integer route index or false if not found
*/
public function findRouteHistory($route)
{
return array_search($route, $this->_routeHistory);
}
/**
* Populate module, controller and action from route string
* @param string $route
* @return eRequest
*/
public function initFromRoute($route)
{
$route = trim($route, '/');
if(!$route)
{
$route = 'index/index/index';
}
$parts = explode('/', $route);
$this->setModule($parts[0])
->setController(vartrue($parts[1], 'index'))
->setAction(vartrue($parts[2], 'index'));
return $this;//->getRoute(true);
}
/**
* Get request parameter
* @param string $key
* @param string $default value if key not set
* @return mixed value
*/
public function getRequestParam($key, $default = null)
{
return (isset($this->_requestParams[$key]) ? $this->_requestParams[$key] : $default);
}
/**
* Check if request parameter exists
* @param string $key
* @return boolean
*/
public function isRequestParam($key)
{
return isset($this->_requestParams[$key]);
}
/**
* Get request parameters array
* @return array value
*/
public function getRequestParams()
{
return $this->_requestParams;
}
/**
* Set request parameter
* @param string $key
* @param mixed $value
* @return eRequest
*/
public function setRequestParam($key, $value)
{
$this->_requestParams[$key] = $value;
return $this;
}
/**
* Set request parameters
* @param array $params
* @return eRequest
*/
public function setRequestParams($params)
{
$this->_requestParams = $params;
return $this;
}
/**
* Populate current request parameters (_GET scope)
* @return eRequest
*/
public function populateRequestParams()
{
$rp = $this->getRequestParams();
foreach ($rp as $key => $value)
{
$_GET[$key] = $value;
}
return $this;
}
/**
* More BC
* @param string $qstring
* @return eRequest
*/
public function setLegacyQstring($qstring = null)
{
if(defined('e_QUERY')) return $this;
if(null === $qstring)
{
$qstring = self::getQueryString();
}
if(!defined('e_SELF'))
{
define("e_SELF", e_REQUEST_SELF);
}
if(!defined('e_QUERY'))
{
define("e_QUERY", $qstring);
}
$_SERVER['QUERY_STRING'] = e_QUERY;
if(strpos(e_QUERY,"=")!==false ) // Fix for legacyQuery using $_GET ie. ?x=y&z=1 etc.
{
parse_str(str_replace(array('&amp;'), array('&'), e_QUERY),$tmp);
foreach($tmp as $key=>$value)
{
$_GET[$key] = $value;
}
}
return $this;
}
/**
* And More BC :/
* @param string $page
* @return eRequest
*/
public function setLegacyPage($page = null)
{
if(defined('e_PAGE')) return $this;
if(null === $page)
{
$page = eFront::isLegacy();
}
if(!$page)
{
define('e_PAGE', $this->singleEntry);
}
else define('e_PAGE', basename(str_replace(array('{', '}'), '/', $page)));
return $this;
}
/**
* And More from the same - BC :/
* @return string
*/
public static function getQueryString()
{
$qstring = '';
if($_SERVER['QUERY_STRING'])
{
$qstring = str_replace(array('{', '}', '%7B', '%7b', '%7D', '%7d'), '', rawurldecode($_SERVER['QUERY_STRING']));
}
$qstring = str_replace('&', '&amp;', e107::getParser()->post_toForm($qstring));
return $qstring;
}
/**
* Basic sanitize method for module, controller and action input values
* @param string $str string to be sanitized
* @param string $pattern optional replace pattern
* @param string $replace optional replace string, defaults to dash
*/
public function sanitize($str, $pattern='', $replace='-')
{
if (!$pattern) $pattern = '/[^\w\pL-]/u';
return preg_replace($pattern, $replace, $str);
}
/**
* Set dispatched status of the request
* @param boolean $mod
* @return eRequest
*/
public function setDispatched($mod)
{
$this->_dispatched = $mod ? true : false;
return $this;
}
/**
* Get dispatched status of the request
* @return boolean
*/
public function isDispatched()
{
return $this->_dispatched;
}
}
/**
*
*/
class eResponse
{
protected $_body = array('default' => '');
protected $_title = array('default' => array());
protected $_e_PAGETITLE = array(); // partial <title> tag.
protected $_e_PAGETITLE_OVERRIDE = array(); // Full <title> tag
protected $_META_DESCRIPTION = array();
protected $_META_KEYWORDS = array();
protected $_render_mod = array('default' => 'default');
protected $_meta_title_separator = ' - ';
protected $_meta_name_only = array(
'keywords', 'viewport', 'robots', 'twitter:url', 'twitter:title', 'twitter:card',
'twitter:image', 'twitter:description',
); // Keep FB happy.
protected $_meta_property_only = array( // Keep FB happy.
'article:section', 'article:tag', 'article:published_time', 'article:modified_time',
'og:description', 'og:image', 'og:title', 'og:updated_time','og:url', 'og:type'
);
protected $_meta_multiple = array();
protected $_meta = array();
protected $_meta_robot_types = array('noindex'=>'NoIndex', 'nofollow'=>'NoFollow','noarchive'=>'NoArchive','noimageindex'=>'NoImageIndex' );
protected $_title_separator = ' &raquo; ';
protected $_content_type = 'html';
protected $_content_type_arr = array(
'html' => 'text/html',
'css' => 'text/css',
'xml' => 'text/xml',
'json' => 'application/json',
'js' => 'application/javascript',
'rss' => 'application/rss+xml',
'soap' => 'application/soap+xml',
);
protected $_params = array(
'render' => true,
'meta' => false,
'jsonNoTitle' => false,
'jsonRender' => false,
);
/**
* @return string[]
*/
public function getRobotTypes()
{
return $this->_meta_robot_types;
}
/**
* @return array
*/
public function getRobotDescriptions()
{
$_meta_robot_descriptions = array(
'noindex' => LAN_ROBOTS_NOINDEX,
'nofollow' => LAN_ROBOTS_NOFOLLOW,
'noarchive' => LAN_ROBOTS_NOARCHIVE,
'noimageindex' => LAN_ROBOTS_NOIMAGE );
return $_meta_robot_descriptions;
}
/**
* @param $key
* @param $value
* @return $this
*/
public function setParam($key, $value)
{
$this->_params[$key] = $value;
return $this;
}
/**
* @param $params
* @return $this
*/
public function setParams($params)
{
$this->_params = $params;
return $this;
}
/**
* @param $key
* @param $default
* @return mixed|null
*/
public function getParam($key, $default = null)
{
return (isset($this->_params[$key]) ? $this->_params[$key] : $default);
}
/**
* @param $key
* @return bool
*/
public function isParam($key)
{
return isset($this->_params[$key]);
}
/**
* @param $typeName
* @param $mediaType
* @return $this
*/
public function addContentType($typeName, $mediaType)
{
$this->_content_type_arr[$typeName] = $mediaType;
return $this;
}
/**
* @return string
*/
public function getContentType()
{
return $this->_content_type;
}
/**
* @param $typeName
* @return mixed|string|void
*/
public function getContentMediaType($typeName)
{
if(isset($this->_content_type_arr[$typeName]))
return $this->_content_type_arr[$typeName];
}
/**
* @param $typeName
* @return void
*/
public function setContentType($typeName)
{
$this->_content_type = $typeName;
}
/**
* @return eResponse
*/
public function sendContentType()
{
$ctypeStr = $this->getContentMediaType($this->getContentType());
if($ctypeStr)
{
header('Content-type: '.$this->getContentMediaType($this->getContentType()).'; charset=utf-8', TRUE);
}
return $this;
}
/**
* @return eResponse
*/
public function addHeader($header, $override = false, $responseCode = null)
{
header($header, $override, $responseCode);
return $this;
}
/**
* Append content
* @param string $body
* @param string $ns namespace
* @return eResponse
*/
public function appendBody($body, $ns = 'default')
{
if(!isset($this->_body[$ns]))
{
$this->_body[$ns] = '';
}
$this->_body[$ns] .= $body;
return $this;
}
/**
* Set content
* @param string $body
* @param string $ns namespace
* @return eResponse
*/
public function setBody($body, $ns = 'default')
{
$this->_body[$ns] = $body;
return $this;
}
/**
* @param $name
* @param $content
* @return $this
*/
public function setMeta($name, $content)
{
foreach($this->_meta as $k=>$v)
{
if(!empty($v['name']) && ($v['name'] === $name))
{
$this->_meta[$k]['content'] = $content;
}
}
return $this;
}
/**
* Removes a Meta tag by name/property.
*
* @param string $name
* 'name' or 'property' for the meta tag we want to remove.
*
* @return eResponse $this
*/
public function removeMeta($name)
{
foreach($this->_meta as $k=>$v)
{
// Meta tags like: <meta content="..." name="description" />
if(isset($v['name']) && $v['name'] === $name)
{
unset($this->_meta[$k]);
continue;
}
// Meta tags like: <meta content="..." property="og:title" />
if(isset($v['property']) && $v['property'] === $name)
{
unset($this->_meta[$k]);
}
}
return $this;
}
/**
* Prepend content
* @param string $body
* @param string $ns namespace
* @return eResponse
*/
function prependBody($body, $ns = 'default')
{
if(!isset($this->_body[$ns]))
{
$this->_body[$ns] = '';
}
// $this->_body[$ns] = $content.$this->_body[$ns];
return $this;
}
/**
* Get content
* @param string $ns
* @param boolean $reset
* @return string
*/
public function getBody($ns = 'default', $reset = false)
{
if(!isset($this->_body[$ns]))
{
$this->_body[$ns] = '';
}
$ret = $this->_body[$ns];
if($reset) unset($this->_body[$ns]);
return $ret;
}
/**
* @param string $title
* @param string $ns
* @return eResponse
*/
function setTitle($title, $ns = 'default')
{
if(!is_string($ns) || empty($ns))
{
$this->_title['default'] = array((string) $title);
}
else
{
$this->_title[$ns] = array((string) $title);
}
return $this;
}
/**
* @param string $title
* @param string $ns
* @return eResponse
*/
function appendTitle($title, $ns = 'default')
{
if(empty($title))
{
return $this;
}
if(!is_string($ns) || empty($ns))
{
$ns = 'default';
}
elseif(!isset($this->_title[$ns]))
{
$this->_title[$ns] = array();
}
$this->_title[$ns][] = (string) $title;
return $this;
}
/**
* @param string $title
* @param string $ns
* @return eResponse
*/
function prependTitle($title, $ns = 'default')
{
if(empty($title))
{
return $this;
}
if(!is_string($ns) || empty($ns))
{
$ns = 'default';
}
elseif(!isset($this->_title[$ns]))
{
$this->_title[$ns] = array();
}
array_unshift($this->_title[$ns], $title);
return $this;
}
/**
* Assemble title
* @param string $ns
* @param bool $reset
* @return string
*/
function getTitle($ns = 'default', $reset = false)
{
if(!is_string($ns) || empty($ns))
{
$ret = implode($this->_title_separator, $this->_title['default']);
if($reset)
$this->_title['default'] = '';
}
elseif(isset($this->_title[$ns]))
{
$ret = implode($this->_title_separator, $this->_title[$ns]);
if($reset)
unset($this->_title[$ns]);
}
else
{
$ret = '';
}
return $ret;
}
/**
*
* @param string $render_mod
* @param mixed $ns
* @return eResponse
*/
function setRenderMod($render_mod, $ns = 'default')
{
$this->_render_mod[$ns] = $render_mod;
return $this;
}
/**
* Retrieve render mod
* @param mixed $ns
* @return mixed
*/
function getRenderMod($ns = 'default')
{
if(!is_string($ns) || empty($ns))
{
$ns = 'default';
}
return vartrue($this->_render_mod[$ns], null);
}
/**
* Generic meta information
* Example usage:
* addMeta('og:title', 'My Title');
* addMeta(null, 30, array('http-equiv' => 'refresh'));
* addMeta(null, null, array('http-equiv' => 'refresh', 'content' => 30)); // same as above
* @param string $name 'name' attribute value, or null to avoid it
* @param string $content 'content' attribute value, or null to avoid it
* @param array $extended format 'attribute_name' => 'value'
* @return eResponse
*/
public function addMeta($name = null, $content = null, $extended = array())
{
if(empty($content)){ return $this; } // content is required, otherwise ignore.
//TODO need an option that allows subsequent entries to overwrite existing ones.
//ie. 'description' and 'keywords' should never be duplicated, but overwritten by plugins and other non-pref-based meta data.
$attr = array();
if(null !== $name)
{
// $key = (substr($name,0,3) == 'og:') ? 'property' : 'name';
// $attr[$key] = $name;
if(!in_array($name, $this->_meta_name_only))
{
$attr['property'] = $name; // giving both should be valid and avoid issues with FB and others.
}
if(!in_array($name, $this->_meta_property_only))
{
$attr['name'] = $name;
}
}
if(null !== $content) $attr['content'] = $content;
if(!empty($extended))
{
if(!empty($attr)) $attr = array_merge($attr, $extended);
else $attr = $extended;
}
if(!empty($attr))
{
if(!in_array($name, $this->_meta_multiple)) // prevent multiple keyword tags.
{
$this->_meta[$name] = $attr;
}
else // multiple allowed.
{
$this->_meta[] = $attr;
}
}
return $this;
}
/**
* Render meta tags, registered via addMeta() method
* @return string
*/
public function renderMeta()
{
$attrData = '';
e107::getEvent()->trigger('system_meta_pre', $this->_meta);
$pref = e107::getPref();
if(!empty($pref['meta_keywords'][e_LANGUAGE])) // Always append (global) meta keywords to the end.
{
$tmp1 = (array) explode(",", $this->getMetaKeywords());
$tmp2 = (array) explode(",", $pref['meta_keywords'][e_LANGUAGE]);
$tmp3 = array_unique(array_merge($tmp1,$tmp2));
$this->setMeta('keywords', implode(',',$tmp3));
}
e107::getDebug()->log($this->_meta);
foreach ($this->_meta as $attr)
{
$attrData .= '<meta';
foreach ($attr as $p => $v)
{
$attrData .= ' '.preg_replace('/[^\w\-]/', '', $p).'="'.str_replace(array('"', '<'), '', $v).'"';
}
$attrData .= ' />'."\n";
}
return $attrData;
}
/**
* Add meta title, description and keywords
*
* @param string $meta property name
* @param string $content meta content
* @return eResponse
*/
function addMetaData($meta, $content)
{
$meta = '_' . $meta;
if(isset($this->$meta) && !empty($content))
{
$content = str_replace('&amp;', '&', $content);
if($meta !== '_e_PAGETITLE' && $meta !== '_e_PAGETITLE_OVERRIDE')
{
$content = htmlspecialchars((string) $content, ENT_QUOTES, 'UTF-8');
}
$this->{$meta}[] = $content;
}
return $this;
}
/**
* Get meta title, description and keywords
*
* @param string $meta property name
* @return string
*/
function getMetaData($meta, $separator = '')
{
$meta = '_' . $meta;
if(isset($this->$meta) && !empty($this->$meta))
{
return implode($separator, $this->$meta);
}
return '';
}
/**
* Return an array of all meta data
* @return array
*/
function getMeta()
{
return $this->_meta;
}
/**
* @param string $title
* @return eResponse
*/
public function addMetaTitle($title, $reset=false, $override=false)
{
if($reset)
{
if($override)
{
$this->_e_PAGETITLE_OVERRIDE = array();
}
else
{
$this->_e_PAGETITLE = array();
}
}
$title = str_replace(['&#39;','&#039;'], "'", $title);
if($override)
{
return $this->addMetaData('e_PAGETITLE_OVERRIDE', $title);
}
return $this->addMetaData('e_PAGETITLE', $title);
}
/**
* @return string
*/
public function getMetaTitle($override = false)
{
if($override)
{
return $this->getMetaData('e_PAGETITLE_OVERRIDE', $this->_meta_title_separator);
}
return $this->getMetaData('e_PAGETITLE', $this->_meta_title_separator);
}
/**
* @param string $description
* @return eResponse
*/
function addMetaDescription($description)
{
return $this->addMetaData('META_DESCRIPTION', $description);
}
/**
* @return string
*/
function getMetaDescription()
{
return $this->getMetaData('META_DESCRIPTION');
}
/**
* @param string $keywords
* @return eResponse
*/
function addMetaKeywords($keywords)
{
return $this->addMetaData('META_KEYWORDS', $keywords);
}
/**
* @return string
*/
function getMetaKeywords()
{
return $this->getMetaData('META_KEYWORDS', ',');
}
/**
* Send e107 meta-data
* @return eResponse
*/
function sendMeta()
{
//HEADERF already included or meta content already sent
if(e_AJAX_REQUEST || defined('USER_AREA') || defined('e_PAGETITLE'))
return $this;
if(!defined('e_PAGETITLE') && !empty($this->_e_PAGETITLE))
{
define('e_PAGETITLE', $this->getMetaTitle());
}
if(!defined('META_DESCRIPTION') && !empty($this->_META_DESCRIPTION))
{
define('META_DESCRIPTION', $this->getMetaDescription());
}
if(!defined('META_KEYWORDS') && !empty($this->_META_KEYWORDS))
{
define('META_KEYWORDS', $this->getMetaKeywords());
}
return $this;
}
/**
* Send Response Output - default method
* TODO - ajax send, using js_manager
* @param string $ns namespace/segment
* @param bool $return
* @param bool $render_message append system messages
* @return null|string
*/
function send($ns = null, $return = true, $render_message = true)
{
$content = $this->getBody($ns, true);
$render = $this->getParam('render');
$meta = $this->getParam('meta');
$this->sendContentType();
if($render_message)
{
$content = eMessage::getInstance()->render().$content;
}
if($meta)
{
$this->sendMeta();
}
//render disabled by the controller
if(!$this->getRenderMod($ns))
{
$render = false;
}
if($render)
{
$render = e107::getRender();
if($return)
{
return $render->tablerender($this->getTitle($ns, true), $content, $this->getRenderMod($ns), true);
}
else
{
$render->tablerender($this->getTitle($ns, true), $content, $this->getRenderMod($ns));
return '';
}
}
elseif($return)
{
return $content;
}
else
{
print $content;
return '';
}
}
/**
* Send AJAX Json Response Output - default method
* It's fully compatible with the core dialog.js
* @param array $override override output associative array (header, body and footer keys)
* @param string $ns namespace/segment
* @param bool $render_message append system messages
*/
function sendJson($override = array(), $ns = null, $render_message = true)
{
if(!$ns) $ns = 'default';
$content = $this->getBody($ns, true);
// separate render parameter for json response, false by default
$render = $this->getParam('jsonRender');
if($render_message)
{
$content = eMessage::getInstance()->render().$content;
}
//render disabled by the controller
if(!$this->getRenderMod($ns))
{
$render = false;
}
$title = '';
if(!$this->getParam('jsonNoTitle'))
{
$titleArray = $this->_title;
$title = isset($titleArray[$ns]) ? array_pop($titleArray[$ns]) : '';
}
if($render)
{
$render = e107::getRender();
$content = $render->tablerender($this->getTitle($ns, true), $content, $this->getRenderMod($ns), true);
}
$jshelper = e107::getJshelper();
$override = array_merge(array(
'header' => $title,
'body' => $content,
// 'footer' => $statusText, // FIXME $statusText has no value.
), $override);
echo $jshelper->buildJsonResponse($override);
$jshelper->sendJsonResponse(null);
}
/**
* JS manager
* @return e_jsmanager
*/
function getJs()
{
return e107::getJs();
}
}
/**
* We move all generic helper functionallity here - a lot of candidates in e107 class
*
*/
class eHelper
{
protected static $_classRegEx = '#[^\w\s\-]#';
protected static $_idRegEx = '#[^\w\-]#';
protected static $_styleRegEx = '#[^\w\s\-\.;:!]#';
protected static $_systemNotify = 'systemNotifications';
/**
* @param $string
* @return array|string|string[]|null
*/
public static function secureClassAttr($string)
{
return preg_replace(self::$_classRegEx, '', $string);
}
/**
* @return array
*/
public static function getSystemNotification()
{
return (array) e107::getSession()->get(self::$_systemNotify);
}
/**
* @param string $id
* @param string $message
* @return array|e_core_session|null
*/
public static function addSystemNotification($id, $message)
{
return e107::getSession()->set(self::$_systemNotify.'/'.$id.'/message', $message);
}
/**
* @param $id
* @return array|e_core_session|null
*/
public static function clearSystemNotification($id = '')
{
$key = !empty($id) ? self::$_systemNotify.'/'.$id : self::$_systemNotify;
return e107::getSession()->clear($key);
}
/**
* @param $string
* @return array|string|string[]|null
*/
public static function secureIdAttr($string)
{
$string = str_replace(array('/','_'),'-',$string);
return preg_replace(self::$_idRegEx, '', $string);
}
/**
* @param $string
* @return array|string|string[]|null
*/
public static function secureStyleAttr($string)
{
return preg_replace(self::$_styleRegEx, '', $string);
}
/**
* @param $safeArray
* @return string
*/
public static function buildAttr($safeArray)
{
return http_build_query($safeArray, null, '&');
}
/**
* @param $title
* @return string
*/
public static function formatMetaTitle($title)
{
$title = trim(str_replace(array('"', "'"), '', strip_tags(e107::getParser()->toHTML($title, TRUE))));
return trim(preg_replace('/[\s,]+/', ' ', str_replace('_', ' ', $title)));
}
/**
* @param $sef
* @return string
*/
public static function secureSef($sef)
{
return trim(preg_replace('/[^\w\pL\s\-+.,]+/u', '', strip_tags(e107::getParser()->toHTML($sef, TRUE))));
}
/**
* @param $keywordString
* @return string
*/
public static function formatMetaKeys($keywordString)
{
$keywordString = preg_replace('/[^\w\pL\s\-.,+]/u', '', strip_tags(e107::getParser()->toHTML($keywordString, TRUE)));
return trim(preg_replace('/[\s]?,[\s]?/', ',', str_replace('_', ' ', $keywordString)));
}
/**
* @param $descrString
* @return string
*/
public static function formatMetaDescription($descrString)
{
$descrString = preg_replace('/[\r]*\n[\r]*/', ' ', trim(str_replace(array('"', "'"), '', strip_tags(e107::getParser()->toHTML($descrString, TRUE)))));
return trim(preg_replace('/[\s]+/', ' ', str_replace('_', ' ', $descrString)));
}
/**
* Convert title to valid SEF URL string
* Type ending with 'l' stands for 'to lowercase', ending with 'c' - 'to camel case'
* @param string $title
* @param string $type dashl|dashc|dash|underscorel|underscorec|underscore|plusl|plusc|plus|none
* @return array|string|string[]
*/
public static function title2sef($title, $type = null)
{
/*$char_map = array(
// Latin
'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'AE', 'Ç' => 'C',
'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I',
'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ő' => 'O',
'Ø' => 'O', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ű' => 'U', 'Ý' => 'Y', 'Þ' => 'TH',
'ß' => 'ss',
'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'ae', 'ç' => 'c',
'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i',
'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ő' => 'o',
'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', 'ű' => 'u', 'ý' => 'y', 'þ' => 'th',
'ÿ' => 'y',
// Latin symbols
'©' => '(c)',
// Greek
'Α' => 'A', 'Β' => 'B', 'Γ' => 'G', 'Δ' => 'D', 'Ε' => 'E', 'Ζ' => 'Z', 'Η' => 'H', 'Θ' => '8',
'Ι' => 'I', 'Κ' => 'K', 'Λ' => 'L', 'Μ' => 'M', 'Ν' => 'N', 'Ξ' => '3', 'Ο' => 'O', 'Π' => 'P',
'Ρ' => 'R', 'Σ' => 'S', 'Τ' => 'T', 'Υ' => 'Y', 'Φ' => 'F', 'Χ' => 'X', 'Ψ' => 'PS', 'Ω' => 'W',
'Ά' => 'A', 'Έ' => 'E', 'Ί' => 'I', 'Ό' => 'O', 'Ύ' => 'Y', 'Ή' => 'H', 'Ώ' => 'W', 'Ϊ' => 'I',
'Ϋ' => 'Y',
'α' => 'a', 'β' => 'b', 'γ' => 'g', 'δ' => 'd', 'ε' => 'e', 'ζ' => 'z', 'η' => 'h', 'θ' => '8',
'ι' => 'i', 'κ' => 'k', 'λ' => 'l', 'μ' => 'm', 'ν' => 'n', 'ξ' => '3', 'ο' => 'o', 'π' => 'p',
'ρ' => 'r', 'σ' => 's', 'τ' => 't', 'υ' => 'y', 'φ' => 'f', 'χ' => 'x', 'ψ' => 'ps', 'ω' => 'w',
'ά' => 'a', 'έ' => 'e', 'ί' => 'i', 'ό' => 'o', 'ύ' => 'y', 'ή' => 'h', 'ώ' => 'w', 'ς' => 's',
'ϊ' => 'i', 'ΰ' => 'y', 'ϋ' => 'y', 'ΐ' => 'i',
// Turkish
'Ş' => 'S', 'İ' => 'I', 'Ç' => 'C', 'Ü' => 'U', 'Ö' => 'O', 'Ğ' => 'G',
'ş' => 's', 'ı' => 'i', 'ç' => 'c', 'ü' => 'u', 'ö' => 'o', 'ğ' => 'g',
// Russian
'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', 'Е' => 'E', 'Ё' => 'Yo', 'Ж' => 'Zh',
'З' => 'Z', 'И' => 'I', 'Й' => 'J', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', 'О' => 'O',
'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C',
'Ч' => 'Ch', 'Ш' => 'Sh', 'Щ' => 'Sh', 'Ъ' => '', 'Ы' => 'Y', 'Ь' => '', 'Э' => 'E', 'Ю' => 'Yu',
'Я' => 'Ya',
'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e', 'ё' => 'yo', 'ж' => 'zh',
'з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', 'о' => 'o',
'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c',
'ч' => 'ch', 'ш' => 'sh', 'щ' => 'sh', 'ъ' => '', 'ы' => 'y', 'ь' => '', 'э' => 'e', 'ю' => 'yu',
'я' => 'ya',
// Ukrainian
'Є' => 'Ye', 'І' => 'I', 'Ї' => 'Yi', 'Ґ' => 'G',
'є' => 'ye', 'і' => 'i', 'ї' => 'yi', 'ґ' => 'g',
// Czech
'Č' => 'C', 'Ď' => 'D', 'Ě' => 'E', 'Ň' => 'N', 'Ř' => 'R', 'Š' => 'S', 'Ť' => 'T', 'Ů' => 'U',
'Ž' => 'Z',
'č' => 'c', 'ď' => 'd', 'ě' => 'e', 'ň' => 'n', 'ř' => 'r', 'š' => 's', 'ť' => 't', 'ů' => 'u',
'ž' => 'z',
// Polish
'Ą' => 'A', 'Ć' => 'C', 'Ę' => 'e', 'Ł' => 'L', 'Ń' => 'N', 'Ó' => 'o', 'Ś' => 'S', 'Ź' => 'Z',
'Ż' => 'Z',
'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l', 'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z',
'ż' => 'z',
// Latvian
'Ā' => 'A', 'Č' => 'C', 'Ē' => 'E', 'Ģ' => 'G', 'Ī' => 'i', 'Ķ' => 'k', 'Ļ' => 'L', 'Ņ' => 'N',
'Š' => 'S', 'Ū' => 'u', 'Ž' => 'Z',
'ā' => 'a', 'č' => 'c', 'ē' => 'e', 'ģ' => 'g', 'ī' => 'i', 'ķ' => 'k', 'ļ' => 'l', 'ņ' => 'n',
'š' => 's', 'ū' => 'u', 'ž' => 'z'
);*/
$tp = e107::getParser();
// issue #3245: strip all html and bbcode before processing
$title = $tp->toText($title);
$title = $tp->toASCII($title);
$title = str_replace(array('/',' ',","),' ',$title);
$title = str_replace(array("&","(",")"),'',$title);
$title = preg_replace('/[^\w\pL\s.-]/u', '', strip_tags(e107::getParser()->toHTML($title, TRUE)));
$title = trim(preg_replace('/[\s]+/', ' ', str_replace('_', ' ', $title)));
$title = str_replace(array(' - ',' -','- ','--'),'-',$title); // cleanup to avoid ---
$words = str_word_count($title,1, '1234567890');
$limited = array_slice($words, 0, 14); // Limit number of words to 14. - any more and it ain't friendly.
$title = implode(" ",$limited);
if(null === $type)
{
$type = e107::getPref('url_sef_translate');
}
switch ($type)
{
case 'dashl': //dasherize, to lower case
return self::dasherize($tp->ustrtolower($title));
break;
case 'dashc': //dasherize, camel case
return self::dasherize(self::camelize($title, true, ' '));
break;
case 'dash': //dasherize
return self::dasherize($title);
break;
case 'underscorel': ///underscore, to lower case
return self::underscore($tp->ustrtolower($title));
break;
case 'underscorec': ///underscore, camel case
return self::underscore(self::camelize($title, true, ' '));
break;
case 'underscore': ///underscore
return self::underscore($title);
break;
case 'plusl': ///plus separator, to lower case
return str_replace(' ', '+', $tp->ustrtolower($title));
break;
case 'plusc': ///plus separator, to lower case
return str_replace(' ', '+', self::camelize($title, true, ' '));
break;
case 'plus': ///plus separator
return str_replace(' ', '+', $title);
break;
case 'none':
default:
return $title;
break;
}
}
/**
* Return a memory value formatted helpfully
* $dp overrides the number of decimal places displayed - realistically, only 0..3 are sensible
* FIXME e107->parseMemorySize() START
* - move here all e107 class ban/ip related methods
* - out of (integer) range case?
* 32 bit systems range: -2147483648 to 2147483647
* 64 bit systems range: -9223372036854775808 9223372036854775807
* {@link http://www.php.net/intval}
* FIXME e107->parseMemorySize() END
*
* @param integer $size
* @param integer $dp
* @return string formatted size
*/
public static function parseMemorySize($size, $dp = 2)
{
if (!$size) { $size = 0; }
if ($size < 4096)
{ // Fairly arbitrary limit below which we always return number of bytes
return number_format($size, 0).CORE_LAN_B;
}
$size = $size / 1024;
$memunit = CORE_LAN_KB;
if ($size > 1024)
{ /* 1.002 mb, etc */
$size = $size / 1024;
$memunit = CORE_LAN_MB;
}
if ($size > 1024)
{ /* show in GB if >1GB */
$size = $size / 1024;
$memunit = CORE_LAN_GB;
}
if ($size > 1024)
{ /* show in TB if >1TB */
$size = $size / 1024;
$memunit = CORE_LAN_TB;
}
return (number_format($size, $dp).$memunit);
}
/**
* Get the current memory usage of the code
* If $separator argument is null, raw data (array) will be returned
*
* @param null|string $separator
* @return string|array memory usage
*/
public static function getMemoryUsage($separator = '/')
{
$ret = array();
if(function_exists("memory_get_usage"))
{
$ret[] = eHelper::parseMemorySize(memory_get_usage());
// With PHP>=5.2.0, can show peak usage as well
if (function_exists("memory_get_peak_usage")) $ret[] = eHelper::parseMemorySize(memory_get_peak_usage(TRUE));
}
else
{
$ret[] = 'Unknown';
}
return (null !== $separator ? implode($separator, $ret) : $ret);
}
/**
* @param $str
* @param $all
* @param $space
* @return string
*/
public static function camelize($str, $all = false, $space = '')
{
// clever recursion o.O
if($all) return self::camelize('-'.$str, false, $space);
$tmp = explode('-', str_replace(array('_', ' '), '-', e107::getParser()->ustrtolower($str)));
return trim(implode($space, array_map('ucfirst', $tmp)), $space);
}
/**
* @param $str
* @param $space
* @return string
*/
public static function labelize($str, $space = ' ')
{
return self::camelize($str, true, ' ');
}
/**
* @param $str
* @return array|string|string[]
*/
public static function dasherize($str)
{
return str_replace(array('_', ' '), '-', $str);
}
/**
* @param $str
* @return array|string|string[]
*/
public static function underscore($str)
{
return str_replace(array('-', ' '), '_', $str);
}
/**
* Parse generic shortcode parameter string
* Format expected: {SC=key=val&key1=val1...}
* Escape strings: \& => &
*
* @param string $parm
* @return array associative param array
*/
public static function scParams($parm)
{
if (!$parm) return array();
if (!is_array($parm))
{
$parm = str_replace('\&', '%%__amp__%%', $parm);
$parm = str_replace('&amp;', '&', $parm); // clean when it comes from the DB
parse_str($parm, $parm);
foreach ($parm as $k => $v)
{
$parm[str_replace('%%__amp__%%', '&', $k)] = str_replace('%%__amp__%%', '\&', $v);
}
}
return $parm;
}
/**
* Parse shortcode parameter string of type 'dual parameters' - advanced, more complex and slower(!) case
* Format expected: {SC=name|key=val&key1=val1...}
* Escape strings: \| => | , \& => & and \&amp; => &amp;
* Return array is formatted like this:
* 1 => string|array (depends on $name2array value) containing first set of parameters;
* 2 => array containing second set of parameters;
* 3 => string containing second set of parameters;
*
* @param string $parmstr
* @param boolean $first2array If true, first key (1) of the returned array will be parsed to array as well
* @return array
*/
public static function scDualParams($parmstr, $first2array = false)
{
if (!$parmstr) return array(1 => '', 2 => array(), 3 => '');
if (is_array($parmstr)) return $parmstr;
$parmstr = str_replace('&amp;', '&', $parmstr); // clean when it comes from the DB
$parm = explode('|', str_replace(array('\|', '\&amp;', '\&'), array('%%__pipe__%%', '%%__ampamp__%%', '%%__amp__%%'), $parmstr), 2);
$multi = str_replace('%%__pipe__%%', '|', $parm[0]);
if ($first2array)
{
parse_str($multi, $multi);
foreach ($multi as $k => $v)
{
$multi[str_replace(array('%%__ampamp__%%', '%%__amp__%%'), array('&amp;', '&'), $k)] = str_replace(array('%%__ampamp__%%', '%%__amp__%%'), array('&amp;', '&'), $v);
}
}
if (varset($parm[1]))
{
// second paramater as a string - allow to be further passed to shortcodes
$parmstr = str_replace(array('%%__pipe__%%', '%%__ampamp__%%', '%%__amp__%%'), array('\|', '\&amp;', '\&'), $parm[1]);
parse_str(str_replace('%%__pipe__%%', '|', $parm[1]), $params);
foreach ($params as $k => $v)
{
$params[str_replace(array('%%__ampamp__%%', '%%__amp__%%'), array('&amp;', '&'), $k)] = str_replace(array('%%__ampamp__%%', '%%__amp__%%'), array('&amp;', '&'), $v);
}
}
else
{
$parmstr = '';
$params = array();
}
return array(1 => $multi, 2 => $params, 3 => $parmstr);
}
/**
* Remove Social Media Trackers from a $_GET array based on key matches.
* @param array $get
* @return array
*/
public static function removeTrackers($get = array())
{
$trackers = array('fbclid','utm_source','utm_medium','utm_content','utm_campaign','elan', 'msclkid', 'gclid', 'gad', 'gad_source', 'mcp_token');
foreach($trackers as $val)
{
if(isset($get[$val]))
{
unset($get[$val]);
}
}
return $get;
}
/**
* Duplicates the value from a title form field into the meta-title form field.
* @param str $titleID eg. news-title
* @param str $metaTitleID eg. news-meta-title
* @return void
*/
public static function syncSEOTitle($titleID, $metaTitleID)
{
e107::js('footer-inline', '
$(window).on("load", function () {
if(!$("#'.$metaTitleID.'").val())
{
var title = $("#'.$titleID.'").val() + " | " + "'.SITENAME.'";
var charlimit = $("#'.$metaTitleID.'").attr("data-char-count");
$("#'.$metaTitleID.'").attr("placeholder",title);
if(title.length > charlimit)
{
$("#'.$metaTitleID.'").addClass("has-error");
}
}
});
$("#'.$metaTitleID.'").on("ready focus", function() {
var title = $("#'.$titleID.'").val() + " | " + "'.SITENAME.'";
if(!$(this).val())
{
$(this).val(title);
}
else
{
$(this).attr("placeholder",title);
}
});
$("#'.$titleID.'").on("input change focus", function()
{
var title = $("#'.$titleID.'").val() + " | " + "'.SITENAME.'";
$("#'.$metaTitleID.'").attr("placeholder",title);
});
');
}
}