Create New Item
Item Type
File
Folder
Item Name
Search file in folder and subfolders...
Are you sure want to rename?
File Manager
/
wp-content
/
plugins
/
vikbooking
/
admin
/
helpers
/
src
/
model
:
shortenurl.php
Advanced Search
Upload
New Item
Settings
Back
Back Up
Advanced Editor
Save
<?php /** * @package VikBooking * @subpackage core * @author E4J s.r.l. * @copyright Copyright (C) 2024 E4J s.r.l. All Rights Reserved. * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL * @link https://vikwp.com */ // No direct access defined('ABSPATH') or die('No script kiddies please!'); /** * VikBooking model shorten URL. * * @since 1.16.10 (J) - 1.6.10 (WP) */ class VBOModelShortenurl { /** @var bool */ protected $onlyRouted = false; /** @var string */ protected $sequenceParamName = 'to'; /** @var array */ protected $booking = []; /** * Proxy for immediately accessing the object. * * @param bool $onlyRouted True if the "tinyurl" menu-item/shortcode must exist. * @param string $sequenceParamName The sequence param name. * * @return VBOModelShortenurl */ public static function getInstance($onlyRouted = false, string $sequenceParamName = 'to') { return new static($onlyRouted, $sequenceParamName); } /** * Class constructor. * * @param bool $onlyRouted True if the "tinyurl" menu-item/shortcode must exist. * @param string $sequenceParamName The sequence param name. */ public function __construct($onlyRouted, string $sequenceParamName) { $this->onlyRouted = (bool) $onlyRouted; $this->sequenceParamName = $sequenceParamName; } /** * Sets the current booking record details. * * @param array $booking The booking record details. * * @return self */ public function setBooking(array $booking) { $this->booking = $booking; return $this; } /** * Routes a sequence into the original (long) URL previously shortened. * * @param string $sequence The sequence code that identifies a URL. * @param bool $hit True for increasing the record hits. * * @return string The original (long) URL. * * @throws Exception */ public function routeToOriginal(string $sequence, $hit = true) { // get the shorten URL record $record = $sequence ? $this->getItem(['sequence' => $sequence]) : null; if (!$record) { throw new Exception('The requested link could not be found.', 404); } if ($hit === true) { // increase visitor hits $record->visits += 1; // update record JFactory::getDbo()->updateObject('#__vikbooking_shortenurls', $record, 'id'); } // return the original URL for the redirection return $record->redirect_uri; } /** * Parses a shortened URL into a long URL. * * @param string $url The full shortened URL. * * @return string The original (long) URL. * * @uses route() */ public function parseShortUrl(string $url) { // parse the shorten URL $shorten_uri = JUri::getInstance($url); // access the query string of the shorten URL $shorten_query = $shorten_uri->getQuery($to_array = true); // get the sequence code from the parsed URL $sequence = $shorten_query[$this->sequenceParamName] ?? ''; return $this->routeToOriginal($sequence); } /** * Parses a long URL into its shorten version, by generating and * storing a sequence code that identifies the original URL. * * @param string $url The long URL to shorten. * * @return string The shortened URL. */ public function getShortUrl(string $url) { // check if the shorten URL record exists $record = $this->getItem([ 'redirect_uri' => $url, ]); if ($record) { // build short URL from existing shorten record return $this->buildShortUrl($record); } // generate a unique secret sequence code identifier $sequence = $this->generateSequence(); while ((bool) $this->getItem(['sequence' => $sequence])) { // sequence code string must be unique $sequence = $this->generateSequence(); } // build shorten URL record $record = new stdClass; $record->sequence = $sequence; $record->redirect_uri = $url; $record->created_on = JFactory::getDate()->toSql(); // store shorten URL record JFactory::getDbo()->insertObject('#__vikbooking_shortenurls', $record, 'id'); if (!($record->id ?? null)) { // fallback on original URL return $url; } // build short URL from the newly created shorten record return $this->buildShortUrl($record); } /** * Routes a shortened URL from a shorten URL record. * * @param object $record The shorten URL record. * * @return string The full routed URL. */ protected function buildShortUrl($record) { $dbo = JFactory::getDbo(); // parse the original redirect URL $original_uri = JUri::getInstance($record->redirect_uri); // access the query string of the original URL $original_query = $original_uri->getQuery($to_array = true); // the views from which the booking language can be detected $booking_views = ['booking', 'precheckin']; if (in_array(($original_query['view'] ?? ''), $booking_views) && ($original_query['sid'] ?? '') && ($original_query['ts'] ?? '')) { if (!$this->booking) { // fetch the involved booking record $q = $dbo->getQuery(true) ->select([ $dbo->qn('id'), $dbo->qn('lang'), ]) ->from($dbo->qn('#__vikbooking_orders')) ->where($dbo->qn('ts') . ' = ' . $dbo->q($original_query['ts'])) ->andWhere([ $dbo->qn('sid') . ' = ' . $dbo->q($original_query['sid']), $dbo->qn('idorderota') . ' = ' . $dbo->q($original_query['sid']), ], 'OR'); $dbo->setQuery($q, 0, 1); $booking = $dbo->loadAssoc(); if ($booking) { $this->booking = $booking; } } } // the language for routing the URL $url_lang = null; if (!empty($this->booking['lang'])) { $url_lang = $this->booking['lang']; } // find the best matching menu item or post ID $bestitemid = VikBooking::findProperItemIdType(['tinyurl'], $url_lang); // build language suffix $lang_suffix = $bestitemid && $url_lang ? '&lang=' . $url_lang : ''; // route final shorten URL $shorten_url = VikBooking::externalroute('index.php?option=com_vikbooking&view=tinyurl&' . $this->sequenceParamName . '=' . $record->sequence . $lang_suffix, false, ($bestitemid ?: null)); if ($this->onlyRouted === true && strpos($shorten_url, 'view=tinyurl') !== false) { // fallback on original URL due to missing routing on menu-item/shortcode return $record->redirect_uri; } // return the routed "tinyurl" link return $shorten_url; } /** * Item loading implementation. * * @param mixed $pk An optional primary key value to load the row by, * or an associative array of fields to match. * * @return object|null The record object on success, null otherwise. */ protected function getItem($pk) { $dbo = JFactory::getDbo(); $q = $dbo->getQuery(true) ->select('*') ->from($dbo->qn('#__vikbooking_shortenurls')); if (is_array($pk)) { foreach ($pk as $column => $value) { if ($column === 'sequence') { // binary match (case-sensitive) $q->where('BINARY ' . $dbo->qn($column) . ' = ' . $dbo->q($value)); } else { // regular match $q->where($dbo->qn($column) . ' = ' . $dbo->q($value)); } } } else { $q->where($dbo->qn('id') . ' = ' . (int) $pk); } $dbo->setQuery($q, 0, 1); return $dbo->loadObject(); } /** * Generates a random sequence code string. * * @param int $length The length of the sequence code to generate. * * @return string The random sequence code string. */ protected function generateSequence(int $length = 12) { $dictionary = [ range(0, 9), range('a', 'z'), range('A', 'Z'), ]; $sequence = ''; for ($i = 0; $i < $length; $i++) { // randomize dictionary level (index) $level = rand(0, count($dictionary) - 1); // toss dictionary level char $token = rand(0, count($dictionary[$level]) - 1); // grab char token $sequence .= $dictionary[$level][$token]; } return $sequence; } }