PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 1.0.95
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v1.0.95
2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 1.1.0 All 77 releases
fluent-community / app / Services / RemoteUrlParser.php

RemoteUrlParser.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 1.0.95, at app/Services/RemoteUrlParser.php

324 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\App\Services;
4
5 use FluentCommunity\Framework\Support\Arr;
6
7 class RemoteUrlParser
8 {
9
10 private static $instance;
11
12 /*
13 * Create a method to call the getInfoFromRemoteUrl static method as magic method
14 */
15 public static function parse($url)
16 {
17 if (!self::$instance) {
18 self::$instance = new self();
19 }
20
21 $oEmbed = self::$instance->getOembed($url);
22
23 if ($oEmbed) {
24 return $oEmbed;
25 }
26
27 return self::$instance->getInfoFromRemoteUrl($url);
28 }
29
30 public function getOembed($url)
31 {
32 $data = (new \WP_oEmbed())->get_data($url);
33
34 if (!$data || is_wp_error($data) || empty($data->provider_name)) {
35 return null;
36 }
37
38 $data = (array)$data;
39
40 return array_filter([
41 'title' => Arr::get($data, 'title'),
42 'author_name' => Arr::get($data, 'author_name'),
43 'type' => 'oembed',
44 'provider' => strtolower(Arr::get($data, 'provider_name')),
45 'content_type' => Arr::get($data, 'type'),
46 'url' => $url,
47 'html' => Arr::get($data, 'html'),
48 'image' => Arr::get($data, 'thumbnail_url'),
49 ]);
50 }
51
52 public function getInfoFromRemoteUrl($url)
53 {
54 $url = untrailingslashit($url);
55
56 if (empty($url)) {
57 return new \WP_Error('rest_invalid_url', __('Invalid URL', 'fluent-community'), array('status' => 404));
58 }
59
60 $cacheKey = 'fcom_url_details_meta_' . md5($url);
61 $cachedReponse = wp_cache_get($cacheKey, 'fluent-community');
62
63 if ($cachedReponse) {
64 return $cachedReponse;
65 }
66
67 $remote_url_response = $this->getRemoteBody($url);
68 if (is_wp_error($remote_url_response) || empty($remote_url_response)) {
69 return $remote_url_response;
70 }
71
72 $html_head = $this->getDocumentHead($remote_url_response);
73
74 $title = $this->getTitle($html_head);
75 if (!$title) {
76 return new \WP_Error('rest_invalid_url', __('Invalid URL'), array('status' => 404));
77 }
78
79 $meta_elements = $this->getMetaWithContentElements($html_head);
80
81 $data = array_filter([
82 'title' => $title,
83 'image' => $this->getImage($meta_elements, $url),
84 'description' => $this->getDescription($meta_elements),
85 'icon' => $this->getIcon($html_head, $url),
86 'type' => 'meta_data',
87 'url' => $url
88 ]);
89
90 wp_cache_set($cacheKey, $data, 'fluent-community', apply_filters('rest_url_details_cache_expiration', HOUR_IN_SECONDS));
91
92 return $data;
93 }
94
95 private function getRemoteBody($url)
96 {
97 $modified_user_agent = 'WP-URLDetails/' . get_bloginfo('version') . ' (+' . get_bloginfo('url') . ')';
98
99 $args = array(
100 'limit_response_size' => 300 * KB_IN_BYTES,
101 'user-agent' => $modified_user_agent,
102 );
103
104 /**
105 * Filters the HTTP request args for URL data retrieval.
106 *
107 * Can be used to adjust response size limit and other WP_Http::request() args.
108 *
109 * @param array $args Arguments used for the HTTP request.
110 * @param string $url The attempted URL.
111 * @since 5.9.0
112 *
113 */
114 $args = apply_filters('rest_url_details_http_request_args', $args, $url);
115
116 $response = wp_safe_remote_get($url, $args);
117
118 if (\WP_Http::OK !== wp_remote_retrieve_response_code($response)) {
119 // Not saving the error response to cache since the error might be temporary.
120 return new \WP_Error(
121 'no_response',
122 __('URL not found. Response returned a non-200 status code for this URL.'),
123 array('status' => \WP_Http::NOT_FOUND)
124 );
125 }
126
127 $remote_body = wp_remote_retrieve_body($response);
128
129 if (empty($remote_body)) {
130 return new \WP_Error(
131 'no_content',
132 __('Unable to retrieve body from response at this URL.'),
133 array('status' => \WP_Http::NOT_FOUND)
134 );
135 }
136
137 return $remote_body;
138 }
139
140 private function getDocumentHead($html)
141 {
142 $head_html = $html;
143
144 // Find the opening `<head>` tag.
145 $head_start = strpos($html, '<head');
146 if (false === $head_start) {
147 // Didn't find it. Return the original HTML.
148 return $html;
149 }
150
151 // Find the closing `</head>` tag.
152 $head_end = strpos($head_html, '</head>');
153 if (false === $head_end) {
154 // Didn't find it. Find the opening `<body>` tag.
155 $head_end = strpos($head_html, '<body');
156
157 // Didn't find it. Return the original HTML.
158 if (false === $head_end) {
159 return $html;
160 }
161 }
162
163 // Extract the HTML from opening tag to the closing tag. Then add the closing tag.
164 $head_html = substr($head_html, $head_start, $head_end);
165 $head_html .= '</head>';
166
167 return $head_html;
168 }
169
170 private function getDescription($meta_elements)
171 {
172 // Bail out if there are no meta elements.
173 if (empty($meta_elements[0])) {
174 return '';
175 }
176
177 $description = $this->getMetadataFromMetaElement(
178 $meta_elements,
179 'name',
180 '(?:description|og:description)'
181 );
182
183 // Bail out if description not found.
184 if ('' === $description) {
185 return '';
186 }
187
188 return $this->prepare_metadata_for_output($description);
189 }
190
191 private function getImage($meta_elements, $url)
192 {
193 $image = $this->getMetadataFromMetaElement(
194 $meta_elements,
195 'property',
196 '(?:og:image|og:image:url)'
197 );
198
199 // Bail out if image not found.
200 if ('' === $image) {
201 return '';
202 }
203
204 // Attempt to convert relative URLs to absolute.
205 $parsed_url = wp_parse_url($url);
206 if (isset($parsed_url['scheme']) && isset($parsed_url['host'])) {
207 $root_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . '/';
208 $image = \WP_Http::make_absolute_url($image, $root_url);
209 }
210
211 if (!$image) {
212 return $image;
213 }
214
215 return sanitize_url(html_entity_decode($image, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
216 }
217
218 private function getTitle($html)
219 {
220 if (!$html) {
221 return '';
222 }
223
224 $pattern = '#<title[^>]*>(.*?)<\s*/\s*title>#is';
225 preg_match($pattern, $html, $match_title);
226
227 if (empty($match_title[1]) || !is_string($match_title[1])) {
228 return '';
229 }
230
231 $title = trim($match_title[1]);
232
233 return $this->prepare_metadata_for_output($title);
234 }
235
236 private function getMetaWithContentElements($html)
237 {
238 $pattern = '#<meta\s' .
239 '[^>]*' .
240 'content=(["\']??)(.*)\1' .
241 '[^>]*' .
242 '\/?>#' .
243 'isU';
244
245 preg_match_all($pattern, $html, $elements);
246
247 return $elements;
248 }
249
250 private function prepare_metadata_for_output($metadata)
251 {
252 $metadata = html_entity_decode($metadata, ENT_QUOTES, get_bloginfo('charset'));
253 $metadata = wp_strip_all_tags($metadata);
254 return $metadata;
255 }
256
257 private function getMetadataFromMetaElement($meta_elements, $attr, $attr_value)
258 {
259 // Bail out if there are no meta elements.
260 if (empty($meta_elements[0])) {
261 return '';
262 }
263
264 $metadata = '';
265 $pattern = '#' .
266 $attr . '=([\"\']??)\s*' . $attr_value . '\s*\1' .
267 '#isU';
268
269 foreach ($meta_elements[0] as $index => $element) {
270 preg_match($pattern, $element, $match);
271
272 if (empty($match)) {
273 continue;
274 }
275
276 if (isset($meta_elements[2][$index]) && is_string($meta_elements[2][$index])) {
277 $metadata = trim($meta_elements[2][$index]);
278 }
279
280 break;
281 }
282
283 return $metadata;
284 }
285
286 private function getIcon($html, $url)
287 {
288 // Grab the icon's link element.
289 $pattern = '#<link\s[^>]*rel=(?:[\"\']??)\s*(?:icon|shortcut icon|icon shortcut)\s*(?:[\"\']??)[^>]*\/?>#isU';
290 preg_match($pattern, $html, $element);
291 if (empty($element[0]) || !is_string($element[0])) {
292 return '';
293 }
294 $element = trim($element[0]);
295
296 // Get the icon's href value.
297 $pattern = '#href=([\"\']??)([^\" >]*?)\\1[^>]*#isU';
298 preg_match($pattern, $element, $icon);
299 if (empty($icon[2]) || !is_string($icon[2])) {
300 return '';
301 }
302 $icon = trim($icon[2]);
303
304 // If the icon is a data URL, return it.
305 $parsed_icon = wp_parse_url($icon);
306 if (isset($parsed_icon['scheme']) && 'data' === $parsed_icon['scheme']) {
307 return $icon;
308 }
309
310 // Attempt to convert relative URLs to absolute.
311 if (!is_string($url) || '' === $url) {
312 return $icon;
313 }
314
315 $parsed_url = wp_parse_url($url);
316 if (isset($parsed_url['scheme']) && isset($parsed_url['host'])) {
317 $root_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . '/';
318 $icon = \WP_Http::make_absolute_url($icon, $root_url);
319 }
320
321 return $icon;
322 }
323 }
324