PluginProbe
VikBooking Hotel Booking Engine & PMS / trunk
VikBooking Hotel Booking Engine & PMS vtrunk
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 1.7.3 All 36 releases
vikbooking / admin / helpers / src / model / shortenurl.php

shortenurl.php in VikBooking Hotel Booking Engine & PMS trunk, at admin/helpers/src/model/shortenurl.php

302 lines 7.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2024 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 * VikBooking model shorten URL.
16 *
17 * @since 1.16.10 (J) - 1.6.10 (WP)
18 */
19 class VBOModelShortenurl
20 {
21 /** @var bool */
22 protected $onlyRouted = false;
23
24 /** @var string */
25 protected $sequenceParamName = 'to';
26
27 /** @var array */
28 protected $booking = [];
29
30 /**
31 * Proxy for immediately accessing the object.
32 *
33 * @param bool $onlyRouted True if the "tinyurl" menu-item/shortcode must exist.
34 * @param string $sequenceParamName The sequence param name.
35 *
36 * @return VBOModelShortenurl
37 */
38 public static function getInstance($onlyRouted = false, string $sequenceParamName = 'to')
39 {
40 return new static($onlyRouted, $sequenceParamName);
41 }
42
43 /**
44 * Class constructor.
45 *
46 * @param bool $onlyRouted True if the "tinyurl" menu-item/shortcode must exist.
47 * @param string $sequenceParamName The sequence param name.
48 */
49 public function __construct($onlyRouted, string $sequenceParamName)
50 {
51 $this->onlyRouted = (bool) $onlyRouted;
52 $this->sequenceParamName = $sequenceParamName;
53 }
54
55 /**
56 * Sets the current booking record details.
57 *
58 * @param array $booking The booking record details.
59 *
60 * @return self
61 */
62 public function setBooking(array $booking)
63 {
64 $this->booking = $booking;
65
66 return $this;
67 }
68
69 /**
70 * Routes a sequence into the original (long) URL previously shortened.
71 *
72 * @param string $sequence The sequence code that identifies a URL.
73 * @param bool $hit True for increasing the record hits.
74 *
75 * @return string The original (long) URL.
76 *
77 * @throws Exception
78 */
79 public function routeToOriginal(string $sequence, $hit = true)
80 {
81 // get the shorten URL record
82 $record = $sequence ? $this->getItem(['sequence' => $sequence]) : null;
83
84 if (!$record) {
85 throw new Exception('The requested link could not be found.', 404);
86 }
87
88 if ($hit === true) {
89 // increase visitor hits
90 $record->visits += 1;
91
92 // update record
93 JFactory::getDbo()->updateObject('#__vikbooking_shortenurls', $record, 'id');
94 }
95
96 // return the original URL for the redirection
97 return $record->redirect_uri;
98 }
99
100 /**
101 * Parses a shortened URL into a long URL.
102 *
103 * @param string $url The full shortened URL.
104 *
105 * @return string The original (long) URL.
106 *
107 * @uses route()
108 */
109 public function parseShortUrl(string $url)
110 {
111 // parse the shorten URL
112 $shorten_uri = JUri::getInstance($url);
113
114 // access the query string of the shorten URL
115 $shorten_query = $shorten_uri->getQuery($to_array = true);
116
117 // get the sequence code from the parsed URL
118 $sequence = $shorten_query[$this->sequenceParamName] ?? '';
119
120 return $this->routeToOriginal($sequence);
121 }
122
123 /**
124 * Parses a long URL into its shorten version, by generating and
125 * storing a sequence code that identifies the original URL.
126 *
127 * @param string $url The long URL to shorten.
128 *
129 * @return string The shortened URL.
130 */
131 public function getShortUrl(string $url)
132 {
133 // check if the shorten URL record exists
134 $record = $this->getItem([
135 'redirect_uri' => $url,
136 ]);
137
138 if ($record) {
139 // build short URL from existing shorten record
140 return $this->buildShortUrl($record);
141 }
142
143 // generate a unique secret sequence code identifier
144 $sequence = $this->generateSequence();
145 while ((bool) $this->getItem(['sequence' => $sequence])) {
146 // sequence code string must be unique
147 $sequence = $this->generateSequence();
148 }
149
150 // build shorten URL record
151 $record = new stdClass;
152 $record->sequence = $sequence;
153 $record->redirect_uri = $url;
154 $record->created_on = JFactory::getDate()->toSql();
155
156 // store shorten URL record
157 JFactory::getDbo()->insertObject('#__vikbooking_shortenurls', $record, 'id');
158
159 if (!($record->id ?? null)) {
160 // fallback on original URL
161 return $url;
162 }
163
164 // build short URL from the newly created shorten record
165 return $this->buildShortUrl($record);
166 }
167
168 /**
169 * Routes a shortened URL from a shorten URL record.
170 *
171 * @param object $record The shorten URL record.
172 *
173 * @return string The full routed URL.
174 */
175 protected function buildShortUrl($record)
176 {
177 $dbo = JFactory::getDbo();
178
179 // parse the original redirect URL
180 $original_uri = JUri::getInstance($record->redirect_uri);
181
182 // access the query string of the original URL
183 $original_query = $original_uri->getQuery($to_array = true);
184
185 // the views from which the booking language can be detected
186 $booking_views = ['booking', 'precheckin'];
187
188 if (in_array(($original_query['view'] ?? ''), $booking_views) && ($original_query['sid'] ?? '') && ($original_query['ts'] ?? '')) {
189 if (!$this->booking) {
190 // fetch the involved booking record
191 $q = $dbo->getQuery(true)
192 ->select([
193 $dbo->qn('id'),
194 $dbo->qn('lang'),
195 ])
196 ->from($dbo->qn('#__vikbooking_orders'))
197 ->where($dbo->qn('ts') . ' = ' . $dbo->q($original_query['ts']))
198 ->andWhere([
199 $dbo->qn('sid') . ' = ' . $dbo->q($original_query['sid']),
200 $dbo->qn('idorderota') . ' = ' . $dbo->q($original_query['sid']),
201 ], 'OR');
202
203 $dbo->setQuery($q, 0, 1);
204 $booking = $dbo->loadAssoc();
205
206 if ($booking) {
207 $this->booking = $booking;
208 }
209 }
210 }
211
212 // the language for routing the URL
213 $url_lang = null;
214 if (!empty($this->booking['lang'])) {
215 $url_lang = $this->booking['lang'];
216 }
217
218 // find the best matching menu item or post ID
219 $bestitemid = VikBooking::findProperItemIdType(['tinyurl'], $url_lang);
220
221 // build language suffix
222 $lang_suffix = $bestitemid && $url_lang ? '&lang=' . $url_lang : '';
223
224 // route final shorten URL
225 $shorten_url = VikBooking::externalroute('index.php?option=com_vikbooking&view=tinyurl&' . $this->sequenceParamName . '=' . $record->sequence . $lang_suffix, false, ($bestitemid ?: null));
226
227 if ($this->onlyRouted === true && strpos($shorten_url, 'view=tinyurl') !== false) {
228 // fallback on original URL due to missing routing on menu-item/shortcode
229 return $record->redirect_uri;
230 }
231
232 // return the routed "tinyurl" link
233 return $shorten_url;
234 }
235
236 /**
237 * Item loading implementation.
238 *
239 * @param mixed $pk An optional primary key value to load the row by,
240 * or an associative array of fields to match.
241 *
242 * @return object|null The record object on success, null otherwise.
243 */
244 protected function getItem($pk)
245 {
246 $dbo = JFactory::getDbo();
247
248 $q = $dbo->getQuery(true)
249 ->select('*')
250 ->from($dbo->qn('#__vikbooking_shortenurls'));
251
252 if (is_array($pk)) {
253 foreach ($pk as $column => $value) {
254 if ($column === 'sequence') {
255 // binary match (case-sensitive)
256 $q->where('BINARY ' . $dbo->qn($column) . ' = ' . $dbo->q($value));
257 } else {
258 // regular match
259 $q->where($dbo->qn($column) . ' = ' . $dbo->q($value));
260 }
261 }
262 } else {
263 $q->where($dbo->qn('id') . ' = ' . (int) $pk);
264 }
265
266 $dbo->setQuery($q, 0, 1);
267
268 return $dbo->loadObject();
269 }
270
271 /**
272 * Generates a random sequence code string.
273 *
274 * @param int $length The length of the sequence code to generate.
275 *
276 * @return string The random sequence code string.
277 */
278 protected function generateSequence(int $length = 12)
279 {
280 $dictionary = [
281 range(0, 9),
282 range('a', 'z'),
283 range('A', 'Z'),
284 ];
285
286 $sequence = '';
287
288 for ($i = 0; $i < $length; $i++) {
289 // randomize dictionary level (index)
290 $level = rand(0, count($dictionary) - 1);
291
292 // toss dictionary level char
293 $token = rand(0, count($dictionary[$level]) - 1);
294
295 // grab char token
296 $sequence .= $dictionary[$level][$token];
297 }
298
299 return $sequence;
300 }
301 }
302