* @copyright four for business AG * @license http://www.contenido.org/license/LIZENZ.txt * @link http://www.4fb.de * @link http://www.contenido.org */ if (!defined('CON_FRAMEWORK')) { die('Illegal call'); } /** * Mod Rewrite controller class. Extracts url parts and sets some necessary globals like: * - $idart * - $idcat * - $client * - $changeclient * - $lang * - $changelang * * @author Murat Purc * @package plugin * @subpackage Mod Rewrite */ class ModRewriteController extends ModRewriteBase { // Error constants const ERROR_CLIENT = 1; const ERROR_LANGUAGE = 2; const ERROR_CATEGORY = 3; const ERROR_ARTICLE = 4; const ERROR_POST_VALIDATION = 5; const FRONT_CONTENT = 'front_content.php'; /** * Extracted request uri path parts by path separator '/' * * @var array */ private $_aParts; /** * Extracted article name from request uri * * @var string */ private $_sArtName; /** * Remaining path for path resolver (see $GLOBALS['path']) * * @var string */ private $_sPath; /** * Incomming URL * * @var string */ private $_sIncommingUrl; /** * Resolved URL * * @var string */ private $_sResolvedUrl; /** * Client id used by this class * * @var int */ private $_iClientMR; /** * Language id used by this class * * @var int */ private $_iLangMR; /** * Flag about occured errors * * @var bool */ private $_bError = false; /** * One of ERROR_* constants or 0 * * @var int */ private $_iError = 0; /** * Flag about found routing definition * * @var bool */ private $_bRoutingFound = false; /** * Constructor, sets several properties. * * @param string $incommingUrl Incomming URL */ public function __construct($incommingUrl) { // CON-1266 make incomming URL lowercase if option "URLS to // lowercase" is set if (1 == $this->getConfig('use_lowercase_uri')) { $incommingUrl = strtolower($incommingUrl); } $this->_sIncommingUrl = $incommingUrl; $this->_aParts = array(); $this->_sArtName = ''; } /** * Getter for overwritten client id (see $GLOBALS['client']) * * @return int Client id */ public function getClient() { return $GLOBALS['client']; } /** * Getter for overwritten change client id (see $GLOBALS['changeclient']) * * @return int Change client id */ public function getChangeClient() { return $GLOBALS['changeclient']; } /** * Getter for article id (see $GLOBALS['idart']) * * @return int Article id */ public function getIdArt() { return $GLOBALS['idart']; } /** * Getter for category id (see $GLOBALS['idcat']) * * @return int Category id */ public function getIdCat() { return $GLOBALS['idcat']; } /** * Getter for language id (see $GLOBALS['lang']) * * @return int Language id */ public function getLang() { return $GLOBALS['lang']; } /** * Getter for change language id (see $GLOBALS['changelang']) * * @return int Change language id */ public function getChangeLang() { return $GLOBALS['changelang']; } /** * Getter for path (see $GLOBALS['path']) * * @return string Path, used by path resolver */ public function getPath() { return $this->_sPath; } /** * Getter for resolved url * * @return string Resolved url */ public function getResolvedUrl() { return $this->_sResolvedUrl; } /** * Returns a flag about found routing definition * * return bool Flag about found routing */ public function getRoutingFoundState() { return $this->_bRoutingFound; } /** * Getter for occured error state * * @return bool Flag for occured error */ public function errorOccured() { return $this->_bError; } /** * Getter for occured error state * * @return int Numeric error code */ public function getError() { return $this->_iError; } /** * Main function to call for mod rewrite related preprocessing jobs. * * Executes some private functions to extract request URI and to set needed membervariables * (client, language, article id, category id, etc.) */ public function execute() { if (parent::isEnabled() == false) { return; } $this->_extractRequestUri(); $this->_initializeClientId(); $this->_setClientId(); mr_loadConfiguration($this->_iClientMR); $this->_setLanguageId(); // second call after setting client and language $this->_extractRequestUri(true); $this->_setPathresolverSetting(); $this->_setIdart(); ModRewriteDebugger::add($this->_aParts, 'ModRewriteController::execute() _setIdart'); $this->_postValidation(); } /** * Extracts request URI and sets member variables $this->_sArtName and $this->_aParts * * @param bool $secondCall Flag about second call of this function, is needed * to re extract url if a routing definition was found */ private function _extractRequestUri($secondCall = false) { global $client; // get REQUEST_URI $requestUri = $_SERVER['REQUEST_URI']; // CON-1266 make request URL lowercase if option "URLS to // lowercase" is set if (1 == $this->getConfig('use_lowercase_uri')) { $requestUri = strtolower($requestUri); } // check for defined rootdir if (parent::getConfig('rootdir') !== '/' && strpos($requestUri, $this->_sIncommingUrl) === 0) { $this->_sIncommingUrl = str_replace(parent::getConfig('rootdir'), '/', $this->_sIncommingUrl); } $aUrlComponents = $this->_parseUrl($this->_sIncommingUrl); if (isset($aUrlComponents['path'])) { if (parent::getConfig('rootdir') !== '/' && strpos($aUrlComponents['path'], parent::getConfig('rootdir')) === 0) { $aUrlComponents['path'] = str_replace(parent::getConfig('rootdir'), '/', $aUrlComponents['path']); } if ($secondCall == true) { // @todo: implement real redirect of old front_content.php style urls // check for routing definition $routings = parent::getConfig('routing'); if (is_array($routings) && isset($routings[$aUrlComponents['path']])) { $aUrlComponents['path'] = $routings[$aUrlComponents['path']]; if (strpos($aUrlComponents['path'], self::FRONT_CONTENT) !== false) { // routing destination contains front_content.php $this->_bRoutingFound = true; // set client language, if not set before mr_setClientLanguageId($client); //rebuild URL $url = mr_buildNewUrl($aUrlComponents['path']); $aUrlComponents = $this->_parseUrl($url); // add query parameter to superglobal _GET if (isset($aUrlComponents['query'])) { $vars = null; parse_str($aUrlComponents['query'], $vars); $_GET = array_merge($_GET, $vars); } $this->_aParts = array(); } } else { return; } } $aPaths = explode('/', $aUrlComponents['path']); foreach ($aPaths as $p => $item) { if (!empty($item)) { // pathinfo would also work $arr = explode('.', $item); $count = count($arr); if ($count > 0 && '.' . strtolower($arr[$count - 1]) == parent::getConfig('file_extension')) { array_pop($arr); $this->_sArtName = trim(implode('.', $arr)); } else { $this->_aParts[] = $item; } } } if ($secondCall == true) { // reprocess extracting client and language $this->_setClientId(); mr_loadConfiguration($this->_iClientMR); $this->_setLanguageId(); } } ModRewriteDebugger::add($this->_aParts, 'ModRewriteController::_extractRequestUri() $this->_aParts'); // loop parts array and remove existing 'front_content.php' if ($this->_hasPartArrayItems()) { foreach ($this->_aParts as $p => $item) { if ($item == self::FRONT_CONTENT) { unset($this->_aParts[$p]); } } } } /** * Tries to initialize the client id. * This is required to load the proper plugin configuration for current client. */ private function _initializeClientId() { global $client, $changeclient, $load_client; $iClient = (isset($client) && (int) $client > 0) ? $client : 0; $iChangeClient = (isset($changeclient) && (int) $changeclient > 0) ? $changeclient : 0; $iLoadClient = (isset($load_client) && (int) $load_client > 0) ? $load_client : 0; $this->_iClientMR = 0; if ($iClient > 0 && $iChangeClient == 0) { $this->_iClientMR = $iClient; } elseif ($iChangeClient > 0) { $this->_iClientMR = $iChangeClient; } else { $this->_iClientMR = $iLoadClient; } if ((int) $this->_iClientMR > 0) { // set global client variable $client = (int) $this->_iClientMR; } } /** * Tries to initialize the language id. */ private function _initializeLanguageId() { global $lang, $changelang, $load_lang; $iLang = (isset($lang) && (int) $lang > 0) ? $lang : 0; $iChangeLang = (isset($changelang) && (int) $changelang > 0) ? $changelang : 0; $iLoadLang = (isset($load_lang) && (int) $load_lang > 0) ? $load_lang : 0; $this->_iLangMR = 0; if ($iLang > 0 && $iChangeLang == 0) { $this->_iLangMR = $iLang; } elseif ($iChangeLang > 0) { $this->_iLangMR = $iChangeLang; } else { $this->_iLangMR = $iLoadLang; } if ((int) $this->_iLangMR > 0) { // set global lang variable $lang = (int) $this->_iLangMR; } } /** * Detects client id from given url */ private function _setClientId() { global $client; if ($this->_bError) { return; } elseif ($this->_isRootRequest()) { // request to root return; } elseif (parent::getConfig('use_client') !== 1) { return; } if (parent::getConfig('use_client_name') == 1) { $detectedClientId = (int) ModRewrite::getClientId(array_shift($this->_aParts)); } else { $detectedClientId = (int) array_shift($this->_aParts); if ($detectedClientId > 0 && !ModRewrite::languageIdExists($detectedClientId)) { $detectedClientId = 0; } } if ($detectedClientId > 0) { // overwrite existing client variables $this->_iClientMR = $detectedClientId; $client = $detectedClientId; } else { $this->_setError(self::ERROR_CLIENT); } } /** * Sets language id */ private function _setLanguageId() { global $lang; if ($this->_bError) { return; } elseif ($this->_isRootRequest()) { // request to root return; } elseif (parent::getConfig('use_language') !== 1) { return; } if (parent::getConfig('use_language_name') == 1) { // thanks to Nicolas Dickinson for multi Client/Language BugFix $languageName = array_shift($this->_aParts); $detectedLanguageId = (int) ModRewrite::getLanguageId($languageName, $this->_iClientMR); } else { $detectedLanguageId = (int) array_shift($this->_aParts); if ($detectedLanguageId > 0 && !ModRewrite::clientIdExists($detectedLanguageId)) { $detectedLanguageId = 0; } } if ($detectedLanguageId > 0) { // overwrite existing language variables $this->_iLangMR = $detectedLanguageId; $lang = $detectedLanguageId; } else { $this->_setError(self::ERROR_LANGUAGE); } } /** * Sets path resolver and category id */ private function _setPathresolverSetting() { global $client, $lang, $load_lang, $idcat; if ($this->_bError) { return; } elseif (!$this->_hasPartArrayItems()) { return; } $this->_sPath = '/' . implode('/', $this->_aParts) . '/'; if (!isset($lang) || (int) $lang <= 0) { if ((int) $load_lang > 0) { // load_client is set in frontend/config.php $lang = (int) $load_lang; } else { $clCol = new cApiClientLanguageCollection(); $clCol->setWhere('idclient', $client); $clCol->query(); if ($clItem = $clCol->next()) { $lang = $clItem->get('idlang'); } } } $idcat = (int) ModRewrite::getCatIdByUrlPath($this->_sPath); if ($idcat == 0) { // category couldn't resolved $this->_setError(self::ERROR_CATEGORY); $idcat = null; } else { // unset $this->_sPath if $idcat could set, otherwhise it would be resolved again. unset($this->_sPath); } ModRewriteDebugger::add($idcat, 'ModRewriteController->_setPathresolverSetting $idcat'); ModRewriteDebugger::add($this->_sPath, 'ModRewriteController->_setPathresolverSetting $this->_sPath'); } /** * Sets article id */ private function _setIdart() { global $idcat, $idart, $lang; if ($this->_bError) { return; } else if ($this->_isRootRequest()) { return; } $iIdCat = (isset($idcat) && (int) $idcat > 0) ? $idcat : 0; $iIdArt = (isset($idart) && (int) $idart > 0) ? $idart : 0; $detectedIdart = 0; $defaultStartArtName = parent::getConfig('default_startart_name'); $currArtName = $this->_sArtName; // startarticle name in url if (parent::getConfig('add_startart_name_to_url') && !empty($currArtName)) { if ($currArtName == $defaultStartArtName) { // stored articlename is the default one, remove it ModRewrite::getArtIdByWebsafeName() // will find the real article name $currArtName = ''; } } // Last check, before detecting article id if ($iIdCat == 0 && $iIdArt == 0 && empty($currArtName)) { // no idcat, idart and article name // must be a request to root or with language name and/or client name part! return; } if ($iIdCat > 0 && $iIdArt == 0 && !empty($currArtName)) { // existing idcat with no idart and with article name $detectedIdart = (int) ModRewrite::getArtIdByWebsafeName($currArtName, $iIdCat, $lang); } elseif ($iIdCat > 0 && $iIdArt == 0 && empty($currArtName)) { if (parent::getConfig('add_startart_name_to_url') && ($currArtName == '' || $defaultStartArtName == '' || $defaultStartArtName == $this->_sArtName)) { // existing idcat without idart and without article name or with default start article name cInclude('classes', 'class.article.php'); $artColl = new ArticleCollection(array('idcat' => $idcat, 'start' => 1)); if ($artItem = $artColl->startArticle()) { $detectedIdart = (int) $artItem->get('idart'); } } } elseif ($iIdCat == 0 && $iIdArt == 0 && !empty($currArtName)) { // no idcat and idart but article name $detectedIdart = (int) ModRewrite::getArtIdByWebsafeName($currArtName, $iIdCat, $lang); } if ($detectedIdart > 0) { $idart = $detectedIdart; } elseif (!empty($currArtName)) { $this->_setError(self::ERROR_ARTICLE); } ModRewriteDebugger::add($detectedIdart, 'ModRewriteController->_setIdart $detectedIdart'); } /** * Does post validation of the extracted data. * * One main goal of this function is to prevent duplicated content, which could happen, if * the configuration 'startfromroot' is activated. */ private function _postValidation() { global $idcat, $idart, $client; if ($this->_bError || $this->_bRoutingFound || !$this->_hasPartArrayItems()) { return; } if (parent::getConfig('startfromroot') == 1 && parent::getConfig('prevent_duplicated_content') == 1) { // prevention of duplicated content if '/firstcat/' is directly requested! $idcat = (isset($idcat) && (int) $idcat > 0) ? $idcat : null; $idart = (isset($idart) && (int) $idart > 0) ? $idart : null; // compose new parameter $param = ''; if ($idcat) { $param .= 'idcat=' . (int) $idcat; } if ($idart) { $param .= ($param !== '') ? '&idart=' . (int) $idart : 'idart=' . (int) $idart; } if ($param == '') { return; } // set client language, if not set before mr_setClientLanguageId($client); //rebuild url $url = mr_buildNewUrl(self::FRONT_CONTENT . '?' . $param); $aUrlComponents = @parse_url($this->_sIncommingUrl); $incommingUrl = (isset($aUrlComponents['path'])) ? $aUrlComponents['path'] : ''; ModRewriteDebugger::add($url, 'ModRewriteController->_postValidation validate url'); ModRewriteDebugger::add($incommingUrl, 'ModRewriteController->_postValidation incommingUrl'); // now the new generated uri should be identical with the request uri if ($incommingUrl !== $url) { $this->_setError(self::ERROR_POST_VALIDATION); $idcat = null; } } } /** * Parses the url using defined separators * * @param string $url Incoming url * @return string Parsed url */ private function _parseUrl($url) { $this->_sResolvedUrl = $url; $oMrUrlUtil = ModRewriteUrlUtil::getInstance(); $url = $oMrUrlUtil->toContenidoUrl($url); return @parse_url($url); } /** * Returns state of parts property. * * @return bool True if $this->_aParts propery contains items */ private function _hasPartArrayItems() { return (!empty($this->_aParts)); } /** * Checks if current request was a root request. * * @return bool */ private function _isRootRequest() { return ($this->_sIncommingUrl == '/' || $this->_sIncommingUrl == ''); } /** * Sets error code and error flag (everything greater than 0 is an error) * @param int $errCode */ private function _setError($errCode) { $this->_iError = (int) $errCode; $this->_bError = ((int) $errCode > 0); } }