1: <?php
2:
3: /**
4: * This file contains the uri builder mod rewrite class.
5: *
6: * @package Plugin
7: * @subpackage ModRewrite
8: * @author Murat Purc <murat@purc.de>
9: * @copyright four for business AG <www.4fb.de>
10: * @license http://www.contenido.org/license/LIZENZ.txt
11: * @link http://www.4fb.de
12: * @link http://www.contenido.org
13: */
14:
15: defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
16:
17: /**
18: * Class to build frontend urls for advandced mod rewrite plugin.
19: *
20: * Extends abstract Contenido_UriBuilder class and implements the
21: * singleton pattern.
22: *
23: * Usage:
24: * <pre>
25: * cInclude('classes', 'uri/class.uriBuilder.MR.php');
26: * $url = 'front_content.php?idart=123';
27: * $mrUriBuilder = cUriBuilderMR::getInstance();
28: * $mrUriBuilder->buildUrl(array($url));
29: * $newUrl = $mrUriBuilder->getUrl();
30: * </pre>
31: *
32: * @todo add handling of absolute paths
33: * @todo standardize handling of fragments
34: * @package Plugin
35: * @subpackage ModRewrite
36: */
37: class cUriBuilderMR extends cUriBuilder {
38:
39: /**
40: * Self instance
41: *
42: * @var cUriBuilderMR
43: */
44: private static $_instance;
45:
46: /**
47: * Cached rootdir.
48: *
49: * The rootdir can differ from the configured one if an alternate
50: * frontendpath is configured as client setting. In order to determine the
51: * current rootdir only once this is cached as static class member.
52: *
53: * @var string
54: */
55: private static $_cachedRootDir;
56:
57: /**
58: * Ampersand used for composing several parameter value pairs
59: *
60: * @var string
61: */
62: private $_sAmp = '&';
63:
64: /**
65: * Is XHTML output?
66: *
67: * @var bool
68: */
69: private $_bIsXHTML = false;
70:
71: /**
72: * Is mod rewrite enabled?
73: *
74: * @var bool
75: */
76: private $_bMREnabled = false;
77:
78: /**
79: * Mod Rewrite configuration
80: *
81: * @var array
82: */
83: private $_aMrCfg = NULL;
84:
85: /**
86: * Constructor to create an instance of this class.
87: *
88: * Tries to set some member variables.
89: */
90: private function __construct() {
91: $this->sHttpBasePath = '';
92: if (ModRewrite::isEnabled()) {
93: $this->_aMrCfg = ModRewrite::getConfig();
94: $this->_bMREnabled = true;
95: $this->_bIsXHTML = (getEffectiveSetting('generator', 'xhtml', 'false') == 'false') ? false : true;
96: $this->_sAmp = ($this->_bIsXHTML) ? '&' : '&';
97: }
98: }
99:
100: /**
101: * Returns a instance of cUriBuilderMR.
102: *
103: * @return cUriBuilderMR
104: */
105: public static function getInstance() {
106: if (self::$_instance == NULL) {
107: self::$_instance = new self();
108: }
109: return self::$_instance;
110: }
111:
112: /**
113: * Builds a URL based on defined mod rewrite settings.
114: *
115: * @param array $params
116: * Parameter array, provides only following parameters:
117: * <code>
118: * $params[0] = 'front_content.php?idart=123...'
119: * </code>
120: * @param bool $bUseAbsolutePath [optional]
121: * Flag to use absolute path (not used at the moment)
122: *
123: * @return string
124: * New build url
125: * @throws cDbException
126: * @throws cException
127: * @throws cInvalidArgumentException
128: */
129: public function buildUrl(array $params, $bUseAbsolutePath = false) {
130: ModRewriteDebugger::add($params, 'cUriBuilderMR::buildUrl() $params');
131: $urlDebug = array();
132: $urlDebug['in'] = $params;
133:
134: $url = self::_buildUrl($params);
135:
136: $urlPrefix = '';
137: if ($bUseAbsolutePath) {
138: $hmlPath = cRegistry::getFrontendUrl();
139: $aComp = parse_url($hmlPath);
140: $urlPrefix = $aComp['scheme'] . '://' . $aComp['host'];
141: if (mr_arrayValue($aComp, 'port', '') !== '') {
142: $urlPrefix .= ':' . $aComp['port'];
143: }
144: }
145:
146: $this->sUrl = $urlPrefix . $url;
147:
148: $urlDebug['out'] = $this->sUrl;
149: ModRewriteDebugger::add($urlDebug, 'cUriBuilderMR::buildUrl() in -> out');
150: }
151:
152: /**
153: * Builds the SEO-URL by analyzing passed arguments
154: * (parameter value pairs).
155: *
156: * @param array $aParams
157: * Parameter array
158: *
159: * @return string
160: * New build pretty url
161: * @throws cDbException
162: * @throws cException
163: * @throws cInvalidArgumentException
164: */
165: private function _buildUrl(array $aParams) {
166: // language should changed, set lang parameter
167: if (isset($aParams['changelang'])) {
168: $aParams['lang'] = $aParams['changelang'];
169: }
170:
171: // build the query
172: $sQuery = http_build_query($aParams);
173:
174: // get pretty url parts
175: $oMRUrlStack = ModRewriteUrlStack::getInstance();
176: $aPretty = $oMRUrlStack->getPrettyUrlParts('front_content.php?' . $sQuery);
177:
178: // get all non CONTENIDO related query parameter
179: $sQuery = $this->_createUrlQueryPart($aParams);
180:
181: // some presettings of variables
182: $aParts = array();
183:
184: // add client id/name if desired
185: $param = $this->_getClientParameter($aParams);
186: if ($param) {
187: $aParts[] = $param;
188: }
189:
190: // add language id/name if desired
191: $param = $this->_getLanguageParameter($aParams);
192: if ($param) {
193: $aParts[] = $param;
194: }
195:
196: // get path part of the url
197: $sPath = $this->_getPath($aPretty);
198: if ($sPath !== '') {
199: $aParts[] = $sPath;
200: }
201: $sPath = implode('/', $aParts) . '/';
202:
203: // get pagename part of the url
204: $sArticle = $this->_getArticleName($aPretty, $aParams);
205:
206: if ($sArticle !== '') {
207: $sFileExt = $this->_aMrCfg['file_extension'];
208: } else {
209: $sFileExt = '';
210: }
211:
212: $sPathAndArticle = $sPath . $sArticle . $sFileExt;
213:
214: // use lowercase url
215: if ($this->_aMrCfg['use_lowercase_uri'] == 1) {
216: $sPathAndArticle = cString::toLowerCase($sPathAndArticle);
217: }
218:
219: // $sUrl = $this->_aMrCfg['rootdir'] . $sPathAndArticle . $sQuery;
220: $sUrl = $sPathAndArticle . $sQuery;
221:
222: // remove double or more join parameter
223: $sUrl = mr_removeMultipleChars('/', $sUrl);
224: if (cString::getPartOfString($sUrl, -2) == '?=') {
225: $sUrl = substr_replace($sUrl, '', -2);
226: }
227:
228: // now convert CONTENIDO url to an AMR url
229: $sUrl = ModRewriteUrlUtil::getInstance()->toModRewriteUrl($sUrl);
230:
231: // prepend rootdir as defined in config
232: // $sUrl = $this->_aMrCfg['rootdir'] . $sUrl;
233: // this version allows for multiple domains of a client
234: $sUrl = self::getMultiClientRootDir($this->_aMrCfg['rootdir']) . $sUrl;
235:
236: // remove double slashes
237: $sUrl = mr_removeMultipleChars('/', $sUrl);
238:
239: return $sUrl;
240: }
241:
242: /**
243: * Returns the defined rootdir.
244: *
245: * Allows for root dir being alternativly defined as path of setting
246: * client/%frontend_path%.
247: *
248: * @param string $configuredRootDir
249: * defined rootdir
250: *
251: * @return string
252: * @throws cDbException
253: * @throws cException
254: * @throws cInvalidArgumentException
255: */
256: public static function getMultiClientRootDir($configuredRootDir) {
257:
258: // return cached rootdir if set
259: if (isset(self::$_cachedRootDir)) {
260: return self::$_cachedRootDir;
261: }
262:
263: // get props of current client
264: $props = cRegistry::getClient()->getProperties();
265: // $props = cRegistry::getClient()->getPropertiesByType('client');
266:
267: // return rootdir as defined in AMR if client has no props
268: if (!is_array($props)) {
269: self::$_cachedRootDir = $configuredRootDir;
270: return $configuredRootDir;
271: }
272:
273: foreach ($props as $prop) {
274: // skip props that are not of type 'client'
275: if ($prop['type'] != 'client') {
276: continue;
277: }
278:
279: // skip props whose name does not contain 'frontend_path'
280: if (false === strstr($prop['name'], 'frontend_path')) {
281: continue;
282: }
283:
284: // current host & path (HTTP_HOST & REQUEST_URI)
285: $httpHost = $_SERVER['HTTP_HOST'];
286: $httpPath = $_SERVER['REQUEST_URI'];
287:
288: // host & path of configured alternative URL
289: $propHost = parse_url($prop['value'], PHP_URL_HOST);
290: $propPath = parse_url($prop['value'], PHP_URL_PATH);
291:
292: // skip if http host does not equal configured host (allowing for
293: // optional www)
294: if ($propHost != $httpHost && ('www.' . $propHost) != $httpHost && $propHost != 'www.' . $httpHost) {
295: continue;
296: }
297:
298: // skip if http path does not start with configured path
299: if (0 !== cString::findFirstPos($httpPath, $propPath)) {
300: continue;
301: }
302:
303: // return path as specified in client settings
304: self::$_cachedRootDir = $propPath;
305: return $propPath;
306: }
307:
308: // return rootdir as defined in AMR
309: self::$_cachedRootDir = $configuredRootDir;
310: return $configuredRootDir;
311: }
312:
313: /**
314: * Loops through given parameter array and creates the query part of
315: * the URL.
316: *
317: * All non CONTENIDO related parameters will be excluded from
318: * composition.
319: *
320: * @param array $aArgs
321: * associative parameter array
322: * @return string
323: * composed query part for the URL
324: * like '?foo=bar&param=value'
325: */
326: private function _createUrlQueryPart(array $aArgs) {
327: // set list of parameter which are to ignore while setting additional
328: // parameter
329: $aIgnoredParams = array(
330: 'idcat',
331: 'idart',
332: 'lang',
333: 'client',
334: 'idcatart',
335: 'idartlang'
336: );
337: if ($this->_aMrCfg['use_language'] == 1) {
338: $aIgnoredParams[] = 'changelang';
339: }
340: if ($this->_aMrCfg['use_client'] == 1) {
341: $aIgnoredParams[] = 'changeclient';
342: }
343:
344: // collect additional non CONTENIDO related parameters
345: $sQuery = '';
346: foreach ($aArgs as $p => $v) {
347: if (!in_array($p, $aIgnoredParams)) {
348: // $sQuery .= urlencode(urldecode($p)) . '=' .
349: // urlencode(urldecode($v)) . $this->_sAmp;
350: $p = urlencode(urldecode($p));
351: if (is_array($v)) {
352: // handle query parameter like foobar[0}=a&foobar[1]=b...
353: foreach ($v as $p2 => $v2) {
354: $p2 = urlencode(urldecode($p2));
355: $v2 = urlencode(urldecode($v2));
356: $sQuery .= $p . '[' . $p2 . ']=' . $v2 . $this->_sAmp;
357: }
358: } else {
359: $v = urlencode(urldecode($v));
360: $sQuery .= $p . '=' . $v . $this->_sAmp;
361: }
362: }
363: }
364: if (cString::getStringLength($sQuery) > 0) {
365: $sQuery = '?' . cString::getPartOfString($sQuery, 0, -cString::getStringLength($this->_sAmp));
366: }
367: return $sQuery;
368: }
369:
370: /**
371: * Returns client id or name depending on settings.
372: *
373: * @param array $aArgs
374: * Additional arguments
375: * @return mixed
376: * Client id, client name or NULL
377: */
378: private function _getClientParameter(array $aArgs) {
379: global $client;
380:
381: // set client if desired
382: if ($this->_aMrCfg['use_client'] == 1) {
383: $iChangeClient = (isset($aArgs['changeclient'])) ? (int) $aArgs['changeclient'] : 0;
384: $idclient = ($iChangeClient > 0) ? $iChangeClient : $client;
385: if ($this->_aMrCfg['use_client_name'] == 1) {
386: return urlencode(ModRewrite::getClientName($idclient));
387: } else {
388: return $idclient;
389: }
390: }
391: return NULL;
392: }
393:
394: /**
395: * Returns language id or name depending on settings.
396: *
397: * @param array $aArgs
398: * Additional arguments
399: * @return mixed
400: * Language id, language name or NULL
401: */
402: private function _getLanguageParameter(array $aArgs) {
403: global $lang;
404:
405: // set language if desired
406: if ($this->_aMrCfg['use_language'] == 1) {
407: $iChangeLang = (isset($aArgs['changelang'])) ? (int) $aArgs['changelang'] : 0;
408: $idlang = ($iChangeLang > 0) ? $iChangeLang : $lang;
409: if ($this->_aMrCfg['use_language_name'] == 1) {
410: return urlencode(ModRewrite::getLanguageName($idlang));
411: } else {
412: return $idlang;
413: }
414: }
415: return NULL;
416: }
417:
418: /**
419: * Returns composed path of url (normally the category structure).
420: *
421: * @param array $aPretty
422: * Pretty url array
423: * @return string
424: * Path
425: */
426: private function _getPath(array $aPretty) {
427: $sPath = (isset($aPretty['urlpath'])) ? $aPretty['urlpath'] : '';
428:
429: // check start directory settings
430: if ($this->_aMrCfg['startfromroot'] == 0 && (cString::getStringLength($sPath) > 0)) {
431: // splitt string in array
432: $aCategories = explode('/', $sPath);
433:
434: // remove first category
435: array_shift($aCategories);
436:
437: // implode array with categories to new string
438: $sPath = implode('/', $aCategories);
439: }
440:
441: return $sPath;
442: }
443:
444: /**
445: * Returns articlename depending on current setting.
446: *
447: * @param array $aPretty
448: * Pretty url array
449: * @param array $aArgs
450: * Additional arguments
451: * @return string
452: * Articlename
453: */
454: private function _getArticleName(array $aPretty, array $aArgs) {
455: $sArticle = (isset($aPretty['urlname'])) ? $aPretty['urlname'] : '';
456:
457: $iIdCat = (isset($aArgs['idcat'])) ? (int) $aArgs['idcat'] : 0;
458: $iIdCatLang = (isset($aArgs['idcatlang'])) ? (int) $aArgs['idcatlang'] : 0;
459: $iIdCatArt = (isset($aArgs['idcatart'])) ? (int) $aArgs['idcatart'] : 0;
460: $iIdArt = (isset($aArgs['idart'])) ? (int) $aArgs['idart'] : 0;
461: $iIdArtLang = (isset($aArgs['idartlang'])) ? (int) $aArgs['idartlang'] : 0;
462:
463: // category id was passed but not article id
464: if (($iIdCat > 0 || $iIdCatLang > 0) && $iIdCatArt == 0 && $iIdArt == 0 && $iIdArtLang == 0) {
465: $sArticle = '';
466: if ($this->_aMrCfg['add_startart_name_to_url']) {
467: if ($this->_aMrCfg['default_startart_name'] !== '') {
468: // use default start article name
469: $sArticle = $this->_aMrCfg['default_startart_name'];
470: } else {
471: $sArticle = (isset($aPretty['urlname'])) ? $aPretty['urlname'] : '';
472: }
473: } else {
474: // url is to create without article name
475: $sArticle = '';
476: }
477: }
478:
479: return $sArticle;
480: }
481: }
482: