PluginProbe
Friends / 4.3.1
Friends v4.3.1
4.3.2 4.3.1 4.3.0 4.2.2 4.2.1 4.2.0 4.1.0 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.0 2.9.1 All 88 releases
friends / feed-parsers / class-feed-parser-microformats.php

class-feed-parser-microformats.php in Friends 4.3.1, at feed-parsers/class-feed-parser-microformats.php

366 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Friends microformats Parser Wrapper
4 *
5 * With this parser, we can import RSS and Atom Feeds for a friend.
6 *
7 * @package Friends
8 */
9
10 namespace Friends;
11
12 /**
13 * This is the class for the feed part of the Friends Plugin.
14 *
15 * @since 1.0
16 *
17 * @package Friends
18 * @author Alex Kirk
19 */
20 class Feed_Parser_Microformats extends Feed_Parser_V2 {
21 const NAME = 'Microformats';
22 const URL = 'https://www.microformats.org/';
23 const SLUG = 'microformats';
24
25 /**
26 * Get the badge for Microformats feeds.
27 *
28 * @return array Badge info.
29 */
30 public function get_badge() {
31 return array(
32 'label' => 'mf2',
33 'color' => '#2271b1',
34 'title' => __( 'Microformats', 'friends' ),
35 );
36 }
37
38 /**
39 * Determines if this is a supported feed and to what degree we feel it's supported.
40 *
41 * @param string $url The url.
42 * @param string $mime_type The mime type.
43 * @param string $title The title.
44 * @param string|null $content The content, it can't be assumed that it's always available.
45 *
46 * @return int Return 0 if unsupported, a positive value representing the confidence for the feed, use 10 if you're reasonably confident.
47 */
48 public function feed_support_confidence( $url, $mime_type, $title, $content = null ) {
49 if ( ! $content ) {
50 return 0;
51 }
52 $feeds = self::discover_available_feeds( $content, $url );
53 if ( isset( $feeds[ $url ] ) ) {
54 return 10;
55 }
56
57 return 0;
58 }
59
60 /**
61 * Format the feed title and autoselect the posts feed.
62 *
63 * @param array $feed_details The feed details.
64 *
65 * @return array The (potentially) modified feed details.
66 */
67 public function update_feed_details( $feed_details ) {
68 $feed_details['title'] = trim( str_replace( array( '&raquo; Feed', '» Feed' ), '', $feed_details['title'] ) );
69
70 foreach ( get_post_format_strings() as $format => $title ) {
71 if ( preg_match( '/\b' . preg_quote( $format, '/' ) . '\b/i', $feed_details['url'] ) ) {
72 $feed_details['post-format'] = $format;
73 break;
74 }
75 }
76
77 return $feed_details;
78 }
79
80 /**
81 * Discover the feeds available at the URL specified.
82 *
83 * @param string $content The content for the URL is already provided here.
84 * @param string $url The url to search.
85 *
86 * @return array A list of supported feeds at the URL.
87 */
88 public function discover_available_feeds( $content, $url ) {
89 if ( ! is_string( $content ) ) {
90 return array();
91 }
92 $discovered_feeds = array();
93 $mf = Mf2\parse( $content, $url );
94 if ( isset( $mf['rel-urls'] ) ) {
95 foreach ( $mf['rel-urls'] as $feed_url => $link ) {
96 foreach ( array( 'me', 'alternate' ) as $rel ) {
97 if ( in_array( $rel, $link['rels'] ) ) {
98 $discovered_feeds[ $feed_url ] = array(
99 'rel' => $rel,
100 );
101 }
102 }
103
104 if ( ! isset( $discovered_feeds[ $feed_url ] ) ) {
105 continue;
106 }
107
108 if ( isset( $link['type'] ) ) {
109 $discovered_feeds[ $feed_url ]['type'] = $link['type'];
110 }
111
112 if ( isset( $link['title'] ) ) {
113 $discovered_feeds[ $feed_url ]['title'] = $link['title'];
114 } elseif ( isset( $link['text'] ) ) {
115 $discovered_feeds[ $feed_url ]['title'] = $link['text'];
116 }
117 }
118 }
119
120 if ( isset( $mf['items'] ) && ! isset( $discovered_feeds[ $url ] ) ) {
121 $feed_items = $this->parse_hfeed( $mf );
122 if ( count( $feed_items ) > 0 ) {
123 $discovered_feeds[ $url ] = array(
124 'type' => 'text/html',
125 'rel' => 'self',
126 'post-format' => 'autodetect',
127 'parser' => 'microformats',
128 );
129 }
130 }
131
132 return $discovered_feeds;
133 }
134
135 /**
136 * Parse an h-card.
137 *
138 * @param array $data The data.
139 * @param bool $category It is a category.
140 *
141 * @return string The discovered value.
142 */
143 private function parse_hcard( $data, $category = false ) {
144 $name = '';
145 $link = '';
146 // Check if h-card is set and pass that information on in the link.
147 if ( isset( $data['type'] ) && in_array( 'h-card', $data['type'] ) ) {
148 if ( isset( $data['properties']['name'][0] ) ) {
149 $name = $data['properties']['name'][0];
150 }
151 if ( isset( $data['properties']['url'][0] ) ) {
152 $link = $data['properties']['url'][0];
153 if ( '' === $name ) {
154 $name = $link;
155 } else {
156 // can't have commas in categories.
157 $name = str_replace( ',', '', $name );
158 }
159 $person_tag = $category ? '<span class="person-tag"></span>' : '';
160 return '<a class="h-card" href="' . $link . '">' . $person_tag . $name . '</a>';
161 }
162 }
163 return isset( $data['value'] ) ? $data['value'] : '';
164 }
165
166 /**
167 * Parse an h-feed.
168 *
169 * @param array $mf The microfeeds2 parser response.
170 *
171 * @return array An array of feed items.
172 */
173 public function parse_hfeed( $mf ) {
174 if ( ! is_array( $mf ) ) {
175 return array();
176 }
177 $feed_items = array();
178 $entries = array();
179
180 // The following section is adapted from the SimplePie source.
181 // 2020-11-30: Modifications: set post format, handle videos.
182 // This should implement the https://www.w3.org/TR/post-type-discovery/ algorithm.
183
184 $feed_author = null;
185 $h_feed = array();
186 foreach ( $mf['items'] as $mf_item ) {
187 if ( in_array( 'h-feed', $mf_item['type'] ) ) {
188 $h_feed = $mf_item;
189 break;
190 }
191 // Also look for h-feed or h-entry in the children of each top level item.
192 if ( ! isset( $mf_item['children'][0]['type'] ) ) {
193 continue;
194 }
195 if ( in_array( 'h-feed', $mf_item['children'][0]['type'] ) ) {
196 $h_feed = $mf_item['children'][0];
197 // In this case the parent of the h-feed may be an h-card, so use it as
198 // the feed_author.
199 if ( in_array( 'h-card', $mf_item['type'] ) ) {
200 $feed_author = $mf_item;
201 }
202 break;
203 } elseif ( in_array( 'h-entry', $mf_item['children'][0]['type'] ) ) {
204 $entries = $mf_item['children'];
205 // In this case the parent of the h-entry list may be an h-card, so use
206 // it as the feed_author.
207 if ( in_array( 'h-card', $mf_item['type'] ) ) {
208 $feed_author = $mf_item;
209 }
210 break;
211 }
212 }
213
214 if ( isset( $h_feed['children'] ) ) {
215 $entries = $h_feed['children'];
216 } elseif ( empty( $entries ) ) {
217 $entries = $mf['items'];
218 }
219
220 foreach ( $entries as $entry ) {
221 if ( isset( $entry['properties']['deleted'][0] ) || ! isset( $entry['properties']['published'][0] ) ) {
222 continue;
223 }
224 $item = new Feed_Item();
225 $item->date = $entry['properties']['published'][0];
226 $item->author = $feed_author;
227
228 if ( isset( $entry['properties']['url'][0] ) ) {
229 $link = $entry['properties']['url'][0];
230 if ( isset( $link['value'] ) ) {
231 $link = $link['value'];
232 }
233 $item->permalink = $link;
234 }
235
236 if ( isset( $entry['properties']['uid'][0] ) ) {
237 $guid = $entry['properties']['uid'][0];
238 if ( isset( $guid['value'] ) ) {
239 $guid = $guid['value'];
240 }
241 $item->permalink = $guid;
242 }
243
244 if ( isset( $entry['properties']['name'][0] ) ) {
245 $title = $entry['properties']['name'][0];
246 if ( isset( $title['value'] ) ) {
247 $title = $title['value'];
248 }
249 $item->title = $title;
250 }
251
252 $content = '';
253 foreach ( array( 'photo', 'video' ) as $media ) {
254 if ( ! isset( $entry['properties'][ $media ][0] ) ) {
255 continue;
256 }
257
258 // If a $media is also in content, don't need to add it again here.
259 if ( isset( $entry['properties']['content'][0]['html'] ) ) {
260 $content = $entry['properties']['content'][0]['html'];
261 }
262 $el_list = array();
263 for ( $j = 0, $l = count( $entry['properties'][ $media ] ); $j < $l; $j++ ) {
264 $el = $entry['properties'][ $media ][ $j ];
265 if ( ! empty( $el ) && strpos( $content, $el ) === false ) {
266 $el_list[] = $el;
267 }
268 }
269
270 if ( count( $el_list ) > 0 ) {
271 $set_id = preg_replace( '/[[:^alnum:]]/', '', $el_list[0] );
272 $content = '<p>';
273 foreach ( $el_list as $j => $el ) {
274 $hidden = 0 === $j ? '' : 'class="hidden" ';
275 $content .= '<a href="' . $el . '" ' . $hidden .
276 'data-lightbox="set-' . $set_id . '">';
277 switch ( $media ) {
278 case 'photo':
279 $content .= '<img src="' . esc_url( $el ) . '" />';
280 break;
281 case 'video':
282 $content .= '<video controls src="' . esc_url( $el ) . '"></video>';
283 break;
284 }
285 $content .= '</a>';
286 }
287 $content .= '<br><b>';
288 switch ( $media ) {
289 case 'photo':
290 // translators: %s is the number of photos.
291 $content .= sprintf( _n( '%d photo', '%d photos', count( $el_list ), 'friends' ), count( $el_list ) );
292 break;
293 case 'video':
294 // translators: %s is the number of videos.
295 $content .= sprintf( _n( '%d video', '%d videos', count( $el_list ), 'friends' ), count( $el_list ) );
296 break;
297 }
298 $content .= '</b></p>';
299 }
300 $item->format = $media;
301 }
302
303 if ( isset( $entry['properties']['content'][0]['html'] ) ) {
304 $content .= $entry['properties']['content'][0]['html'];
305 if ( isset( $entry['properties']['in-reply-to'][0] ) ) {
306 $in_reply_to = '';
307 if ( is_string( $entry['properties']['in-reply-to'][0] ) ) {
308 $in_reply_to = $entry['properties']['in-reply-to'][0];
309 } elseif ( isset( $entry['properties']['in-reply-to'][0]['value'] ) ) {
310 $in_reply_to = $entry['properties']['in-reply-to'][0]['value'];
311 }
312 if ( '' !== $in_reply_to ) {
313 $content .= '<p><span class="in-reply-to"></span> ' .
314 '<a href="' . $in_reply_to . '">' . $in_reply_to . '</a><p>';
315 }
316 }
317 $item->content = $content;
318 }
319
320 if ( isset( $entry['properties']['category'] ) ) {
321 $category_csv = '';
322 // Categories can also contain h-cards.
323 foreach ( $entry['properties']['category'] as $category ) {
324 if ( '' !== $category_csv ) {
325 $category_csv .= ', ';
326 }
327 if ( is_string( $category ) ) {
328 // Can't have commas in categories.
329 $category_csv .= str_replace( ',', '', $category );
330 } else {
331 $category_csv .= $this->parse_hcard( $category, true );
332 }
333 }
334 $item->category = $category_csv;
335 }
336
337 if ( ! $item->post_format && in_array( 'h-as-note', $entry['type'] ) ) {
338 $item->post_format = 'status';
339 $item->title = '';
340 }
341
342 $feed_items[] = $item;
343 }
344
345 return $feed_items;
346 }
347
348 /**
349 * Fetches a feed and returns the processed items.
350 *
351 * @param string $url The url.
352 * @param User_Feed $user_feed The user feed.
353 *
354 * @return array An array of feed items.
355 */
356 public function fetch_feed( $url, ?User_Feed $user_feed = null ) {
357 $mf = Mf2\fetch( $url );
358 if ( ! $mf ) {
359 // translators: %s is a URL.
360 return new \WP_Error( 'microformats Parser', sprintf( __( 'Could not parse %s.', 'friends' ), $url ) );
361 }
362
363 return $this->parse_hfeed( $mf );
364 }
365 }
366