*/ private array $preparedRegexPatternCache = array(); /** * @param ABJ_404_Solution_Functions $functions * @param ABJ_404_Solution_PluginLogic $logic * @param ABJ_404_Solution_Logging $logger * @param ABJ_404_Solution_ContentRepository $contentRepository * @param mixed $viewReadService * @param string|int|null $custom404PageID */ public function __construct($functions, $logic, $logger, $contentRepository, $viewReadService, $custom404PageID) { $this->f = $functions; $this->logic = $logic; $this->logger = $logger; $this->contentRepository = $contentRepository; $this->viewReadService = $viewReadService; $this->custom404PageID = $custom404PageID; } /** @return array> */ private function getRedirectsWithRegEx(): array { if (!is_object($this->viewReadService) || !is_callable(array($this->viewReadService, 'getRedirectsWithRegEx'))) { return array(); } $rows = call_user_func(array($this->viewReadService, 'getRedirectsWithRegEx')); return $this->normalizeRows($rows); } /** @return array> */ private function getManualRedirectsWithRegexMetachars(): array { if (!is_object($this->viewReadService) || !is_callable(array($this->viewReadService, 'getManualRedirectsWithRegexMetachars'))) { return array(); } $rows = call_user_func(array($this->viewReadService, 'getManualRedirectsWithRegexMetachars')); return $this->normalizeRows($rows); } /** * @param mixed $rows * @return array> */ private function normalizeRows($rows): array { if (!is_array($rows)) { return array(); } $normalized = array(); foreach ($rows as $row) { if (is_array($row)) { $normalized[] = $row; } } return $normalized; } /** Find a match using the user-defined regex patterns. * @param string $requestedURL * @param array|null $options * @return array|null */ function getPermalinkUsingRegEx(string $requestedURL, $options = null) { if (!is_array($options)) { $options = $this->logic->getOptions(); } $isDebug = $this->logger->isDebug(); $regexURLsRows = $this->getRedirectsWithRegEx(); $manualWithMetachars = $this->getManualRedirectsWithRegexMetachars(); if (!empty($manualWithMetachars)) { $filtered = array(); foreach ($manualWithMetachars as $manualRow) { $manualUrl = isset($manualRow['url']) && is_string($manualRow['url']) ? $manualRow['url'] : ''; if (!ABJ_404_Solution_RegexAutoPromote::looksLikeUnambiguousRegex($manualUrl)) { continue; } $glob = ABJ_404_Solution_RegexAutoPromote::applyGlobFixup($manualUrl); $manualRow['url'] = $glob['url']; $filtered[] = $manualRow; } if (!empty($filtered)) { if ($isDebug) { $this->logger->debugMessage( "Runtime regex fallback: trying " . count($filtered) . " MANUAL row(s) with regex metachars against URL: " . $requestedURL ); } $regexURLsRows = array_merge($regexURLsRows, $filtered); } } foreach ($regexURLsRows as $row) { $regexURL = $row['url']; if ($isDebug) { abj_service('request_context')->debug_info = 'Applying custom regex "' . $regexURL . '" to URL: ' . $requestedURL; } $regexURLStr = is_string($regexURL) ? $regexURL : ''; $preparedURL = $this->getPreparedRegexPattern($regexURLStr); if (@$this->f->regexMatch($preparedURL, $requestedURL)) { if ($isDebug) { abj_service('request_context')->debug_info = 'Cleared after regex.'; } $rowType = isset($row['type']) && is_scalar($row['type']) ? (int)$row['type'] : 0; $rowDest = isset($row['final_dest']) && is_scalar($row['final_dest']) ? (string)$row['final_dest'] : ''; if ($rowType === (int)ABJ404_TYPE_EXTERNAL) { $permalink = array( 'id' => 0, 'type' => ABJ404_TYPE_EXTERNAL, 'link' => $rowDest, 'title' => '', 'score' => 100, ); } else { $idAndType = $rowDest . '|' . $row['type']; $permalink = ABJ_404_Solution_Functions::permalinkInfoToArray($idAndType, 0, null, $options); } $permalink['matching_regex'] = $regexURL; $permalink['code'] = isset($row['code']) && is_scalar($row['code']) ? (int)$row['code'] : 0; $originalPermalink = $isDebug ? $permalink : null; $permLinkStr = isset($permalink['link']) && is_string($permalink['link']) ? $permalink['link'] : ''; $hasCaptureGroup = ($this->f->strpos($regexURLStr, '(') !== false); $hasReplacementToken = ($this->f->strpos($permLinkStr, '$') !== false); if ($hasCaptureGroup && $hasReplacementToken) { $results = array(); @$this->f->regexMatch($regexURLStr, $requestedURL, $results); $final = $permLinkStr; for ($x = 1; $x < count($results); $x++) { $final = $this->f->str_replace('$' . $x, $results[$x], $final); } $permalink['link'] = $final; } if ($isDebug) { $this->logger->debugMessage("Found matching regex. Original permalink" . json_encode($originalPermalink) . ", final: " . json_encode($permalink)); } return $permalink; } if ($isDebug) { abj_service('request_context')->debug_info = 'Cleared after regex.'; } } return null; } /** * @param string $regexURL * @return string */ private function getPreparedRegexPattern($regexURL) { if (isset($this->preparedRegexPatternCache[$regexURL])) { return $this->preparedRegexPatternCache[$regexURL]; } $prepared = $this->f->str_replace('/', '\/', $regexURL); $this->preparedRegexPatternCache[$regexURL] = $prepared; return $prepared; } /** Find a match using an exact slug match. * @param string $requestedURL * @return array|null */ function getPermalinkUsingSlug(string $requestedURL) { $exploded = array_filter(explode('/', $requestedURL)); if (count($exploded) === 0) { return null; } $postSlug = end($exploded); $postsBySlugRows = $this->contentRepository->getPublishedPagesAndPostsIDs($postSlug); if (count($postsBySlugRows) == 1) { $post = reset($postsBySlugRows); $postId = (is_object($post) && property_exists($post, 'id')) ? $post->id : null; if ($postId === null) { return null; } $permalink = array(); $permalink['id'] = $postId; $permalink['type'] = ABJ404_TYPE_POST; $permalink['score'] = 100; $permalink['title'] = get_the_title($postId); $permalink['link'] = get_permalink($postId); return $permalink; } else if (count($postsBySlugRows) > 1) { $this->logger->debugMessage("More than one post found with the slug, so no redirect was " . "created. Slug: " . $postSlug); } else { $this->logger->debugMessage("No posts or pages matching slug: " . esc_html($postSlug)); } return null; } /** * @param string $requestedURL * @return bool */ function requestIsForAnImage(string $requestedURL): bool { $imageExtensions = array(".jpg", ".jpeg", ".gif", ".png", ".tif", ".tiff", ".bmp", ".pdf", ".jif", ".jif", ".jp2", ".jpx", ".j2k", ".j2c", ".pcd"); $returnVal = false; foreach ($imageExtensions as $extension) { if ($this->f->endsWithCaseInsensitive($requestedURL, $extension)) { $returnVal = true; break; } } return $returnVal; } /** * @param array $rowsAsObject * @return array> */ function getOnlyIDandTermID(array $rowsAsObject): array { $rows = array(); $objectRow = array_pop($rowsAsObject); while ($objectRow != null) { $rows[] = array( 'id' => property_exists($objectRow, 'id') == true ? $objectRow->id : null, 'term_id' => property_exists($objectRow, 'term_id') == true ? $objectRow->term_id : null, 'url' => property_exists($objectRow, 'url') == true ? $objectRow->url : null ); $objectRow = array_pop($rowsAsObject); } return $rows; } /** * @param string $requestedURL * @return array */ function getFromPermalinkCache(string $requestedURL): array { $ctx = abj_service('request_context'); if (!empty($ctx->permalinks_found)) { $permalinks = json_decode($ctx->permalinks_found, true); if (is_array($permalinks)) { return $permalinks; } } $returnValue = $this->contentRepository->getSpellingPermalinksFromCache($requestedURL); if (is_array($returnValue) && !empty($returnValue)) { return $returnValue; } return array(); } /** * @param int $id * @param string $rowType * @return string|null * @throws Exception */ function getPermalink($id, $rowType) { if ($rowType == 'pages') { $link = $this->contentRepository->getPermalinkFromCache($id); if ($link === null || trim((string)$link) === '') { $linkResult = get_the_permalink($id); $link = ($linkResult !== false) ? $linkResult : null; } return $this->f->normalizeUrlString($link); } else if ($rowType == 'tags') { return $this->f->normalizeUrlString(get_tag_link($id)); } else if ($rowType == 'categories') { return $this->f->normalizeUrlString(get_category_link($id)); } else if ($rowType == 'image') { $src = wp_get_attachment_image_src($id, "attached-image"); if ($src == false || !is_array($src)) { return null; } return $this->f->normalizeUrlString($src[0]); } else { throw new \Exception("Unknown row type ..."); // allow-raw-error: assertion, should never reach user } } /** Turns "/abc/defg" into "defg" * @param string $url * @return string */ function getLastURLPart($url) { $parts = explode("/", $url); $lastPart = ''; for ($i = count($parts) - 1; $i >= 0; $i--) { $lastPart = $parts[$i]; if (trim($lastPart) != "") { break; } } if (trim($lastPart) == "") { return $url; } return $lastPart; } }