PluginProbe
FeedWordPress / 2010.0528
FeedWordPress v2010.0528
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 +921 -1669 trunk2010.0528 View file →
@@ -1,8 +1,6 @@
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';
2 +require_once(dirname(__FILE__).'/feedtime.class.php');
5 3
6 4 /**
7 5 * class SyndicatedPost: FeedWordPress uses to manage the conversion of
8 6 * incoming items from the feed parser into posts for the WordPress
@@ -11,31 +9,24 @@
11 9 * as some utility methods for extracting useful data from many
12 10 * different feed formats, which may be useful to FeedWordPress users
13 11 * who make use of feed data in PHP add-ons and filters.
14 12 *
15 - * @version 2017.1018
13 + * @version 2010.0528
16 14 */
17 15 class SyndicatedPost {
18 - /** @var MagpieRSS|null MagpieRSS representation. */
19 - var $item = null;
20 - /** @var SimplePie_Item|null SimplePie_Item representation. */
21 - var $entry = null;
16 + var $item = null; // MagpieRSS representation
17 + var $entry = null; // SimplePie_Item representation
22 18
23 19 var $link = null;
24 20 var $feed = null;
25 21 var $feedmeta = null;
22 +
23 + var $xmlns = array ();
26 24
27 - var $xmlns = array();
25 + var $post = array ();
28 26
29 - var $post = array();
30 -
31 - var $named = array();
32 - var $preset_terms = array();
33 - var $feed_terms = array();
34 -
35 27 var $_freshness = null;
36 28 var $_wp_id = null;
37 - var $_wp_post = null;
38 29
39 30 /**
40 31 * SyndicatedPost constructor: Given a feed item and the source from
41 32 * which it was taken, prepare a post that can be inserted into the
@@ -44,27 +35,17 @@
44 35 *
45 36 * @param array $item The item syndicated from the feed.
46 37 * @param SyndicatedLink $source The feed it was syndicated from.
47 38 */
48 - public function __construct( $item, $source ) {
49 - if ( empty( $item ) and empty( $source ) )
50 - return;
39 + function SyndicatedPost ($item, $source) {
40 + global $wpdb;
51 41
52 - if ( is_array( $item )
53 - and isset( $item['simplepie'] )
54 - and isset( $item['magpie'] ) ) :
42 + if (is_array($item)
43 + and isset($item['simplepie'])
44 + and isset($item['magpie'])) :
55 45 $this->entry = $item['simplepie'];
56 46 $this->item = $item['magpie'];
57 47 $item = $item['magpie'];
58 - elseif ( is_a( $item, 'SimplePie_Item' ) ) :
59 - $this->entry = $item;
60 -
61 - // convert to Magpie for compat purposes
62 - $mpie = new MagpieFromSimplePie( $source->simplepie, $this->entry );
63 - $this->item = $mpie->get_item();
64 -
65 - // done with conversion object
66 - $mpie = NULL; unset( $mpie );
67 48 else :
68 49 $this->item = $item;
69 50 endif;
70 51
@@ -71,41 +52,51 @@
71 52 $this->link = $source;
72 53 $this->feed = $source->magpie;
73 54 $this->feedmeta = $source->settings;
74 55
75 - FeedWordPress::diagnostic( 'feed_items', 'Considering item [' . $this->guid() . '] "' . $this->entry->get_title().'"');
76 -
77 56 # Dealing with namespaces can get so fucking fucked.
78 57 $this->xmlns['forward'] = $source->magpie->_XMLNS_FAMILIAR;
79 58 $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();
59 + foreach ($this->xmlns['forward'] as $url => $ns) :
60 + if (!isset($this->xmlns['reverse'][$ns])) :
61 + $this->xmlns['reverse'][$ns] = array();
83 62 endif;
84 - $this->xmlns['reverse'][ $ns ][] = $url;
63 + $this->xmlns['reverse'][$ns][] = $url;
85 64 endforeach;
86 -
65 +
87 66 // Fucking SimplePie.
88 67 $this->xmlns['reverse']['rss'][] = '';
89 68
69 + # These globals were originally an ugly kludge around a bug in
70 + # apply_filters from WordPress 1.5. The bug was fixed in 1.5.1,
71 + # and I sure hope at this point that nobody writing filters for
72 + # FeedWordPress is still relying on them.
73 + #
74 + # Anyway, I hereby declare them DEPRECATED as of 8 February
75 + # 2010. I'll probably remove the globals within 1-2 releases in
76 + # the interests of code hygiene and memory usage. If you
77 + # currently use them in your filters, I advise you switch off to
78 + # accessing the public members SyndicatedPost::feed and
79 + # SyndicatedPost::feedmeta.
80 +
81 + global $fwp_channel, $fwp_feedmeta;
82 + $fwp_channel = $this->feed; $fwp_feedmeta = $this->feedmeta;
83 +
90 84 // Trigger global syndicated_item filter.
91 - $changed = apply_filters( 'syndicated_item', $this->item, $this );
92 - $this->item = $changed;
93 -
85 + $this->item = apply_filters('syndicated_item', $this->item, $this);
86 +
94 87 // Allow for feed-specific syndicated_item filters.
95 - $changed = apply_filters(
96 - "syndicated_item_" . $source->uri(),
88 + $this->item = apply_filters(
89 + "syndicated_item_".$source->uri(),
97 90 $this->item,
98 91 $this
99 92 );
100 - $this->item = $changed;
101 93
102 94 # Filters can halt further processing by returning NULL
103 - if ( is_null( $this->item ) ) :
95 + if (is_null($this->item)) :
104 96 $this->post = NULL;
105 97 else :
106 -
107 - # Note that nothing is run through esc_sql() here.
98 + # Note that nothing is run through $wpdb->escape() here.
108 99 # That's deliberate. The escaping is done at the point
109 100 # of insertion, not here, to avoid double-escaping and
110 101 # to avoid screwing with syndicated_post filters
111 102
@@ -110,16 +101,14 @@
110 101 # to avoid screwing with syndicated_post filters
111 102
112 103 $this->post['post_title'] = apply_filters(
113 104 'syndicated_item_title',
114 - $this->entry->get_title(),
115 - $this
105 + $this->entry->get_title(), $this
116 106 );
117 107
118 - $this->named['author'] = apply_filters(
108 + $this->post['named']['author'] = apply_filters(
119 109 'syndicated_item_author',
120 - $this->author(),
121 - $this
110 + $this->author(), $this
122 111 );
123 112 // This just gives us an alphanumeric name for the author.
124 113 // We look up (or create) the numeric ID for the author
125 114 // in SyndicatedPost::add().
@@ -125,173 +114,197 @@
125 114 // in SyndicatedPost::add().
126 115
127 116 $this->post['post_content'] = apply_filters(
128 117 'syndicated_item_content',
129 - $this->content(),
130 - $this
118 + $this->content(), $this
131 119 );
132 -
133 - $excerpt = apply_filters( 'syndicated_item_excerpt', $this->excerpt(), $this );
134 -
135 - if ( !empty( $excerpt ) ):
120 +
121 + $excerpt = apply_filters('syndicated_item_excerpt', $this->excerpt(), $this);
122 + if (!is_null($excerpt)):
136 123 $this->post['post_excerpt'] = $excerpt;
137 124 endif;
125 +
126 + $this->post['epoch']['issued'] = apply_filters('syndicated_item_published', $this->published(), $this);
127 + $this->post['epoch']['created'] = apply_filters('syndicated_item_created', $this->created(), $this);
128 + $this->post['epoch']['modified'] = apply_filters('syndicated_item_updated', $this->updated(), $this);
138 129
139 130 // 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 ) );
131 + $offset = (int) get_option('gmt_offset') * 60 * 60;
132 + $this->post['post_date'] = gmdate('Y-m-d H:i:s', $this->published(/*fallback=*/ true, /*default=*/ -1) + $offset);
133 + $this->post['post_modified'] = gmdate('Y-m-d H:i:s', $this->updated(/*fallback=*/ true, /*default=*/ -1) + $offset);
134 + $this->post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $this->published(/*fallback=*/ true, /*default=*/ -1));
135 + $this->post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $this->updated(/*fallback=*/ true, /*default=*/ -1));
143 136
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 );
148 -
149 137 // Use feed-level preferences or the global default.
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' );
138 + $this->post['post_status'] = $this->link->syndicated_status('post', 'publish');
139 + $this->post['comment_status'] = $this->link->syndicated_status('comment', 'closed');
140 + $this->post['ping_status'] = $this->link->syndicated_status('ping', 'closed');
153 141
154 142 // Unique ID (hopefully a unique tag: URI); failing that, the permalink
155 - $this->post['guid'] = apply_filters( 'syndicated_item_guid', $this->guid(), $this );
143 + $this->post['guid'] = apply_filters('syndicated_item_guid', $this->guid(), $this);
156 144
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 ) );
145 + // 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
146 + $default_custom_settings = get_option('feedwordpress_custom_settings');
147 + if ($default_custom_settings and !is_array($default_custom_settings)) :
148 + $default_custom_settings = unserialize($default_custom_settings);
149 + endif;
150 + if (!is_array($default_custom_settings)) :
151 + $default_custom_settings = array();
152 + endif;
153 +
154 + $custom_settings = (isset($this->link->settings['postmeta']) ? $this->link->settings['postmeta'] : null);
155 + if ($custom_settings and !is_array($custom_settings)) :
156 + $custom_settings = unserialize($custom_settings);
157 + endif;
158 + if (!is_array($custom_settings)) :
159 + $custom_settings = array();
160 + endif;
161 +
162 + $postMetaIn = array_merge($default_custom_settings, $custom_settings);
161 163 $postMetaOut = array();
162 164
163 - foreach ( $postMetaIn as $key => $meta ) :
164 - $postMetaOut[ $key ] = $meta->do_substitutions( $this );
165 + // Big ugly fuckin loop to do any element substitutions
166 + // that we may need.
167 + foreach ($postMetaIn as $key => $values) :
168 + if (is_string($values)) : $values = array($values); endif;
169 +
170 + $postMetaOut[$key] = array();
171 + foreach ($values as $value) :
172 + if (preg_match('/\$\( ([^)]+) \)/x', $value, $ref)) :
173 + $elements = $this->query($ref[1]);
174 + foreach ($elements as $element) :
175 + $postMetaOut[$key][] = str_replace(
176 + $ref[0],
177 + $element,
178 + $value
179 + );
180 + endforeach;
181 + else :
182 + $postMetaOut[$key][] = $value;
183 + endif;
184 + endforeach;
165 185 endforeach;
166 186
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 );
187 + foreach ($postMetaOut as $key => $values) :
188 + $this->post['meta'][$key] = array();
189 + foreach ($values as $value) :
190 + $this->post['meta'][$key][] = apply_filters("syndicated_post_meta_{$key}", $value, $this);
174 191 endforeach;
175 192 endforeach;
176 -
193 +
177 194 // RSS 2.0 / Atom 1.0 enclosure support
178 195 $enclosures = $this->entry->get_enclosures();
179 - if ( is_array( $enclosures ) ) : foreach ( $enclosures as $enclosure ) :
196 + if (is_array($enclosures)) : foreach ($enclosures as $enclosure) :
180 197 $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 );
198 + apply_filters('syndicated_item_enclosure_url', $enclosure->get_link(), $this)."\n".
199 + apply_filters('syndicated_item_enclosure_length', $enclosure->get_length(), $this)."\n".
200 + apply_filters('syndicated_item_enclosure_type', $enclosure->get_type(), $this);
184 201 endforeach; endif;
185 202
186 - // In case you want to point back to the blog this was
187 - // syndicated from.
203 + // In case you want to point back to the blog this was syndicated from
204 + if (isset($this->feed->channel['title'])) :
205 + $this->post['meta']['syndication_source'] = apply_filters('syndicated_item_source_title', $this->feed->channel['title'], $this);
206 + endif;
188 207
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 -
208 + if (isset($this->feed->channel['link'])) :
209 + $this->post['meta']['syndication_source_uri'] = apply_filters('syndicated_item_source_link', $this->feed->channel['link'], $this);
210 + endif;
211 +
205 212 // Make use of atom:source data, if present in an aggregated feed
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;
213 + if (isset($this->item['source_title'])) :
214 + $this->post['meta']['syndication_source_original'] = $this->item['source_title'];
215 + endif;
214 216
215 - $sourcemeta["{$key}_original"] = apply_filters(
216 - 'syndicated_item_original_source_' . $what,
217 - $value,
218 - $this
219 - );
220 - endif;
221 - endforeach;
217 + if (isset($this->item['source_link'])) :
218 + $this->post['meta']['syndication_source_uri_original'] = $this->item['source_link'];
222 219 endif;
220 +
221 + if (isset($this->item['source_id'])) :
222 + $this->post['meta']['syndication_source_id_original'] = $this->item['source_id'];
223 + endif;
223 224
224 - foreach ( $sourcemeta as $meta_key => $value ) :
225 - if ( !is_null( $value ) ) :
226 - $this->post['meta'][ $meta_key ] = $value;
227 - endif;
228 - endforeach;
229 -
230 225 // Store information on human-readable and machine-readable comment URIs
231 -
226 +
232 227 // 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;
228 + $commentLink = apply_filters('syndicated_item_comments', $this->comment_link(), $this);
229 + if (!is_null($commentLink)) : $this->post['meta']['rss:comments'] = $commentLink; endif;
235 230
236 231 // 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;
232 + $commentFeed = apply_filters('syndicated_item_commentrss', $this->comment_feed(), $this);
233 + if (!is_null($commentFeed)) : $this->post['meta']['wfw:commentRSS'] = $commentFeed; endif;
239 234 // Yeah, yeah, now I know that it's supposed to be
240 235 // wfw:commentRss. Oh well. Path dependence, sucka.
241 236
242 237 // Store information to identify the feed that this came from
243 - if ( isset( $this->feedmeta['link/uri'] ) ) :
238 + if (isset($this->feedmeta['link/uri'])) :
244 239 $this->post['meta']['syndication_feed'] = $this->feedmeta['link/uri'];
245 240 endif;
246 - if ( isset( $this->feedmeta['link/id'] ) ) :
241 + if (isset($this->feedmeta['link/id'])) :
247 242 $this->post['meta']['syndication_feed_id'] = $this->feedmeta['link/id'];
248 243 endif;
249 244
250 - if ( isset( $this->item['source_link_self'] ) ) :
245 + if (isset($this->item['source_link_self'])) :
251 246 $this->post['meta']['syndication_feed_original'] = $this->item['source_link_self'];
252 247 endif;
253 248
254 249 // In case you want to know the external permalink...
255 - $this->post['meta']['syndication_permalink'] = apply_filters( 'syndicated_item_link', $this->permalink() );
250 + $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $this->permalink());
256 251
257 252 // Store a hash of the post content for checking whether something needs to be updated
258 253 $this->post['meta']['syndication_item_hash'] = $this->update_hash();
259 254
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 );
255 + // Feed-by-feed options for author and category creation
256 + $this->post['named']['unfamiliar']['author'] = (isset($this->feedmeta['unfamiliar author']) ? $this->feedmeta['unfamiliar author'] : null);
257 + $this->post['named']['unfamiliar']['category'] = (isset($this->feedmeta['unfamiliar category']) ? $this->feedmeta['unfamiliar category'] : null);
264 258
265 - $this->post['post_type'] = apply_filters(
266 - 'syndicated_post_type',
267 - $this->link->setting( 'syndicated post type', 'syndicated_post_type', 'post' ),
268 - $this
269 - );
259 + // Categories: start with default categories, if any
260 + $fc = get_option("feedwordpress_syndication_cats");
261 + if ($fc) :
262 + $this->post['named']['preset/category'] = explode("\n", $fc);
263 + else :
264 + $this->post['named']['preset/category'] = array();
265 + endif;
266 +
267 + if (isset($this->feedmeta['cats']) and is_array($this->feedmeta['cats'])) :
268 + $this->post['named']['preset/category'] = array_merge($this->post['named']['preset/category'], $this->feedmeta['cats']);
269 + endif;
270 +
271 + // Now add categories from the post, if we have 'em
272 + $this->post['named']['category'] = array();
273 + if ( isset($this->item['category#']) ) :
274 + for ($i = 1; $i <= $this->item['category#']; $i++) :
275 + $cat_idx = (($i > 1) ? "#{$i}" : "");
276 + $cat = $this->item["category{$cat_idx}"];
277 +
278 + if ( isset($this->feedmeta['cat_split']) and strlen($this->feedmeta['cat_split']) > 0) :
279 + $pcre = "\007".$this->feedmeta['cat_split']."\007";
280 + $this->post['named']['category'] = array_merge($this->post['named']['category'], preg_split($pcre, $cat, -1 /*=no limit*/, PREG_SPLIT_NO_EMPTY));
281 + else :
282 + $this->post['named']['category'][] = $cat;
283 + endif;
284 + endfor;
285 + endif;
286 + $this->post['named']['category'] = apply_filters('syndicated_item_categories', $this->post['named']['category'], $this);
287 +
288 + // Tags: start with default tags, if any
289 + $ft = get_option("feedwordpress_syndication_tags");
290 + if ($ft) :
291 + $this->post['tags_input'] = explode(FEEDWORDPRESS_CAT_SEPARATOR, $ft);
292 + else :
293 + $this->post['tags_input'] = array();
294 + endif;
295 +
296 + if (isset($this->feedmeta['tags']) and is_array($this->feedmeta['tags'])) :
297 + $this->post['tags_input'] = array_merge($this->post['tags_input'], $this->feedmeta['tags']);
298 + endif;
299 + $this->post['tags_input'] = apply_filters('syndicated_item_tags', $this->post['tags_input'], $this);
270 300 endif;
271 - } /* SyndicatedPost::__construct() */
301 + } /* SyndicatedPost::SyndicatedPost() */
272 302
273 303 #####################################
274 304 #### EXTRACT DATA FROM FEED ITEM ####
275 305 #####################################
276 -
277 - function substitution_function ($name) {
278 - $ret = NULL;
279 -
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;
291 - return $ret;
292 - }
293 -
306 +
294 307 /**
295 308 * SyndicatedPost::query uses an XPath-like syntax to query arbitrary
296 309 * elements within the syndicated item.
297 310 *
@@ -298,168 +311,211 @@
298 311 * @param string $path
299 312 * @returns array of string values representing contents of matching
300 313 * elements or attributes
301 314 */
302 - public function query ($path) {
303 - $xq = new SyndicatedPostXPathQuery(array("path" => $path));
315 + function query ($path) {
316 + $urlHash = array();
304 317
305 - $feedChannel = array_merge(
306 - $this->get_feed_root_element(),
307 - $this->get_feed_channel_elements()
308 - );
318 + // Allow {url} notation for namespaces. URLs will contain : and /, so...
319 + preg_match_all('/{([^}]+)}/', $path, $match, PREG_SET_ORDER);
320 + foreach ($match as $ref) :
321 + $urlHash[md5($ref[1])] = $ref[1];
322 + endforeach;
323 +
324 + foreach ($urlHash as $hash => $url) :
325 + $path = str_replace('{'.$url.'}', '{#'.$hash.'}', $path);
326 + endforeach;
309 327
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 - ));
328 + $path = explode('/', $path);
329 + foreach ($path as $index => $node) :
330 + if (preg_match('/{#([^}]+)}/', $node, $ref)) :
331 + if (isset($urlHash[$ref[1]])) :
332 + $path[$index] = str_replace(
333 + '{#'.$ref[1].'}',
334 + '{'.$urlHash[$ref[1]].'}',
335 + $node
336 + );
337 + endif;
338 + endif;
339 + endforeach;
322 340
323 - return $matches;
324 - } /* SyndicatedPost::query() */
341 + // Start out with a get_item_tags query.
342 + $node = '';
343 + while (strlen($node)==0 and !is_null($node)) :
344 + $node = array_shift($path);
345 + endwhile;
346 +
347 + switch ($node) :
348 + case 'feed' :
349 + case 'channel' :
350 + $method = "get_${node}_tags";
351 + $node = array_shift($path);
352 + break;
353 + case 'item' :
354 + $node = array_shift($path);
355 + default :
356 + $method = NULL;
357 + endswitch;
325 358
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);
359 + $data = array();
360 + if (!is_null($node)) :
361 + list($namespaces, $element) = $this->xpath_extended_name($node);
362 +
363 + $matches = array();
364 + foreach ($namespaces as $ns) :
365 + if (!is_null($method)) :
366 + $el = $this->link->simplepie->{$method}($ns, $element);
367 + else :
368 + $el = $this->entry->get_item_tags($ns, $element);
369 + endif;
370 +
371 + if (!is_null($el)) :
372 + $matches = array_merge($matches, $el);
373 + endif;
331 374 endforeach;
332 - endforeach;
333 - return $matches;
334 - } /* SyndicatedPost::get_feed_root_element() */
375 + $data = $matches;
376 +
377 + $node = array_shift($path);
378 + endif;
335 379
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 - );
380 + while (!is_null($node)) :
381 + if (strlen($node) > 0) :
382 + $matches = array();
383 +
384 + list($ns, $element) = $this->xpath_extended_name($node);
385 +
386 + if (preg_match('/^@(.*)$/', $element, $ref)) :
387 + $element = $ref[1];
388 + $axis = 'attribs';
389 + else :
390 + $axis = 'child';
391 + endif;
343 392
393 + foreach ($data as $datum) :
394 + foreach ($namespaces as $ns) :
395 + if (!is_string($datum)
396 + and isset($datum[$axis][$ns][$element])) :
397 + if (is_string($datum[$axis][$ns][$element])) :
398 + $matches[] = $datum[$axis][$ns][$element];
399 + else :
400 + $matches = array_merge($matches, $datum[$axis][$ns][$element]);
401 + endif;
402 + endif;
403 + endforeach;
404 + endforeach;
405 +
406 + $data = $matches;
407 + endif;
408 + $node = array_shift($path);
409 + endwhile;
410 +
344 411 $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);
412 + foreach ($data as $datum) :
413 + if (is_string($datum)) :
414 + $matches[] = $datum;
415 + elseif (isset($datum['data'])) :
416 + $matches[] = $datum['data'];
349 417 endif;
350 418 endforeach;
351 419 return $matches;
352 - } /* SyndicatedPost::get_feed_channel_elements() */
420 + } /* SyndicatedPost::query() */
353 421
354 - public function get_categories ($params = array()) {
355 - return $this->entry->get_categories();
356 - }
422 + function xpath_default_namespace () {
423 + // Get the default namespace.
424 + $type = $this->link->simplepie->get_type();
425 + if ($type & SIMPLEPIE_TYPE_ATOM_10) :
426 + $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_10;
427 + elseif ($type & SIMPLEPIE_TYPE_ATOM_03) :
428 + $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_03;
429 + elseif ($type & SIMPLEPIE_TYPE_RSS_090) :
430 + $defaultNS = SIMPLEPIE_NAMESPACE_RSS_090;
431 + elseif ($type & SIMPLEPIE_TYPE_RSS_10) :
432 + $defaultNS = SIMPLEPIE_NAMESPACE_RSS_10;
433 + elseif ($type & SIMPLEPIE_TYPE_RSS_20) :
434 + $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20;
435 + else :
436 + $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20;
437 + endif;
438 + return $defaultNS;
439 + } /* SyndicatedPost::xpath_default_namespace() */
440 +
441 + function xpath_extended_name ($node) {
442 + $ns = NULL; $element = NULL;
443 +
444 + if (substr($node, 0, 1)=='@') :
445 + $attr = '@'; $node = substr($node, 1);
446 + else :
447 + $attr = '';
448 + endif;
449 +
450 + if (preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) :
451 + $ns = array($ref[1]); $element = $ref[2];
452 + elseif (strpos($node, ':') !== FALSE) :
453 + list($xmlns, $element) = explode(':', $node, 2);
454 + if (isset($this->xmlns['reverse'][$xmlns])) :
455 + $ns = $this->xmlns['reverse'][$xmlns];
456 + else :
457 + $ns = array($xmlns);
458 + endif;
459 +
460 + // Fucking SimplePie. For attributes in default xmlns.
461 + if ($xmlns==$this->xmlns['forward'][$defaultNS[0]]) :
462 + $ns[] = '';
463 + endif;
464 + else :
465 + // Often in SimplePie, the default namespace gets stored
466 + // as an empty string rather than a URL.
467 + $ns = array($this->xpath_default_namespace(), '');
468 + $element = $node;
469 + endif;
470 + return array(array_unique($ns), $attr.$element);
471 + } /* SyndicatedPost::xpath_extended_name () */
357 472
358 - public function title ($params = array()) {
359 - return $this->entry->get_title();
360 - } /* SyndicatedPost::title () */
361 -
362 - public function content ($params = array())
363 - {
364 - $params = wp_parse_args($params, array(
365 - "full only" => false,
366 - ));
367 -
473 + function content () {
368 474 $content = NULL;
369 -
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
376 -
377 - // atom:content, standard method of providing full HTML content
378 - // in Atom feeds.
379 475 if (isset($this->item['atom_content'])) :
380 476 $content = $this->item['atom_content'];
381 - elseif (isset($this->item['atom']['atom_content'])) :
382 - $content = $this->item['atom']['atom_content'];
383 -
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 477 elseif (isset($this->item['xhtml']['body'])) :
389 478 $content = $this->item['xhtml']['body'];
390 479 elseif (isset($this->item['xhtml']['div'])) :
391 480 $content = $this->item['xhtml']['div'];
392 -
393 - // content:encoded, most common method of providing full HTML in
394 - // RSS 2.0 feeds.
395 481 elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']):
396 482 $content = $this->item['content']['encoded'];
397 -
398 - // Fall back on elements that sometimes may contain full HTML
399 - // but sometimes not.
400 - elseif ( ! $params['full only']) :
401 -
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'];
407 - endif;
408 -
483 + else:
484 + $content = $this->item['description'];
409 485 endif;
410 -
411 486 return $content;
412 487 } /* SyndicatedPost::content() */
413 488
414 - public function excerpt () {
489 + function excerpt () {
415 490 # Identify and sanitize excerpt: atom:summary, or rss:description
416 491 $excerpt = $this->entry->get_description();
417 -
492 +
418 493 # Many RSS feeds use rss:description, inadvisably, to
419 494 # carry the entire post (typically with escaped HTML).
420 495 # If that's what happened, we don't want the full
421 496 # content for the excerpt.
422 497 $content = $this->content();
423 -
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 ) :
498 + if ( is_null($excerpt) or $excerpt == $content ) :
428 499 # If content is available, generate an excerpt.
429 500 if ( strlen(trim($content)) > 0 ) :
430 501 $excerpt = strip_tags($content);
431 502 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');
436 - endif;
437 - $excerpt = mb_substr($excerpt,0,252,$encoding).'...';
503 + $excerpt = substr($excerpt,0,252).'...';
438 504 endif;
439 505 endif;
440 506 endif;
441 -
442 507 return $excerpt;
443 508 } /* SyndicatedPost::excerpt() */
444 509
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 () {
510 + function permalink () {
452 511 // Handles explicit <link> elements and also RSS 2.0 cases with
453 512 // <guid isPermaLink="true">, etc. Hooray!
454 513 $permalink = $this->entry->get_link();
455 514 return $permalink;
456 - } /* SyndicatedPost::permalink () */
515 + }
457 516
458 - public function created ($params = array()) {
459 - $unfiltered = false; // $default = NULL; // seems to be unused on this function (gwyneth 20230916)
460 - extract($params);
461 -
517 + function created () {
462 518 $date = '';
463 519 if (isset($this->item['dc']['created'])) :
464 520 $date = $this->item['dc']['created'];
465 521 elseif (isset($this->item['dcterms']['created'])) :
@@ -467,26 +523,14 @@
467 523 elseif (isset($this->item['created'])): // Atom 0.3
468 524 $date = $this->item['created'];
469 525 endif;
470 526
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;
527 + $epoch = new FeedTime($date);
528 + return $epoch->timestamp();
477 529 } /* SyndicatedPost::created() */
478 530
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 -
531 + function published ($fallback = true, $default = NULL) {
487 532 $date = '';
488 - $tstamp = null;
489 533
490 534 # RSS is a fucking mess. Figure out whether we have a date in
491 535 # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix
492 536 # epoch format for reformatting. If we can't find anything,
@@ -501,41 +545,30 @@
501 545 $date = $this->item['issued'];
502 546 elseif (isset($this->item['pubdate'])): // RSS 2.0
503 547 $date = $this->item['pubdate'];
504 548 endif;
505 -
549 +
506 550 if (strlen($date) > 0) :
507 551 $time = new FeedTime($date);
508 - $tstamp = $time->timestamp();
552 + $epoch = $time->timestamp();
509 553 elseif ($fallback) : // Fall back to <updated> / <modified> if present
510 - $tstamp = $this->updated(/*fallback=*/ false, /*default=*/ $default);
554 + $epoch = $this->updated(/*fallback=*/ false, /*default=*/ $default);
511 555 endif;
512 -
556 +
513 557 # If everything failed, then default to the current time.
514 - if (is_null($tstamp)) :
558 + if (is_null($epoch)) :
515 559 if (-1 == $default) :
516 - $tstamp = time();
560 + $epoch = time();
517 561 else :
518 - $tstamp = $default;
562 + $epoch = $default;
519 563 endif;
520 564 endif;
521 -
522 - if ( ! $unfiltered) :
523 - $tstamp = apply_filters('syndicated_item_published', $tstamp, $this);
524 - endif;
525 - return $tstamp;
565 +
566 + return $epoch;
526 567 } /* SyndicatedPost::published() */
527 568
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 -
569 + function updated ($fallback = true, $default = -1) {
536 570 $date = '';
537 - $tstamp = null;
538 571
539 572 # As far as I know, only dcterms and Atom have reliable ways to
540 573 # specify when something was *modified* last. If neither is
541 574 # available, then we'll try to get the time of publication.
@@ -544,159 +577,61 @@
544 577 elseif (isset($this->item['dcterms']['modified'])) : // Dublin Core extensions
545 578 $date = $this->item['dcterms']['modified'];
546 579 elseif (isset($this->item['modified'])): // Atom 0.3
547 580 $date = $this->item['modified'];
548 - elseif (isset($this->item['updated'])): // Atom 1.0
581 + elseif (isset($this->item['updated'])): // Atom 1.0
549 582 $date = $this->item['updated'];
550 583 endif;
551 -
584 +
552 585 if (strlen($date) > 0) :
553 586 $time = new FeedTime($date);
554 - $tstamp = $time->timestamp();
587 + $epoch = $time->timestamp();
555 588 elseif ($fallback) : // Fall back to issued / dc:date
556 - $tstamp = $this->published(/*fallback=*/ false, /*default=*/ $default);
589 + $epoch = $this->published(/*fallback=*/ false, /*default=*/ $default);
557 590 endif;
558 -
591 +
559 592 # If everything failed, then default to the current time.
560 - if (is_null($tstamp)) :
593 + if (is_null($epoch)) :
561 594 if (-1 == $default) :
562 - $tstamp = time();
595 + $epoch = time();
563 596 else :
564 - $tstamp = $default;
597 + $epoch = $default;
565 598 endif;
566 599 endif;
567 600
568 - if ( ! $unfiltered) :
569 - $tstamp = apply_filters('syndicated_item_updated', $tstamp, $this);
570 - endif;
571 - return $tstamp;
601 + return $epoch;
572 602 } /* SyndicatedPost::updated() */
573 603
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];
589 - }
590 -
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;
604 + function update_hash () {
605 + return md5(serialize($this->item));
605 606 } /* SyndicatedPost::update_hash() */
606 607
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);
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);
652 -
653 - return $guid;
654 - } /* SyndicatedPost::normalize_guid() */
655 -
656 - public function guid () {
608 + function guid () {
657 609 $guid = null;
658 - if (isset($this->item['id'])): // Atom 0.3 / 1.0
610 + if (isset($this->item['id'])): // Atom 0.3 / 1.0
659 611 $guid = $this->item['id'];
660 - elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
612 + elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
661 613 $guid = $this->item['atom']['id'];
662 - elseif (isset($this->item['guid'])) : // RSS 2.0
614 + elseif (isset($this->item['guid'])) : // RSS 2.0
663 615 $guid = $this->item['guid'];
664 - elseif (isset($this->item['dc']['identifier'])) : // yeah, right
616 + elseif (isset($this->item['dc']['identifier'])) :// yeah, right
665 617 $guid = $this->item['dc']['identifier'];
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 -
618 + else :
673 619 // The feed does not seem to have provided us with a
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());
620 + // unique identifier, so we'll have to cobble together
621 + // a tag: URI that might work for us. The base of the
622 + // URI will be the host name of the feed source ...
623 + $bits = parse_url($this->feedmeta['link/uri']);
678 624 $guid = 'tag:'.$bits['host'];
679 625
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 -
691 626 // If we have a date of creation, then we can use that
692 627 // to uniquely identify the item. (On the other hand, if
693 628 // the feed producer was consicentious enough to
694 629 // generate dates of creation, she probably also was
695 630 // conscientious enough to generate unique identifiers.)
696 - elseif ( !is_null($this->created())) :
631 + if (!is_null($this->created())) :
697 632 $guid .= '://post.'.date('YmdHis', $this->created());
698 -
633 +
699 634 // Otherwise, use both the URI of the item, *and* the
700 635 // item's title. We have to use both because titles are
701 636 // often not unique, and sometimes links aren't unique
702 637 // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
@@ -703,88 +638,58 @@
703 638 // some podcasts). But it's rare to have *both* the same
704 639 // title *and* the same link for two different items. So
705 640 // this is about the best we can do.
706 641 else :
707 - $link = $this->permalink();
708 - if (is_null($link)) : $link = $this->link->uri(); endif;
709 - $guid .= '://'.md5($link.'/'.$this->title());
642 + $guid .= '://'.md5($this->item['link'].'/'.$this->item['title']);
710 643 endif;
711 644 endif;
712 645 return $guid;
713 646 } /* SyndicatedPost::guid() */
714 -
715 - public function author () {
647 +
648 + function author () {
716 649 $author = array ();
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;
650 +
651 + if (isset($this->item['author_name'])):
652 + $author['name'] = $this->item['author_name'];
653 + elseif (isset($this->item['dc']['creator'])):
654 + $author['name'] = $this->item['dc']['creator'];
655 + elseif (isset($this->item['dc']['contributor'])):
656 + $author['name'] = $this->item['dc']['contributor'];
657 + elseif (isset($this->feed->channel['dc']['creator'])) :
658 + $author['name'] = $this->feed->channel['dc']['creator'];
659 + elseif (isset($this->feed->channel['dc']['contributor'])) :
660 + $author['name'] = $this->feed->channel['dc']['contributor'];
661 + elseif (isset($this->feed->channel['author_name'])) :
662 + $author['name'] = $this->feed->channel['author_name'];
663 + elseif ($this->feed->is_rss() and isset($this->item['author'])) :
664 + // The author element in RSS is allegedly an
665 + // e-mail address, but lots of people don't use
666 + // it that way. So let's make of it what we can.
667 + $author = parse_email_with_realname($this->item['author']);
668 +
669 + if (!isset($author['name'])) :
670 + if (isset($author['email'])) :
671 + $author['name'] = $author['email'];
672 + else :
673 + $author['name'] = $this->feed->channel['title'];
755 674 endif;
756 675 endif;
676 + else :
677 + $author['name'] = $this->feed->channel['title'];
757 678 endif;
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;
679 +
680 + if (isset($this->item['author_email'])):
681 + $author['email'] = $this->item['author_email'];
682 + elseif (isset($this->feed->channel['author_email'])) :
683 + $author['email'] = $this->feed->channel['author_email'];
767 684 endif;
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;
685 +
686 + if (isset($this->item['author_url'])):
687 + $author['uri'] = $this->item['author_url'];
688 + elseif (isset($this->feed->channel['author_url'])) :
689 + $author['uri'] = $this->item['author_url'];
690 + else:
691 + $author['uri'] = $this->feed->channel['link'];
787 692 endif;
788 693
789 694 return $author;
790 695 } /* SyndicatedPost::author() */
@@ -789,142 +694,8 @@
789 694 return $author;
790 695 } /* SyndicatedPost::author() */
791 696
792 697 /**
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 - /**
927 698 * SyndicatedPost::isTaggedAs: Test whether a feed item is
928 699 * tagged / categorized with a given string. Case and leading and
929 700 * trailing whitespace are ignored.
930 701 *
@@ -929,9 +700,9 @@
929 700 * trailing whitespace are ignored.
930 701 *
931 702 * @param string $tag Tag to check for
932 703 *
933 - * @return bool Whether or not at least one of the categories / tags on
704 + * @return bool Whether or not at least one of the categories / tags on
934 705 * $this->item is set to $tag (modulo case and leading and trailing
935 706 * whitespace)
936 707 */
937 708 function isTaggedAs ($tag) {
@@ -944,9 +715,9 @@
944 715 // If we have the new MagpieRSS, the number of category elements
945 716 // on this item is stored under index "category#".
946 717 if (isset($this->item['category#'])) :
947 718 $numberOfCategories = (int) $this->item['category#'];
948 -
719 +
949 720 // We REALLY shouldn't have the old and busted MagpieRSS, but in
950 721 // case we do, it doesn't support multiple categories, but there
951 722 // might still be a single value under the "category" index.
952 723 elseif (isset($this->item['category'])) :
@@ -985,60 +756,32 @@
985 756 * that will be returned to only those with a MIME type which
986 757 * matches this regular expression.
987 758 * @return array
988 759 */
989 - function enclosures( $type = '/.*/' ) {
760 + function enclosures ($type = '/.*/') {
990 761 $enclosures = array();
991 762
992 - if ( isset( $this->item['enclosure#'] ) ) :
763 + if (isset($this->item['enclosure#'])) :
993 764 // 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)
765 + for ($i = 1; $i <= $this->item['enclosure#']; $i++) :
766 + $eid = (($i > 1) ? "#{$id}" : "");
996 767
997 768 // Does it match the type we want?
998 - if ( preg_match($type, $this->item["enclosure{$eid}@type"] ) ) :
769 + if (preg_match($type, $this->item["enclosure{$eid}@type"])) :
999 770 $enclosures[] = array(
1000 - "url" => $this->item["enclosure{$eid}@url"],
1001 - "type" => $this->item["enclosure{$eid}@type"],
771 + "url" => $this->item["enclosure{$eid}@url"],
772 + "type" => $this->item["enclosure{$eid}@type"],
1002 773 "length" => $this->item["enclosure{$eid}@length"],
1003 774 );
1004 775 endif;
1005 776 endfor;
1006 777 endif;
1007 - return $enclosures;
778 + return $enclosures;
1008 779 } /* SyndicatedPost::enclosures() */
1009 780
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 781 function comment_link () {
1039 782 $url = null;
1040 -
783 +
1041 784 // RSS 2.0 has a standard <comments> element:
1042 785 // "<comments> is an optional sub-element of <item>. If present,
1043 786 // it is the url of the comments page for the item."
1044 787 // <http://cyber.law.harvard.edu/rss/rss.html#ltcommentsgtSubelementOfLtitemgt>
@@ -1049,15 +792,15 @@
1049 792 // The convention in Atom feeds is to use a standard <link>
1050 793 // element with @rel="replies" and @type="text/html".
1051 794 // Unfortunately, SimplePie_Item::get_links() allows us to filter
1052 795 // by the value of @rel, but not by the value of @type. *sigh*
1053 -
796 +
1054 797 // Try Atom 1.0 first
1055 798 $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link');
1056 -
799 +
1057 800 // Fall back and try Atom 0.3
1058 801 if (is_null($linkElements)) : $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'); endif;
1059 -
802 +
1060 803 // Now loop through the elements, screening by @rel and @type
1061 804 if (is_array($linkElements)) : foreach ($linkElements as $link) :
1062 805 $rel = (isset($link['attribs']['']['rel']) ? $link['attribs']['']['rel'] : 'alternate');
1063 806 $type = (isset($link['attribs']['']['type']) ? $link['attribs']['']['type'] : NULL);
@@ -1142,19 +885,18 @@
1142 885 var $_base = null;
1143 886
1144 887 function resolve_single_relative_uri ($refs) {
1145 888 $tag = FeedWordPressHTML::attributeMatch($refs);
1146 - $url = SimplePie_Misc::absolutize_url($tag['value'], $this->_base);
1147 -
889 + $url = Relative_URI::resolve($tag['value'], $this->_base);
1148 890 return $tag['prefix'] . $url . $tag['suffix'];
1149 891 } /* function SyndicatedPost::resolve_single_relative_uri() */
1150 892
1151 - static function resolve_relative_uris ($content, $obj) {
893 + function resolve_relative_uris ($content, $obj) {
1152 894 $set = $obj->link->setting('resolve relative', 'resolve_relative', 'yes');
1153 - if ($set and $set != 'no') :
895 + if ($set and $set != 'no') :
1154 896 // Fallback: if we don't have anything better, use the
1155 897 // item link from the feed
1156 - $obj->_base = $obj->permalink(); // Reset the base for resolving relative URIs
898 + $obj->_base = $obj->item['link']; // Reset the base for resolving relative URIs
1157 899
1158 900 // What we should do here, properly, is to use
1159 901 // SimplePie_Item::get_base() -- but that method is
1160 902 // currently broken. Or getting down and dirty in the
@@ -1164,22 +906,16 @@
1164 906
1165 907 foreach ($obj->uri_attrs as $pair) :
1166 908 list($tag, $attr) = $pair;
1167 909 $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>
1173 910 $content = preg_replace_callback (
1174 911 $pattern,
1175 - array($obj, 'resolve_single_relative_uri'),
912 + array(&$obj, 'resolve_single_relative_uri'),
1176 913 $content
1177 914 );
1178 -
1179 915 endforeach;
1180 916 endif;
1181 -
917 +
1182 918 return $content;
1183 919 } /* function SyndicatedPost::resolve_relative_uris () */
1184 920
1185 921 var $strip_attrs = array (
@@ -1192,9 +928,9 @@
1192 928 $tag = FeedWordPressHTML::attributeMatch($refs);
1193 929 return $tag['before_attribute'].$tag['after_attribute'];
1194 930 }
1195 931
1196 - static function sanitize_content ($content, $obj) {
932 + function sanitize_content ($content, $obj) {
1197 933 # This kind of sucks. I intend to replace it with
1198 934 # lib_filter sometime soon.
1199 935 foreach ($obj->strip_attrs as $pair):
1200 936 list($tag,$attr) = $pair;
@@ -1201,9 +937,9 @@
1201 937 $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
1202 938
1203 939 $content = preg_replace_callback (
1204 940 $pattern,
1205 - array($obj, 'strip_attribute_from_tag'),
941 + array(&$obj, 'strip_attribute_from_tag'),
1206 942 $content
1207 943 );
1208 944 endforeach;
1209 945 return $content;
@@ -1229,235 +965,76 @@
1229 965 * match the latest revision, or a previously syndicated post that is
1230 966 * still up-to-date.
1231 967 *
1232 968 * @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 969 * 0 = post already syndicated; no update needed
1235 970 * 1 = post already syndicated, but needs to be updated to latest
1236 971 * 2 = post has not yet been syndicated; needs to be created
1237 972 */
1238 - function freshness ($format = 'number') {
973 + function freshness () {
974 + global $wpdb;
1239 975
1240 976 if ($this->filtered()) : // This should never happen.
1241 - FeedWordPressDiagnostic::critical_bug('SyndicatedPost', $this, __LINE__, __FILE__);
977 + FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
1242 978 endif;
979 +
980 + if (is_null($this->_freshness)) :
981 + $guid = $wpdb->escape($this->guid());
1243 982
1244 - if (is_null($this->_freshness)) : // Not yet checked and cached.
1245 - $guid = $this->post['guid'];
1246 - $eguid = esc_sql($this->post['guid']);
983 + $result = $wpdb->get_row("
984 + SELECT id, guid, post_modified_gmt
985 + FROM $wpdb->posts WHERE guid='$guid'
986 + ");
1247 987
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;
988 + if (!$result) :
1266 989 $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.
990 + else:
991 + $stored_update_hashes = get_post_custom_values('syndication_item_hash', $result->id);
992 + if (count($stored_update_hashes) > 0) :
993 + $stored_update_hash = $stored_update_hashes[0];
994 + $update_hash_changed = ($stored_update_hash != $this->update_hash());
1310 995 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;
996 + $update_hash_changed = false;
1335 997 endif;
1336 998
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));
999 + preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref);
1343 1000
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);
1001 + $last_rev_ts = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]);
1002 + $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
1003 +
1004 + $frozen_values = get_post_custom_values('_syndication_freeze_updates', $result->id);
1005 + $frozen_post = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]);
1006 + $frozen_feed = ('yes' == $this->link->setting('freeze updates', 'freeze_updates', NULL));
1352 1007
1008 + // Check timestamps...
1009 + $updated = (
1010 + !is_null($updated_ts)
1011 + and ($updated_ts > $last_rev_ts)
1012 + );
1013 +
1014 +
1015 + // Or the hash...
1016 + $updated = ($updated or $update_hash_changed);
1017 +
1018 + // But only if the post is not frozen.
1019 + $updated = (
1020 + $updated
1021 + and !$frozen_post
1022 + and !$frozen_feed
1023 + );
1024 +
1353 1025 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 - );
1026 + $this->_freshness = 1; // Updated content
1027 + $this->_wp_id = $result->id;
1371 1028 else :
1372 - FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is a duplicate of an existing post.');
1373 1029 $this->_freshness = 0; // Same old, same old
1374 - $this->_wp_id = $old_post->ID;
1030 + $this->_wp_id = $result->id;
1375 1031 endif;
1376 1032 endif;
1377 1033 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 );
1034 + return $this->_freshness;
1415 1035 }
1416 1036
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 1037 #################################################
1461 1038 #### INTERNAL STORAGE AND MANAGEMENT METHODS ####
1462 1039 #################################################
1463 1040
@@ -1462,11 +1039,11 @@
1462 1039 #################################################
1463 1040
1464 1041 function wp_id () {
1465 1042 if ($this->filtered()) : // This should never happen.
1466 - FeedWordPressDiagnostic::critical_bug('SyndicatedPost', $this, __LINE__, __FILE__);
1043 + FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
1467 1044 endif;
1468 -
1045 +
1469 1046 if (is_null($this->_wp_id) and is_null($this->_freshness)) :
1470 1047 $fresh = $this->freshness(); // sets WP DB id in the process
1471 1048 endif;
1472 1049 return $this->_wp_id;
@@ -1471,181 +1048,59 @@
1471 1048 endif;
1472 1049 return $this->_wp_id;
1473 1050 }
1474 1051
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 - );
1052 + function store () {
1053 + global $wpdb;
1495 1054
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;
1055 + if ($this->filtered()) : // This should never happen.
1056 + FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
1499 1057 endif;
1500 - } /* SyndicatedPost::secure_author_id() */
1058 +
1059 + $freshness = $this->freshness();
1060 + if ($freshness > 0) :
1061 + # -- Look up, or create, numeric ID for author
1062 + $this->post['post_author'] = $this->author_id (
1063 + FeedWordPress::on_unfamiliar('author', $this->post['named']['unfamiliar']['author'])
1064 + );
1501 1065
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);
1066 + if (is_null($this->post['post_author'])) :
1067 + $this->post = NULL;
1068 + endif;
1069 + endif;
1070 +
1071 + if (!$this->filtered() and $freshness > 0) :
1072 + # -- Look up, or create, numeric ID for categories
1073 + list($pcats, $ptags) = $this->category_ids (
1074 + $this->post['named']['category'],
1075 + FeedWordPress::on_unfamiliar('category', $this->post['named']['unfamiliar']['category']),
1076 + /*tags_too=*/ true
1077 + );
1524 1078
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'];
1079 + $this->post['post_category'] = $pcats;
1080 + $this->post['tags_input'] = array_merge($this->post['tags_input'], $ptags);
1530 1081
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,
1082 + if (is_null($this->post['post_category'])) :
1083 + // filter mode on, no matching categories; drop the post
1084 + $this->post = NULL;
1085 + else :
1086 + // filter mode off or at least one match; now add on the feed and global presets
1087 + $this->post['post_category'] = array_merge (
1088 + $this->post['post_category'],
1089 + $this->category_ids (
1090 + $this->post['named']['preset/category'],
1091 + 'default'
1560 1092 )
1561 1093 );
1562 1094
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;
1095 + if (count($this->post['post_category']) < 1) :
1096 + $this->post['post_category'][] = 1; // Default to category 1 ("Uncategorized" / "General") if nothing else
1575 1097 endif;
1576 1098 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 1099 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 -
1100 +
1101 + if (!$this->filtered() and $freshness > 0) :
1102 + unset($this->post['named']);
1648 1103 $this->post = apply_filters('syndicated_post', $this->post, $this);
1649 1104
1650 1105 // Allow for feed-specific syndicated_post filters.
1651 1106 $this->post = apply_filters(
@@ -1653,9 +1108,9 @@
1653 1108 $this->post,
1654 1109 $this
1655 1110 );
1656 1111 endif;
1657 -
1112 +
1658 1113 // Hook in early to make sure these get inserted if at all possible
1659 1114 add_action(
1660 1115 /*hook=*/ 'transition_post_status',
1661 1116 /*callback=*/ array($this, 'add_rss_meta'),
@@ -1662,301 +1117,222 @@
1662 1117 /*priority=*/ -10000, /* very early */
1663 1118 /*arguments=*/ 3
1664 1119 );
1665 1120
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;
1121 + if (!$this->filtered() and $freshness == 2) :
1122 + // The item has not yet been added. So let's add it.
1123 + $this->insert_new();
1124 + do_action('post_syndicated_item', $this->wp_id(), $this);
1672 1125
1673 - $this->insert_post(/*update=*/ $this->fresh_content_is_update(), $this->freshness());
1126 + $ret = 'new';
1127 + elseif (!$this->filtered() and $freshness == 1) :
1128 + $this->post['ID'] = $this->wp_id();
1129 + $this->update_existing();
1130 + do_action('update_syndicated_item', $this->wp_id(), $this);
1674 1131
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');
1132 + $ret = 'updated';
1133 + else :
1134 + $ret = false;
1681 1135 endif;
1682 1136
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 1137 // Remove add_rss_meta hook
1702 1138 remove_action(
1703 1139 /*hook=*/ 'transition_post_status',
1704 - /*callback=*/ array($this, 'add_rss_meta'),
1705 - /*priority=*/ -10000, /* very early */
1706 - /*arguments=*/ 3
1140 + /*callback=*/ array($this, 'add_rss_meta')
1707 1141 );
1708 1142
1709 1143 return $ret;
1710 1144 } /* function SyndicatedPost::store () */
1145 +
1146 + function insert_new () {
1147 + global $wpdb, $wp_db_version;
1711 1148
1712 - function insert_post ($update = false, $freshness = 2) {
1713 1149 $dbpost = $this->normalize_post(/*new=*/ true);
1150 + if (!is_null($dbpost)) :
1151 + if ($this->use_api('wp_insert_post')) :
1152 + $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
1153 +
1154 + // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
1155 + add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1156 +
1157 + // Kludge to prevent kses filters from stripping the
1158 + // content of posts when updating without a logged in
1159 + // user who has `unfiltered_html` capability.
1160 + add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1161 +
1162 + $this->_wp_id = wp_insert_post($dbpost);
1163 +
1164 + // Turn off ridiculous fucking kludges #1 and #2
1165 + remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1166 + remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1167 +
1168 + $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
1169 +
1170 + // Unfortunately, as of WordPress 2.3, wp_insert_post()
1171 + // *still* offers no way to use a guid of your choice,
1172 + // and munges your post modified timestamp, too.
1173 + $result = $wpdb->query("
1174 + UPDATE $wpdb->posts
1175 + SET
1176 + guid='{$dbpost['guid']}',
1177 + post_modified='{$dbpost['post_modified']}',
1178 + post_modified_gmt='{$dbpost['post_modified_gmt']}'
1179 + WHERE ID='{$this->_wp_id}'
1180 + ");
1181 + else :
1182 + # The right way to do this is the above. But, alas,
1183 + # in earlier versions of WordPress, wp_insert_post has
1184 + # too much behavior (mainly related to pings) that can't
1185 + # be overridden. In WordPress 1.5, it's enough of a
1186 + # resource hog to make PHP segfault after inserting
1187 + # 50-100 posts. This can get pretty annoying, especially
1188 + # if you are trying to update your feeds for the first
1189 + # time.
1190 +
1191 + $result = $wpdb->query("
1192 + INSERT INTO $wpdb->posts
1193 + SET
1194 + guid = '{$dbpost['guid']}',
1195 + post_author = '{$dbpost['post_author']}',
1196 + post_date = '{$dbpost['post_date']}',
1197 + post_date_gmt = '{$dbpost['post_date_gmt']}',
1198 + post_content = '{$dbpost['post_content']}',"
1199 + .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
1200 + post_title = '{$dbpost['post_title']}',
1201 + post_name = '{$dbpost['post_name']}',
1202 + post_modified = '{$dbpost['post_modified']}',
1203 + post_modified_gmt = '{$dbpost['post_modified_gmt']}',
1204 + comment_status = '{$dbpost['comment_status']}',
1205 + ping_status = '{$dbpost['ping_status']}',
1206 + post_status = '{$dbpost['post_status']}'
1207 + ");
1208 + $this->_wp_id = $wpdb->insert_id;
1209 +
1210 + $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
1211 +
1212 + // WordPress 1.5.x - 2.0.x
1213 + wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
1214 +
1215 + // Since we are not going through official channels, we need to
1216 + // manually tell WordPress that we've published a new post.
1217 + // We need to make sure to do this in order for FeedWordPress
1218 + // to play well with the staticize-reloaded plugin (something
1219 + // that a large aggregator website is going to *want* to be
1220 + // able to use).
1221 + do_action('publish_post', $this->_wp_id);
1222 + endif;
1223 + endif;
1224 + } /* SyndicatedPost::insert_new() */
1714 1225
1715 - $ret = null;
1226 + function update_existing () {
1227 + global $wpdb;
1716 1228
1717 - if ( !is_null($dbpost)) :
1718 - $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
1229 + // Why the fuck doesn't wp_insert_post already do this?
1230 + $dbpost = $this->normalize_post(/*new=*/ false);
1231 + if (!is_null($dbpost)) :
1232 + if ($this->use_api('wp_insert_post')) :
1233 + $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
1234 +
1235 + // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
1236 + add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1237 +
1238 + // Kludge to prevent kses filters from stripping the
1239 + // content of posts when updating without a logged in
1240 + // user who has `unfiltered_html` capability.
1241 + add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1719 1242
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;
1243 + // Don't munge status fields that the user may have reset manually
1244 + if (function_exists('get_post_field')) :
1245 + $doNotMunge = array('post_status', 'comment_status', 'ping_status');
1246 + foreach ($doNotMunge as $field) :
1247 + $dbpost[$field] = get_post_field($field, $this->wp_id());
1248 + endforeach;
1732 1249 endif;
1733 - endforeach;
1734 1250
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;
1251 + $this->_wp_id = wp_insert_post($dbpost);
1252 +
1253 + // Turn off ridiculous fucking kludges #1 and #2
1254 + remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
1255 + remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
1256 +
1257 + $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
1258 +
1259 + // Unfortunately, as of WordPress 2.3, wp_insert_post()
1260 + // munges your post modified timestamp.
1261 + $result = $wpdb->query("
1262 + UPDATE $wpdb->posts
1263 + SET
1264 + post_modified='{$dbpost['post_modified']}',
1265 + post_modified_gmt='{$dbpost['post_modified_gmt']}'
1266 + WHERE ID='{$this->_wp_id}'
1267 + ");
1268 + else :
1269 +
1270 + $result = $wpdb->query("
1271 + UPDATE $wpdb->posts
1272 + SET
1273 + post_author = '{$dbpost['post_author']}',
1274 + post_content = '{$dbpost['post_content']}',"
1275 + .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
1276 + post_title = '{$dbpost['post_title']}',
1277 + post_name = '{$dbpost['post_name']}',
1278 + post_modified = '{$dbpost['post_modified']}',
1279 + post_modified_gmt = '{$dbpost['post_modified_gmt']}'
1280 + WHERE guid='{$dbpost['guid']}'
1281 + ");
1282 +
1283 + // WordPress 2.1.x and up
1284 + if (function_exists('wp_set_post_categories')) :
1285 + wp_set_post_categories($this->wp_id(), $this->post['post_category']);
1286 + // WordPress 1.5.x - 2.0.x
1287 + elseif (function_exists('wp_set_post_cats')) :
1288 + wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
1289 + // This should never happen.
1290 + else :
1291 + FeedWordPress::critical_bug(__CLASS__.'::'.__FUNCTION.'(): no post categorizing function', array("dbpost" => $dbpost, "this" => $this), __LINE__);
1810 1292 endif;
1293 +
1294 + // Since we are not going through official channels, we need to
1295 + // manually tell WordPress that we've published a new post.
1296 + // We need to make sure to do this in order for FeedWordPress
1297 + // to play well with the staticize-reloaded plugin (something
1298 + // that a large aggregator website is going to *want* to be
1299 + // able to use).
1300 + do_action('edit_post', $this->post['ID']);
1811 1301 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 1302 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 1303 } /* SyndicatedPost::update_existing() */
1859 1304
1860 1305 /**
1861 1306 * SyndicatedPost::normalize_post()
1862 1307 *
1863 - * @param bool $new If true, this post is to be inserted anew. If false, it is an update of an existing post. (Unused)
1308 + * @param bool $new If true, this post is to be inserted anew. If false, it is an update of an existing post.
1864 1309 * @return array A normalized representation of the post ready to be inserted into the database or sent to the WordPress API functions
1865 1310 */
1866 - function normalize_post( $new = true ) {
1867 - $out = $this->post;
1311 + function normalize_post ($new = true) {
1312 + global $wpdb;
1868 1313
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;
1314 + $out = array();
1882 1315
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) :
1316 + // Why the fuck doesn't wp_insert_post already do this?
1317 + foreach ($this->post as $key => $value) :
1904 1318 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 -
1319 + $out[$key] = $wpdb->escape($value);
1916 1320 else :
1917 1321 $out[$key] = $value;
1918 1322 endif;
1919 -
1920 1323 endforeach;
1921 1324
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 -
1325 + if (strlen($out['post_title'].$out['post_content'].$out['post_excerpt']) == 0) :
1326 + // FIXME: Option for filtering out empty posts
1958 1327 endif;
1328 + if (strlen($out['post_title'])==0) :
1329 + $offset = (int) get_option('gmt_offset') * 60 * 60;
1330 + $out['post_title'] =
1331 + $this->post['meta']['syndication_source']
1332 + .' '.gmdate('Y-m-d H:i:s', $this->published() + $offset);
1333 + // FIXME: Option for what to fill a blank title with...
1334 + endif;
1959 1335
1960 1336 return $out;
1961 1337 }
1962 1338
@@ -1965,57 +1341,35 @@
1965 1341 *
1966 1342 * @param array $dbpost An array representing the post we attempted to insert or update
1967 1343 * @param mixed $ns A string or array representing the namespace (class, method) whence this method was called.
1968 1344 */
1969 - function validate_post_id ($dbpost, $is_update, $ns) {
1345 + function validate_post_id ($dbpost, $ns) {
1970 1346 if (is_array($ns)) : $ns = implode('::', $ns);
1971 1347 else : $ns = (string) $ns; endif;
1972 -
1348 +
1973 1349 // 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,
1350 + if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
1351 + FeedWordPress::critical_bug(
1352 + /*name=*/ $ns.'::_wp_id',
2000 1353 /*var =*/ array(
2001 1354 "\$this->_wp_id" => $this->_wp_id,
2002 1355 "\$dbpost" => $dbpost,
1356 + "\$this" => $this
2003 1357 ),
2004 - /*line # =*/ __LINE__, /*filename=*/ __FILE__
1358 + /*line # =*/ __LINE__
2005 1359 );
2006 1360 endif;
2007 1361 } /* SyndicatedPost::validate_post_id() */
2008 -
1362 +
2009 1363 /**
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.
1364 + * SyndicatedPost::fix_revision_meta() - Fixes the way WP 2.6+ fucks up
1365 + * meta-data (authorship, etc.) when storing revisions of an updated
1366 + * syndicated post.
2013 1367 *
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.
1368 + * In their infinite wisdom, the WordPress coders have made it completely
1369 + * impossible for a plugin that uses wp_insert_post() to set certain
1370 + * meta-data (such as the author) when you store an old revision of an
1371 + * updated post. Instead, it uses the WordPress defaults (= currently
2018 1372 * active user ID if the process is running with a user logged in, or
2019 1373 * = #0 if there is no user logged in). This results in bogus authorship
2020 1374 * data for revisions that are syndicated from off the feed, unless we
2021 1375 * use a ridiculous kludge like this to end-run the munging of meta-data
@@ -2022,209 +1376,99 @@
2022 1376 * by _wp_put_post_revision.
2023 1377 *
2024 1378 * @param int $revision_id The revision ID to fix up meta-data
2025 1379 */
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 -
1380 + function fix_revision_meta ($revision_id) {
1381 + global $wpdb;
1382 +
1383 + $post_author = (int) $this->post['post_author'];
1384 +
2029 1385 $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 -
1386 + $wpdb->query("
1387 + UPDATE $wpdb->posts
1388 + SET post_author={$this->post['post_author']}
1389 + WHERE post_type = 'revision' AND ID='$revision_id'
1390 + ");
2040 1391 } /* SyndicatedPost::fix_revision_meta () */
2041 1392
2042 1393 /**
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.
1394 + * SyndicatedPost::avoid_kses_munge() -- If FeedWordPress is processing
1395 + * an automatic update, that generally means that wp_insert_post() is
1396 + * being called under the user credentials of whoever is viewing the
1397 + * blog at the time -- usually meaning no user at all. But if WordPress
1398 + * gets a wp_insert_post() when current_user_can('unfiltered_html') is
1399 + * false, it will run the content of the post through a kses function
1400 + * that strips out lots of HTML tags -- notably <object> and some others.
1401 + * This causes problems for syndicating (for example) feeds that contain
1402 + * YouTube videos. It also produces an unexpected asymmetry between
1403 + * automatically-initiated updates and updates initiated manually from
1404 + * the WordPress Dashboard (which are usually initiated under the
1405 + * credentials of a logged-in admin, and so don't get run through the
1406 + * kses function). So, to avoid the whole mess, what we do here is
1407 + * just forcibly disable the kses munging for a single syndicated post,
1408 + * by restoring the contents of the `post_content` field.
2051 1409 *
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.
1410 + * @param string $content The content of the post, after other filters have gotten to it
1411 + * @return string The original content of the post, before other filters had a chance to munge it.
2058 1412 */
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) {
1413 + function avoid_kses_munge ($content) {
1414 + global $wpdb;
1415 + return $wpdb->escape($this->post['post_content']);
1416 + }
1417 +
1418 + // SyndicatedPost::add_rss_meta: adds interesting meta-data to each entry
1419 + // using the space for custom keys. The set of keys and values to add is
1420 + // specified by the keys and values of $post['meta']. This is used to
1421 + // store anything that the WordPress user might want to access from a
1422 + // template concerning the post's original source that isn't provided
1423 + // for by standard WP meta-data (i.e., any interesting data about the
1424 + // syndicated post other than author, title, timestamp, categories, and
1425 + // guid). It's also used to hook into WordPress's support for
1426 + // enclosures.
1427 + function add_rss_meta ($new_status, $old_status, $post) {
2105 1428 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 () */
1429 + if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
1430 + $postId = $this->wp_id();
1431 +
1432 + // Aggregated posts should NOT send out pingbacks.
1433 + // WordPress 2.1-2.2 claim you can tell them not to
1434 + // using $post_pingback, but they don't listen, so we
1435 + // make sure here.
1436 + $result = $wpdb->query("
1437 + DELETE FROM $wpdb->postmeta
1438 + WHERE post_id='$postId' AND meta_key='_pingme'
1439 + ");
2113 1440
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'])).'}');
1441 + foreach ( $this->post['meta'] as $key => $values ) :
2133 1442
2134 - if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
2135 - $postId = $post->ID;
1443 + $eKey = $wpdb->escape($key);
2136 1444
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.
1445 + // If this is an update, clear out the old
1446 + // values to avoid duplication.
2141 1447 $result = $wpdb->query("
2142 1448 DELETE FROM $wpdb->postmeta
2143 - WHERE post_id='$postId' AND meta_key='_pingme'
1449 + WHERE post_id='$postId' AND meta_key='$eKey'
2144 1450 ");
2145 1451
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;
1452 + // Allow for either a single value or an array
1453 + if (!is_array($values)) $values = array($values);
1454 + foreach ( $values as $value ) :
1455 + add_post_meta($postId, $key, $value, /*unique=*/ false);
2162 1456 endforeach;
2163 -
2164 - if ( $result === false)
2165 - {
2166 - error_log( "delete query failed: " . $wpdb->last_error );
2167 - }
2168 - endif;
1457 + endforeach;
2169 1458 endif;
2170 1459 } /* SyndicatedPost::add_rss_meta () */
2171 1460
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 - */
1461 + // SyndicatedPost::author_id (): get the ID for an author name from
1462 + // the feed. Create the author if necessary.
2181 1463 function author_id ($unfamiliar_author = 'create') {
2182 1464 global $wpdb;
2183 1465
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 -
1466 + $a = $this->author();
1467 + $author = $a['name'];
2218 1468 $email = (isset($a['email']) ? $a['email'] : NULL);
2219 - $authorUrl = (isset($a['uri']) ? $a['uri'] : NULL);
1469 + $url = (isset($a['uri']) ? $a['uri'] : NULL);
2220 1470
2221 -
2222 - $hostUrl = $this->link->homepage();
2223 - if (is_null($hostUrl) or (strlen($hostUrl) < 0)) :
2224 - $hostUrl = $this->link->uri();
2225 - endif;
2226 -
2227 1471 $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
2228 1472 if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
2229 1473 $test_email = $email;
2230 1474 else :
@@ -2232,43 +1476,21 @@
2232 1476 endif;
2233 1477
2234 1478 // Never can be too careful...
2235 1479 $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 1480 $login = apply_filters('pre_user_login', $login);
2257 1481
2258 1482 $nice_author = sanitize_title($author);
2259 1483 $nice_author = apply_filters('pre_user_nicename', $nice_author);
2260 1484
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);
1485 + $reg_author = $wpdb->escape(preg_quote($author));
1486 + $author = $wpdb->escape($author);
1487 + $email = $wpdb->escape($email);
1488 + $test_email = $wpdb->escape($test_email);
1489 + $url = $wpdb->escape($url);
2266 1490
2267 1491 // 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))])) :
1492 + if (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) :
2271 1493 $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))];
2272 1494 else :
2273 1495 $author_rule = NULL;
2274 1496 endif;
@@ -2273,17 +1495,15 @@
2273 1495 $author_rule = NULL;
2274 1496 endif;
2275 1497
2276 1498 // 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)) :
1499 + if (is_numeric($author_rule) and get_userdata((int) $author_rule)) :
2280 1500 $id = (int) $author_rule;
2281 1501
2282 1502 // User name is filtered out
2283 1503 elseif ('filter' == $author_rule) :
2284 1504 $id = NULL;
2285 -
1505 +
2286 1506 else :
2287 1507 // Check the database for an existing author record that might fit
2288 1508
2289 1509 // First try the user core data table.
@@ -2288,15 +1508,17 @@
2288 1508
2289 1509 // First try the user core data table.
2290 1510 $id = $wpdb->get_var(
2291 1511 "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 -
1512 + WHERE
1513 + TRIM(LCASE(user_login)) = TRIM(LCASE('$login'))
1514 + OR (
1515 + LENGTH(TRIM(LCASE(user_email))) > 0
1516 + AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
1517 + )
1518 + OR TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author'))
1519 + ");
1520 +
2299 1521 // If that fails, look for aliases in the user meta data table
2300 1522 if (is_null($id)) :
2301 1523 $id = $wpdb->get_var(
2302 1524 "SELECT user_id FROM $wpdb->usermeta
@@ -2318,13 +1540,13 @@
2318 1540 if (is_null($id)) :
2319 1541 if ($unfamiliar_author === 'create') :
2320 1542 $userdata = array();
2321 1543
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.)
1544 + // WordPress 3 is going to pitch a fit if we attempt to register
1545 + // more than one user account with an empty e-mail address, so we
1546 + // need *something* here. Ugh.
2325 1547 if (strlen($email) == 0 or FeedWordPress::is_null_email($email)) :
2326 - $url = parse_url($hostUrl);
1548 + $url = parse_url($this->feed->channel['link']);
2327 1549 $email = $nice_author.'@'.$url['host'];
2328 1550 endif;
2329 1551
2330 1552 #-- user table data
@@ -2332,82 +1554,15 @@
2332 1554 $userdata['user_login'] = $login;
2333 1555 $userdata['user_nicename'] = $nice_author;
2334 1556 $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
2335 1557 $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 -
1558 + $userdata['user_url'] = $url;
2343 1559 $userdata['display_name'] = $author;
2344 - $userdata['role'] = 'contributor';
2345 1560
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 ) ) :
1561 + $id = wp_insert_user($userdata);
1562 + elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
2408 1563 $id = (int) $unfamiliar_author;
2409 - elseif ( $unfamiliar_author === 'default' ) :
1564 + elseif ($unfamiliar_author === 'default') :
2410 1565 $id = 1;
2411 1566 endif;
2412 1567 endif;
2413 1568 endif;
@@ -2413,30 +1568,127 @@
2413 1568 endif;
2414 1569
2415 1570 if ($id) :
2416 1571 $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id;
1572 + endif;
1573 + return $id;
1574 + } // function SyndicatedPost::author_id ()
2417 1575
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');
1576 + // look up (and create) category ids from a list of categories
1577 + function category_ids ($cats, $unfamiliar_category = 'create', $tags_too = false) {
1578 + global $wpdb;
1579 +
1580 + // We need to normalize whitespace because (1) trailing
1581 + // whitespace can cause PHP and MySQL not to see eye to eye on
1582 + // VARCHAR comparisons for some versions of MySQL (cf.
1583 + // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
1584 + // because I doubt most people want to make a semantic
1585 + // distinction between 'Computers' and 'Computers '
1586 + $cats = array_map('trim', $cats);
1587 +
1588 + $tags = array();
1589 +
1590 + $cat_ids = array ();
1591 + foreach ($cats as $cat_name) :
1592 + if (preg_match('/^{#([0-9]+)}$/', $cat_name, $backref)) :
1593 + $cat_id = (int) $backref[1];
1594 + if (function_exists('is_term') and is_term($cat_id, 'category')) :
1595 + $cat_ids[] = $cat_id;
1596 + elseif (get_category($cat_id)) :
1597 + $cat_ids[] = $cat_id;
1598 + endif;
1599 + elseif (strlen($cat_name) > 0) :
1600 + $esc = $wpdb->escape($cat_name);
1601 + $resc = $wpdb->escape(preg_quote($cat_name));
1602 +
1603 + // WordPress 2.3+
1604 + if (function_exists('is_term')) :
1605 + $cat_id = is_term($cat_name, 'category');
1606 + if ($cat_id) :
1607 + $cat_ids[] = $cat_id['term_id'];
1608 + // There must be a better way to do this...
1609 + elseif ($results = $wpdb->get_results(
1610 + "SELECT term_id
1611 + FROM $wpdb->term_taxonomy
1612 + WHERE
1613 + LOWER(description) RLIKE
1614 + CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)')"
1615 + )) :
1616 + foreach ($results AS $term) :
1617 + $cat_ids[] = (int) $term->term_id;
1618 + endforeach;
1619 + elseif ('tag'==$unfamiliar_category) :
1620 + $tags[] = $cat_name;
1621 + elseif ('create'===$unfamiliar_category) :
1622 + $term = wp_insert_term($cat_name, 'category');
1623 + if (is_wp_error($term)) :
1624 + FeedWordPress::noncritical_bug('term insertion problem', array('cat_name' => $cat_name, 'term' => $term, 'this' => $this), __LINE__);
1625 + else :
1626 + $cat_ids[] = $term['term_id'];
1627 + endif;
1628 + endif;
1629 +
1630 + // WordPress 1.5.x - 2.2.x
1631 + else :
1632 + $results = $wpdb->get_results(
1633 + "SELECT cat_ID
1634 + FROM $wpdb->categories
1635 + WHERE
1636 + (LOWER(cat_name) = LOWER('$esc'))
1637 + OR (LOWER(category_description)
1638 + RLIKE CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)'))
1639 + ");
1640 + if ($results) :
1641 + foreach ($results as $term) :
1642 + $cat_ids[] = (int) $term->cat_ID;
1643 + endforeach;
1644 + elseif ('create'===$unfamiliar_category) :
1645 + if (function_exists('wp_insert_category')) :
1646 + $cat_id = wp_insert_category(array('cat_name' => $esc));
1647 + // And into the database we go.
1648 + else :
1649 + $nice_kitty = sanitize_title($cat_name);
1650 + $wpdb->query(sprintf("
1651 + INSERT INTO $wpdb->categories
1652 + SET
1653 + cat_name='%s',
1654 + category_nicename='%s'
1655 + ", $esc, $nice_kitty
1656 + ));
1657 + $cat_id = $wpdb->insert_id;
1658 + endif;
1659 + $cat_ids[] = $cat_id;
1660 + endif;
1661 + endif;
2424 1662 endif;
1663 + endforeach;
1664 +
1665 + if ((count($cat_ids) == 0) and ($unfamiliar_category === 'filter')) :
1666 + $cat_ids = NULL; // Drop the post
1667 + else :
1668 + $cat_ids = array_unique($cat_ids);
2425 1669 endif;
2426 - return $id;
2427 - } /* function SyndicatedPost::author_id () */
1670 +
1671 + if ($tags_too) : $ret = array($cat_ids, $tags);
1672 + else : $ret = $cat_ids;
1673 + endif;
2428 1674
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 () */
1675 + return $ret;
1676 + } // function SyndicatedPost::category_ids ()
2441 1677
1678 + function use_api ($tag) {
1679 + global $wp_db_version;
1680 + switch ($tag) :
1681 + case 'wp_insert_post':
1682 + // Before 2.2, wp_insert_post does too much of the wrong stuff to use it
1683 + // In 1.5 it was such a resource hog it would make PHP segfault on big updates
1684 + $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_21);
1685 + break;
1686 + case 'post_status_pending':
1687 + $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_23);
1688 + break;
1689 + endswitch;
1690 + return $ret;
1691 + } // function SyndicatedPost::use_api ()
1692 +
2442 1693 } /* class SyndicatedPost */
1694 +