1: <?php
2: /**
3: * This file is part of the PHPLucidFrame library.
4: * Core utility for system routing
5: *
6: * @package PHPLucidFrame\Core
7: * @since PHPLucidFrame v 1.0.0
8: * @copyright Copyright (c), PHPLucidFrame.
9: * @link http://phplucidframe.com
10: * @license http://www.opensource.org/licenses/mit-license.php MIT License
11: * This source file is subject to the MIT license that is bundled
12: * with this source code in the file LICENSE
13: */
14:
15: use LucidFrame\Core\Router;
16:
17: /**
18: *
19: * @internal
20: * @ignore
21: * Route to a page according to request
22: * internally called by /app/index.php
23: * @return string
24: */
25: function router()
26: {
27: if (PHP_SAPI === 'cli') {
28: return '';
29: }
30:
31: # Get a route from the defined custom routes (if any)
32: $_page = Router::match();
33: if ($_page) {
34: $pathToPage = Router::getAbsolutePathToRoot($_page);
35: if (is_file($pathToPage) && file_exists($pathToPage)) {
36: return $pathToPage;
37: }
38: }
39:
40: $q = route_path();
41:
42: # if it is still empty, set it to the system default
43: if (empty($q)) {
44: $q = 'home';
45: }
46:
47: # Get the complete path to root
48: $_page = Router::getAbsolutePathToRoot($q);
49: if (!empty($_page) && is_file($_page) && file_exists($_page)) {
50: return $_page;
51: }
52:
53: if (preg_match('/(.*)(401|403|404) {1}$/', $_page, $matches)) {
54: return _i('inc/tpl/' . $matches[2] . '.php');
55: }
56:
57: # Search the physical directory according to the routing path
58: $_page = route_search();
59: if ($_page && is_file($_page) && file_exists($_page)) {
60: return $_page;
61: }
62:
63: if (in_array(_arg(0), array('401', '403'))) {
64: _header(_arg(0));
65: return _i('inc/tpl/' . _arg(0) . '.php');
66: } else {
67: _header(404);
68: return _i('inc/tpl/404.php');
69: }
70: }
71:
72: /**
73: * Search the physical directory according to the routing path
74: *
75: * @return mixed The path if found; otherwise return false
76: */
77: function route_search()
78: {
79: $q = route_path();
80: $seg = explode('/', $q);
81: $count = sizeof($seg);
82: $sites = _cfg('sites');
83:
84: if ($seg[0] == LC_NAMESPACE && is_array($sites) && array_key_exists(LC_NAMESPACE, $sites)) {
85: $seg[0] = $sites[LC_NAMESPACE];
86: }
87:
88: $path = implode('/', $seg);
89: if (is_file($path) && file_exists($path)) {
90: if (count($seg) > 1) {
91: _cfg('cleanRoute', implode('/', array_slice($seg, 0, count($seg) - 1)));
92: } else {
93: _cfg('cleanRoute', '');
94: }
95: return $path;
96: }
97:
98: $append = array('/index.php', '/view.php', '.php');
99: for ($i = $count; $i > 0; $i--) {
100: # try to look for
101: # ~/path/to/the-given-name/index.php
102: # ~/path/to/the-given-name.php
103: foreach ($append as $a) {
104: $cleanRoute = implode('/', array_slice($seg, 0, $i));
105: $path = $cleanRoute . $a;
106: if (is_file($path) && file_exists($path)) {
107: _cfg('cleanRoute', rtrim($cleanRoute, '/'));
108:
109: $definedRoutes = Router::getRoutes();
110: // Find matching routes for this clean route
111: $routes = array_filter($definedRoutes, function ($route) use ($cleanRoute) {
112: return ltrim($route['to'], '/') == ltrim($cleanRoute, '/');
113: });
114:
115: foreach ($routes as $key => $value) {
116: if (!in_array($_SERVER['REQUEST_METHOD'], $value['method'])) {
117: if ($key == Router::getMatchedName()) {
118: _header(405);
119: throw new \RuntimeException(sprintf('The Router does not allow the method "%s" for "%s".', $_SERVER['REQUEST_METHOD'], $key));
120: } else {
121: _header(404);
122: throw new \RuntimeException(sprintf('The Router is not found for "%s".', Router::getMatchedName()));
123: }
124: }
125: }
126:
127: return $path;
128: }
129: }
130: }
131:
132: return false;
133: }
134:
135: /**
136: * Get the routing path
137: * Alias `_r()`
138: *
139: * @return string
140: */
141: function route_path()
142: {
143: $path = '';
144:
145: if (isset($_GET[ROUTE])) {
146: $path = urldecode($_GET[ROUTE]);
147: }
148:
149: return $path;
150: }
151:
152: /**
153: * Return the absolute URL path appended the query string if necessary
154: * Alias `_url()`
155: *
156: * @param string $path Routing path such as "foo/bar"; Named route such as "fool_bar"; NULL for the current path
157: * @param array $queryStr Query string as
158: * array(
159: * $value1, // no key here
160: * 'key1' => $value2,
161: * 'key3' => $value3 or array($value3, $value4)
162: * )
163: * @param string $lang Language code to be prepended to $path such as "en/foo/bar".
164: * It will be useful for site language switch redirect
165: * @return string
166: */
167: function route_url($path = null, $queryStr = array(), $lang = '')
168: {
169: global $lc_cleanURL;
170: global $lc_translationEnabled;
171: global $lc_sites;
172: global $lc_langInURI;
173:
174: $forceExcludeLangInURL = $lang === false;
175:
176: if ($path && stripos($path, 'http') === 0) {
177: return $path;
178: }
179:
180: $customRoute = Router::getPathByName($path);
181: if ($customRoute !== null) {
182: $path = $customRoute ? $customRoute : 'home';
183: if ($queryStr && is_array($queryStr) && count($queryStr)) {
184: foreach ($queryStr as $key => $value) {
185: $path = str_replace('{' . $key . '}', urlencode($value), $path);
186: }
187: }
188:
189: $queryStr = array(); // clean query strings to not be processed later
190: }
191:
192: if ($path && is_string($path)) {
193: $path = rtrim($path, '/');
194: } else {
195: $r = (_isRewriteRule()) ? REQUEST_URI : route_path();
196: $path = route_updateQueryStr($r, $queryStr);
197: }
198:
199: $q = '';
200: if ($queryStr && is_array($queryStr) && count($queryStr)) {
201: foreach ($queryStr as $key => $value) {
202: if (is_array($value)) {
203: $v = array_map('urlencode', $value);
204: $value = implode('/', $v);
205: } else {
206: $value = urlencode($value);
207: }
208: if (is_numeric($key)) {
209: if ($lc_cleanURL) {
210: $q .= '/' . $value;
211: } else {
212: $q .= '&' . $value;
213: }
214: } else {
215: if ($lc_cleanURL) {
216: $q .= '/-' . $key . '/' . $value;
217: } else {
218: $q .= '&' . $key . '=' . $value;
219: }
220: }
221: }
222: }
223:
224: if (is_array($lc_sites) && array_key_exists(LC_NAMESPACE, $lc_sites)) {
225: $regex = '/\b^(' . $lc_sites[LC_NAMESPACE] . ') {1}\b/i';
226: $path = preg_replace($regex, LC_NAMESPACE, $path);
227: }
228:
229: # If URI contains the language code, force to include it in the URI
230: if (is_null($lc_langInURI)) {
231: $lc_langInURI = _getLangInURI();
232: }
233:
234: if (empty($lang) && $lc_langInURI) {
235: $lang = $lc_langInURI;
236: }
237:
238: $url = WEB_ROOT;
239: if ($lang && $lc_translationEnabled && !$forceExcludeLangInURL) {
240: if ($lc_cleanURL) {
241: $url .= $lang . '/';
242: } else {
243: $q .= '&lang=' . $lang;
244: }
245: }
246:
247: if (strtolower($path) == 'home') {
248: $path = '';
249: $q = ltrim($q, '/');
250: }
251:
252: if ($lc_cleanURL) {
253: $url .= $path . $q;
254: } else {
255: $url .= $path . '?' . ltrim($q, '&');
256: $url = trim($url, '?');
257: }
258:
259: $url = preg_replace('/(\s) {1,}/', '+', $url); # replace the space with "+"
260: $url = preg_replace('/\?&/', '?', $url);
261: $url = preg_replace('/&&/', '&', $url);
262:
263: return rtrim($url, '/');
264: }
265:
266: /**
267: * Update the route path with the given query string
268: *
269: * @param string $path The route path which may contain the query string
270: * @param array $queryStr Query string as
271: * array(
272: * $value1, // no key here
273: * 'key1' => $value2,
274: * 'key3' => $value3 or array($value3, $value4)
275: * )
276: * @return string The updated route path
277: */
278: function route_updateQueryStr($path, &$queryStr = array())
279: {
280: global $lc_cleanURL;
281:
282: if (is_array($queryStr) && count($queryStr)) {
283: if ($lc_cleanURL) {
284: # For clean URLs like /path/query/str/-key/value
285: foreach ($queryStr as $key => $value) {
286: $route = _arg($key, $path);
287: if ($route) {
288: if (is_string($key)) {
289: $regex = '/(\-' . $key . '\/)';
290: if (is_array($route)) {
291: $regex .= '(' . implode('\/', $route) . '+)';
292: } else {
293: $regex .= '(' . $route . '+)';
294: }
295: $regex .= '/i';
296: } elseif (is_numeric($key)) {
297: $regex = '/\b(' . $route . '){1}\b/i';
298: } else {
299: continue;
300: }
301: } else {
302: # if the key could not be retrieved from URI, skip it
303: continue;
304: }
305: if (preg_match($regex, $path)) {
306: # find the key in URI
307: if (is_array($value)) {
308: $v = array_map('urlencode', $value);
309: $value = implode('/', $v);
310: } else {
311: $value = urlencode($value);
312: }
313: if (is_numeric($key)) {
314: $path = preg_replace($regex, $value, $path); # no key
315: } else {
316: $path = preg_replace($regex, '-' . $key . '/' . $value, $path);
317: }
318: unset($queryStr[$key]); # removed the replaced query string from the array
319: }
320: }
321: } else {
322: # For unclean URLs like /path/query/str?key=value
323: parse_str($_SERVER['QUERY_STRING'], $serverQueryStr);
324: $queryStr = array_merge($serverQueryStr, $queryStr);
325: }
326: }
327:
328: return $path;
329: }
330:
331: /**
332: * Initialize a route to define
333: *
334: * @param string $name The route name that is unique to the mapped path
335: * @return object Router
336: */
337: function route($name)
338: {
339: return new Router($name);
340: }
341:
342: /**
343: * Define route group
344: *
345: * @param string $prefix A prefix for the group of the routes
346: * @param callable $callback The callback function that defines each route in the group
347: */
348: function route_group($prefix, $callback)
349: {
350: Router::group($prefix, $callback);
351: }
352:
353: /**
354: * Get the current route name
355: *
356: * @return string The route name defined in route.config.php
357: */
358: function route_name()
359: {
360: return Router::getMatchedName();
361: }
362:
363: /**
364: * Check if the current route is equal to the given uri or route name
365: *
366: * @param string $uri URI string or the route name defined in route.config.php
367: * @return boolean true if it is matched, otherwise false
368: */
369: function route_equal($uri)
370: {
371: $uri = trim($uri, '/');
372:
373: return $uri == _rr() || $uri == route_name();
374: }
375:
376: /**
377: * Check if the current route uri is started with the given uri
378: *
379: * @param string $uri URI string
380: * @param array $except Array of URI string to be excluded in check
381: * @return boolean true/false
382: */
383: function route_start($uri, array $except = array())
384: {
385: if (call_user_func_array('route_except', $except) === false) {
386: return false;
387: }
388:
389: if ($uri) {
390: $uri = trim($uri, '/');
391: }
392:
393: return $uri && stripos(_rr(), $uri) === 0;
394: }
395:
396: /**
397: * Check if the current route uri contains the given URI or list of URIs
398: *
399: * @param array|string $uri URI string or array of URI strings
400: * @param array $except Array of URI string to be excluded in check
401: * @return boolean true/false
402: */
403: function route_contain($uri, array $except = array())
404: {
405: if (call_user_func_array('route_except', $except) === false) {
406: return false;
407: }
408:
409: $args = is_array($uri) ? $uri : array($uri);
410: foreach ($args as $uri) {
411: if ($uri) {
412: $uri = trim($uri, '/');
413: }
414:
415: if (stristr(_rr(), $uri)) {
416: return true;
417: }
418: }
419:
420: return false;
421: }
422:
423: /**
424: * Check if the current route uri is in th exception list
425: *
426: * @param string $args Variable list of URI strings
427: * @return boolean true/false
428: * @since PHPLucidFrame v 3.0.0
429: */
430: function route_except()
431: {
432: $except = func_get_args();
433: if (count($except)) {
434: foreach ($except as $string) {
435: if ($string) {
436: $string = trim($string, '/');
437: }
438:
439: if (stripos(_rr(), $string) === 0 || route_name() == $string) {
440: return false;
441: }
442: }
443: }
444:
445: return true;
446: }
447: