PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 1.0.96
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v1.0.96
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.96, at app/Services/RemoteUrlParser.php

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