log = $log; $this->oembed_extractor = $oembed_extractor; $this->http = $http; $this->embed_entries = $embed_entries; $this->thumb_download_timeout = $thumb_download_timeout; $this->tmp_dir = $tmp_dir; } private function make_tmp_file(): string { $ret = \tempnam($this->tmp_dir, self::TMP_FILE_PREFIX); if ($ret === false) { throw new \RuntimeException("Could not create temporary file in {$this->tmp_dir}"); } \register_shutdown_function(fn() => @unlink($ret)); return $ret; } /** * Downloads the thumbnail into a temporary file. * * @return ?string The path to the temporary file, null if the file was too large. */ private function fetchThumbnail(string $thumbnail_url): ?string { $tmp_file = $this->make_tmp_file(); $fd = \fopen($tmp_file, 'w+b'); if ($fd === false) { throw new \RuntimeException("Could not open temporary file $tmp_file for read/write"); } $ret = $this->http->requestGetInto($thumbnail_url, null, $fd, $this->thumb_download_timeout); return $ret ? $tmp_file : null; } /** * Returns the path to the thumbnail from a matched url, if any. * * @param string $url The url to embed. * @param int $entry_index The index of the embedding entry. * @return ?array Returns the url to the thumbnail and the path to the fallback. */ private function extractThumb(string $url, int $entry_index) { $embed_entry = $this->embed_entries[$entry_index]; $match_regex = $embed_entry['match_regex']; $type = $embed_entry['type']; if ($type === 'oembed') { $thumbnail_url_fallback = $embed_entry['thumbnail_url_fallback'] ?? null; $provider = $embed_entry['provider_url']; $oembed_resp = $this->oembed_extractor->fetch($provider, $url); return [ $oembed_resp->thumbnail_url, $thumbnail_url_fallback ]; } elseif ($type === 'regex') { $thumbnail_url_regex = $embed_entry['thumbnail_url']; // Plz somebody review this. return [ \preg_replace($match_regex, $thumbnail_url_regex, $url), null ]; } else { $this->log->log(LogDriver::ERROR, "Unknown embed type '$type' in embed entry $entry_index, ignoring the entry"); return [ null, null ]; } } /** * Find the embed entry matching with the url, if any. * * @param string $url Url to embed. MUST BE ALREADY VALIDATED. * @return int The index of the matched embed entry or null. */ public function matchEmbed(string $url): ?int { for ($i = 0; $i < \count($this->embed_entries); $i++) { $match_regex = $this->embed_entries[$i]['match_regex']; if (\preg_match($match_regex, $url, $matches)) { return $i; } } return null; } /** * Get the embed's thumbnail if possible. May download it from the network into a temporary file, or use a static file. * * @param string $url Url to embed. MUST BE ALREADY VALIDATED. * @param int The index of the matched embed entry. * @return ?array Null if no thumbnail can be selected, otherwise an array with the local file path to the thumbnail * and if the the file is a temporary or a static one. */ public function getEmbedThumb(string $url, int $entry_index): ?array { $ret = $this->extractThumb($url, $entry_index); list($thumbnail_url, $thumbnail_url_fallback) = $ret; if (!isset($thumbnail_url)) { return null; } if (\filter_var($thumbnail_url, \FILTER_VALIDATE_URL) === false) { $this->log->log(LogDriver::ERROR, "Thumbnail URL '$thumbnail_url' from embed entry $entry_index is not a valid URL, trying fallback"); } else { $tmp_file = $this->fetchThumbnail($thumbnail_url); if ($tmp_file !== null) { return [ $tmp_file, true ]; } $this->log->log(LogDriver::NOTICE, "Thumbnail at '$thumbnail_url' was too large, trying fallback"); } if ($thumbnail_url_fallback === null) { return null; } return [ $thumbnail_url_fallback, false ]; } public function renderEmbed(string $url, int $entry_index, string $thumbnail_path): string { $embed_entry = $this->embed_entries[$entry_index]; $match_regex = $embed_entry['match_regex']; $html = $embed_entry['html']; $ret = \preg_replace($match_regex, $html, $url); if (!\is_string($ret)) { throw new \RuntimeException("Error while applying regex replacement for embed entry $entry_index"); } \str_replace('%%embed_url%%', $url, $ret); \str_replace('%%thumbnail_path%%', $thumbnail_path, $ret); return $ret; } }