PluginProbe
FeedWordPress / trunk
FeedWordPress vtrunk
trunk 0.8 0.9 0.91 0.95 0.96 0.97 0.98 0.981 0.99 0.991 0.992 0.993 2008.1030 2008.1101 2008.1105 2008.1214 2009.0612 2009.0613 2009.0618 2009.0707 2009.1111 2009.1112 2010.0127 2010.0528 All 65 releases
← All changes | syndicatedpost.class.php +2141 -1001 2010.0127trunk View file →
@@ -1,1101 +1,702 @@
1 1 <?php
2 +require_once dirname(__FILE__) . '/feedtime.class.php';
3 +require_once dirname(__FILE__) . '/syndicatedpostterm.class.php';
4 +require_once dirname(__FILE__) . '/syndicatedpostxpathquery.class.php';
5 +
6 +/**
7 + * class SyndicatedPost: FeedWordPress uses to manage the conversion of
8 + * incoming items from the feed parser into posts for the WordPress
9 + * database. It contains several internal management methods primarily
10 + * of interest to someone working on the FeedWordPress source, as well
11 + * as some utility methods for extracting useful data from many
12 + * different feed formats, which may be useful to FeedWordPress users
13 + * who make use of feed data in PHP add-ons and filters.
14 + *
15 + * @version 2017.1018
16 + */
2 17 class SyndicatedPost {
18 + /** @var MagpieRSS|null MagpieRSS representation. */
3 19 var $item = null;
4 -
20 + /** @var SimplePie_Item|null SimplePie_Item representation. */
21 + var $entry = null;
22 +
5 23 var $link = null;
6 24 var $feed = null;
7 25 var $feedmeta = null;
8 -
9 - var $post = array ();
10 26
27 + var $xmlns = array();
28 +
29 + var $post = array();
30 +
31 + var $named = array();
32 + var $preset_terms = array();
33 + var $feed_terms = array();
34 +
11 35 var $_freshness = null;
12 36 var $_wp_id = null;
37 + var $_wp_post = null;
13 38
14 - function SyndicatedPost ($item, $link) {
15 - global $wpdb;
39 + /**
40 + * SyndicatedPost constructor: Given a feed item and the source from
41 + * which it was taken, prepare a post that can be inserted into the
42 + * WordPress database on request, or updated in place if it has already
43 + * been syndicated.
44 + *
45 + * @param array $item The item syndicated from the feed.
46 + * @param SyndicatedLink $source The feed it was syndicated from.
47 + */
48 + public function __construct( $item, $source ) {
49 + if ( empty( $item ) and empty( $source ) )
50 + return;
16 51
17 - $this->link = $link;
18 - $feedmeta = $link->settings;
19 - $feed = $link->magpie;
52 + if ( is_array( $item )
53 + and isset( $item['simplepie'] )
54 + and isset( $item['magpie'] ) ) :
55 + $this->entry = $item['simplepie'];
56 + $this->item = $item['magpie'];
57 + $item = $item['magpie'];
58 + elseif ( is_a( $item, 'SimplePie_Item' ) ) :
59 + $this->entry = $item;
20 60
21 - # This is ugly as all hell. I'd like to use apply_filters()'s
22 - # alleged support for a variable argument count, but this seems
23 - # to have been broken in WordPress 1.5. It'll be fixed somehow
24 - # in WP 1.5.1, but I'm aiming at WP 1.5 compatibility across
25 - # the board here.
26 - #
27 - # Cf.: <http://mosquito.wordpress.org/view.php?id=901>
28 - global $fwp_channel, $fwp_feedmeta;
29 - $fwp_channel = $feed; $fwp_feedmeta = $feedmeta;
61 + // convert to Magpie for compat purposes
62 + $mpie = new MagpieFromSimplePie( $source->simplepie, $this->entry );
63 + $this->item = $mpie->get_item();
30 64
31 - $this->feed = $feed;
32 - $this->feedmeta = $feedmeta;
65 + // done with conversion object
66 + $mpie = NULL; unset( $mpie );
67 + else :
68 + $this->item = $item;
69 + endif;
33 70
34 - $this->item = $item;
35 - $this->item = apply_filters('syndicated_item', $this->item, $this);
71 + $this->link = $source;
72 + $this->feed = $source->magpie;
73 + $this->feedmeta = $source->settings;
36 74
75 + FeedWordPress::diagnostic( 'feed_items', 'Considering item [' . $this->guid() . '] "' . $this->entry->get_title().'"');
76 +
77 + # Dealing with namespaces can get so fucking fucked.
78 + $this->xmlns['forward'] = $source->magpie->_XMLNS_FAMILIAR;
79 + $this->xmlns['reverse'] = array();
80 + foreach ( $this->xmlns['forward'] as $url => $ns ) :
81 + if ( ! isset( $this->xmlns['reverse'][ $ns ] ) ) :
82 + $this->xmlns['reverse'][ $ns ] = array();
83 + endif;
84 + $this->xmlns['reverse'][ $ns ][] = $url;
85 + endforeach;
86 +
87 + // Fucking SimplePie.
88 + $this->xmlns['reverse']['rss'][] = '';
89 +
90 + // Trigger global syndicated_item filter.
91 + $changed = apply_filters( 'syndicated_item', $this->item, $this );
92 + $this->item = $changed;
93 +
94 + // Allow for feed-specific syndicated_item filters.
95 + $changed = apply_filters(
96 + "syndicated_item_" . $source->uri(),
97 + $this->item,
98 + $this
99 + );
100 + $this->item = $changed;
101 +
37 102 # Filters can halt further processing by returning NULL
38 - if (is_null($this->item)) :
103 + if ( is_null( $this->item ) ) :
39 104 $this->post = NULL;
40 105 else :
41 - # Note that nothing is run through $wpdb->escape() here.
106 +
107 + # Note that nothing is run through esc_sql() here.
42 108 # That's deliberate. The escaping is done at the point
43 109 # of insertion, not here, to avoid double-escaping and
44 110 # to avoid screwing with syndicated_post filters
45 111
46 - $this->post['post_title'] = apply_filters('syndicated_item_title', $this->item['title'], $this);
112 + $this->post['post_title'] = apply_filters(
113 + 'syndicated_item_title',
114 + $this->entry->get_title(),
115 + $this
116 + );
47 117
48 - // This just gives us an alphanumeric representation of
49 - // the author. We will look up (or create) the numeric
50 - // ID for the author in SyndicatedPost::add()
51 - $this->post['named']['author'] = apply_filters('syndicated_item_author', $this->author(), $this);
118 + $this->named['author'] = apply_filters(
119 + 'syndicated_item_author',
120 + $this->author(),
121 + $this
122 + );
123 + // This just gives us an alphanumeric name for the author.
124 + // We look up (or create) the numeric ID for the author
125 + // in SyndicatedPost::add().
52 126
53 - # Identify content and sanitize it.
54 - # ---------------------------------
55 - if (isset($this->item['atom_content'])) :
56 - $content = $this->item['atom_content'];
57 - elseif (isset($this->item['xhtml']['body'])) :
58 - $content = $this->item['xhtml']['body'];
59 - elseif (isset($this->item['xhtml']['div'])) :
60 - $content = $this->item['xhtml']['div'];
61 - elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']):
62 - $content = $this->item['content']['encoded'];
63 - else:
64 - $content = $this->item['description'];
65 - endif;
66 - $this->post['post_content'] = apply_filters('syndicated_item_content', $content, $this);
127 + $this->post['post_content'] = apply_filters(
128 + 'syndicated_item_content',
129 + $this->content(),
130 + $this
131 + );
67 132
68 - # Identify and sanitize excerpt
69 - $excerpt = NULL;
70 - if ( isset($this->item['description']) and $this->item['description'] ) :
71 - $excerpt = $this->item['description'];
72 - elseif ( isset($content) and $content ) :
73 - $excerpt = strip_tags($content);
74 - if (strlen($excerpt) > 255) :
75 - $excerpt = substr($excerpt,0,252).'...';
76 - endif;
77 - endif;
78 - $excerpt = apply_filters('syndicated_item_excerpt', $excerpt, $this);
133 + $excerpt = apply_filters( 'syndicated_item_excerpt', $this->excerpt(), $this );
79 134
80 - if (!is_null($excerpt)):
135 + if ( !empty( $excerpt ) ):
81 136 $this->post['post_excerpt'] = $excerpt;
82 137 endif;
83 -
84 - // This is unnecessary if we use wp_insert_post
85 - if (!$this->use_api('wp_insert_post')) :
86 - $this->post['post_name'] = sanitize_title($this->post['post_title']);
87 - endif;
88 138
89 - $this->post['epoch']['issued'] = apply_filters('syndicated_item_published', $this->published(), $this);
90 - $this->post['epoch']['created'] = apply_filters('syndicated_item_created', $this->created(), $this);
91 - $this->post['epoch']['modified'] = apply_filters('syndicated_item_updated', $this->updated(), $this);
139 + // Dealing with timestamps in WordPress is so fucking fucked.
140 + $offset = (int) get_option( 'gmt_offset' ) * 60 * 60;
141 + $post_date_gmt = $this->published( array( 'default' => -1 ) );
142 + $post_modified_gmt = $this->updated( array( 'default' => -1 ) );
92 143
93 - // Dealing with timestamps in WordPress is so fucking fucked.
94 - $offset = (int) get_option('gmt_offset') * 60 * 60;
95 - $this->post['post_date'] = gmdate('Y-m-d H:i:s', $this->published() + $offset);
96 - $this->post['post_modified'] = gmdate('Y-m-d H:i:s', $this->updated() + $offset);
97 - $this->post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $this->published());
98 - $this->post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $this->updated());
144 + $this->post['post_date_gmt'] = gmdate( 'Y-m-d H:i:s', $post_date_gmt );
145 + $this->post['post_date'] = gmdate( 'Y-m-d H:i:s', $post_date_gmt + $offset );
146 + $this->post['post_modified_gmt'] = gmdate( 'Y-m-d H:i:s', $post_modified_gmt );
147 + $this->post['post_modified'] = gmdate( 'Y-m-d H:i:s', $post_modified_gmt + $offset );
99 148
100 149 // Use feed-level preferences or the global default.
101 - $this->post['post_status'] = $this->link->syndicated_status('post', 'publish');
102 - $this->post['comment_status'] = $this->link->syndicated_status('comment', 'closed');
103 - $this->post['ping_status'] = $this->link->syndicated_status('ping', 'closed');
150 + $this->post['post_status'] = $this->link->syndicated_status( 'post', 'publish' );
151 + $this->post['comment_status'] = $this->link->syndicated_status( 'comment', 'closed' );
152 + $this->post['ping_status'] = $this->link->syndicated_status( 'ping', 'closed' );
104 153
105 154 // Unique ID (hopefully a unique tag: URI); failing that, the permalink
106 - $this->post['guid'] = apply_filters('syndicated_item_guid', $this->guid(), $this);
155 + $this->post['guid'] = apply_filters( 'syndicated_item_guid', $this->guid(), $this );
107 156
108 - // User-supplied custom settings to apply to each post. Do first so that FWP-generated custom settings will overwrite if necessary; thus preventing any munging
109 - $default_custom_settings = get_option('feedwordpress_custom_settings');
110 - if ($default_custom_settings and !is_array($default_custom_settings)) :
111 - $default_custom_settings = unserialize($default_custom_settings);
112 - endif;
113 - if (!is_array($default_custom_settings)) :
114 - $default_custom_settings = array();
115 - endif;
116 -
117 - $custom_settings = (isset($this->link->settings['postmeta']) ? $this->link->settings['postmeta'] : null);
118 - if ($custom_settings and !is_array($custom_settings)) :
119 - $custom_settings = unserialize($custom_settings);
120 - endif;
121 - if (!is_array($custom_settings)) :
122 - $custom_settings = array();
123 - endif;
124 - $this->post['meta'] = array_merge($default_custom_settings, $custom_settings);
157 + // User-supplied custom settings to apply to each post.
158 + // Do first so that FWP-generated custom settings will
159 + // overwrite if necessary; thus preventing any munging.
160 + $postMetaIn = $this->link->postmeta( array( "parsed" => true ) );
161 + $postMetaOut = array();
125 162
163 + foreach ( $postMetaIn as $key => $meta ) :
164 + $postMetaOut[ $key ] = $meta->do_substitutions( $this );
165 + endforeach;
166 +
167 + foreach ( $postMetaOut as $key => $values ) :
168 + if ( is_null( $values ) ) { // have chosen to replace value with empty string
169 + $values = [ '' ];
170 + }
171 + $this->post['meta'][ $key ] = array();
172 + foreach ( $values as $value ) :
173 + $this->post['meta'][ $key ][] = apply_filters( "syndicated_post_meta_{$key}", $value, $this );
174 + endforeach;
175 + endforeach;
176 +
126 177 // RSS 2.0 / Atom 1.0 enclosure support
127 - if ( isset($this->item['enclosure#']) ) :
128 - for ($i = 1; $i <= $this->item['enclosure#']; $i++) :
129 - $eid = (($i > 1) ? "#{$id}" : "");
130 - $this->post['meta']['enclosure'][] =
131 - apply_filters('syndicated_item_enclosure_url', $this->item["enclosure{$eid}@url"], $this)."\n".
132 - apply_filters('syndicated_item_enclosure_length', $this->item["enclosure{$eid}@length"], $this)."\n".
133 - apply_filters('syndicated_item_enclosure_type', $this->item["enclosure{$eid}@type"], $this);
134 - endfor;
135 - endif;
178 + $enclosures = $this->entry->get_enclosures();
179 + if ( is_array( $enclosures ) ) : foreach ( $enclosures as $enclosure ) :
180 + $this->post['meta']['enclosure'][] =
181 + apply_filters( 'syndicated_item_enclosure_url', $enclosure->get_link(), $this ) . "\n".
182 + apply_filters( 'syndicated_item_enclosure_length', $enclosure->get_length(), $this ) . "\n".
183 + apply_filters( 'syndicated_item_enclosure_type', $enclosure->get_type(), $this );
184 + endforeach; endif;
136 185
137 - // In case you want to point back to the blog this was syndicated from
138 - if (isset($this->feed->channel['title'])) :
139 - $this->post['meta']['syndication_source'] = apply_filters('syndicated_item_source_title', $this->feed->channel['title'], $this);
140 - endif;
186 + // In case you want to point back to the blog this was
187 + // syndicated from.
141 188
142 - if (isset($this->feed->channel['link'])) :
143 - $this->post['meta']['syndication_source_uri'] = apply_filters('syndicated_item_source_link', $this->feed->channel['link'], $this);
144 - endif;
145 -
189 + $sourcemeta['syndication_source'] = apply_filters(
190 + 'syndicated_item_source_title',
191 + $this->link->name(),
192 + $this
193 + );
194 + $sourcemeta['syndication_source_uri'] = apply_filters(
195 + 'syndicated_item_source_link',
196 + $this->link->homepage(),
197 + $this
198 + );
199 + $sourcemeta['syndication_source_id'] = apply_filters(
200 + 'syndicated_item_source_id',
201 + $this->link->guid(),
202 + $this
203 + );
204 +
146 205 // Make use of atom:source data, if present in an aggregated feed
147 - if (isset($this->item['source_title'])) :
148 - $this->post['meta']['syndication_source_original'] = $this->item['source_title'];
206 + $entry_source = $this->source();
207 + if ( ! is_null( $entry_source ) ) :
208 + foreach ( $entry_source as $what => $value ) :
209 + if ( ! is_null( $value ) ) :
210 + if ( 'title' == $what ) : $key = 'syndication_source';
211 + elseif ( 'feed' == $what ) : $key = 'syndication_feed';
212 + else : $key = "syndication_source_{$what}";
213 + endif;
214 +
215 + $sourcemeta["{$key}_original"] = apply_filters(
216 + 'syndicated_item_original_source_' . $what,
217 + $value,
218 + $this
219 + );
220 + endif;
221 + endforeach;
149 222 endif;
150 223
151 - if (isset($this->item['source_link'])) :
152 - $this->post['meta']['syndication_source_uri_original'] = $this->item['source_link'];
153 - endif;
154 -
155 - if (isset($this->item['source_id'])) :
156 - $this->post['meta']['syndication_source_id_original'] = $this->item['source_id'];
157 - endif;
224 + foreach ( $sourcemeta as $meta_key => $value ) :
225 + if ( !is_null( $value ) ) :
226 + $this->post['meta'][ $meta_key ] = $value;
227 + endif;
228 + endforeach;
158 229
159 230 // Store information on human-readable and machine-readable comment URIs
160 - if (isset($this->item['comments'])) :
161 - $this->post['meta']['rss:comments'] = apply_filters('syndicated_item_comments', $this->item['comments']);
162 - endif;
163 -
164 - // RSS 2.0 comment feeds extension
165 - if (isset($this->item['wfw']['commentrss'])) :
166 - $this->post['meta']['wfw:commentRSS'] = apply_filters('syndicated_item_commentrss', $this->item['wfw']['commentrss']);
167 - endif;
168 231
169 - // Atom 1.0 comment feeds link-rel
170 - if (isset($this->item['link_replies'])) :
171 - // There may be multiple <link rel="replies"> elements; feeds have a feed MIME type
172 - $N = isset($this->item['link_replies#']) ? $this->item['link_replies#'] : 1;
173 - for ($i = 1; $i <= $N; $i++) :
174 - $currentElement = 'link_replies'.(($i > 1) ? '#'.$i : '');
175 - if (isset($this->item[$currentElement.'@type'])
176 - and preg_match("\007application/(atom|rss|rdf)\+xml\007i", $this->item[$currentElement.'@type'])) :
177 - $this->post['meta']['wfw:commentRSS'] = apply_filters('syndicated_item_commentrss', $this->item[$currentElement]);
178 - endif;
179 - endfor;
180 - endif;
232 + // Human-readable comment URI
233 + $commentLink = apply_filters( 'syndicated_item_comments', $this->comment_link(), $this );
234 + if ( ! is_null( $commentLink) ) : $this->post['meta']['rss:comments'] = $commentLink; endif;
181 235
236 + // Machine-readable content feed URI
237 + $commentFeed = apply_filters( 'syndicated_item_commentrss', $this->comment_feed(), $this );
238 + if ( ! is_null( $commentFeed ) ) : $this->post['meta']['wfw:commentRSS'] = $commentFeed; endif;
239 + // Yeah, yeah, now I know that it's supposed to be
240 + // wfw:commentRss. Oh well. Path dependence, sucka.
241 +
182 242 // Store information to identify the feed that this came from
183 - if (isset($this->feedmeta['link/uri'])) :
243 + if ( isset( $this->feedmeta['link/uri'] ) ) :
184 244 $this->post['meta']['syndication_feed'] = $this->feedmeta['link/uri'];
185 245 endif;
186 - if (isset($this->feedmeta['link/id'])) :
246 + if ( isset( $this->feedmeta['link/id'] ) ) :
187 247 $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id'];
188 248 endif;
189 249
190 - if (isset($this->item['source_link_self'])) :
250 + if ( isset( $this->item['source_link_self'] ) ) :
191 251 $this->post['meta']['syndication_feed_original'] = $this->item['source_link_self'];
192 252 endif;
193 253
194 254 // In case you want to know the external permalink...
195 - if (isset($this->item['link'])) :
196 - $permalink = $this->item['link'];
255 + $this->post['meta']['syndication_permalink'] = apply_filters( 'syndicated_item_link', $this->permalink() );
197 256
198 - // No <link> element. See if this feed has <guid isPermalink="true"> ....
199 - elseif (isset($this->item['guid'])) :
200 - if (isset($this->item['guid@ispermalink']) and strtolower(trim($this->item['guid@ispermalink'])) != 'false') :
201 - $permalink = $this->item['guid'];
202 - endif;
203 - endif;
204 -
205 - $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $permalink);
206 -
207 257 // Store a hash of the post content for checking whether something needs to be updated
208 258 $this->post['meta']['syndication_item_hash'] = $this->update_hash();
209 259
210 - // Feed-by-feed options for author and category creation
211 - $this->post['named']['unfamiliar']['author'] = (isset($this->feedmeta['unfamiliar author']) ? $this->feedmeta['unfamiliar author'] : null);
212 - $this->post['named']['unfamiliar']['category'] = (isset($this->feedmeta['unfamiliar category']) ? $this->feedmeta['unfamiliar category'] : null);
260 + // Categories, Tags, and other Terms: from settings assignments (global settings, subscription settings),
261 + // and from feed assignments (item metadata, post content)
262 + $this->preset_terms = apply_filters( 'syndicated_item_preset_terms', $this->get_terms_from_settings(), $this );
263 + $this->feed_terms = apply_filters( 'syndicated_item_feed_terms', $this->get_terms_from_feeds(), $this );
213 264
214 - // Categories: start with default categories, if any
215 - $fc = get_option("feedwordpress_syndication_cats");
216 - if ($fc) :
217 - $this->post['named']['preset/category'] = explode("\n", $fc);
218 - else :
219 - $this->post['named']['preset/category'] = array();
220 - endif;
221 -
222 - if (isset($this->feedmeta['cats']) and is_array($this->feedmeta['cats'])) :
223 - $this->post['named']['preset/category'] = array_merge($this->post['named']['preset/category'], $this->feedmeta['cats']);
224 - endif;
225 -
226 - // Now add categories from the post, if we have 'em
227 - $this->post['named']['category'] = array();
228 - if ( isset($this->item['category#']) ) :
229 - for ($i = 1; $i <= $this->item['category#']; $i++) :
230 - $cat_idx = (($i > 1) ? "#{$i}" : "");
231 - $cat = $this->item["category{$cat_idx}"];
232 -
233 - if ( isset($this->feedmeta['cat_split']) and strlen($this->feedmeta['cat_split']) > 0) :
234 - $pcre = "\007".$this->feedmeta['cat_split']."\007";
235 - $this->post['named']['category'] = array_merge($this->post['named']['category'], preg_split($pcre, $cat, -1 /*=no limit*/, PREG_SPLIT_NO_EMPTY));
236 - else :
237 - $this->post['named']['category'][] = $cat;
238 - endif;
239 - endfor;
240 - endif;
241 - $this->post['named']['category'] = apply_filters('syndicated_item_categories', $this->post['named']['category'], $this);
242 -
243 - // Tags: start with default tags, if any
244 - $ft = get_option("feedwordpress_syndication_tags");
245 - if ($ft) :
246 - $this->post['tags_input'] = explode(FEEDWORDPRESS_CAT_SEPARATOR, $ft);
247 - else :
248 - $this->post['tags_input'] = array();
249 - endif;
250 -
251 - if (isset($this->feedmeta['tags']) and is_array($this->feedmeta['tags'])) :
252 - $this->post['tags_input'] = array_merge($this->post['tags_input'], $this->feedmeta['tags']);
253 - endif;
254 - $this->post['tags_input'] = apply_filters('syndicated_item_tags', $this->post['tags_input'], $this);
255 - endif;
256 - } // SyndicatedPost::SyndicatedPost()
257 -
258 - function filtered () {
259 - return is_null($this->post);
260 - }
261 -
262 - function freshness () {
263 - global $wpdb;
264 -
265 - if ($this->filtered()) : // This should never happen.
266 - FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
267 - endif;
268 -
269 - if (is_null($this->_freshness)) :
270 - $guid = $wpdb->escape($this->guid());
271 -
272 - $result = $wpdb->get_row("
273 - SELECT id, guid, post_modified_gmt
274 - FROM $wpdb->posts WHERE guid='$guid'
275 - ");
276 -
277 - if (!$result) :
278 - $this->_freshness = 2; // New content
279 - else:
280 - $stored_update_hashes = get_post_custom_values('syndication_item_hash', $result->id);
281 - if (count($stored_update_hashes) > 0) :
282 - $stored_update_hash = $stored_update_hashes[0];
283 - $update_hash_changed = ($stored_update_hash != $this->update_hash());
284 - else :
285 - $update_hash_changed = false;
286 - endif;
287 -
288 - preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref);
289 -
290 - $last_rev_ts = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]);
291 - $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
292 -
293 - $frozen_values = get_post_custom_values('_syndication_freeze_updates', $result->id);
294 - $frozen_post = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]);
295 - $frozen_feed = ('yes' == $this->link->setting('freeze updates', 'freeze_updates', NULL));
296 -
297 - // Check timestamps...
298 - $updated = (
299 - !is_null($updated_ts)
300 - and ($updated_ts > $last_rev_ts)
301 - );
302 -
303 -
304 - // Or the hash...
305 - $updated = ($updated or $update_hash_changed);
306 -
307 - // But only if the post is not frozen.
308 - $updated = (
309 - $updated
310 - and !$frozen_post
311 - and !$frozen_feed
312 - );
313 -
314 - if ($updated) :
315 - $this->_freshness = 1; // Updated content
316 - $this->_wp_id = $result->id;
317 - else :
318 - $this->_freshness = 0; // Same old, same old
319 - $this->_wp_id = $result->id;
320 - endif;
321 - endif;
322 - endif;
323 - return $this->_freshness;
324 - }
325 -
326 - function wp_id () {
327 - if ($this->filtered()) : // This should never happen.
328 - FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
329 - endif;
330 -
331 - if (is_null($this->_wp_id) and is_null($this->_freshness)) :
332 - $fresh = $this->freshness(); // sets WP DB id in the process
333 - endif;
334 - return $this->_wp_id;
335 - }
336 -
337 - function store () {
338 - global $wpdb;
339 -
340 - if ($this->filtered()) : // This should never happen.
341 - FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
342 - endif;
343 -
344 - $freshness = $this->freshness();
345 - if ($freshness > 0) :
346 - # -- Look up, or create, numeric ID for author
347 - $this->post['post_author'] = $this->author_id (
348 - FeedWordPress::on_unfamiliar('author', $this->post['named']['unfamiliar']['author'])
265 + $this->post['post_type'] = apply_filters(
266 + 'syndicated_post_type',
267 + $this->link->setting( 'syndicated post type', 'syndicated_post_type', 'post' ),
268 + $this
349 269 );
350 -
351 - if (is_null($this->post['post_author'])) :
352 - $this->post = NULL;
353 - endif;
354 270 endif;
355 -
356 - if (!$this->filtered() and $freshness > 0) :
357 - # -- Look up, or create, numeric ID for categories
358 - list($pcats, $ptags) = $this->category_ids (
359 - $this->post['named']['category'],
360 - FeedWordPress::on_unfamiliar('category', $this->post['named']['unfamiliar']['category']),
361 - /*tags_too=*/ true
362 - );
271 + } /* SyndicatedPost::__construct() */
363 272
364 - $this->post['post_category'] = $pcats;
365 - $this->post['tags_input'] = array_merge($this->post['tags_input'], $ptags);
273 + #####################################
274 + #### EXTRACT DATA FROM FEED ITEM ####
275 + #####################################
366 276
367 - if (is_null($this->post['post_category'])) :
368 - // filter mode on, no matching categories; drop the post
369 - $this->post = NULL;
370 - else :
371 - // filter mode off or at least one match; now add on the feed and global presets
372 - $this->post['post_category'] = array_merge (
373 - $this->post['post_category'],
374 - $this->category_ids (
375 - $this->post['named']['preset/category'],
376 - 'default'
377 - )
378 - );
277 + function substitution_function ($name) {
278 + $ret = NULL;
379 279
380 - if (count($this->post['post_category']) < 1) :
381 - $this->post['post_category'][] = 1; // Default to category 1 ("Uncategorized" / "General") if nothing else
382 - endif;
383 - endif;
384 - endif;
385 -
386 - if (!$this->filtered() and $freshness > 0) :
387 - unset($this->post['named']);
388 - $this->post = apply_filters('syndicated_post', $this->post, $this);
389 - endif;
390 -
391 - if (!$this->filtered() and $freshness == 2) :
392 - // The item has not yet been added. So let's add it.
393 - $this->insert_new();
394 - $this->add_rss_meta();
395 - do_action('post_syndicated_item', $this->wp_id(), $this);
396 -
397 - $ret = 'new';
398 - elseif (!$this->filtered() and $freshness == 1) :
399 - $this->post['ID'] = $this->wp_id();
400 - $this->update_existing();
401 - $this->add_rss_meta();
402 - do_action('update_syndicated_item', $this->wp_id(), $this);
403 -
404 - $ret = 'updated';
405 - else :
406 - $ret = false;
407 - endif;
408 -
280 + switch ($name) :
281 + // Allowed PHP string functions
282 + case 'trim':
283 + case 'ltrim':
284 + case 'rtrim':
285 + case 'strtoupper':
286 + case 'strtolower':
287 + case 'urlencode':
288 + case 'urldecode':
289 + $ret = $name;
290 + endswitch;
409 291 return $ret;
410 - } // function SyndicatedPost::store ()
411 -
412 - function insert_new () {
413 - global $wpdb, $wp_db_version;
414 -
415 - $dbpost = $this->normalize_post(/*new=*/ true);
416 - if (!is_null($dbpost)) :
417 - if ($this->use_api('wp_insert_post')) :
418 - $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
419 -
420 - // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
421 - add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
422 -
423 - // Kludge to prevent kses filters from stripping the
424 - // content of posts when updating without a logged in
425 - // user who has `unfiltered_html` capability.
426 - add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
427 -
428 - $this->_wp_id = wp_insert_post($dbpost);
429 -
430 - // Turn off ridiculous fucking kludges #1 and #2
431 - remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
432 - remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
433 -
434 - $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
435 -
436 - // Unfortunately, as of WordPress 2.3, wp_insert_post()
437 - // *still* offers no way to use a guid of your choice,
438 - // and munges your post modified timestamp, too.
439 - $result = $wpdb->query("
440 - UPDATE $wpdb->posts
441 - SET
442 - guid='{$dbpost['guid']}',
443 - post_modified='{$dbpost['post_modified']}',
444 - post_modified_gmt='{$dbpost['post_modified_gmt']}'
445 - WHERE ID='{$this->_wp_id}'
446 - ");
447 - else :
448 - # The right way to do this is the above. But, alas,
449 - # in earlier versions of WordPress, wp_insert_post has
450 - # too much behavior (mainly related to pings) that can't
451 - # be overridden. In WordPress 1.5, it's enough of a
452 - # resource hog to make PHP segfault after inserting
453 - # 50-100 posts. This can get pretty annoying, especially
454 - # if you are trying to update your feeds for the first
455 - # time.
456 -
457 - $result = $wpdb->query("
458 - INSERT INTO $wpdb->posts
459 - SET
460 - guid = '{$dbpost['guid']}',
461 - post_author = '{$dbpost['post_author']}',
462 - post_date = '{$dbpost['post_date']}',
463 - post_date_gmt = '{$dbpost['post_date_gmt']}',
464 - post_content = '{$dbpost['post_content']}',"
465 - .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
466 - post_title = '{$dbpost['post_title']}',
467 - post_name = '{$dbpost['post_name']}',
468 - post_modified = '{$dbpost['post_modified']}',
469 - post_modified_gmt = '{$dbpost['post_modified_gmt']}',
470 - comment_status = '{$dbpost['comment_status']}',
471 - ping_status = '{$dbpost['ping_status']}',
472 - post_status = '{$dbpost['post_status']}'
473 - ");
474 - $this->_wp_id = $wpdb->insert_id;
475 -
476 - $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
477 -
478 - // WordPress 1.5.x - 2.0.x
479 - wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
480 -
481 - // Since we are not going through official channels, we need to
482 - // manually tell WordPress that we've published a new post.
483 - // We need to make sure to do this in order for FeedWordPress
484 - // to play well with the staticize-reloaded plugin (something
485 - // that a large aggregator website is going to *want* to be
486 - // able to use).
487 - do_action('publish_post', $this->_wp_id);
488 - endif;
489 - endif;
490 - } /* SyndicatedPost::insert_new() */
491 -
492 - function update_existing () {
493 - global $wpdb;
494 -
495 - // Why the fuck doesn't wp_insert_post already do this?
496 - $dbpost = $this->normalize_post(/*new=*/ false);
497 - if (!is_null($dbpost)) :
498 - if ($this->use_api('wp_insert_post')) :
499 - $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
500 -
501 - // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
502 - add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
503 -
504 - // Kludge to prevent kses filters from stripping the
505 - // content of posts when updating without a logged in
506 - // user who has `unfiltered_html` capability.
507 - add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
508 -
509 - // Don't munge status fields that the user may have reset manually
510 - if (function_exists('get_post_field')) :
511 - $doNotMunge = array('post_status', 'comment_status', 'ping_status');
512 - foreach ($doNotMunge as $field) :
513 - $dbpost[$field] = get_post_field($field, $this->wp_id());
514 - endforeach;
515 - endif;
516 -
517 - $this->_wp_id = wp_insert_post($dbpost);
518 -
519 - // Turn off ridiculous fucking kludges #1 and #2
520 - remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
521 - remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
522 -
523 - $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
524 -
525 - // Unfortunately, as of WordPress 2.3, wp_insert_post()
526 - // munges your post modified timestamp.
527 - $result = $wpdb->query("
528 - UPDATE $wpdb->posts
529 - SET
530 - post_modified='{$dbpost['post_modified']}',
531 - post_modified_gmt='{$dbpost['post_modified_gmt']}'
532 - WHERE ID='{$this->_wp_id}'
533 - ");
534 - else :
535 -
536 - $result = $wpdb->query("
537 - UPDATE $wpdb->posts
538 - SET
539 - post_author = '{$dbpost['post_author']}',
540 - post_content = '{$dbpost['post_content']}',"
541 - .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
542 - post_title = '{$dbpost['post_title']}',
543 - post_name = '{$dbpost['post_name']}',
544 - post_modified = '{$dbpost['post_modified']}',
545 - post_modified_gmt = '{$dbpost['post_modified_gmt']}'
546 - WHERE guid='{$dbpost['guid']}'
547 - ");
548 -
549 - // WordPress 2.1.x and up
550 - if (function_exists('wp_set_post_categories')) :
551 - wp_set_post_categories($this->wp_id(), $this->post['post_category']);
552 - // WordPress 1.5.x - 2.0.x
553 - elseif (function_exists('wp_set_post_cats')) :
554 - wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
555 - // This should never happen.
556 - else :
557 - FeedWordPress::critical_bug(__CLASS__.'::'.__FUNCTION.'(): no post categorizing function', array("dbpost" => $dbpost, "this" => $this), __LINE__);
558 - endif;
559 -
560 - // Since we are not going through official channels, we need to
561 - // manually tell WordPress that we've published a new post.
562 - // We need to make sure to do this in order for FeedWordPress
563 - // to play well with the staticize-reloaded plugin (something
564 - // that a large aggregator website is going to *want* to be
565 - // able to use).
566 - do_action('edit_post', $this->post['ID']);
567 - endif;
568 - endif;
569 - } /* SyndicatedPost::update_existing() */
570 -
571 - /**
572 - * SyndicatedPost::normalize_post()
573 - *
574 - * @param bool $new If true, this post is to be inserted anew. If false, it is an update of an existing post.
575 - * @return array A normalized representation of the post ready to be inserted into the database or sent to the WordPress API functions
576 - */
577 - function normalize_post ($new = true) {
578 - global $wpdb;
579 -
580 - $out = array();
581 -
582 - // Why the fuck doesn't wp_insert_post already do this?
583 - foreach ($this->post as $key => $value) :
584 - if (is_string($value)) :
585 - $out[$key] = $wpdb->escape($value);
586 - else :
587 - $out[$key] = $value;
588 - endif;
589 - endforeach;
590 -
591 - if (strlen($out['post_title'].$out['post_content'].$out['post_excerpt']) == 0) :
592 - // FIXME: Option for filtering out empty posts
593 - endif;
594 - if (strlen($out['post_title'])==0) :
595 - $offset = (int) get_option('gmt_offset') * 60 * 60;
596 - $out['post_title'] =
597 - $this->post['meta']['syndication_source']
598 - .' '.gmdate('Y-m-d H:i:s', $this->published() + $offset);
599 - // FIXME: Option for what to fill a blank title with...
600 - endif;
601 -
602 - return $out;
603 292 }
604 293
605 294 /**
606 - * SyndicatedPost::validate_post_id()
295 + * SyndicatedPost::query uses an XPath-like syntax to query arbitrary
296 + * elements within the syndicated item.
607 297 *
608 - * @param array $dbpost An array representing the post we attempted to insert or update
609 - * @param mixed $ns A string or array representing the namespace (class, method) whence this method was called.
298 + * @param string $path
299 + * @returns array of string values representing contents of matching
300 + * elements or attributes
610 301 */
611 - function validate_post_id ($dbpost, $ns) {
612 - if (is_array($ns)) : $ns = implode('::', $ns);
613 - else : $ns = (string) $ns; endif;
614 -
615 - // This should never happen.
616 - if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
617 - FeedWordPress::critical_bug(
618 - /*name=*/ $ns.'::_wp_id',
619 - /*var =*/ array(
620 - "\$this->_wp_id" => $this->_wp_id,
621 - "\$dbpost" => $dbpost,
622 - "\$this" => $this
623 - ),
624 - /*line # =*/ __LINE__
625 - );
626 - endif;
627 - } /* SyndicatedPost::validate_post_id() */
628 -
629 - /**
630 - * SyndicatedPost::fix_revision_meta() - Fixes the way WP 2.6+ fucks up
631 - * meta-data (authorship, etc.) when storing revisions of an updated
632 - * syndicated post.
633 - *
634 - * In their infinite wisdom, the WordPress coders have made it completely
635 - * impossible for a plugin that uses wp_insert_post() to set certain
636 - * meta-data (such as the author) when you store an old revision of an
637 - * updated post. Instead, it uses the WordPress defaults (= currently
638 - * active user ID if the process is running with a user logged in, or
639 - * = #0 if there is no user logged in). This results in bogus authorship
640 - * data for revisions that are syndicated from off the feed, unless we
641 - * use a ridiculous kludge like this to end-run the munging of meta-data
642 - * by _wp_put_post_revision.
643 - *
644 - * @param int $revision_id The revision ID to fix up meta-data
645 - */
646 - function fix_revision_meta ($revision_id) {
647 - global $wpdb;
648 -
649 - $post_author = (int) $this->post['post_author'];
650 -
651 - $revision_id = (int) $revision_id;
652 - $wpdb->query("
653 - UPDATE $wpdb->posts
654 - SET post_author={$this->post['post_author']}
655 - WHERE post_type = 'revision' AND ID='$revision_id'
656 - ");
657 - } /* SyndicatedPost::fix_revision_meta () */
302 + public function query ($path) {
303 + $xq = new SyndicatedPostXPathQuery(array("path" => $path));
658 304
659 - /**
660 - * SyndicatedPost::avoid_kses_munge() -- If FeedWordPress is processing
661 - * an automatic update, that generally means that wp_insert_post() is
662 - * being called under the user credentials of whoever is viewing the
663 - * blog at the time -- usually meaning no user at all. But if WordPress
664 - * gets a wp_insert_post() when current_user_can('unfiltered_html') is
665 - * false, it will run the content of the post through a kses function
666 - * that strips out lots of HTML tags -- notably <object> and some others.
667 - * This causes problems for syndicating (for example) feeds that contain
668 - * YouTube videos. It also produces an unexpected asymmetry between
669 - * automatically-initiated updates and updates initiated manually from
670 - * the WordPress Dashboard (which are usually initiated under the
671 - * credentials of a logged-in admin, and so don't get run through the
672 - * kses function). So, to avoid the whole mess, what we do here is
673 - * just forcibly disable the kses munging for a single syndicated post,
674 - * by restoring the contents of the `post_content` field.
675 - *
676 - * @param string $content The content of the post, after other filters have gotten to it
677 - * @return string The original content of the post, before other filters had a chance to munge it.
678 - */
679 - function avoid_kses_munge ($content) {
680 - global $wpdb;
681 - return $wpdb->escape($this->post['post_content']);
682 - }
683 -
684 - // SyndicatedPost::add_rss_meta: adds interesting meta-data to each entry
685 - // using the space for custom keys. The set of keys and values to add is
686 - // specified by the keys and values of $post['meta']. This is used to
687 - // store anything that the WordPress user might want to access from a
688 - // template concerning the post's original source that isn't provided
689 - // for by standard WP meta-data (i.e., any interesting data about the
690 - // syndicated post other than author, title, timestamp, categories, and
691 - // guid). It's also used to hook into WordPress's support for
692 - // enclosures.
693 - function add_rss_meta () {
694 - global $wpdb;
695 - if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
696 - $postId = $this->wp_id();
697 -
698 - // Aggregated posts should NOT send out pingbacks.
699 - // WordPress 2.1-2.2 claim you can tell them not to
700 - // using $post_pingback, but they don't listen, so we
701 - // make sure here.
702 - $result = $wpdb->query("
703 - DELETE FROM $wpdb->postmeta
704 - WHERE post_id='$postId' AND meta_key='_pingme'
705 - ");
305 + $feedChannel = array_merge(
306 + $this->get_feed_root_element(),
307 + $this->get_feed_channel_elements()
308 + );
706 309
707 - foreach ( $this->post['meta'] as $key => $values ) :
310 + $matches = $xq->match(array(
311 + "type" => $this->link->simplepie->get_type(),
312 + "xmlns" => $this->xmlns,
313 + "map" => array(
314 + "/" => array($this->entry->data),
315 + "item" => array($this->entry->data),
316 + "feed" => $feedChannel,
317 + "channel" => $feedChannel
318 + ),
319 + "context" => $this->entry->data,
320 + "parent" => $feedChannel,
321 + ));
708 322
709 - $key = $wpdb->escape($key);
323 + return $matches;
324 + } /* SyndicatedPost::query() */
710 325
711 - // If this is an update, clear out the old
712 - // values to avoid duplication.
713 - $result = $wpdb->query("
714 - DELETE FROM $wpdb->postmeta
715 - WHERE post_id='$postId' AND meta_key='$key'
716 - ");
717 -
718 - // Allow for either a single value or an array
719 - if (!is_array($values)) $values = array($values);
720 - foreach ( $values as $value ) :
721 - $value = $wpdb->escape($value);
722 - $result = $wpdb->query("
723 - INSERT INTO $wpdb->postmeta
724 - SET
725 - post_id='$postId',
726 - meta_key='$key',
727 - meta_value='$value'
728 - ");
729 - if (!$result) :
730 - $err = mysql_error();
731 - if (FEEDWORDPRESS_DEBUG) :
732 - echo "[DEBUG:".date('Y-m-d H:i:S')."][feedwordpress]: post metadata insertion FAILED for field '$key' := '$value': [$err]";
733 - endif;
734 - endif;
735 - endforeach;
326 + function get_feed_root_element () {
327 + $matches = array();
328 + foreach ($this->link->simplepie->data['child'] as $ns => $root) :
329 + foreach ($root as $element => $data) :
330 + $matches = array_merge($matches, $data);
736 331 endforeach;
737 - endif;
738 - } /* SyndicatedPost::add_rss_meta () */
332 + endforeach;
333 + return $matches;
334 + } /* SyndicatedPost::get_feed_root_element() */
739 335
740 - // SyndicatedPost::author_id (): get the ID for an author name from
741 - // the feed. Create the author if necessary.
742 - function author_id ($unfamiliar_author = 'create') {
743 - global $wpdb;
336 + function get_feed_channel_elements () {
337 + $rss = array(
338 + SIMPLEPIE_NAMESPACE_RSS_090,
339 + SIMPLEPIE_NAMESPACE_RSS_10,
340 + 'http://backend.userland.com/RSS2',
341 + SIMPLEPIE_NAMESPACE_RSS_20,
342 + );
744 343
745 - $a = $this->author();
746 - $author = $a['name'];
747 - $email = (isset($a['email']) ? $a['email'] : NULL);
748 - $url = (isset($a['uri']) ? $a['uri'] : NULL);
344 + $matches = array();
345 + foreach ($rss as $ns) :
346 + $data = $this->link->simplepie->get_feed_tags($ns, 'channel');
347 + if ( !is_null($data)) :
348 + $matches = array_merge($matches, $data);
349 + endif;
350 + endforeach;
351 + return $matches;
352 + } /* SyndicatedPost::get_feed_channel_elements() */
749 353
750 - $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
751 - if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
752 - $test_email = $email;
753 - else :
754 - $test_email = NULL;
755 - endif;
354 + public function get_categories ($params = array()) {
355 + return $this->entry->get_categories();
356 + }
756 357
757 - // Never can be too careful...
758 - $login = sanitize_user($author, /*strict=*/ true);
759 - $login = apply_filters('pre_user_login', $login);
358 + public function title ($params = array()) {
359 + return $this->entry->get_title();
360 + } /* SyndicatedPost::title () */
760 361
761 - $nice_author = sanitize_title($author);
762 - $nice_author = apply_filters('pre_user_nicename', $nice_author);
362 + public function content ($params = array())
363 + {
364 + $params = wp_parse_args($params, array(
365 + "full only" => false,
366 + ));
763 367
764 - $reg_author = $wpdb->escape(preg_quote($author));
765 - $author = $wpdb->escape($author);
766 - $email = $wpdb->escape($email);
767 - $test_email = $wpdb->escape($test_email);
768 - $url = $wpdb->escape($url);
368 + $content = NULL;
769 369
770 - // Check for an existing author rule....
771 - if (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) :
772 - $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))];
773 - else :
774 - $author_rule = NULL;
775 - endif;
370 + // FIXME: This is one of the main places in the code still using
371 + // the outmoded SimplePie - to - Magpie construction. We could
372 + // replace using SimplePie_Item::get_tags() here. (Or if really
373 + // ambitious we could attempt to just use
374 + // SimplePie_Item::get_content() with content-only set to TRUE
375 + // and some sanitization in effect. -CJ 1jul14
776 376
777 - // User name is mapped to a particular author. If that author ID exists, use it.
778 - if (is_numeric($author_rule) and get_userdata((int) $author_rule)) :
779 - $id = (int) $author_rule;
377 + // atom:content, standard method of providing full HTML content
378 + // in Atom feeds.
379 + if (isset($this->item['atom_content'])) :
380 + $content = $this->item['atom_content'];
381 + elseif (isset($this->item['atom']['atom_content'])) :
382 + $content = $this->item['atom']['atom_content'];
780 383
781 - // User name is filtered out
782 - elseif ('filter' == $author_rule) :
783 - $id = NULL;
784 -
785 - else :
786 - // Check the database for an existing author record that might fit
384 + // Some exotics: back in the day, before widespread convergence
385 + // on content:encoding, some RSS feeds took advantage of XML
386 + // namespaces to use an inline xhtml:body or xhtml:div element
387 + // for full-content HTML. (E.g. Sam Ruby's feed, IIRC.)
388 + elseif (isset($this->item['xhtml']['body'])) :
389 + $content = $this->item['xhtml']['body'];
390 + elseif (isset($this->item['xhtml']['div'])) :
391 + $content = $this->item['xhtml']['div'];
787 392
788 - #-- WordPress 2.0+
789 - if (fwp_test_wp_version(FWP_SCHEMA_HAS_USERMETA)) :
393 + // content:encoded, most common method of providing full HTML in
394 + // RSS 2.0 feeds.
395 + elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']):
396 + $content = $this->item['content']['encoded'];
790 397
791 - // First try the user core data table.
792 - $id = $wpdb->get_var(
793 - "SELECT ID FROM $wpdb->users
794 - WHERE
795 - TRIM(LCASE(user_login)) = TRIM(LCASE('$login'))
796 - OR (
797 - LENGTH(TRIM(LCASE(user_email))) > 0
798 - AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
799 - )
800 - OR TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author'))
801 - ");
802 -
803 - // If that fails, look for aliases in the user meta data table
804 - if (is_null($id)) :
805 - $id = $wpdb->get_var(
806 - "SELECT user_id FROM $wpdb->usermeta
807 - WHERE
808 - (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
809 - OR (
810 - meta_key = 'description'
811 - AND TRIM(LCASE(meta_value))
812 - RLIKE CONCAT(
813 - '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
814 - TRIM(LCASE('$reg_author')),
815 - '( |\\t|\\r)*(\\n|\$)'
816 - )
817 - )
818 - ");
819 - endif;
398 + // Fall back on elements that sometimes may contain full HTML
399 + // but sometimes not.
400 + elseif ( ! $params['full only']) :
820 401
821 - #-- WordPress 1.5.x
822 - else :
823 - $id = $wpdb->get_var(
824 - "SELECT ID from $wpdb->users
825 - WHERE
826 - TRIM(LCASE(user_login)) = TRIM(LCASE('$login')) OR
827 - (
828 - LENGTH(TRIM(LCASE(user_email))) > 0
829 - AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
830 - ) OR
831 - TRIM(LCASE(user_firstname)) = TRIM(LCASE('$author')) OR
832 - TRIM(LCASE(user_nickname)) = TRIM(LCASE('$author')) OR
833 - TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author')) OR
834 - TRIM(LCASE(user_description)) = TRIM(LCASE('$author')) OR
835 - (
836 - LOWER(user_description)
837 - RLIKE CONCAT(
838 - '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
839 - LCASE('$reg_author'),
840 - '( |\\t|\\r)*(\\n|\$)'
841 - )
842 - )
843 - ");
844 -
402 + // description element is sometimes used for full HTML
403 + // sometimes for summary text in RSS. (By the letter of
404 + // the standard, it should just be for summary text.)
405 + if (isset($this->item['description'])) :
406 + $content = $this->item['description'];
845 407 endif;
846 408
847 - // ... if you don't find one, then do what you need to do
848 - if (is_null($id)) :
849 - if ($unfamiliar_author === 'create') :
850 - $userdata = array();
851 -
852 - #-- user table data
853 - $userdata['ID'] = NULL; // new user
854 - $userdata['user_login'] = $login;
855 - $userdata['user_nicename'] = $nice_author;
856 - $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
857 - $userdata['user_email'] = $email;
858 - $userdata['user_url'] = $url;
859 - $userdata['display_name'] = $author;
860 -
861 - $id = wp_insert_user($userdata);
862 - elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
863 - $id = (int) $unfamiliar_author;
864 - elseif ($unfamiliar_author === 'default') :
865 - $id = 1;
866 - endif;
867 - endif;
868 409 endif;
869 410
870 - if ($id) :
871 - $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id;
872 - endif;
873 - return $id;
874 - } // function SyndicatedPost::author_id ()
411 + return $content;
412 + } /* SyndicatedPost::content() */
875 413
876 - // look up (and create) category ids from a list of categories
877 - function category_ids ($cats, $unfamiliar_category = 'create', $tags_too = false) {
878 - global $wpdb;
414 + public function excerpt () {
415 + # Identify and sanitize excerpt: atom:summary, or rss:description
416 + $excerpt = $this->entry->get_description();
879 417
880 - // We need to normalize whitespace because (1) trailing
881 - // whitespace can cause PHP and MySQL not to see eye to eye on
882 - // VARCHAR comparisons for some versions of MySQL (cf.
883 - // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
884 - // because I doubt most people want to make a semantic
885 - // distinction between 'Computers' and 'Computers '
886 - $cats = array_map('trim', $cats);
418 + # Many RSS feeds use rss:description, inadvisably, to
419 + # carry the entire post (typically with escaped HTML).
420 + # If that's what happened, we don't want the full
421 + # content for the excerpt.
422 + $content = $this->content();
887 423
888 - $tags = array();
889 -
890 - $cat_ids = array ();
891 - foreach ($cats as $cat_name) :
892 - if (preg_match('/^{#([0-9]+)}$/', $cat_name, $backref)) :
893 - $cat_id = (int) $backref[1];
894 - if (function_exists('is_term') and is_term($cat_id, 'category')) :
895 - $cat_ids[] = $cat_id;
896 - elseif (get_category($cat_id)) :
897 - $cat_ids[] = $cat_id;
898 - endif;
899 - elseif (strlen($cat_name) > 0) :
900 - $esc = $wpdb->escape($cat_name);
901 - $resc = $wpdb->escape(preg_quote($cat_name));
902 -
903 - // WordPress 2.3+
904 - if (function_exists('is_term')) :
905 - $cat_id = is_term($cat_name, 'category');
906 - if ($cat_id) :
907 - $cat_ids[] = $cat_id['term_id'];
908 - // There must be a better way to do this...
909 - elseif ($results = $wpdb->get_results(
910 - "SELECT term_id
911 - FROM $wpdb->term_taxonomy
912 - WHERE
913 - LOWER(description) RLIKE
914 - CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)')"
915 - )) :
916 - foreach ($results AS $term) :
917 - $cat_ids[] = (int) $term->term_id;
918 - endforeach;
919 - elseif ('tag'==$unfamiliar_category) :
920 - $tags[] = $cat_name;
921 - elseif ('create'===$unfamiliar_category) :
922 - $term = wp_insert_term($cat_name, 'category');
923 - if (is_wp_error($term)) :
924 - FeedWordPress::noncritical_bug('term insertion problem', array('cat_name' => $cat_name, 'term' => $term, 'this' => $this), __LINE__);
925 - else :
926 - $cat_ids[] = $term['term_id'];
927 - endif;
424 + // Ignore whitespace, case, and tag cruft.
425 + $theExcerpt = preg_replace('/\s+/', '', strtolower(strip_tags(html_entity_decode($excerpt))));
426 + $theContent = preg_replace('/\s+/', '', strtolower(strip_tags(html_entity_decode($content))));
427 + if ( empty($excerpt) or $theExcerpt == $theContent ) :
428 + # If content is available, generate an excerpt.
429 + if ( strlen(trim($content)) > 0 ) :
430 + $excerpt = strip_tags($content);
431 + if (strlen($excerpt) > 255) :
432 + if (is_object($this->link) and is_object($this->link->simplepie)) :
433 + $encoding = $this->link->simplepie->get_encoding();
434 + else :
435 + $encoding = get_option('blog_charset', 'utf8');
928 436 endif;
929 -
930 - // WordPress 1.5.x - 2.2.x
931 - else :
932 - $results = $wpdb->get_results(
933 - "SELECT cat_ID
934 - FROM $wpdb->categories
935 - WHERE
936 - (LOWER(cat_name) = LOWER('$esc'))
937 - OR (LOWER(category_description)
938 - RLIKE CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)'))
939 - ");
940 - if ($results) :
941 - foreach ($results as $term) :
942 - $cat_ids[] = (int) $term->cat_ID;
943 - endforeach;
944 - elseif ('create'===$unfamiliar_category) :
945 - if (function_exists('wp_insert_category')) :
946 - $cat_id = wp_insert_category(array('cat_name' => $esc));
947 - // And into the database we go.
948 - else :
949 - $nice_kitty = sanitize_title($cat_name);
950 - $wpdb->query(sprintf("
951 - INSERT INTO $wpdb->categories
952 - SET
953 - cat_name='%s',
954 - category_nicename='%s'
955 - ", $esc, $nice_kitty
956 - ));
957 - $cat_id = $wpdb->insert_id;
958 - endif;
959 - $cat_ids[] = $cat_id;
960 - endif;
437 + $excerpt = mb_substr($excerpt,0,252,$encoding).'...';
961 438 endif;
962 439 endif;
963 - endforeach;
964 -
965 - if ((count($cat_ids) == 0) and ($unfamiliar_category === 'filter')) :
966 - $cat_ids = NULL; // Drop the post
967 - else :
968 - $cat_ids = array_unique($cat_ids);
969 440 endif;
970 -
971 - if ($tags_too) : $ret = array($cat_ids, $tags);
972 - else : $ret = $cat_ids;
973 - endif;
974 441
975 - return $ret;
976 - } // function SyndicatedPost::category_ids ()
442 + return $excerpt;
443 + } /* SyndicatedPost::excerpt() */
977 444
978 - function use_api ($tag) {
979 - global $wp_db_version;
980 - switch ($tag) :
981 - case 'wp_insert_post':
982 - // Before 2.2, wp_insert_post does too much of the wrong stuff to use it
983 - // In 1.5 it was such a resource hog it would make PHP segfault on big updates
984 - $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_21);
985 - break;
986 - case 'post_status_pending':
987 - $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_23);
988 - break;
989 - endswitch;
990 - return $ret;
991 - } // function SyndicatedPost::use_api ()
445 + /**
446 + * SyndicatedPost::permalink: returns the permalink for the post, as provided by the
447 + * source feed.
448 + *
449 + * @return string The URL of the original permalink for this syndicated post
450 + */
451 + public function permalink () {
452 + // Handles explicit <link> elements and also RSS 2.0 cases with
453 + // <guid isPermaLink="true">, etc. Hooray!
454 + $permalink = $this->entry->get_link();
455 + return $permalink;
456 + } /* SyndicatedPost::permalink () */
992 457
993 - #### EXTRACT DATA FROM FEED ITEM ####
458 + public function created ($params = array()) {
459 + $unfiltered = false; // $default = NULL; // seems to be unused on this function (gwyneth 20230916)
460 + extract($params);
994 461
995 - function created () {
996 - $epoch = null;
462 + $date = '';
997 463 if (isset($this->item['dc']['created'])) :
998 - $epoch = @parse_w3cdtf($this->item['dc']['created']);
464 + $date = $this->item['dc']['created'];
999 465 elseif (isset($this->item['dcterms']['created'])) :
1000 - $epoch = @parse_w3cdtf($this->item['dcterms']['created']);
466 + $date = $this->item['dcterms']['created'];
1001 467 elseif (isset($this->item['created'])): // Atom 0.3
1002 - $epoch = @parse_w3cdtf($this->item['created']);
468 + $date = $this->item['created'];
1003 469 endif;
1004 - return $epoch;
1005 - }
1006 - function published ($fallback = true) {
1007 - $epoch = null;
1008 470
471 + $time = new FeedTime($date);
472 + $tstamp = $time->timestamp();
473 + if ( ! $unfiltered) :
474 + apply_filters('syndicated_item_created', $tstamp, $this);
475 + endif;
476 + return $tstamp;
477 + } /* SyndicatedPost::created() */
478 +
479 + public function published ($params = array(), $default = NULL) {
480 + $fallback = true; $unfiltered = false;
481 + if ( !is_array($params)) : // Old style
482 + $fallback = $params;
483 + else : // New style
484 + extract($params);
485 + endif;
486 +
487 + $date = '';
488 + $tstamp = null;
489 +
1009 490 # RSS is a fucking mess. Figure out whether we have a date in
1010 491 # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix
1011 492 # epoch format for reformatting. If we can't find anything,
1012 493 # we'll use the last-updated time.
1013 494 if (isset($this->item['dc']['date'])): // Dublin Core
1014 - $epoch = @parse_w3cdtf($this->item['dc']['date']);
495 + $date = $this->item['dc']['date'];
1015 496 elseif (isset($this->item['dcterms']['issued'])) : // Dublin Core extensions
1016 - $epoch = @parse_w3cdtf($this->item['dcterms']['issued']);
497 + $date = $this->item['dcterms']['issued'];
1017 498 elseif (isset($this->item['published'])) : // Atom 1.0
1018 - $epoch = @parse_w3cdtf($this->item['published']);
499 + $date = $this->item['published'];
1019 500 elseif (isset($this->item['issued'])): // Atom 0.3
1020 - $epoch = @parse_w3cdtf($this->item['issued']);
1021 - elseif (isset($this->item['pubdate'])): // RSS 2.0
1022 - $epoch = strtotime($this->item['pubdate']);
501 + $date = $this->item['issued'];
502 + elseif (isset($this->item['pubdate'])): // RSS 2.0
503 + $date = $this->item['pubdate'];
504 + endif;
505 +
506 + if (strlen($date) > 0) :
507 + $time = new FeedTime($date);
508 + $tstamp = $time->timestamp();
1023 509 elseif ($fallback) : // Fall back to <updated> / <modified> if present
1024 - $epoch = $this->updated(/*fallback=*/ false);
510 + $tstamp = $this->updated(/*fallback=*/ false, /*default=*/ $default);
1025 511 endif;
1026 -
512 +
1027 513 # If everything failed, then default to the current time.
1028 - if (is_null($epoch)) :
514 + if (is_null($tstamp)) :
1029 515 if (-1 == $default) :
1030 - $epoch = time();
516 + $tstamp = time();
1031 517 else :
1032 - $epoch = $default;
518 + $tstamp = $default;
1033 519 endif;
1034 520 endif;
1035 -
1036 - return $epoch;
1037 - }
1038 - function updated ($fallback = true, $default = -1) {
1039 - $epoch = null;
1040 521
522 + if ( ! $unfiltered) :
523 + $tstamp = apply_filters('syndicated_item_published', $tstamp, $this);
524 + endif;
525 + return $tstamp;
526 + } /* SyndicatedPost::published() */
527 +
528 + public function updated ($params = array(), $default = -1) {
529 + $fallback = true; $unfiltered = false;
530 + if ( !is_array($params)) : // Old style
531 + $fallback = $params;
532 + else : // New style
533 + extract($params);
534 + endif;
535 +
536 + $date = '';
537 + $tstamp = null;
538 +
1041 539 # As far as I know, only dcterms and Atom have reliable ways to
1042 540 # specify when something was *modified* last. If neither is
1043 541 # available, then we'll try to get the time of publication.
1044 542 if (isset($this->item['dc']['modified'])) : // Not really correct
1045 - $epoch = @parse_w3cdtf($this->item['dc']['modified']);
543 + $date = $this->item['dc']['modified'];
1046 544 elseif (isset($this->item['dcterms']['modified'])) : // Dublin Core extensions
1047 - $epoch = @parse_w3cdtf($this->item['dcterms']['modified']);
545 + $date = $this->item['dcterms']['modified'];
1048 546 elseif (isset($this->item['modified'])): // Atom 0.3
1049 - $epoch = @parse_w3cdtf($this->item['modified']);
1050 - elseif (isset($this->item['updated'])): // Atom 1.0
1051 - $epoch = @parse_w3cdtf($this->item['updated']);
547 + $date = $this->item['modified'];
548 + elseif (isset($this->item['updated'])): // Atom 1.0
549 + $date = $this->item['updated'];
550 + endif;
551 +
552 + if (strlen($date) > 0) :
553 + $time = new FeedTime($date);
554 + $tstamp = $time->timestamp();
1052 555 elseif ($fallback) : // Fall back to issued / dc:date
1053 - $epoch = $this->published(/*fallback=*/ false, /*default=*/ $default);
556 + $tstamp = $this->published(/*fallback=*/ false, /*default=*/ $default);
1054 557 endif;
1055 -
558 +
1056 559 # If everything failed, then default to the current time.
1057 - if (is_null($epoch)) :
560 + if (is_null($tstamp)) :
1058 561 if (-1 == $default) :
1059 - $epoch = time();
562 + $tstamp = time();
1060 563 else :
1061 - $epoch = $default;
564 + $tstamp = $default;
1062 565 endif;
1063 566 endif;
1064 567
1065 - return $epoch;
568 + if ( ! $unfiltered) :
569 + $tstamp = apply_filters('syndicated_item_updated', $tstamp, $this);
570 + endif;
571 + return $tstamp;
572 + } /* SyndicatedPost::updated() */
573 +
574 + var $_hashes = array();
575 + function stored_hashes ($id = NULL) {
576 + if (is_null($id)) :
577 + $id = $this->wp_id();
578 + endif;
579 +
580 + if ( !isset($this->_hashes[$id])) :
581 + $this->_hashes[$id] = get_post_custom_values(
582 + 'syndication_item_hash', $id
583 + );
584 + if (is_null($this->_hashes[$id])) :
585 + $this->_hashes[$id] = array();
586 + endif;
587 + endif;
588 + return $this->_hashes[$id];
1066 589 }
1067 590
1068 - function update_hash () {
1069 - return md5(serialize($this->item));
591 + function update_hash ($hashed = true) {
592 + // Basis for tracking possible changes to item.
593 + $hash = array(
594 + "title" => $this->entry->get_title(),
595 + "link" => $this->permalink(),
596 + "content" => $this->content(),
597 + "excerpt" => $this->excerpt(),
598 + );
599 +
600 + if ($hashed) :
601 + $hash = md5(serialize($hash));
602 + endif;
603 +
604 + return $hash;
605 + } /* SyndicatedPost::update_hash() */
606 +
607 + /**
608 + * SyndicatedPost::normalize_guid_prefix(): generates a normalized URL
609 + * prefix (including scheme, authority, full path, and the beginning of
610 + * a query string) for creating guids that conform to WordPress's
611 + * internal constraints on the URL space for valid guids. To create a
612 + * normalized guid, just concatenate a valid URL query parameter value
613 + * to the returned URL.
614 + *
615 + * @return string The URL prefix generated.
616 + *
617 + * @uses trailingslashit()
618 + * @uses home_url()
619 + * @uses apply_filters()
620 + */
621 + static function normalize_guid_prefix () {
622 + $scheme = 'http';
623 + $url = trailingslashit(home_url(/*path=*/ '', $scheme));
624 + return apply_filters('syndicated_item_guid_normalized_prefix', $url . '?guid=', $scheme);
625 + } /* SyndicatedPost::normalize_guid_prefix() */
626 +
627 + static function normalize_guid ($guid) {
628 + $guid = trim($guid);
629 + if (preg_match('/^[0-9a-z]{32}$/i', $guid)) : // MD5
630 + $guid = SyndicatedPost::normalize_guid_prefix().strtolower($guid);
631 + elseif ((strlen(esc_url($guid)) == 0) or (esc_url($guid) != $guid)) :
632 + $guid = SyndicatedPost::normalize_guid_prefix().md5($guid);
633 + endif;
634 + $guid = trim($guid);
635 +
636 + return $guid;
637 + } /* SyndicatedPost::normalize_guid() */
638 +
639 + static function alternative_guid_prefix () {
640 + $scheme = 'https';
641 + $url = trailingslashit(home_url(/*path=*/ '', $scheme));
642 + return apply_filters('syndicated_item_guid_normalized_prefix', $url . '?guid=', $scheme);
1070 643 }
644 + static function alternative_guid ($guid) {
645 + $guid = trim($guid);
646 + if (preg_match('/^[0-9a-z]{32}$/i', $guid)) : // MD5
647 + $guid = SyndicatedPost::alternative_guid_prefix().strtolower($guid);
648 + elseif ((strlen(esc_url($guid)) == 0) or (esc_url($guid) != $guid)) :
649 + $guid = SyndicatedPost::alternative_guid_prefix().md5($guid);
650 + endif;
651 + $guid = trim($guid);
1071 652
1072 - function guid () {
653 + return $guid;
654 + } /* SyndicatedPost::normalize_guid() */
655 +
656 + public function guid () {
1073 657 $guid = null;
1074 - if (isset($this->item['id'])): // Atom 0.3 / 1.0
658 + if (isset($this->item['id'])): // Atom 0.3 / 1.0
1075 659 $guid = $this->item['id'];
1076 - elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
660 + elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
1077 661 $guid = $this->item['atom']['id'];
1078 - elseif (isset($this->item['guid'])) : // RSS 2.0
662 + elseif (isset($this->item['guid'])) : // RSS 2.0
1079 663 $guid = $this->item['guid'];
1080 - elseif (isset($this->item['dc']['identifier'])) :// yeah, right
664 + elseif (isset($this->item['dc']['identifier'])) : // yeah, right
1081 665 $guid = $this->item['dc']['identifier'];
1082 - else :
666 + endif;
667 +
668 + // Un-set or too long to use as-is. Generate a tag: URI.
669 + if (is_null($guid) or strlen($guid) > 250) :
670 + // In case we need to check this again
671 + $original_guid = $guid;
672 +
1083 673 // The feed does not seem to have provided us with a
1084 - // unique identifier, so we'll have to cobble together
1085 - // a tag: URI that might work for us. The base of the
1086 - // URI will be the host name of the feed source ...
1087 - $bits = parse_url($this->feedmeta['link/uri']);
674 + // usable unique identifier, so we'll have to cobble
675 + // together a tag: URI that might work for us. The base
676 + // of the URI will be the host name of the feed source ...
677 + $bits = parse_url($this->link->uri());
1088 678 $guid = 'tag:'.$bits['host'];
1089 679
680 + // Some ill-mannered feeds (for example, certain feeds
681 + // coming from Google Calendar) have extraordinarily long
682 + // guids -- so long that they exceed the 255 character
683 + // width of the WordPress guid field. But if the string
684 + // gets clipped by MySQL, uniqueness tests will fail
685 + // forever after and the post will be endlessly
686 + // reduplicated. So, instead, Guids Of A Certain Length
687 + // are hashed down into a nice, manageable tag: URI.
688 + if ( !is_null($original_guid)) :
689 + $guid .= ',2010-12-03:id.'.md5($original_guid);
690 +
1090 691 // If we have a date of creation, then we can use that
1091 692 // to uniquely identify the item. (On the other hand, if
1092 693 // the feed producer was consicentious enough to
1093 694 // generate dates of creation, she probably also was
1094 695 // conscientious enough to generate unique identifiers.)
1095 - if (!is_null($this->created())) :
696 + elseif ( !is_null($this->created())) :
1096 697 $guid .= '://post.'.date('YmdHis', $this->created());
1097 -
698 +
1098 699 // Otherwise, use both the URI of the item, *and* the
1099 700 // item's title. We have to use both because titles are
1100 701 // often not unique, and sometimes links aren't unique
1101 702 // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
@@ -1102,64 +703,228 @@
1102 703 // some podcasts). But it's rare to have *both* the same
1103 704 // title *and* the same link for two different items. So
1104 705 // this is about the best we can do.
1105 706 else :
1106 - $guid .= '://'.md5($this->item['link'].'/'.$this->item['title']);
707 + $link = $this->permalink();
708 + if (is_null($link)) : $link = $this->link->uri(); endif;
709 + $guid .= '://'.md5($link.'/'.$this->title());
1107 710 endif;
1108 711 endif;
1109 712 return $guid;
1110 - }
1111 -
1112 - function author () {
713 + } /* SyndicatedPost::guid() */
714 +
715 + public function author () {
1113 716 $author = array ();
1114 -
1115 - if (isset($this->item['author_name'])):
1116 - $author['name'] = $this->item['author_name'];
1117 - elseif (isset($this->item['dc']['creator'])):
1118 - $author['name'] = $this->item['dc']['creator'];
1119 - elseif (isset($this->item['dc']['contributor'])):
1120 - $author['name'] = $this->item['dc']['contributor'];
1121 - elseif (isset($this->feed->channel['dc']['creator'])) :
1122 - $author['name'] = $this->feed->channel['dc']['creator'];
1123 - elseif (isset($this->feed->channel['dc']['contributor'])) :
1124 - $author['name'] = $this->feed->channel['dc']['contributor'];
1125 - elseif (isset($this->feed->channel['author_name'])) :
1126 - $author['name'] = $this->feed->channel['author_name'];
1127 - elseif ($this->feed->is_rss() and isset($this->item['author'])) :
1128 - // The author element in RSS is allegedly an
1129 - // e-mail address, but lots of people don't use
1130 - // it that way. So let's make of it what we can.
1131 - $author = parse_email_with_realname($this->item['author']);
1132 -
1133 - if (!isset($author['name'])) :
1134 - if (isset($author['email'])) :
1135 - $author['name'] = $author['email'];
1136 - else :
1137 - $author['name'] = $this->feed->channel['title'];
717 +
718 + $aa = $this->entry->get_authors();
719 + if (is_countable($aa) and count($aa) > 0) :
720 + $a = reset($aa);
721 +
722 + $author = array(
723 + 'name' => $a->get_name(),
724 + 'email' => $a->get_email(),
725 + 'uri' => $a->get_link(),
726 + );
727 + endif;
728 +
729 + if (FEEDWORDPRESS_COMPATIBILITY) :
730 + // Search through the MagpieRSS elements: Atom, Dublin Core, RSS
731 + if (isset($this->item['author_name'])):
732 + $author['name'] = $this->item['author_name'];
733 + elseif (isset($this->item['dc']['creator'])):
734 + $author['name'] = $this->item['dc']['creator'];
735 + elseif (isset($this->item['dc']['contributor'])):
736 + $author['name'] = $this->item['dc']['contributor'];
737 + elseif (isset($this->feed->channel['dc']['creator'])) :
738 + $author['name'] = $this->feed->channel['dc']['creator'];
739 + elseif (isset($this->feed->channel['dc']['contributor'])) :
740 + $author['name'] = $this->feed->channel['dc']['contributor'];
741 + elseif (isset($this->feed->channel['author_name'])) :
742 + $author['name'] = $this->feed->channel['author_name'];
743 + elseif ($this->feed->is_rss() and isset($this->item['author'])) :
744 + // The author element in RSS is allegedly an
745 + // e-mail address, but lots of people don't use
746 + // it that way. So let's make of it what we can.
747 + $author = parse_email_with_realname($this->item['author']);
748 +
749 + if ( !isset($author['name'])) :
750 + if (isset($author['email'])) :
751 + $author['name'] = $author['email'];
752 + else :
753 + $author['name'] = $this->feed->channel['title'];
754 + endif;
1138 755 endif;
1139 756 endif;
1140 - else :
1141 - $author['name'] = $this->feed->channel['title'];
1142 757 endif;
1143 -
1144 - if (isset($this->item['author_email'])):
1145 - $author['email'] = $this->item['author_email'];
1146 - elseif (isset($this->feed->channel['author_email'])) :
1147 - $author['email'] = $this->feed->channel['author_email'];
758 +
759 + if ( !isset($author['name']) or is_null($author['name'])) :
760 + // Nothing found. Try some crappy defaults.
761 + if ($this->link->name()) :
762 + $author['name'] = $this->link->name();
763 + else :
764 + $url = parse_url($this->link->uri());
765 + $author['name'] = $url['host'];
766 + endif;
1148 767 endif;
1149 -
1150 - if (isset($this->item['author_url'])):
1151 - $author['uri'] = $this->item['author_url'];
1152 - elseif (isset($this->feed->channel['author_url'])) :
1153 - $author['uri'] = $this->item['author_url'];
1154 - else:
1155 - $author['uri'] = $this->feed->channel['link'];
768 +
769 + if (FEEDWORDPRESS_COMPATIBILITY) :
770 + if (isset($this->item['author_email'])):
771 + $author['email'] = $this->item['author_email'];
772 + elseif (isset($this->feed->channel['author_email'])) :
773 + $author['email'] = $this->feed->channel['author_email'];
774 + endif;
775 +
776 + if (isset($this->item['author_uri'])):
777 + $author['uri'] = $this->item['author_uri'];
778 + elseif (isset($this->item['author_url'])):
779 + $author['uri'] = $this->item['author_url'];
780 + elseif (isset($this->feed->channel['author_uri'])) :
781 + $author['uri'] = $this->item['author_uri'];
782 + elseif (isset($this->feed->channel['author_url'])) :
783 + $author['uri'] = $this->item['author_url'];
784 + elseif (isset($this->feed->channel['link'])) :
785 + $author['uri'] = $this->feed->channel['link'];
786 + endif;
1156 787 endif;
1157 788
1158 789 return $author;
1159 - } // SyndicatedPost::author()
790 + } /* SyndicatedPost::author() */
1160 791
1161 792 /**
793 + * SyndicatedPost::get_terms_from_settings(): Return an array of terms to associate with the incoming
794 + * post based on the Categories, Tags, and other terms associated with each new post by the user's
795 + * settings (global and feed-specific).
796 + *
797 + * @since 2016.0331
798 + * @return array of lists, each element has the taxonomy for a key ('category', 'post_tag', etc.),
799 + * and a list of term codes (either alphanumeric names, or ID numbers encoded in a format that
800 + * SyndicatedLink::category_ids() can understand) within that taxonomy
801 + *
802 + */
803 + public function get_terms_from_settings () {
804 + // Categories: start with default categories, if any.
805 + $cats = array();
806 + if ('no' != $this->link->setting('add/category', NULL, 'yes')) :
807 + $fc = get_option("feedwordpress_syndication_cats");
808 + if ($fc) :
809 + $cats = array_merge($cats, explode("\n", $fc));
810 + endif;
811 + endif;
812 +
813 + $fc = $this->link->setting('cats',NULL, array());
814 + if (is_array($fc)) :
815 + $cats = array_merge($cats, $fc);
816 + endif;
817 + $preset_terms['category'] = $cats;
818 +
819 + // Tags: start with default tags, if any
820 + $tags = array();
821 + if ('no' != $this->link->setting('add/post_tag', NULL, 'yes')) :
822 + $ft = get_option("feedwordpress_syndication_tags", NULL);
823 + $tags = (is_null($ft) ? array() : explode(FEEDWORDPRESS_CAT_SEPARATOR, $ft));
824 + endif;
825 +
826 + $ft = $this->link->setting('tags', NULL, array());
827 + if (is_array($ft)) :
828 + $tags = array_merge($tags, $ft);
829 + endif;
830 + $preset_terms['post_tag'] = $tags;
831 +
832 + $taxonomies = $this->link->taxonomies();
833 + $feedTerms = $this->link->setting('terms', NULL, array());
834 + $globalTerms = get_option('feedwordpress_syndication_terms', array());
835 + $specials = array('category' => 'cats', 'post_tag' => 'tags');
836 +
837 + foreach ($taxonomies as $tax) :
838 + // category and tag settings have already previously been handled
839 + // but if this is from another taxonomy, then...
840 + if ( !isset($specials[$tax])) :
841 + $terms = array();
842 +
843 + // See if we should get the globals
844 + if ('no' != $this->link->setting("add/$tax", NULL, 'yes')) :
845 + if (isset($globalTerms[$tax])) :
846 + $terms = $globalTerms[$tax];
847 + endif;
848 + endif;
849 +
850 + // Now merge in the locals
851 + if (isset($feedTerms[$tax])) :
852 + $terms = array_merge($terms, $feedTerms[$tax]);
853 + endif;
854 +
855 + // That's all, folks.
856 + $preset_terms[$tax] = $terms;
857 + endif;
858 + endforeach;
859 +
860 + return $preset_terms;
861 + } /* SyndicatedPost::get_terms_from_settings () */
862 +
863 + /**
864 + * SyndicatedPost::get_terms_from_feeds(): Return an array of terms to associate with the incoming
865 + * post based on the contents of the subscribed feed (atom:category and rss:category elements, dc:subject
866 + * elements, tags embedded using microformats in the post content, etc.)
867 + *
868 + * @since 2016.0331
869 + * @return array of lists, each element has the taxonomy for a key ('category', 'post_tag', etc.),
870 + * and a list of alphanumeric term names
871 + */
872 + public function get_terms_from_feeds () {
873 + // Now add categories from the post, if we have 'em
874 + $cats = array();
875 + $post_cats = $this->entry->get_categories();
876 + if (is_array($post_cats)) : foreach ($post_cats as $cat) :
877 + $cat_name = $cat->get_term();
878 + if ( ! $cat_name) : $cat_name = $cat->get_label(); endif;
879 +
880 + if ($this->link->setting('cat_split', NULL, NULL)) :
881 + $pcre = "\007".$this->feedmeta['cat_split']."\007";
882 + $cats = array_merge(
883 + $cats,
884 + preg_split(
885 + $pcre,
886 + $cat_name,
887 + -1 /*=no limit*/,
888 + PREG_SPLIT_NO_EMPTY
889 + )
890 + );
891 + else :
892 + $cats[] = $cat_name;
893 + endif;
894 + endforeach; endif;
895 +
896 + $feed_terms['category'] = apply_filters('syndicated_item_categories', $cats, $this);
897 +
898 + // Scan post for /a[@rel='tag'] and use as tags if present
899 + $tags = $this->inline_tags();
900 + $feed_terms['post_tag'] = apply_filters('syndicated_item_tags', $tags, $this);
901 +
902 + return $feed_terms;
903 + } /* SyndicatedPost::get_terms_from_feeds () */
904 +
905 + /**
906 + * SyndicatedPost::inline_tags: Return a list of all the tags embedded
907 + * in post content using the a[@rel="tag"] microformat.
908 + *
909 + * @since 2010.0630
910 + * @return array of string values containing the name of each tag
911 + */
912 + function inline_tags () {
913 + $tags = array();
914 + $content = $this->content();
915 + $pattern = FeedWordPressHTML::tagWithAttributeRegex('a', 'rel', 'tag');
916 + preg_match_all($pattern, $content, $refs, PREG_SET_ORDER);
917 + if (is_countable($refs) and count($refs) > 0) :
918 + foreach ($refs as $ref) :
919 + $tag = FeedWordPressHTML::tagWithAttributeMatch($ref);
920 + $tags[] = $tag['content'];
921 + endforeach;
922 + endif;
923 + return $tags;
924 + }
925 +
926 + /**
1162 927 * SyndicatedPost::isTaggedAs: Test whether a feed item is
1163 928 * tagged / categorized with a given string. Case and leading and
1164 929 * trailing whitespace are ignored.
1165 930 *
@@ -1164,9 +929,9 @@
1164 929 * trailing whitespace are ignored.
1165 930 *
1166 931 * @param string $tag Tag to check for
1167 932 *
1168 - * @return bool Whether or not at least one of the categories / tags on
933 + * @return bool Whether or not at least one of the categories / tags on
1169 934 * $this->item is set to $tag (modulo case and leading and trailing
1170 935 * whitespace)
1171 936 */
1172 937 function isTaggedAs ($tag) {
@@ -1179,9 +944,9 @@
1179 944 // If we have the new MagpieRSS, the number of category elements
1180 945 // on this item is stored under index "category#".
1181 946 if (isset($this->item['category#'])) :
1182 947 $numberOfCategories = (int) $this->item['category#'];
1183 -
948 +
1184 949 // We REALLY shouldn't have the old and busted MagpieRSS, but in
1185 950 // case we do, it doesn't support multiple categories, but there
1186 951 // might still be a single value under the "category" index.
1187 952 elseif (isset($this->item['category'])) :
@@ -1210,8 +975,143 @@
1210 975
1211 976 return $isSoTagged;
1212 977 } /* SyndicatedPost::isTaggedAs() */
1213 978
979 + /**
980 + * SyndicatedPost::enclosures: returns an array with any enclosures
981 + * that may be attached to this syndicated item.
982 + *
983 + * @param string $type If you only want enclosures that match a certain
984 + * MIME type or group of MIME types, you can limit the enclosures
985 + * that will be returned to only those with a MIME type which
986 + * matches this regular expression.
987 + * @return array
988 + */
989 + function enclosures( $type = '/.*/' ) {
990 + $enclosures = array();
991 +
992 + if ( isset( $this->item['enclosure#'] ) ) :
993 + // Loop through enclosure, enclosure#2, enclosure#3, ....
994 + for ( $i = 1; $i <= $this->item['enclosure#']; $i++ ) :
995 + $eid = ( ( $i > 1 ) ? "#{$i}" : "" ); // this was set as #{$id} — was that a typo? (gwyneth 20230919)
996 +
997 + // Does it match the type we want?
998 + if ( preg_match($type, $this->item["enclosure{$eid}@type"] ) ) :
999 + $enclosures[] = array(
1000 + "url" => $this->item["enclosure{$eid}@url"],
1001 + "type" => $this->item["enclosure{$eid}@type"],
1002 + "length" => $this->item["enclosure{$eid}@length"],
1003 + );
1004 + endif;
1005 + endfor;
1006 + endif;
1007 + return $enclosures;
1008 + } /* SyndicatedPost::enclosures() */
1009 +
1010 + function source ($what = NULL) {
1011 + $ret = NULL;
1012 + $source = $this->entry->get_source();
1013 + if ($source) :
1014 + $ret = array();
1015 + $ret['title'] = $source->get_title();
1016 + $ret['uri'] = $source->get_link();
1017 + $ret['feed'] = $source->get_link(0, 'self');
1018 +
1019 + if ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id')) :
1020 + $ret['id'] = $id_tags[0]['data'];
1021 + elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'id')) :
1022 + $ret['id'] = $id_tags[0]['data'];
1023 + elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid')) :
1024 + $ret['id'] = $id_tags[0]['data'];
1025 + elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'guid')) :
1026 + $ret['id'] = $id_tags[0]['data'];
1027 + elseif ($id_tags = $source->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'guid')) :
1028 + $ret['id'] = $id_tags[0]['data'];
1029 + endif;
1030 + endif;
1031 +
1032 + if ( !is_null($what) and is_scalar($what)) :
1033 + $ret = $ret[$what];
1034 + endif;
1035 + return $ret;
1036 + }
1037 +
1038 + function comment_link () {
1039 + $url = null;
1040 +
1041 + // RSS 2.0 has a standard <comments> element:
1042 + // "<comments> is an optional sub-element of <item>. If present,
1043 + // it is the url of the comments page for the item."
1044 + // <http://cyber.law.harvard.edu/rss/rss.html#ltcommentsgtSubelementOfLtitemgt>
1045 + if (isset($this->item['comments'])) :
1046 + $url = $this->item['comments'];
1047 + endif;
1048 +
1049 + // The convention in Atom feeds is to use a standard <link>
1050 + // element with @rel="replies" and @type="text/html".
1051 + // Unfortunately, SimplePie_Item::get_links() allows us to filter
1052 + // by the value of @rel, but not by the value of @type. *sigh*
1053 +
1054 + // Try Atom 1.0 first
1055 + $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link');
1056 +
1057 + // Fall back and try Atom 0.3
1058 + if (is_null($linkElements)) : $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'); endif;
1059 +
1060 + // Now loop through the elements, screening by @rel and @type
1061 + if (is_array($linkElements)) : foreach ($linkElements as $link) :
1062 + $rel = (isset($link['attribs']['']['rel']) ? $link['attribs']['']['rel'] : 'alternate');
1063 + $type = (isset($link['attribs']['']['type']) ? $link['attribs']['']['type'] : NULL);
1064 + $href = (isset($link['attribs']['']['href']) ? $link['attribs']['']['href'] : NULL);
1065 +
1066 + if (strtolower($rel)=='replies' and $type=='text/html' and !is_null($href)) :
1067 + $url = $href;
1068 + endif;
1069 + endforeach; endif;
1070 +
1071 + return $url;
1072 + }
1073 +
1074 + function comment_feed () {
1075 + $feed = null;
1076 +
1077 + // Well Formed Web comment feeds extension for RSS 2.0
1078 + // <http://www.sellsbrothers.com/spout/default.aspx?content=archive.htm#exposingRssComments>
1079 + //
1080 + // N.B.: Correct capitalization is wfw:commentRss, but
1081 + // wfw:commentRSS is common in the wild (partly due to a typo in
1082 + // the original spec). In any case, our item array is normalized
1083 + // to all lowercase anyways.
1084 + if (isset($this->item['wfw']['commentrss'])) :
1085 + $feed = $this->item['wfw']['commentrss'];
1086 + endif;
1087 +
1088 + // In Atom 1.0, the convention is to use a standard link element
1089 + // with @rel="replies". Sometimes this is also used to pass a
1090 + // link to the human-readable comments page, so we also need to
1091 + // check link/@type for a feed MIME type.
1092 + //
1093 + // Which is why I'm not using the SimplePie_Item::get_links()
1094 + // method here, incidentally: it doesn't allow you to filter by
1095 + // @type. *sigh*
1096 + if (isset($this->item['link_replies'])) :
1097 + // There may be multiple <link rel="replies"> elements; feeds have a feed MIME type
1098 + $N = isset($this->item['link_replies#']) ? $this->item['link_replies#'] : 1;
1099 + for ($i = 1; $i <= $N; $i++) :
1100 + $currentElement = 'link_replies'.(($i > 1) ? '#'.$i : '');
1101 + if (isset($this->item[$currentElement.'@type'])
1102 + and preg_match("\007application/(atom|rss|rdf)\+xml\007i", $this->item[$currentElement.'@type'])) :
1103 + $feed = $this->item[$currentElement];
1104 + endif;
1105 + endfor;
1106 + endif;
1107 + return $feed;
1108 + } /* SyndicatedPost::comment_feed() */
1109 +
1110 + ##################################
1111 + #### BUILT-IN CONTENT FILTERS ####
1112 + ##################################
1113 +
1214 1114 var $uri_attrs = array (
1215 1115 array('a', 'href'),
1216 1116 array('applet', 'codebase'),
1217 1117 array('area', 'href'),
@@ -1242,34 +1142,44 @@
1242 1142 var $_base = null;
1243 1143
1244 1144 function resolve_single_relative_uri ($refs) {
1245 1145 $tag = FeedWordPressHTML::attributeMatch($refs);
1246 - $url = Relative_URI::resolve($tag['value'], $this->_base);
1146 + $url = SimplePie_Misc::absolutize_url($tag['value'], $this->_base);
1147 +
1247 1148 return $tag['prefix'] . $url . $tag['suffix'];
1248 1149 } /* function SyndicatedPost::resolve_single_relative_uri() */
1249 1150
1250 - function resolve_relative_uris ($content, $obj) {
1151 + static function resolve_relative_uris ($content, $obj) {
1251 1152 $set = $obj->link->setting('resolve relative', 'resolve_relative', 'yes');
1252 - if ($set and $set != 'no') :
1253 - # The MagpieRSS upgrade has some `xml:base` support baked in.
1254 - # However, sometimes people do silly things, like putting
1255 - # relative URIs out on a production RSS 2.0 feed or other feeds
1256 - # with no good support for `xml:base`. So we'll do our best to
1257 - # try to catch any remaining relative URIs and resolve them as
1258 - # best we can.
1259 - $obj->_base = $obj->item['link']; // Reset the base for resolving relative URIs
1260 -
1153 + if ($set and $set != 'no') :
1154 + // Fallback: if we don't have anything better, use the
1155 + // item link from the feed
1156 + $obj->_base = $obj->permalink(); // Reset the base for resolving relative URIs
1157 +
1158 + // What we should do here, properly, is to use
1159 + // SimplePie_Item::get_base() -- but that method is
1160 + // currently broken. Or getting down and dirty in the
1161 + // SimplePie representation of the content tags and
1162 + // grabbing the xml_base member for the content element.
1163 + // Maybe someday...
1164 +
1261 1165 foreach ($obj->uri_attrs as $pair) :
1262 1166 list($tag, $attr) = $pair;
1263 1167 $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
1168 +
1169 + // FIXME: Encountered issue while testing an extremely long (= 88827 characters) item
1170 + // Relying on preg_replace_callback() here can cause a PHP seg fault on my development
1171 + // server. preg_match_all() causes a similar problem. Apparently this is a PCRE issue
1172 + // Cf. discussion of similar issue <https://bugs.php.net/bug.php?id=65009>
1264 1173 $content = preg_replace_callback (
1265 1174 $pattern,
1266 - array(&$obj, 'resolve_single_relative_uri'),
1175 + array($obj, 'resolve_single_relative_uri'),
1267 1176 $content
1268 1177 );
1178 +
1269 1179 endforeach;
1270 1180 endif;
1271 -
1181 +
1272 1182 return $content;
1273 1183 } /* function SyndicatedPost::resolve_relative_uris () */
1274 1184
1275 1185 var $strip_attrs = array (
@@ -1282,9 +1192,9 @@
1282 1192 $tag = FeedWordPressHTML::attributeMatch($refs);
1283 1193 return $tag['before_attribute'].$tag['after_attribute'];
1284 1194 }
1285 1195
1286 - function sanitize_content ($content, $obj) {
1196 + static function sanitize_content ($content, $obj) {
1287 1197 # This kind of sucks. I intend to replace it with
1288 1198 # lib_filter sometime soon.
1289 1199 foreach ($obj->strip_attrs as $pair):
1290 1200 list($tag,$attr) = $pair;
@@ -1291,12 +1201,1242 @@
1291 1201 $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
1292 1202
1293 1203 $content = preg_replace_callback (
1294 1204 $pattern,
1295 - array(&$obj, 'strip_attribute_from_tag'),
1205 + array($obj, 'strip_attribute_from_tag'),
1296 1206 $content
1297 1207 );
1298 1208 endforeach;
1299 1209 return $content;
1210 + } /* SyndicatedPost::sanitize() */
1211 +
1212 + #####################
1213 + #### POST STATUS ####
1214 + #####################
1215 +
1216 + /**
1217 + * SyndicatedPost::filtered: check whether or not this post has been
1218 + * screened out by a registered filter.
1219 + *
1220 + * @return bool TRUE iff post has been filtered out by a previous filter
1221 + */
1222 + function filtered () {
1223 + return is_null($this->post);
1224 + } /* SyndicatedPost::filtered() */
1225 +
1226 + /**
1227 + * SyndicatedPost::freshness: check whether post is a new post to be
1228 + * inserted, a previously syndicated post that needs to be updated to
1229 + * match the latest revision, or a previously syndicated post that is
1230 + * still up-to-date.
1231 + *
1232 + * @return int A status code representing the freshness of the post
1233 + * -1 = post already syndicated; has a revision that needs to be stored, but not updated to
1234 + * 0 = post already syndicated; no update needed
1235 + * 1 = post already syndicated, but needs to be updated to latest
1236 + * 2 = post has not yet been syndicated; needs to be created
1237 + */
1238 + function freshness ($format = 'number') {
1239 +
1240 + if ($this->filtered()) : // This should never happen.
1241 + FeedWordPressDiagnostic::critical_bug('SyndicatedPost', $this, __LINE__, __FILE__);
1242 + endif;
1243 +
1244 + if (is_null($this->_freshness)) : // Not yet checked and cached.
1245 + $guid = $this->post['guid'];
1246 + $eguid = esc_sql($this->post['guid']);
1247 +
1248 + $q = new WP_Query(array(
1249 + 'fields' => '_synfresh', // id, guid, post_modified_gmt
1250 + 'ignore_sticky_posts' => true,
1251 + 'guid' => $eguid, // it's always better to use the escaped version, right? (gwyneth 20230915)
1252 + ));
1253 +
1254 + $old_post = NULL;
1255 + if ($q->have_posts()) :
1256 + while ($q->have_posts()) : $q->the_post();
1257 + if (get_post_type($q->post->ID) == $this->post['post_type']):
1258 + $old_post = $q->post;
1259 + endif;
1260 + endwhile;
1261 + endif;
1262 +
1263 + if (is_null($old_post)) : // No post with this guid
1264 + FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is a NEW POST.');
1265 + $this->_wp_id = NULL;
1266 + $this->_freshness = 2; // New content
1267 + else :
1268 + // Presume there is nothing new until we find
1269 + // something new.
1270 + $updated = false;
1271 + $live = false;
1272 +
1273 + // Pull the list of existing revisions to get
1274 + // timestamps.
1275 + $revisions = wp_get_post_revisions($old_post->ID);
1276 + foreach ($revisions as $rev) :
1277 + $revisions_ts[] = mysql2date('G', $rev->post_modified_gmt);
1278 + endforeach;
1279 +
1280 + $revisions_ts[] = mysql2date('G', $old_post->post_modified_gmt);
1281 + $last_rev_ts = end($revisions_ts);
1282 + $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
1283 +
1284 + // If we have an explicit updated timestamp,
1285 + // check that against existing stamps.
1286 + if ( !is_null($updated_ts)) :
1287 + $updated = !in_array($updated_ts, $revisions_ts);
1288 +
1289 + // If this a newer revision, make it go
1290 + // live. If an older one, just record
1291 + // the contents.
1292 + $live = ($updated and ($updated_ts > $last_rev_ts));
1293 + endif;
1294 +
1295 + // This is a revision we haven't seen before, judging by the date.
1296 +
1297 + $updatedReason = NULL;
1298 + if ($updated) :
1299 + $updatedReason = preg_replace(
1300 + "/\s+/", " ",
1301 + 'has been marked with a new timestamp ('
1302 + .date('Y-m-d H:i:s', $updated_ts)
1303 + ." > "
1304 + .date('Y-m-d H:i:s', $last_rev_ts)
1305 + .')'
1306 + );
1307 +
1308 + // The date does not indicate a new revision, so
1309 + // let's check the hash.
1310 + else :
1311 + // Or the hash...
1312 + $hash = $this->update_hash();
1313 + $seen = $this->stored_hashes($old_post->ID);
1314 + if (is_countable($seen) and count($seen) > 0) :
1315 + $updated = !in_array($hash, $seen); // Not seen yet?
1316 + else :
1317 + $updated = true; // Can't find syndication meta-data
1318 + endif;
1319 +
1320 + if ($updated and FeedWordPressDiagnostic::is_on('feed_items:freshness:reasons')) :
1321 + // In the absence of definitive
1322 + // timestamp information, we
1323 + // just have to assume that a
1324 + // hash we haven't seen before
1325 + // is a newer version.
1326 + $live = true;
1327 +
1328 + $updatedReason = ' has a not-yet-seen update hash: '
1329 + .MyPHP::val($hash)
1330 + .' not in {'
1331 + .implode(", ", array_map(array('FeedWordPress', 'val'), $seen))
1332 + .'}. Basis: '
1333 + .MyPHP::val(array_keys($this->update_hash(false)));
1334 + endif;
1335 + endif;
1336 +
1337 + $frozen = false;
1338 + if ($updated) : // Ignore if the post is frozen
1339 + $frozen = ('yes' == $this->link->setting('freeze updates', 'freeze_updates', NULL));
1340 + if ( ! $frozen) :
1341 + $frozen_value = get_post_meta($old_post->ID, '_syndication_freeze_updates', /*single=*/ true);
1342 + $frozen = ( !is_null($frozen_value) and ('yes' == $frozen_value));
1343 +
1344 + if ($frozen) :
1345 + $updatedReason = ' IS BLOCKED FROM BEING UPDATED BY A UPDATE LOCK ON THIS POST, EVEN THOUGH IT '.$updatedReason;
1346 + endif;
1347 + else :
1348 + $updatedReason = ' IS BLOCKED FROM BEING UPDATED BY A FEEDWORDPRESS UPDATE LOCK, EVEN THOUGH IT '.$updatedReason;
1349 + endif;
1350 + endif;
1351 + $live = ($live and ! $frozen);
1352 +
1353 + if ($updated) :
1354 + FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is an update of an existing post.');
1355 + if ( !is_null($updatedReason)) :
1356 + $updatedReason = preg_replace('/\s+/', ' ', $updatedReason);
1357 + FeedWordPress::diagnostic('feed_items:freshness:reasons', 'Item ['.$guid.'] "'.$this->entry->get_title().'" '.$updatedReason);
1358 + endif;
1359 +
1360 + $this->_freshness = apply_filters('syndicated_item_freshness', ($live ? 1 : -1), $updated, $frozen, $updated_ts, $last_rev_ts, $this);
1361 +
1362 + $this->_wp_id = $old_post->ID;
1363 + $this->_wp_post = $old_post;
1364 +
1365 + // We want this to keep a running list of all the
1366 + // processed update hashes.
1367 + $this->post['meta']['syndication_item_hash'] = array_merge(
1368 + $this->stored_hashes(),
1369 + array($this->update_hash())
1370 + );
1371 + else :
1372 + FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is a duplicate of an existing post.');
1373 + $this->_freshness = 0; // Same old, same old
1374 + $this->_wp_id = $old_post->ID;
1375 + endif;
1376 + endif;
1377 + endif;
1378 +
1379 + switch ($format) :
1380 + case 'status' :
1381 + switch ($this->_freshness) :
1382 + case -1:
1383 + $ret = 'stored';
1384 + break;
1385 + case 0:
1386 + $ret = NULL;
1387 + break;
1388 + case 1:
1389 + $ret = 'updated';
1390 + break;
1391 + case 2:
1392 + default:
1393 + $ret = 'new';
1394 + break;
1395 + endswitch;
1396 + break;
1397 + case 'number' :
1398 + default :
1399 + $ret = $this->_freshness;
1400 + endswitch;
1401 +
1402 +
1403 + return $ret;
1404 + } /* SyndicatedPost::freshness () */
1405 +
1406 + function has_fresh_content () {
1407 + return ( ! $this->filtered() and $this->freshness() != 0 );
1408 + } /* SyndicatedPost::has_fresh_content () */
1409 +
1410 + function this_revision_needs_original_post ($freshness = NULL) {
1411 + if (is_null($freshness)) :
1412 + $freshness = $this->freshness();
1413 + endif;
1414 + return ( $freshness >= 2 );
1300 1415 }
1301 -} // class SyndicatedPost
1302 1416
1417 + function this_revision_is_current ($freshness = NULL) {
1418 + if (is_null($freshness)) :
1419 + $freshness = $this->freshness();
1420 + endif;
1421 + return ( $freshness >= 1 );
1422 + } /* SyndicatedPost::this_revision_is_current () */
1423 +
1424 + function fresh_content_is_update () {
1425 + return ($this->freshness() < 2);
1426 + } /* SyndicatedPost::fresh_content_is_update () */
1427 +
1428 + function fresh_storage_diagnostic () {
1429 + $ret = NULL;
1430 + switch ($this->freshness()) :
1431 + case -1 :
1432 + $ret = 'Storing alternate revision of existing post # '.$this->wp_id().', "'.$this->post['post_title'].'"';
1433 + break;
1434 + case 1 :
1435 + $ret = 'Updating existing post # '.$this->wp_id().', "'.$this->post['post_title'].'"';
1436 + break;
1437 + case 2 :
1438 + default :
1439 + $ret = 'Inserting new post "'.$this->post['post_title'].'"';
1440 + break;
1441 + endswitch;
1442 + return $ret;
1443 + } /* SyndicatedPost::fresh_storage_diagnostic() */
1444 +
1445 + function fresh_storage_hook () {
1446 + $ret = NULL;
1447 + switch ($this->freshness()) :
1448 + case -1 :
1449 + case 1 :
1450 + $ret = 'update_syndicated_item';
1451 + break;
1452 + case 2 :
1453 + default :
1454 + $ret = 'post_syndicated_item';
1455 + break;
1456 + endswitch;
1457 + return $ret;
1458 + } /* SyndicatedPost::fresh_storage_hook () */
1459 +
1460 + #################################################
1461 + #### INTERNAL STORAGE AND MANAGEMENT METHODS ####
1462 + #################################################
1463 +
1464 + function wp_id () {
1465 + if ($this->filtered()) : // This should never happen.
1466 + FeedWordPressDiagnostic::critical_bug('SyndicatedPost', $this, __LINE__, __FILE__);
1467 + endif;
1468 +
1469 + if (is_null($this->_wp_id) and is_null($this->_freshness)) :
1470 + $fresh = $this->freshness(); // sets WP DB id in the process
1471 + endif;
1472 + return $this->_wp_id;
1473 + }
1474 +
1475 + /**
1476 + * SyndicatedPost::secure_author_id(). Look up, or create, a numeric ID
1477 + * for the author of the incoming post.
1478 + *
1479 + * side effect: int|NULL stored in $this->post['post_author']
1480 + * side effect: IF no valid author is found, NULL stored in $this->post
1481 + * side effect: diagnostic output in case item is rejected with NULL author
1482 + *
1483 + * @used-by SyndicatedPost::store
1484 + *
1485 + * @uses SyndicatedPost::post
1486 + * @uses SyndicatedPost::author_id
1487 + * @uses SyndicatedLink::setting
1488 + * @uses FeedWordPress::diagnostic
1489 + */
1490 + protected function secure_author_id () {
1491 + # -- Look up, or create, numeric ID for author
1492 + $this->post['post_author'] = $this->author_id (
1493 + $this->link->setting('unfamiliar author', 'unfamiliar_author', 'create')
1494 + );
1495 +
1496 + if (is_null($this->post['post_author'])) :
1497 + FeedWordPress::diagnostic('feed_items:rejected', 'Filtered out item ['.$this->guid().'] without syndication: no author available');
1498 + $this->post = NULL;
1499 + endif;
1500 + } /* SyndicatedPost::secure_author_id() */
1501 +
1502 + /**
1503 + * SyndicatedPost::secure_term_ids(). Look up, or create, numeric IDs
1504 + * for the terms (categories, tags, etc.) assigned to the incoming post,
1505 + * whether by global settings, feed settings, or by the tags on the feed.
1506 + *
1507 + * side effect: array of term ids stored in $this->post['tax_input']
1508 + * side effect: IF settings or filters determine post should be filtered out,
1509 + * NULL stored in $this->post
1510 + *
1511 + * @used-by SyndicatedPost::store
1512 + *
1513 + * @uses apply_filters
1514 + * @uses SyndicatedLink::setting
1515 + * @uses SyndicatedPost::category_ids
1516 + * @uses SyndicatedPost::preset_terms
1517 + * @uses SyndicatedPost::post
1518 + */
1519 + protected function secure_term_ids () {
1520 + $mapping = apply_filters('syndicated_post_terms_mapping', array(
1521 + 'category' => array('abbr' => 'cats', 'unfamiliar' => 'category', 'domain' => array('category', 'post_tag')),
1522 + 'post_tag' => array('abbr' => 'tags', 'unfamiliar' => 'post_tag', 'domain' => array('post_tag')),
1523 + ), $this);
1524 +
1525 + $termSet = array(); $valid = null;
1526 + foreach ($this->feed_terms as $what => $anTerms) :
1527 + // Default to using the inclusive procedures (for cats) rather than exclusive (for inline tags)
1528 + $taxes = (isset($mapping[$what]) ? $mapping[$what] : $mapping['category']);
1529 + $unfamiliar = $taxes['unfamiliar'];
1530 +
1531 + if ( !is_null($this->post)) : // Not filtered out yet
1532 + # -- Look up, or create, numeric ID for categories
1533 + $taxonomies = $this->link->setting("match/".$taxes['abbr'], 'match_'.$taxes['abbr'], $taxes['domain']);
1534 +
1535 + // Eliminate dummy variables
1536 + $taxonomies = array_filter( $taxonomies, 'remove_dummy_zero' );
1537 +
1538 + // Allow FWP add-on filters to control the taxonomies we use to search for a term
1539 + $taxonomies = apply_filters("syndicated_post_terms_match", $taxonomies, $what, $this);
1540 + $taxonomies = apply_filters("syndicated_post_terms_match_{$what}", $taxonomies, $this);
1541 +
1542 + // Allow FWP add-on filters to control with greater precision what happens on unmatched
1543 + $unmatched = apply_filters("syndicated_post_terms_unfamiliar",
1544 + $this->link->setting(
1545 + "unfamiliar {$unfamiliar}",
1546 + "unfamiliar_{$unfamiliar}",
1547 + 'create:'.$unfamiliar
1548 + ),
1549 + $what,
1550 + $this
1551 + );
1552 +
1553 + $terms = $this->category_ids (
1554 + $anTerms,
1555 + $unmatched,
1556 + /*taxonomies=*/ $taxonomies,
1557 + array(
1558 + 'singleton' => false, // I don't like surprises
1559 + 'filters' => true,
1560 + )
1561 + );
1562 +
1563 + if (is_null($terms) or is_null($termSet)) :
1564 + // filtered out -- no matches
1565 + else :
1566 + $valid = true;
1567 +
1568 + // filter mode off, or at least one match
1569 + foreach ($terms as $tax => $term_ids) :
1570 + if ( !isset($termSet[$tax])) :
1571 + $termSet[$tax] = array();
1572 + endif;
1573 + $termSet[$tax] = array_merge($termSet[$tax], $term_ids);
1574 + endforeach;
1575 + endif;
1576 + endif;
1577 + endforeach;
1578 +
1579 + if (is_null($valid)) : // Plonked
1580 + $this->post = NULL;
1581 + else : // We can proceed
1582 + $this->post['tax_input'] = array();
1583 + foreach ($termSet as $tax => $term_ids) :
1584 + if ( !isset($this->post['tax_input'][$tax])) :
1585 + $this->post['tax_input'][$tax] = array();
1586 + endif;
1587 + $this->post['tax_input'][$tax] = array_merge(
1588 + $this->post['tax_input'][$tax],
1589 + $term_ids
1590 + );
1591 + endforeach;
1592 +
1593 + // Now let's add on the feed and global presets
1594 + foreach ($this->preset_terms as $tax => $term_ids) :
1595 + if ( !isset($this->post['tax_input'][$tax])) :
1596 + $this->post['tax_input'][$tax] = array();
1597 + endif;
1598 +
1599 + $this->post['tax_input'][$tax] = array_merge (
1600 + $this->post['tax_input'][$tax],
1601 + $this->category_ids (
1602 + /*terms=*/ $term_ids,
1603 + /*unfamiliar=*/ 'create:'.$tax, // These are presets; for those added in a tagbox editor, the tag may not yet exist
1604 + /*taxonomies=*/ array($tax),
1605 + array(
1606 + 'singleton' => true,
1607 + ))
1608 + );
1609 + endforeach;
1610 + endif;
1611 + } /* SyndicatedPost::secure_term_ids() */
1612 +
1613 + /**
1614 + * SyndicatedPost::store
1615 + *
1616 + * @uses SyndicatedPost::secure_author_id
1617 + */
1618 + public function store () {
1619 + if ($this->filtered()) : // This should never happen.
1620 + FeedWordPressDiagnostic::critical_bug('SyndicatedPost', $this, __LINE__, __FILE__);
1621 + endif;
1622 +
1623 + $freshness = $this->freshness(); // see above: this sets WP DB id in the process (gwyneth 20230919)
1624 + if ($this->has_fresh_content()) :
1625 + $this->secure_author_id();
1626 + endif;
1627 +
1628 + if ($this->has_fresh_content()) : // Was this filtered during author_id lookup?
1629 + $this->secure_term_ids();
1630 + endif;
1631 +
1632 + // We have to check again in case the post has been filtered
1633 + // during the category/tags/taxonomy terms lookup
1634 + if ($this->has_fresh_content()) :
1635 + // Filter some individual fields
1636 +
1637 + // If there already is a post slug (from syndication or by manual
1638 + // editing) don't cause WP to overwrite it by sending in a NULL
1639 + // post_name. Props Chris Fritz 2012-11-28.
1640 + $post_name = (is_null($this->_wp_post) ? NULL : $this->_wp_post->post_name);
1641 +
1642 + // Allow filters to set post slug. Props niska.
1643 + $post_name = apply_filters('syndicated_post_slug', $post_name, $this);
1644 + if ( !empty($post_name)) :
1645 + $this->post['post_name'] = $post_name;
1646 + endif;
1647 +
1648 + $this->post = apply_filters('syndicated_post', $this->post, $this);
1649 +
1650 + // Allow for feed-specific syndicated_post filters.
1651 + $this->post = apply_filters(
1652 + "syndicated_post_".$this->link->uri(),
1653 + $this->post,
1654 + $this
1655 + );
1656 + endif;
1657 +
1658 + // Hook in early to make sure these get inserted if at all possible
1659 + add_action(
1660 + /*hook=*/ 'transition_post_status',
1661 + /*callback=*/ array($this, 'add_rss_meta'),
1662 + /*priority=*/ -10000, /* very early */
1663 + /*arguments=*/ 3
1664 + );
1665 +
1666 + $ret = false;
1667 + if ($this->has_fresh_content()) :
1668 + $diag = $this->fresh_storage_diagnostic();
1669 + if ( !is_null($diag)) :
1670 + FeedWordPress::diagnostic('syndicated_posts', $diag);
1671 + endif;
1672 +
1673 + $this->insert_post(/*update=*/ $this->fresh_content_is_update(), $this->freshness());
1674 +
1675 + $hook = $this->fresh_storage_hook();
1676 + if ( !is_null($hook)) :
1677 + do_action($hook, $this->wp_id(), $this);
1678 + endif;
1679 +
1680 + $ret = $this->freshness('status');
1681 + endif;
1682 +
1683 + // If this is a legit, non-filtered post, tag it as found on the
1684 + // feed regardless of fresh or stale status
1685 + if ( ! $this->filtered()) :
1686 + $key = '_feedwordpress_retire_me_' . $this->link->id;
1687 + delete_post_meta($this->wp_id(), $key);
1688 +
1689 + $status = get_post_field('post_status', $this->wp_id());
1690 + if ('fwpretired'==$status and $this->link->is_non_incremental()) :
1691 + FeedWordPress::diagnostic('syndicated_posts', "Un-retiring previously retired post # ".$this->wp_id()." due to re-appearance on non-incremental feed.");
1692 + set_post_field('post_status', $this->post['post_status'], $this->wp_id());
1693 + wp_transition_post_status($this->post['post_status'], $status, $this->post);
1694 + elseif ('fwpzapped'==$status) :
1695 + // Set this new revision up to be
1696 + // blanked on the next update.
1697 + add_post_meta($this->wp_id(), '_feedwordpress_zapped_blank_me', 2, /*single=*/ true);
1698 + endif;
1699 + endif;
1700 +
1701 + // Remove add_rss_meta hook
1702 + remove_action(
1703 + /*hook=*/ 'transition_post_status',
1704 + /*callback=*/ array($this, 'add_rss_meta'),
1705 + /*priority=*/ -10000, /* very early */
1706 + /*arguments=*/ 3
1707 + );
1708 +
1709 + return $ret;
1710 + } /* function SyndicatedPost::store () */
1711 +
1712 + function insert_post ($update = false, $freshness = 2) {
1713 + $dbpost = $this->normalize_post(/*new=*/ true);
1714 +
1715 + $ret = null;
1716 +
1717 + if ( !is_null($dbpost)) :
1718 + $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
1719 +
1720 + // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
1721 + add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1722 +
1723 + // Kludge to prevent kses filters from stripping the
1724 + // content of posts when updating without a logged in
1725 + // user who has `unfiltered_html` capability.
1726 + $mungers = array('wp_filter_kses', 'wp_filter_post_kses');
1727 + $removed = array();
1728 + foreach ($mungers as $munger) :
1729 + if (has_filter('content_save_pre', $munger)) :
1730 + remove_filter('content_save_pre', $munger);
1731 + $removed[] = $munger;
1732 + endif;
1733 + endforeach;
1734 +
1735 + if ($update and function_exists('get_post_field')) :
1736 + // Don't munge status fields that the user may
1737 + // have reset manually
1738 + $doNotMunge = array('post_status', 'comment_status', 'ping_status');
1739 +
1740 + foreach ($doNotMunge as $field) :
1741 + $dbpost[$field] = get_post_field($field, $this->wp_id());
1742 + endforeach;
1743 + endif;
1744 +
1745 + // WP3's wp_insert_post scans current_user_can() for the
1746 + // tax_input, with no apparent way to override. Ugh.
1747 + add_action(
1748 + /*hook=*/ 'transition_post_status',
1749 + /*callback=*/ array($this, 'add_terms'),
1750 + /*priority=*/ -10001, /* very early */
1751 + /*arguments=*/ 3
1752 + );
1753 +
1754 + // WP3 appears to override whatever you give it for
1755 + // post_modified. Ugh.
1756 + add_action(
1757 + /*hook=*/ 'transition_post_status',
1758 + /*callback=*/ array($this, 'fix_post_modified_ts'),
1759 + /*priority=*/ -10000, /* very early */
1760 + /*arguments=*/ 3
1761 + );
1762 +
1763 + if ($update) :
1764 + $this->post['ID'] = $this->wp_id();
1765 + $dbpost['ID'] = $this->post['ID'];
1766 + endif;
1767 +
1768 + // O.K., is this a new post? If so, we need to create
1769 + // the basic post record before we do anything else.
1770 + if ($this->this_revision_needs_original_post()) :
1771 + // *sigh*, for handling inconsistent slash expectations < 3.6
1772 + $sdbpost = $this->db_sanitize_post($dbpost);
1773 +
1774 + // Go ahead and insert the first post record to
1775 + // anchor the revision history.
1776 +
1777 + $this->_wp_id = wp_insert_post($sdbpost, /*return wp_error=*/ true);
1778 +
1779 + $dbpost['ID'] = $this->_wp_id;
1780 + endif;
1781 +
1782 + // Sanity check: if the attempt to insert post
1783 + // returned an error, then feeding that error
1784 + // object in to _wp_put_post_revision() would
1785 + // cause a fatal error. Better to break out.
1786 + if ( !is_wp_error($this->_wp_id)) :
1787 + // Now that we've made sure the original exists, insert
1788 + // this version here as a revision.
1789 + $revision_id = _wp_put_post_revision($dbpost, /*autosave=*/ false);
1790 +
1791 + if ( ! $this->this_revision_needs_original_post()) :
1792 +
1793 + if ($this->this_revision_is_current()) :
1794 +
1795 + wp_restore_post_revision($revision_id);
1796 +
1797 + else :
1798 +
1799 + // If we do not activate this revision, then the
1800 + // add_rss_meta will not be called, which is
1801 + // more or less as it should be, but that means
1802 + // we have to actively record this revision's
1803 + // update hash from here.
1804 + $postId = $this->post['ID'];
1805 + $key = 'syndication_item_hash';
1806 + $hash = $this->update_hash();
1807 + FeedWordPress::diagnostic('syndicated_posts:meta_data', "Adding post meta-datum to post [$postId]: [$key] = ".FeedWordPress::val($hash, /*no newlines=*/ true));
1808 + add_post_meta( $postId, $key, $hash, /*unique=*/ false );
1809 + endif;
1810 + endif;
1811 + endif;
1812 +
1813 + remove_action(
1814 + /*hook=*/ 'transition_post_status',
1815 + /*callback=*/ array($this, 'add_terms'),
1816 + /*priority=*/ -10001, /* very early */
1817 + /*arguments=*/ 3
1818 + );
1819 +
1820 + remove_action(
1821 + /*hook=*/ 'transition_post_status',
1822 + /*callback=*/ array($this, 'fix_post_modified_ts'),
1823 + /*priority=*/ -10000, /* very early */
1824 + /*arguments=*/ 3
1825 + );
1826 +
1827 + // Turn off ridiculous fucking kludges #1 and #2
1828 + remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1829 + foreach ($removed as $filter) :
1830 + add_filter('content_save_pre', $filter);
1831 + endforeach;
1832 +
1833 + $this->validate_post_id($dbpost, $update, array(__CLASS__, __FUNCTION__));
1834 +
1835 + $ret = $this->_wp_id;
1836 + endif;
1837 + return $ret;
1838 + } /* function SyndicatedPost::insert_post () */
1839 +
1840 + /**
1841 + * SyndicatedPost::insert_new(). Uses the data collected in this post object to insert
1842 + * a new post into the wp_posts table.
1843 + *
1844 + * @uses SyndicatedPost::insert_post
1845 + */
1846 + function insert_new () {
1847 + $this->insert_post(/*update=*/ false, 1);
1848 + } /* SyndicatedPost::insert_new() */
1849 +
1850 + /**
1851 + * SyndicatedPost::insert_new(). Uses the data collected in this post object to update
1852 + * an existing post in the wp_posts table.
1853 + *
1854 + * @uses SyndicatedPost::insert_post
1855 + */
1856 + function update_existing () {
1857 + $this->insert_post(/*update=*/ true, 2);
1858 + } /* SyndicatedPost::update_existing() */
1859 +
1860 + /**
1861 + * SyndicatedPost::normalize_post()
1862 + *
1863 + * @param bool $new If true, this post is to be inserted anew. If false, it is an update of an existing post. (Unused)
1864 + * @return array A normalized representation of the post ready to be inserted into the database or sent to the WordPress API functions
1865 + */
1866 + function normalize_post( $new = true ) {
1867 + $out = $this->post;
1868 +
1869 + $fullPost = $out['post_title'].$out['post_content'];
1870 + $fullPost .= (isset($out['post_excerpt']) ? $out['post_excerpt'] : '');
1871 + if (strlen($fullPost) < 1) :
1872 + // FIXME: Option for filtering out empty posts
1873 + endif;
1874 + if (strlen($out['post_title'])==0) :
1875 + $offset = (int) get_option('gmt_offset') * 60 * 60;
1876 + if (isset($this->post['meta']['syndication_source'])) :
1877 + $source_title = $this->post['meta']['syndication_source'];
1878 + else :
1879 + $feed_url = parse_url($this->post['meta']['syndication_feed']);
1880 + $source_title = $feed_url['host'];
1881 + endif;
1882 +
1883 + $out['post_title'] = $source_title
1884 + .' '.gmdate('Y-m-d H:i:s', $this->published() + $offset);
1885 + // FIXME: Option for what to fill a blank title with...
1886 + endif;
1887 +
1888 + // Normalize the guid if necessary.
1889 + $out['guid'] = SyndicatedPost::normalize_guid($out['guid']);
1890 +
1891 + return $out;
1892 + }
1893 +
1894 + public function db_sanitize_post_check_encoding ($out) {
1895 + // Check encoding recursively: every string field needs to be checked
1896 + // for character encoding issues. This is a bit problematic because we
1897 + // *should* be using DB_CHARSET, but DB_CHARSET sometimes has values
1898 + // that work for MySQL but not for PHP mb_check_encoding. So instead
1899 + // we must rely on WordPress setting blog_charset and hope that the user
1900 + // has got their database encoding set up to roughly match
1901 + $charset = get_option('blog_charset', 'utf8');
1902 +
1903 + foreach ($out as $key => $value) :
1904 + if (is_string($value)) :
1905 +
1906 + if ( !function_exists('mb_check_encoding') or mb_check_encoding($value, $charset)) :
1907 + $out[$key] = $value;
1908 + else :
1909 + $fromCharset = mb_detect_encoding($value, mb_detect_order(), /*strict=*/ true);
1910 + $out[$key] = mb_convert_encoding($value, $charset, $fromCharset);
1911 + endif;
1912 +
1913 + elseif (is_array($value)) :
1914 + $out[$key] = $this->db_sanitize_post_check_encoding($value);
1915 +
1916 + else :
1917 + $out[$key] = $value;
1918 + endif;
1919 +
1920 + endforeach;
1921 +
1922 + return $out;
1923 + } /* SyndicatedPost::db_sanitize_post_check_encoding () */
1924 +
1925 + function db_sanitize_post ($out) {
1926 + global $wp_db_version;
1927 +
1928 + $out = $this->db_sanitize_post_check_encoding($out);
1929 +
1930 + // < 3.6. Core API, including `wp_insert_post()`, expects
1931 + // properly slashed data. If `wp_slash()` exists, then
1932 + // this is after the big change-over in how data slashing
1933 + // was handled.
1934 + if ( !function_exists('wp_slash')) :
1935 +
1936 + foreach ($out as $key => $value) :
1937 + if (is_string($value)) :
1938 + $out[$key] = esc_sql($value);
1939 + else :
1940 + $out[$key] = $value;
1941 + endif;
1942 + endforeach;
1943 +
1944 + // For revisions [@23416,@23554), core API expects
1945 + // unslashed data. Cf. <https://core.trac.wordpress.org/browser/trunk/wp-includes/post.php?rev=23416>
1946 + // NOOP for those revisions.
1947 +
1948 + // In revisions @23554 to present, `wp_insert_post()`
1949 + // expects slashed data once again.
1950 + // Cf. <https://core.trac.wordpress.org/changeset/23554/trunk/wp-includes/post.php?contextall=1>
1951 + // But at least now we can use the wp_slash API function to do that.
1952 + // Hooray.
1953 +
1954 + elseif ($wp_db_version >= 23524) :
1955 +
1956 + $out = wp_slash($out);
1957 +
1958 + endif;
1959 +
1960 + return $out;
1961 + }
1962 +
1963 + /**
1964 + * SyndicatedPost::validate_post_id()
1965 + *
1966 + * @param array $dbpost An array representing the post we attempted to insert or update
1967 + * @param mixed $ns A string or array representing the namespace (class, method) whence this method was called.
1968 + */
1969 + function validate_post_id ($dbpost, $is_update, $ns) {
1970 + if (is_array($ns)) : $ns = implode('::', $ns);
1971 + else : $ns = (string) $ns; endif;
1972 +
1973 + // This should never happen.
1974 + if ( !is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
1975 + $verb = ($is_update ? 'update existing' : 'insert new');
1976 + $guid = $this->guid();
1977 + $url = $this->permalink();
1978 + $feed = $this->link->uri(array('add_params' => true));
1979 +
1980 + // wp_insert_post failed. Diagnostics, or barf up a critical bug
1981 + // notice if we are in debug mode.
1982 + $mesg = "Failed to $verb item [$guid]. WordPress API returned no valid post ID.\n"
1983 + ."\t\tID = ".serialize($this->_wp_id)."\n"
1984 + ."\t\tURL = ".MyPHP::val($url)
1985 + ."\t\tFeed = ".MyPHP::val($feed);
1986 +
1987 + FeedWordPress::diagnostic('updated_feeds:errors', "WordPress API error: $mesg");
1988 + FeedWordPress::diagnostic('feed_items:rejected', $mesg);
1989 +
1990 + $mesg = <<<EOM
1991 +The WordPress API returned an invalid post ID
1992 + when FeedWordPress tried to $verb item $guid
1993 + [URL: $url]
1994 + from the feed at $feed
1995 +
1996 +$ns::_wp_id
1997 +EOM;
1998 + FeedWordPressDiagnostic::noncritical_bug(
1999 + /*message=*/ $mesg,
2000 + /*var =*/ array(
2001 + "\$this->_wp_id" => $this->_wp_id,
2002 + "\$dbpost" => $dbpost,
2003 + ),
2004 + /*line # =*/ __LINE__, /*filename=*/ __FILE__
2005 + );
2006 + endif;
2007 + } /* SyndicatedPost::validate_post_id() */
2008 +
2009 + /**
2010 + * SyndicatedPost::fix_revision_meta() - Ensures that we get the meta
2011 + * data (authorship, guid, etc.) that we want when storing revisions of
2012 + * a syndicated post.
2013 + *
2014 + * In their infinite wisdom, the WordPress coders seem to have made it
2015 + * completely impossible for a plugin that uses wp_insert_post() to set
2016 + * certain meta-data (such as the author) when you store an old revision
2017 + * of an updated post. Instead, it uses the WordPress defaults (= cur.
2018 + * active user ID if the process is running with a user logged in, or
2019 + * = #0 if there is no user logged in). This results in bogus authorship
2020 + * data for revisions that are syndicated from off the feed, unless we
2021 + * use a ridiculous kludge like this to end-run the munging of meta-data
2022 + * by _wp_put_post_revision.
2023 + *
2024 + * @param int $revision_id The revision ID to fix up meta-data
2025 + */
2026 + function fix_revision_meta( $revision_id ) {
2027 + $post_author = (int) $this->post['post_author']; // is this a global? And if it is, where is it defined? (gwyneth 20230919)
2028 +
2029 + $revision_id = (int) $revision_id;
2030 +
2031 + // Let's fix the author.
2032 + set_post_field( 'post_author', $this->post['post_author'], $revision_id );
2033 +
2034 + // Let's fix the GUID to a dummy URL with the update hash.
2035 + set_post_field( 'guid', 'http://feedwordpress.radgeek.com/?rev=' . $this->update_hash(), $revision_id );
2036 +
2037 + // Let's fire an event for add-ons and filters
2038 + do_action( 'syndicated_post_fix_revision_meta', $revision_id, $this );
2039 +
2040 + } /* SyndicatedPost::fix_revision_meta () */
2041 +
2042 + /**
2043 + * SyndicatedPost::add_terms() -- if FeedWordPress is processing an
2044 + * automatic update, that generally means that wp_insert_post() is being
2045 + * called under the user credentials of whoever is viewing the blog at
2046 + * the time -- which usually means no user at all. But wp_insert_post()
2047 + * checks current_user_can() before assigning any of the terms in a
2048 + * post's tax_input structure -- which is unfortunate, since
2049 + * current_user_can() always returns FALSE when there is no current user
2050 + * logged in. Meaning that automatic updates get no terms assigned.
2051 + *
2052 + * So, wp_insert_post() is not going to do the term assignments for us.
2053 + * If you want something done right....
2054 + *
2055 + * @param string $new_status Unused action parameter.
2056 + * @param string $old_status Unused action parameter.
2057 + * @param object $post The database record for the post just inserted.
2058 + */
2059 + function add_terms ($new_status, $old_status, $post) {
2060 +
2061 + if ($new_status!='inherit') : // Bail if we are creating a revision.
2062 + if ( is_array($this->post) and isset($this->post['tax_input']) and is_array($this->post['tax_input']) ) :
2063 + foreach ($this->post['tax_input'] as $taxonomy => $terms) :
2064 + if (is_array($terms)) :
2065 + $terms = array_filter($terms); // strip out empties
2066 + endif;
2067 +
2068 + $res = wp_set_post_terms(
2069 + /*post_id=*/ $post->ID,
2070 + /*terms=*/ $terms,
2071 + /*taxonomy=*/ $taxonomy
2072 + );
2073 +
2074 + FeedWordPress::diagnostic(
2075 + 'syndicated_posts:categories',
2076 + 'Category: post('.json_encode($post->ID).') '.$taxonomy
2077 + .' := '
2078 + .json_encode($terms)
2079 + .' / result: '
2080 + .json_encode($res)
2081 + );
2082 +
2083 + endforeach;
2084 + endif;
2085 + endif;
2086 +
2087 + } /* SyndicatedPost::add_terms () */
2088 +
2089 + /**
2090 + * SyndicatedPost::fix_post_modified_ts() -- We would like to set
2091 + * post_modified and post_modified_gmt to reflect the value of
2092 + * <atom:updated> or equivalent elements on the feed. Unfortunately,
2093 + * wp_insert_post() refuses to acknowledge explicitly-set post_modified
2094 + * fields and overwrites them, either with the post_date (if new) or the
2095 + * current timestamp (if updated).
2096 + *
2097 + * So, wp_insert_post() is not going to do the last-modified assignments
2098 + * for us. If you want something done right....
2099 + *
2100 + * @param string $new_status Unused action parameter.
2101 + * @param string $old_status Unused action parameter.
2102 + * @param object $post The database record for the post just inserted.
2103 + */
2104 + function fix_post_modified_ts ($new_status, $old_status, $post) {
2105 + global $wpdb;
2106 + if ($new_status!='inherit') : // Bail if we are creating a revision.
2107 + $wpdb->update( $wpdb->posts, /*data=*/ array(
2108 + 'post_modified' => $this->post['post_modified'],
2109 + 'post_modified_gmt' => $this->post['post_modified_gmt'],
2110 + ), /*where=*/ array('ID' => $post->ID) );
2111 + endif;
2112 + } /* SyndicatedPost::fix_post_modified_ts () */
2113 +
2114 + /**
2115 + * SyndicatedPost::add_rss_meta: adds interesting meta-data to each entry
2116 + * using the space for custom keys. The set of keys and values to add is
2117 + * specified by the keys and values of $post['meta']. This is used to
2118 + * store anything that the WordPress user might want to access from a
2119 + * template concerning the post's original source that isn't provided
2120 + * for by standard WP meta-data (i.e., any interesting data about the
2121 + * syndicated post other than author, title, timestamp, categories, and
2122 + * guid). It's also used to hook into WordPress's support for
2123 + * enclosures.
2124 + *
2125 + * @param string $new_status Unused action parameter.
2126 + * @param string $old_status Unused action parameter.
2127 + * @param object $post The database record for the post just inserted.
2128 + */
2129 + function add_rss_meta($new_status, $old_status, $post) {
2130 + global $wpdb;
2131 + if ($new_status!='inherit') : // Bail if we are creating a revision.
2132 + FeedWordPress::diagnostic('syndicated_posts:meta_data', 'Adding post meta-data: {'.implode(", ", array_keys($this->post['meta'])).'}');
2133 +
2134 + if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
2135 + $postId = $post->ID;
2136 +
2137 + // Aggregated posts should NOT send out pingbacks.
2138 + // WordPress 2.1-2.2 claim you can tell them not to
2139 + // using $post_pingback, but they don't listen, so we
2140 + // make sure here.
2141 + $result = $wpdb->query("
2142 + DELETE FROM $wpdb->postmeta
2143 + WHERE post_id='$postId' AND meta_key='_pingme'
2144 + ");
2145 +
2146 + foreach ( $this->post['meta'] as $key => $values ) :
2147 + $eKey = esc_sql($key);
2148 +
2149 + // If this is an update, clear out the old
2150 + // values to avoid duplication.
2151 + $result = $wpdb->query("
2152 + DELETE FROM $wpdb->postmeta
2153 + WHERE post_id='$postId' AND meta_key='$eKey'
2154 + ");
2155 +
2156 + // Allow for either a single value or an array
2157 + if ( !is_array($values)) $values = array($values);
2158 + foreach ( $values as $value ) :
2159 + FeedWordPress::diagnostic('syndicated_posts:meta_data', "Adding post meta-datum to post [$postId]: [$key] = ".MyPHP::val($value, /*no newlines=*/ true));
2160 + add_post_meta($postId, $key, $value, /*unique=*/ false);
2161 + endforeach;
2162 + endforeach;
2163 +
2164 + if ( $result === false)
2165 + {
2166 + error_log( "delete query failed: " . $wpdb->last_error );
2167 + }
2168 + endif;
2169 + endif;
2170 + } /* SyndicatedPost::add_rss_meta () */
2171 +
2172 + /**
2173 + * SyndicatedPost::author_id (): get the ID for an author name from
2174 + * the feed. Create the author if necessary.
2175 + *
2176 + * @param string $unfamiliar_author
2177 + *
2178 + * @return NULL|int The numeric ID of the author to attribute the post to
2179 + * NULL if the post should be filtered out.
2180 + */
2181 + function author_id ($unfamiliar_author = 'create') {
2182 + global $wpdb;
2183 +
2184 + $a = $this->named['author'];
2185 +
2186 + $source = $this->source();
2187 + $forbidden = apply_filters('feedwordpress_forbidden_author_names',
2188 + array('admin', 'administrator', 'www', 'root'));
2189 +
2190 + // Prepare the list of candidates to try for author name: name from
2191 + // feed, original source title (if any), immediate source title live
2192 + // from feed, subscription title, prettied version of feed homepage URL,
2193 + // prettied version of feed URL, or, failing all, use "unknown author"
2194 + // as last resort
2195 +
2196 + $candidates = array();
2197 + $candidates[] = $a['name'];
2198 + if ( !is_null($source)) : $candidates[] = $source['title']; endif;
2199 + $candidates[] = $this->link->name(/*fromFeed=*/ true);
2200 + $candidates[] = $this->link->name(/*fromFeed=*/ false);
2201 + if (strlen($this->link->homepage()) > 0) : $candidates[] = feedwordpress_display_url($this->link->homepage()); endif;
2202 + $candidates[] = feedwordpress_display_url($this->link->uri());
2203 + $candidates[] = 'unknown author';
2204 +
2205 + // Pick the first one that works from the list, screening against empty
2206 + // or forbidden names.
2207 +
2208 + $author = NULL;
2209 + foreach ($candidates as $candidate) {
2210 + if ( !is_null($candidate)
2211 + and (strlen(trim($candidate)) > 0)
2212 + and !in_array(strtolower(trim($candidate)), $forbidden)) :
2213 + $author = $candidate;
2214 + break;
2215 + endif;
2216 + }
2217 +
2218 + $email = (isset($a['email']) ? $a['email'] : NULL);
2219 + $authorUrl = (isset($a['uri']) ? $a['uri'] : NULL);
2220 +
2221 +
2222 + $hostUrl = $this->link->homepage();
2223 + if (is_null($hostUrl) or (strlen($hostUrl) < 0)) :
2224 + $hostUrl = $this->link->uri();
2225 + endif;
2226 +
2227 + $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
2228 + if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
2229 + $test_email = $email;
2230 + else :
2231 + $test_email = NULL;
2232 + endif;
2233 +
2234 + // Never can be too careful...
2235 + $login = sanitize_user($author, /*strict=*/ true);
2236 +
2237 + // Possible for, e.g., foreign script author names
2238 + if (strlen($login) < 1) :
2239 + // No usable characters in author name for a login.
2240 + // (Sometimes results from, e.g., foreign scripts.)
2241 + //
2242 + // We just need *something* in Western alphanumerics,
2243 + // so let's try the domain name.
2244 + //
2245 + // Uniqueness will be guaranteed below if necessary.
2246 +
2247 + $url = parse_url($hostUrl);
2248 +
2249 + $login = sanitize_user($url['host'], /*strict=*/ true);
2250 + if (strlen($login) < 1) :
2251 + // This isn't working. Frak it.
2252 + $login = 'syndicated';
2253 + endif;
2254 + endif;
2255 +
2256 + $login = apply_filters('pre_user_login', $login);
2257 +
2258 + $nice_author = sanitize_title($author);
2259 + $nice_author = apply_filters('pre_user_nicename', $nice_author);
2260 +
2261 + $reg_author = esc_sql(preg_quote($author ?: "(unknown)"));
2262 + $author = esc_sql($author);
2263 + $email = esc_sql($email);
2264 + $test_email = esc_sql($test_email);
2265 + $authorUrl = esc_sql($authorUrl);
2266 +
2267 + // Check for an existing author rule....
2268 + if (isset($this->link->settings['map authors']['name']['*'])) :
2269 + $author_rule = $this->link->settings['map authors']['name']['*'];
2270 + elseif (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) :
2271 + $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))];
2272 + else :
2273 + $author_rule = NULL;
2274 + endif;
2275 +
2276 + // User name is mapped to a particular author. If that author ID exists, use it.
2277 + // Note: get_userdata() is a pluggable function, so it might not be available yet;
2278 + // check if it exists before using it, or WP will throw a fatal error. (gwyneth 20220223)
2279 + if (is_numeric($author_rule) and function_exists('get_userdata') and get_userdata((int) $author_rule)) :
2280 + $id = (int) $author_rule;
2281 +
2282 + // User name is filtered out
2283 + elseif ('filter' == $author_rule) :
2284 + $id = NULL;
2285 +
2286 + else :
2287 + // Check the database for an existing author record that might fit
2288 +
2289 + // First try the user core data table.
2290 + $id = $wpdb->get_var(
2291 + "SELECT ID FROM $wpdb->users
2292 + WHERE TRIM(LCASE(display_name)) = TRIM(LCASE('$author'))
2293 + OR TRIM(LCASE(user_login)) = TRIM(LCASE('$author'))
2294 + OR (
2295 + LENGTH(TRIM(LCASE(user_email))) > 0
2296 + AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
2297 + )");
2298 +
2299 + // If that fails, look for aliases in the user meta data table
2300 + if (is_null($id)) :
2301 + $id = $wpdb->get_var(
2302 + "SELECT user_id FROM $wpdb->usermeta
2303 + WHERE
2304 + (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
2305 + OR (
2306 + meta_key = 'description'
2307 + AND TRIM(LCASE(meta_value))
2308 + RLIKE CONCAT(
2309 + '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
2310 + TRIM(LCASE('$reg_author')),
2311 + '( |\\t|\\r)*(\\n|\$)'
2312 + )
2313 + )
2314 + ");
2315 + endif;
2316 +
2317 + // ... if you don't find one, then do what you need to do
2318 + if (is_null($id)) :
2319 + if ($unfamiliar_author === 'create') :
2320 + $userdata = array();
2321 +
2322 + #-- we need *something* for the email here or WordPress
2323 + #-- is liable to pitch a fit. So, make something up if
2324 + #-- necessary. (Ugh.)
2325 + if (strlen($email) == 0 or FeedWordPress::is_null_email($email)) :
2326 + $url = parse_url($hostUrl);
2327 + $email = $nice_author.'@'.$url['host'];
2328 + endif;
2329 +
2330 + #-- user table data
2331 + $userdata['ID'] = NULL; // new user
2332 + $userdata['user_login'] = $login;
2333 + $userdata['user_nicename'] = $nice_author;
2334 + $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
2335 + $userdata['user_email'] = $email;
2336 + $userdata['user_url'] = $authorUrl;
2337 + $userdata['nickname'] = $author;
2338 +
2339 + $parts = preg_split('/\s+/', trim($author), 2);
2340 + if (isset($parts[0])) : $userdata['first_name'] = $parts[0]; endif;
2341 + if (isset($parts[1])) : $userdata['last_name'] = $parts[1]; endif;
2342 +
2343 + $userdata['display_name'] = $author;
2344 + $userdata['role'] = 'contributor';
2345 +
2346 + #-- loop. Keep trying to add the user until you get it
2347 + #-- right. Or until PHP crashes, I guess.
2348 + $insanity = 0;
2349 + do {
2350 + $id = wp_insert_user($userdata);
2351 + if (is_wp_error($id)) :
2352 + $codes = $id->get_error_code();
2353 + switch ($codes) :
2354 + case 'empty_user_login' :
2355 + case 'existing_user_login' :
2356 + case 'invalid_username' :
2357 + // Add a random disambiguator
2358 + $userdata['user_login'] .= substr(md5(uniqid(microtime())), 0, 6);
2359 + break;
2360 + case 'user_login_too_long' :
2361 + // Limit length to 53 characters; if we end up needing a random disambiguator,
2362 + // we should still have space to add it.
2363 + $userdata['user_login'] = mb_substr( $userdata['user_login'], 0, 53 );
2364 + break;
2365 + case 'user_nicename_too_long' :
2366 + // Add a limited 50 characters user_nicename based on user_login
2367 + $userdata['user_nicename'] = mb_substr( $userdata['user_login'], 0, 50 );
2368 + break;
2369 + case 'existing_user_email' :
2370 + // Disassemble email for username, host
2371 + $parts = explode('@', $userdata['user_email'], 2);
2372 +
2373 + // Add a random disambiguator as a gmail-style username extension
2374 + $parts[0] .= '+'.substr(md5(uniqid(microtime())), 0, 6);
2375 +
2376 + // Reassemble
2377 + $userdata['user_email'] = $parts[0].'@'.$parts[1];
2378 + break;
2379 + default :
2380 + if ( $insanity > 10 ) :
2381 + // Try some settings that are unlikely to cause complaint...
2382 + $url = parse_url($hostUrl);
2383 +
2384 + $userdata['user_login'] = substr(md5(uniqid(microtime())), 0, 6);
2385 + $userdata['user_nicename'] = $userdata['user_login'];
2386 + $userdata['user_email'] = 'noreply@' . $url['host'];
2387 + elseif ( $insanity > 50 ) :
2388 + // Stop doing the same thing and expecting a different result
2389 + break;
2390 + endif;
2391 + endswitch;
2392 + endif;
2393 + $insanity = $insanity + 1;
2394 + } while (is_wp_error($id));
2395 +
2396 + // $id should now contain the numeric ID of a newly minted
2397 + // user account. Let's mark them as having been generated
2398 + // by FeedWordPress in the usermeta table, as per the
2399 + // suggestion of @boonebgorges, in case we need to process,
2400 + // winnow, filter, or merge syndicated author accounts, &c.
2401 + if ( !is_wp_error($id)) :
2402 + add_user_meta($id, 'feedwordpress_generated', 1);
2403 + else :
2404 + $id = null;
2405 + endif;
2406 + // also see comment above regarding get_userdata(). (gwyneth 20220223)
2407 + elseif ( is_numeric( $unfamiliar_author ) and function_exists( 'get_userdata ') and get_userdata((int) $unfamiliar_author ) ) :
2408 + $id = (int) $unfamiliar_author;
2409 + elseif ( $unfamiliar_author === 'default' ) :
2410 + $id = 1;
2411 + endif;
2412 + endif;
2413 + endif;
2414 +
2415 + if ($id) :
2416 + $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id;
2417 +
2418 + // Multisite: Check whether the author has been recorded
2419 + // on *this* blog before. If not, put her down as a
2420 + // Contributor for *this* blog.
2421 + $user = new WP_User((int) $id);
2422 + if (empty($user->roles)) :
2423 + $user->add_role('contributor');
2424 + endif;
2425 + endif;
2426 + return $id;
2427 + } /* function SyndicatedPost::author_id () */
2428 +
2429 + /**
2430 + * category_ids: look up (and create) category ids from a list of
2431 + * categories
2432 + *
2433 + * @param array $cats
2434 + * @param string $unfamiliar_category
2435 + * @param array|null $taxonomies
2436 + * @return array
2437 + */
2438 + function category_ids ($cats, $unfamiliar_category = 'create', $taxonomies = NULL, $params = array()) {
2439 + return $this->link->category_ids($this, $cats, $unfamiliar_category, $taxonomies, $params);
2440 + } /* SyndicatedPost::category_ids () */
2441 +
2442 +} /* class SyndicatedPost */