& !5Wؔ}!&Ȥ&  () 5x&&P\&6   (& ut1!&8& 6-ٰi!&H& 98i!&X& S<SPoUAh& kqzj!&x& vp HpVA& %Q{XyA& z;Rp/!&& `n!&& *p(!&Ƞ& g+W٘!&ؠ& .2jؘ!&& vRo!&& % So!&& v:SVA(& /EH(O'x0!&8& !5Wؔ}!&H& db->delete('tredirect', ['cToUrl', 'cFromUrl'], [$source, $destination]); } $target = $this->getRedirectByTarget($source); if ($target !== null) { $this->saveExt($target->cFromUrl, $destination, false, $handling, false, $type); $ins = new stdClass(); $ins->cToUrl = Text::convertUTF8($destination); $ins->cAvailable = 'y'; $ins->paramHandling = $handling; $ins->type = $type; $this->db->update('tredirect', 'cToUrl', $source, $ins); } $redirect = $this->find($source); if ($redirect === null) { $ins = new stdClass(); $ins->cFromUrl = Text::convertUTF8($source); $ins->cToUrl = Text::convertUTF8($destination); $ins->cAvailable = 'y'; $ins->paramHandling = $handling; $ins->type = $type; if ($this->db->insert('tredirect', $ins) > 0) { return true; } } elseif ( ($overwriteExisting || empty($redirect->cToUrl)) && $this->normalize($redirect->cFromUrl) === $source ) { // the redirect already exists with empty cToUrl or updateExisting is allowed => update $update = $this->db->update( 'tredirect', 'cFromUrl', $source, (object)['cToUrl' => Text::convertUTF8($destination), 'type' => $type] ); return $update > 0; } } return false; } /** * @param string $url * @return string|false */ public function test(string $url): bool|string { $url = $this->normalize($url); if (\mb_strlen($url) === 0 || !$this->isValid($url)) { return false; } $redirectUrl = false; $parsedUrl = \parse_url($url); $queryString = null; if (isset($parsedUrl['query'], $parsedUrl['path'])) { $url = $parsedUrl['path']; $queryString = $parsedUrl['query']; } $foundRedirectWithQuery = false; if (!empty($queryString)) { $item = $this->find($url . '?' . $queryString); if ($item !== null) { $url .= '?' . $queryString; $foundRedirectWithQuery = true; } else { $item = $this->find($url); if ($item !== null) { if ((int)$item->paramHandling === 0) { $item = null; } elseif ((int)$item->paramHandling === 1) { $foundRedirectWithQuery = true; } } } } else { $item = $this->find($url); } if ($item === null) { if (!isset($_GET['notrack']) && Settings::boolValue(Globals::REDIRECTS_404)) { $item = new stdClass(); $item->cFromUrl = $url . (!empty($queryString) ? '?' . $queryString : ''); $item->cToUrl = ''; $item->cAvailable = ''; $item->nCount = 0; $item->type = self::TYPE_404; unset($item->kRedirect); $item->kRedirect = $this->db->insert('tredirect', $item); } } elseif (\mb_strlen($item->cToUrl) > 0) { $redirectUrl = $item->cToUrl; $redirectUrl .= $queryString !== null && !$foundRedirectWithQuery ? '?' . $queryString : ''; } $referer = $_SERVER['HTTP_REFERER'] ?? ''; if (\mb_strlen($referer) > 0) { $referer = $this->normalize($referer); } $ip = Request::getRealIP(); // Eintrag für diese IP bereits vorhanden? $entry = $this->db->getSingleObject( 'SELECT * FROM tredirectreferer tr LEFT JOIN tredirect t ON t.kRedirect = tr.kRedirect WHERE tr.cIP = :ip AND t.cFromUrl = :frm LIMIT 1', ['ip' => $ip, 'frm' => $url] ); if ($entry === null || (\is_object($entry) && (int)$entry->nCount === 0)) { $ins = new stdClass(); $ins->kRedirect = $item !== null ? $item->kRedirect : 0; $ins->kBesucherBot = (int)($_SESSION['oBesucher']->kBesucherBot ?? 0); $ins->cRefererUrl = \is_string($referer) ? $referer : ''; $ins->cIP = $ip; $ins->dDate = \time(); $this->db->insert('tredirectreferer', $ins); // this counts only how many different referrers are hitting that url if ($item !== null) { ++$item->nCount; $this->db->update('tredirect', 'kRedirect', $item->kRedirect, $item); } } return $redirectUrl; } /** * @param string $url * @return bool */ public function isValid(string $url): bool { $extension = \pathinfo($url, \PATHINFO_EXTENSION); $invalidExtensions = [ 'jpg', 'gif', 'bmp', 'xml', 'ico', 'txt', 'png' ]; if (\mb_strlen($extension) > 0) { $extension = \mb_convert_case($extension, \MB_CASE_LOWER); if (\in_array($extension, $invalidExtensions, true)) { return false; } } return true; } /** * @param string $cUrl * @return string */ public function normalize(string $cUrl): string { $url = new URL(); $url->setUrl($cUrl); return '/' . \trim($url->normalize(), '\\/'); } /** * @param string $whereSQL * @param string $orderSQL * @param string $limitSQL * @return stdClass[] */ public static function getRedirects(string $whereSQL = '', string $orderSQL = '', string $limitSQL = ''): array { $redirects = Shop::Container()->getDB()->getObjects( 'SELECT * FROM tredirect' . ($whereSQL !== '' ? ' WHERE ' . $whereSQL : '') . ($orderSQL !== '' ? ' ORDER BY ' . $orderSQL : '') . ($limitSQL !== '' ? ' LIMIT ' . $limitSQL : '') ); foreach ($redirects as $redirect) { $redirect->kRedirect = (int)$redirect->kRedirect; $redirect->paramHandling = (int)$redirect->paramHandling; $redirect->nCount = (int)$redirect->nCount; $redirect->cFromUrl = Text::filterXSS($redirect->cFromUrl); $redirect->oRedirectReferer_arr = self::getReferers($redirect->kRedirect); foreach ($redirect->oRedirectReferer_arr as $referer) { $referer->cRefererUrl = Text::filterXSS($referer->cRefererUrl); } } return $redirects; } /** * @param string $whereSQL * @return int */ public static function getRedirectCount(string $whereSQL = ''): int { return Shop::Container()->getDB()->getSingleInt( 'SELECT COUNT(kRedirect) AS cnt FROM tredirect' . ($whereSQL !== '' ? ' WHERE ' . $whereSQL : ''), 'cnt' ); } /** * @param int $kRedirect * @param int $limit * @return stdClass[] */ public static function getReferers(int $kRedirect, int $limit = 100): array { return Shop::Container()->getDB()->getObjects( 'SELECT tredirectreferer.*, tbesucherbot.cName AS cBesucherBotName, tbesucherbot.cUserAgent AS cBesucherBotAgent FROM tredirectreferer LEFT JOIN tbesucherbot ON tredirectreferer.kBesucherBot = tbesucherbot.kBesucherBot WHERE kRedirect = :kr ORDER BY dDate ASC LIMIT :lmt', ['kr' => $kRedirect, 'lmt' => $limit] ); } /** * @return int */ public static function getTotalRedirectCount(): int { return Shop::Container()->getDB()->getSingleInt( 'SELECT COUNT(kRedirect) AS cnt FROM tredirect', 'cnt' ); } /** * @param string $url - one of * * full URL (must be inside the same shop) e.g. http://www.shop.com/path/to/page * * url path e.g. /path/to/page * * path relative to the shop root url * @return bool */ public static function checkAvailability(string $url): bool { if (empty($url)) { return false; } $parsedUrl = \parse_url($url); $parsedShopUrl = \parse_url(Shop::getURL() . '/'); if (!\is_array($parsedShopUrl) || !\is_array($parsedUrl)) { return false; } $fullUrlParts = $parsedUrl; if (!isset($parsedUrl['host']) && isset($parsedShopUrl['scheme'], $parsedShopUrl['host'])) { $fullUrlParts['scheme'] = $parsedShopUrl['scheme']; $fullUrlParts['host'] = $parsedShopUrl['host']; } elseif (($parsedUrl['host'] ?? '???') !== ($parsedShopUrl['host'] ?? '?')) { return false; } if (!isset($parsedUrl['path'])) { $fullUrlParts['path'] = $parsedShopUrl['path'] ?? ''; } elseif (!\str_starts_with($parsedUrl['path'], $parsedShopUrl['path'] ?? 'invalid')) { if (isset($parsedUrl['host'])) { return false; } $fullUrlParts['path'] = ($parsedShopUrl['path'] ?? '') . \ltrim($parsedUrl['path'], '/'); } if (isset($fullUrlParts['query'])) { $fullUrlParts['query'] .= '¬rack'; } else { $fullUrlParts['query'] = 'notrack'; } if (!\DEFAULT_CURL_OPT_VERIFYPEER) { \stream_context_set_default([ 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false, ], ]); } $headers = \get_headers(URL::unparseURL($fullUrlParts)); if ($headers !== false) { foreach ($headers as $header) { if (\preg_match('/^HTTP\\/\\d+\\.\\d+\\s+2\\d\\d\\s+.*$/', $header)) { return true; } } } return false; } /** * @param int $kRedirect */ public static function deleteRedirect(int $kRedirect): void { Shop::Container()->getDB()->delete('tredirect', 'kRedirect', $kRedirect); Shop::Container()->getDB()->delete('tredirectreferer', 'kRedirect', $kRedirect); } /** * @return int */ public static function deleteUnassigned(): int { return Shop::Container()->getDB()->getAffectedRows( "DELETE tredirect, tredirectreferer FROM tredirect LEFT JOIN tredirectreferer ON tredirect.kRedirect = tredirectreferer.kRedirect WHERE tredirect.cToUrl = ''" ); } /** * @param array|null $hookInfos * @param bool $forceExit * @return array */ public static function urlNotFoundRedirect(array $hookInfos = null, bool $forceExit = false): array { $shopSubPath = \parse_url(Shop::getURL(), \PHP_URL_PATH) ?: ''; $url = \preg_replace('/^' . \preg_quote($shopSubPath, '/') . '/', '', $_SERVER['REQUEST_URI'] ?? '', 1); $redirect = new self(); $redirectUrl = $redirect->test($url); if ($redirectUrl !== false && $redirectUrl !== $url && '/' . $redirectUrl !== $url) { $parsed = \parse_url($redirectUrl); if (!isset($parsed['scheme'])) { $redirectUrl = \str_starts_with($redirectUrl, '/') ? Shop::getURL() . $redirectUrl : Shop::getURL() . '/' . $redirectUrl; } \http_response_code(301); \header('Location: ' . $redirectUrl); exit; } \http_response_code(404); if ($forceExit || !$redirect->isValid($url)) { exit; } $isFileNotFound = true; \executeHook(\HOOK_PAGE_NOT_FOUND_PRE_INCLUDE, [ 'isFileNotFound' => &$isFileNotFound, $hookInfos['key'] => &$hookInfos['value'] ]); $hookInfos['isFileNotFound'] = $isFileNotFound; return $hookInfos; } }