PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.16
VikBooking Hotel Booking Engine & PMS v1.8.16
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
← All changes | libraries/adapter/application/route.php +474 -0 1.6.0 → 1.8.16 View file →
@@ -1,0 +1,474 @@
1 +<?php
2 +/**
3 + * @package VikWP - Libraries
4 + * @subpackage adapter.application
5 + * @author E4J s.r.l.
6 + * @copyright Copyright (C) 2023 E4J s.r.l. All Rights Reserved.
7 + * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 + * @link https://vikwp.com
9 + */
10 +
11 +// No direct access
12 +defined('ABSPATH') or die('No script kiddies please!');
13 +
14 +/**
15 + * Route adapter class.
16 + *
17 + * @since 10.0
18 + */
19 +class JRoute
20 +{
21 + /**
22 + * A list of component shortcodes.
23 + *
24 + * @var array
25 + */
26 + protected static $shortcodes = array();
27 +
28 + /**
29 + * Translates an internal URL to a human-readable URL.
30 + *
31 + * @param string $url Absolute or Relative URI.
32 + * @param boolean $xhtml Replace & by &amp; for XML compliance.
33 + * @param integer $ssl Secure state for the resolved URI.
34 + * 0: (default) No change, use the protocol currently used in the request.
35 + * 1: Make URI secure using global secure site URI.
36 + * 2: Make URI unsecure using the global unsecure site URI.
37 + *
38 + * @return string The translated humanly readable URL.
39 + *
40 + * @uses getPermalink()
41 + * @uses replace()
42 + */
43 + public static function _($url, $xhtml = true, $ssl = null)
44 + {
45 + // parse URL to obtain query args (true: as array)
46 + $args = JUri::getInstance($url)->getQuery(true);
47 +
48 + // prepend current URI if starts with 'index.php'
49 + if (strpos($url, 'index.php') === 0)
50 + {
51 + $url = static::getPermalink();
52 + $url .= (strpos($url, '?') !== false ? '&' : '?') . http_build_query($args);
53 + }
54 +
55 + // if 'option' and 'view' arguments are not empty, try to route URLs
56 + if (!empty($args['option']) && (!empty($args['view']) || !empty($args['Itemid'])))
57 + {
58 + $url = static::replace($args, $url);
59 + }
60 +
61 + /**
62 + * Prepend base URL in case the system
63 + * was not able to find a permalink.
64 + *
65 + * @since 10.1.29
66 + */
67 + if (preg_match("/^\?/", $url))
68 + {
69 + $url = JUri::root() . $url;
70 + }
71 +
72 + // replace & with &amp; for XML compliance
73 + if ($xhtml)
74 + {
75 + $url = str_replace('&', '&amp;', $url);
76 + }
77 +
78 + if ($ssl == 1)
79 + {
80 + // force HTTPS
81 + $url = str_replace('http://', 'https://', $url);
82 + }
83 + else if ($ssl == 2)
84 + {
85 + // force HTTP
86 + $url = str_replace('https://', 'http://', $url);
87 + }
88 +
89 + return $url;
90 + }
91 +
92 + /**
93 + * Proxy for underscore method.
94 + * Needed to bypass the issue reported here:
95 + * @link https://meta.trac.wordpress.org/ticket/3601
96 + *
97 + * @since 10.1.32 Renamed from `u`.
98 + */
99 + public static function rewrite($url, $xhtml = true, $ssl = null)
100 + {
101 + return static::_($url, $xhtml, $ssl);
102 + }
103 +
104 + /**
105 + * Replaces the plain URL with the rewritten one.
106 + *
107 + * @param array $args The URL parts.
108 + * @param string $url The URL to rewrite.
109 + *
110 + * @return string The rewritten URL, if possible.
111 + *
112 + * @uses getPermalink()
113 + * @uses matchShortcode()
114 + * @uses withQueryString()
115 + */
116 + protected static function replace(array $args, $url)
117 + {
118 + // make safe by replacing initial 'com_' (if any)
119 + $option = preg_replace("/^com_/", "", $args['option']);
120 + unset($args['option']);
121 +
122 + // check if the shortcodes have been already cached
123 + if (!isset(static::$shortcodes[$option]))
124 + {
125 + // get model to access shortcodes
126 + $model = JModel::getInstance($option, 'shortcodes', 'admin');
127 +
128 + /**
129 + * Make sure the model exists for the requested option.
130 + *
131 + * @since 10.1.19
132 + */
133 + if ($model)
134 + {
135 + // cache the component shortcodes
136 + static::$shortcodes[$option] = $model->all();
137 + }
138 + else
139 + {
140 + // create an empty list
141 + static::$shortcodes[$option] = array();
142 + }
143 + }
144 +
145 + $post_id = false;
146 +
147 + if (!empty($args['view']))
148 + {
149 + // try to rewrite the URL with a matching shortcode
150 + $post_id = static::matchShortcode(static::$shortcodes[$option], $args);
151 + }
152 +
153 + if (!$post_id && !empty($args['Itemid']))
154 + {
155 + // if the Itemid is set, get the POST permalink directly
156 + $post_id = (int) $args['Itemid'];
157 + }
158 +
159 + if ($post_id)
160 + {
161 + $url = static::getPermalink($post_id);
162 + $url = static::withQueryString($url, $post_id, static::$shortcodes[$option], $args);
163 + }
164 +
165 + /**
166 + * Try to instantiate an external router.
167 + *
168 + * @since 10.1.19
169 + */
170 + $router = JFactory::getApplication()->getRouter($option);
171 +
172 + if ($router)
173 + {
174 + // route URL using the specified arguments
175 + $url = $router->build($url);
176 + }
177 +
178 + return $url;
179 + }
180 +
181 + /**
182 + * Wrapper method to extend the function used to obtain
183 + * the current permalink or the post link.
184 + *
185 + * @param mixed $post_id The post ID. Null to obtain the current link.
186 + *
187 + * @return string The permalink.
188 + */
189 + protected static function getPermalink($post_id = null)
190 + {
191 + // if the post ID is set, get the permalink directly
192 + if ($post_id)
193 + {
194 + /**
195 + * Added support to WPML, since it is unable to properly obtain the
196 + * correct permalink depending the requested language tag.
197 + *
198 + * @since 10.1.55
199 + */
200 + $wpml_post_lang = apply_filters('wpml_post_language_details', null, $post_id);
201 +
202 + /**
203 + * Make sure we haven't received an error object from WPML.
204 + *
205 + * @since 10.1.60
206 + */
207 + if ($wpml_post_lang && !is_wp_error($wpml_post_lang))
208 + {
209 + // WPML supported, go ahead
210 + return apply_filters('wpml_permalink', get_permalink($post_id), $wpml_post_lang['language_code']);
211 + }
212 +
213 + // WPML not supported, fallback to the default behavior
214 + return get_permalink($post_id);
215 + }
216 +
217 + // otherwise get the current permalink
218 + $link = get_permalink();
219 +
220 + // return the permalink only if it is not empty
221 + if ($link)
222 + {
223 + return $link;
224 + }
225 +
226 + // obtain the post ID from the current URL
227 + $post_id = url_to_postid(JUri::current());
228 +
229 + // if the current URL doesn't match any post, return an empty string
230 + if (!$post_id)
231 + {
232 + return '';
233 + }
234 +
235 + // recursive call to obtain the permalink of the post found
236 + return static::getPermalink($post_id);
237 + }
238 +
239 + /**
240 + * Returns a URL containing the specified query string.
241 + *
242 + * @param string $url The URL to which append the query string.
243 + * @param integer $post_id The post ID.
244 + * @param array $shortcodes The shortcodes list.
245 + * @param array $args The args to build the query string.
246 + *
247 + * @return string The URL with the query string.
248 + */
249 + protected static function withQueryString($url, $post_id, array $shortcodes, array $args = array())
250 + {
251 + // unset always the Item ID
252 + unset($args['Itemid']);
253 +
254 + // iterate the shortcodes
255 + foreach ($shortcodes as $shortcode)
256 + {
257 + // process only if the given post ID matches the one assigned to the shortcode
258 + if ($post_id == $shortcode->post_id)
259 + {
260 + // get shortcodes parameters, view and language
261 + $json = (array) json_decode($shortcode->json, true);
262 + $json['view'] = $shortcode->type;
263 + $json['lang'] = $shortcode->lang;
264 +
265 + // get the keys that the shortcode doesn't contain
266 + $diff = self::diff($args, $json);
267 +
268 + // if there are differences between the args, append them to the URL
269 + if (count($diff))
270 + {
271 + $url .= (strpos($url, '?') !== false ? '&' : '?') . http_build_query($diff);
272 + }
273 +
274 + // return the URL with the query string (do not proceed)
275 + return $url;
276 + }
277 + }
278 +
279 + if (count($args))
280 + {
281 + /**
282 + * if no shortcodes found, attach the whole query string
283 + *
284 + * @since 10.1.8
285 + */
286 + $url .= (strpos($url, '?') !== false ? '&' : '?') . http_build_query($args);
287 + }
288 +
289 + // nothing found, return plain URL
290 + return $url;
291 + }
292 +
293 + /**
294 + * Checks if there is a shortcode object that matches the given URL parts.
295 + * In addition, the shortcode MUST own an existing post ID.
296 + *
297 + * @param array $shortcodes The shortcode objects list.
298 + * @param array $args The URL query parts.
299 + *
300 + * @return mixed The post ID found, false on failure.
301 + */
302 + protected static function matchShortcode($shortcodes, array $args)
303 + {
304 + $post_id = false;
305 + $last_count = 0;
306 +
307 + if (empty($args['lang']))
308 + {
309 + // inject current langtag within URL arguments, only if not specified
310 + // by query string
311 + $args['lang'] = JFactory::getLanguage()->getTag();
312 + }
313 +
314 + foreach ($shortcodes as $shortcode)
315 + {
316 + $count = 0;
317 +
318 + // decode JSON data
319 + $json = json_decode($shortcode->json, true);
320 + // inject VIEW name
321 + $json['view'] = $shortcode->type;
322 + // inject shortcode LANGTAG
323 + $json['lang'] = $shortcode->lang;
324 +
325 + // proceed only if the view is the same
326 + if ($json['view'] == $args['view'])
327 + {
328 + // iterate the URL arguments
329 + foreach ($args as $k => $v)
330 + {
331 + // the property should be matched only if the shortcode
332 + // owns a parameter with the same name (defined in xml view)
333 + // and value.
334 + if (isset($json[$k]))
335 + {
336 + /**
337 + * Improved the comparison of the language tag, which might be
338 + * handled in different ways from different sections of WP.
339 + *
340 + * @since 10.1.38
341 + */
342 + if ($k === 'lang')
343 + {
344 + // check whether at least a language code doesn't mention the country code
345 + if (!preg_match("/[_\-]/", $json[$k]) || !preg_match("/[_\-]/", $v))
346 + {
347 + // remove country code from locale assigned to the shortcode
348 + $json[$k] = preg_split("/[_\-]/", $json[$k]);
349 + $json[$k] = array_shift($json[$k]);
350 +
351 + // remove country code from URL locale
352 + $v = preg_split("/[_\-]/", $v);
353 + $v = array_shift($v);
354 + }
355 +
356 + // normalize locale separators
357 + $json[$k] = str_replace('-', '_', $json[$k]);
358 + $v = str_replace('-', '_', $v);
359 + }
360 +
361 + // make sure both the values are scalar
362 + if (is_scalar($json[$k]) && is_scalar($v))
363 + {
364 + if (!strcmp($json[$k], $v))
365 + {
366 + // if the shortcode owns the parameter and it is equals
367 + // to the one specified in the URL, increase the counter
368 + $count++;
369 + }
370 + }
371 + /**
372 + * Added support for arrays in query string, such as cid[].
373 + *
374 + * @since 10.1.27
375 + */
376 + else
377 + {
378 + // treat both the arguments as arrays
379 + $json[$k] = (array) $json[$k];
380 + $v = (array) $v;
381 +
382 + // sort them to maximize the possibility of having matching URLs
383 + sort($json[$k]);
384 + sort($v);
385 +
386 + if ($json[$k] == $v)
387 + {
388 + // increase the counter as we have a matching parameter
389 + $count++;
390 + }
391 + }
392 + }
393 + }
394 +
395 + // if the count of matches is higher than the last one,
396 + // update the flags with the shortcode post ID and the current count
397 + if ($count > $last_count)
398 + {
399 + $post_id = $shortcode->post_id;
400 + $last_count = $count;
401 + }
402 + }
403 + }
404 +
405 + return $post_id;
406 + }
407 +
408 + /**
409 + * Implementation of array_diff_assoc with recursion.
410 + *
411 + * @param array array1 The array to compare from.
412 + * @param array array2 An array to compare against.
413 + *
414 + * @return array Returns an array containing all the values from array1
415 + * that are not present in the second array.
416 + *
417 + * @since 10.1.27
418 + */
419 + private static function diff($array1, $array2)
420 + {
421 + $difference = array();
422 +
423 + foreach ($array1 as $key => $value)
424 + {
425 + /**
426 + * Improved the comparison of the language tag, which might be
427 + * handled in different ways from different sections of WP.
428 + *
429 + * @since 10.1.49
430 + */
431 + if ($key === 'lang')
432 + {
433 + // check whether at least a language code doesn't mention the country code
434 + if (!preg_match("/[_\-]/", $value) || !preg_match("/[_\-]/", $array2[$key]))
435 + {
436 + // remove country code from locale assigned to the shortcode
437 + $array2[$key] = preg_split("/[_\-]/", $array2[$key]);
438 + $array2[$key] = array_shift($array2[$key]);
439 +
440 + // remove country code from URL locale
441 + $value = preg_split("/[_\-]/", $value);
442 + $value = array_shift($value);
443 + }
444 +
445 + // normalize locale separators
446 + $array2[$key] = str_replace('-', '_', $array2[$key]);
447 + $value = str_replace('-', '_', $value);
448 + }
449 +
450 + if (is_array($value))
451 + {
452 + if (!isset($array2[$key]) || !is_array($array2[$key]))
453 + {
454 + $difference[$key] = $value;
455 + }
456 + else
457 + {
458 + $new_diff = self::diff($value, $array2[$key]);
459 +
460 + if (!empty($new_diff))
461 + {
462 + $difference[$key] = $new_diff;
463 + }
464 + }
465 + }
466 + else if (!array_key_exists($key, $array2) || $array2[$key] !== $value)
467 + {
468 + $difference[$key] = $value;
469 + }
470 + }
471 +
472 + return $difference;
473 + }
474 +}