FeedWordPress - Version 2016.0420

Version Description

  • WORDPRESS COMPATIBILITY: Tested with new versions of WordPress up to 4.5.

  • FILTERS AND ADD-ONS: Allow filters and add-ons to filter terms and taxonomy (categories, tags, custom taxonomies, etc.) more thoroughly and more fine-grainedly using syndicated_post_terms_match, syndicated_post_terms_match_{taxonomy}, syndicated_post_terms_unfamiliar, syndicated_post_terms_mapping, syndicated_item_feed_terms, and syndicated_item_preset_terms filters.

  • FILTERS AND ADD-ONS: Globals $fwp_channel and $fwp_feedmeta REMOVED. These global variables, originally introduced to allow filters access to information about the source feed in syndicated_item filters were deprecated 6+ years ago. If you have any filters or add-ons which still depend on these global variables, you've been using obsolete techniques and you should see about fixing them to access data about the source feed using the SyndicatedPost::link element instead. For documentation, see the FeedWordPress documentation wiki at http://feedwordpress.radgeek.com/wiki/syndicatedpost and http://feedwordpress.radgeek.com/wiki/syndicatedlink.

  • BUGFIX: Syndication > Diagnostics HTTP diagnostic test widget was broken due to a dumb error on my part. Now fixed.

  • SMALL CODING CHANGES: Lots of small changes to code organization, incorporation of some PHP 5.x coding conventions, etc.

Download this release

Release Info

Developer radgeek
Plugin Icon wp plugin FeedWordPress
Version 2016.0420
Comparing to
See all releases

Code changes from version 2015.0514 to 2016.0420

diagnostics-page.php CHANGED
@@ -356,23 +356,36 @@ function clone_http_test_args_keyvalue_prototype () {
356
  <td><a href="#http-test-args" onclick="return clone_http_test_args_keyvalue_prototype();">+ Add</a></td>
357
  </tr>
358
  </table>
 
 
359
 
 
 
 
 
 
 
 
360
  <?php if (isset($page->test_html['http_test'])) : ?>
 
 
 
 
361
  <div style="position: relative">
362
  <div style="width: 100%; overflow: scroll; background-color: #eed">
363
  <pre style="white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -o-pre-wrap;"><?php print $page->test_html['http_test']; ?></pre>
364
  </div>
365
  </div>
366
- <?php endif; ?>
367
  </td>
368
  </tr>
 
369
  </table>
370
 
371
  <?php
372
  } /* FeedWordPressDiagnosticsPage::tests_box () */
373
 
374
- var $test_html;
375
- static function do_http_test ($post) {
376
  if (isset($post['http_test_url']) and isset($post['http_test_method'])) :
377
  $url = $post['http_test_url'];
378
 
@@ -410,6 +423,7 @@ function clone_http_test_args_keyvalue_prototype () {
410
  break;
411
  endswitch;
412
 
 
413
  $this->test_html['http_test'] = esc_html(MyPHP::val($out));
414
  endif;
415
  } /* FeedWordPressDiagnosticsPage::do_http_test () */
356
  <td><a href="#http-test-args" onclick="return clone_http_test_args_keyvalue_prototype();">+ Add</a></td>
357
  </tr>
358
  </table>
359
+ </td>
360
+ </tr>
361
 
362
+ <tr>
363
+ <th>XPath:</th>
364
+ <td><div><input type="text" name="http_test_xpath" value="" placeholder="xpath-like query" /></div>
365
+ <div><p>Leave blank to test HTTP, fill in to test a query.</p></div>
366
+ </td>
367
+ </tr>
368
+
369
  <?php if (isset($page->test_html['http_test'])) : ?>
370
+ <tr>
371
+ <th scope="row">RESULTS:</th>
372
+ <td>
373
+ <div>URL: <code><?php print esc_html($page->test_html['url']); ?></code></div>
374
  <div style="position: relative">
375
  <div style="width: 100%; overflow: scroll; background-color: #eed">
376
  <pre style="white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -o-pre-wrap;"><?php print $page->test_html['http_test']; ?></pre>
377
  </div>
378
  </div>
 
379
  </td>
380
  </tr>
381
+ <?php endif; ?>
382
  </table>
383
 
384
  <?php
385
  } /* FeedWordPressDiagnosticsPage::tests_box () */
386
 
387
+ private $test_html;
388
+ public function do_http_test ($post) {
389
  if (isset($post['http_test_url']) and isset($post['http_test_method'])) :
390
  $url = $post['http_test_url'];
391
 
423
  break;
424
  endswitch;
425
 
426
+ $this->test_html['url'] = $url;
427
  $this->test_html['http_test'] = esc_html(MyPHP::val($out));
428
  endif;
429
  } /* FeedWordPressDiagnosticsPage::do_http_test () */
feedwordpress-elements.js CHANGED
@@ -629,7 +629,7 @@ function fwp_xpathtest_ok (response, result_id, destination) {
629
 
630
  if (response.results instanceof Array) {
631
  for (var i = 0; i < response.results.length; i++) {
632
- resultsHtml += '<li>result['+i.toString()+'] = <code>'+response.results[i]+'</code></li>';
633
  }
634
  } else {
635
  resultsHtml += '<li>result = <code>' + response.results + '</code></li>';
629
 
630
  if (response.results instanceof Array) {
631
  for (var i = 0; i < response.results.length; i++) {
632
+ resultsHtml += '<li>result['+(i+1).toString()+'] = <code>'+response.results[i]+'</code></li>';
633
  }
634
  } else {
635
  resultsHtml += '<li>result = <code>' + response.results + '</code></li>';
feedwordpress.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: FeedWordPress
4
  Plugin URI: http://feedwordpress.radgeek.com/
5
  Description: simple and flexible Atom/RSS syndication for WordPress
6
- Version: 2015.0514
7
  Author: Charles Johnson
8
  Author URI: http://radgeek.com/
9
  License: GPL
@@ -11,7 +11,7 @@ License: GPL
11
 
12
  /**
13
  * @package FeedWordPress
14
- * @version 2015.0514
15
  */
16
 
17
  # This uses code derived from:
@@ -32,7 +32,7 @@ License: GPL
32
 
33
  # -- Don't change these unless you know what you're doing...
34
 
35
- define ('FEEDWORDPRESS_VERSION', '2015.0514');
36
  define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'http://radgeek.com/contact');
37
 
38
  if (!defined('FEEDWORDPRESS_BLEG')) :
3
  Plugin Name: FeedWordPress
4
  Plugin URI: http://feedwordpress.radgeek.com/
5
  Description: simple and flexible Atom/RSS syndication for WordPress
6
+ Version: 2016.0420
7
  Author: Charles Johnson
8
  Author URI: http://radgeek.com/
9
  License: GPL
11
 
12
  /**
13
  * @package FeedWordPress
14
+ * @version 2016.0420
15
  */
16
 
17
  # This uses code derived from:
32
 
33
  # -- Don't change these unless you know what you're doing...
34
 
35
+ define ('FEEDWORDPRESS_VERSION', '2016.0420');
36
  define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'http://radgeek.com/contact');
37
 
38
  if (!defined('FEEDWORDPRESS_BLEG')) :
readme.txt CHANGED
@@ -3,8 +3,8 @@ Contributors: Charles Johnson
3
  Donate link: http://feedwordpress.radgeek.com/
4
  Tags: syndication, aggregation, feed, atom, rss
5
  Requires at least: 3.0
6
- Tested up to: 4.2.2
7
- Stable tag: 2015.0514
8
 
9
  FeedWordPress syndicates content from feeds you choose into your WordPress weblog.
10
 
@@ -20,7 +20,7 @@ appears as a series of special posts in your WordPress posts database. If you
20
  syndicate several feeds then you can use WordPress's posts database and
21
  templating engine as the back-end of an aggregation ("planet") website. It was
22
  developed, originally, because I needed a more flexible replacement for
23
- [Planet][] to use at [Feminist Blogs][].
24
 
25
  [Planet]: http://www.planetplanet.org/
26
  [Feminist Blogs]: http://feministblogs.org/
@@ -94,6 +94,33 @@ outs, see the documentation at the [FeedWordPress project homepage][].
94
 
95
  == Changelog ==
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  = 2015.0514 =
98
 
99
  * IMPORTANT SECURITY UPDATE: This version includes two important fixes for
3
  Donate link: http://feedwordpress.radgeek.com/
4
  Tags: syndication, aggregation, feed, atom, rss
5
  Requires at least: 3.0
6
+ Tested up to: 4.5.2
7
+ Stable tag: 2016.0420
8
 
9
  FeedWordPress syndicates content from feeds you choose into your WordPress weblog.
10
 
20
  syndicate several feeds then you can use WordPress's posts database and
21
  templating engine as the back-end of an aggregation ("planet") website. It was
22
  developed, originally, because I needed a more flexible replacement for
23
+ [Planet][] to use at Feminist Blogs, an aggregator site that I used to administer.
24
 
25
  [Planet]: http://www.planetplanet.org/
26
  [Feminist Blogs]: http://feministblogs.org/
94
 
95
  == Changelog ==
96
 
97
+ = 2016.0420 =
98
+
99
+ * WORDPRESS COMPATIBILITY: Tested with new versions of WordPress up to 4.5.
100
+
101
+ * FILTERS AND ADD-ONS: Allow filters and add-ons to filter terms and taxonomy
102
+ (categories, tags, custom taxonomies, etc.) more thoroughly and more
103
+ fine-grainedly using syndicated_post_terms_match, syndicated_post_terms_match_{taxonomy},
104
+ syndicated_post_terms_unfamiliar, syndicated_post_terms_mapping,
105
+ syndicated_item_feed_terms, and syndicated_item_preset_terms filters.
106
+
107
+ * FILTERS AND ADD-ONS: Globals $fwp_channel and $fwp_feedmeta REMOVED.
108
+ These global variables, originally introduced to allow filters access to
109
+ information about the source feed in `syndicated_item` filters were
110
+ deprecated 6+ years ago. If you have any filters or add-ons which still
111
+ depend on these global variables, you've been using obsolete techniques
112
+ and you should see about fixing them to access data about the source feed
113
+ using the SyndicatedPost::link element instead. For documentation, see
114
+ the FeedWordPress documentation wiki at
115
+ <http://feedwordpress.radgeek.com/wiki/syndicatedpost> and
116
+ <http://feedwordpress.radgeek.com/wiki/syndicatedlink>.
117
+
118
+ * BUGFIX: Syndication > Diagnostics HTTP diagnostic test widget was broken due to
119
+ a dumb error on my part. Now fixed.
120
+
121
+ * SMALL CODING CHANGES: Lots of small changes to code organization, incorporation
122
+ of some PHP 5.x coding conventions, etc.
123
+
124
  = 2015.0514 =
125
 
126
  * IMPORTANT SECURITY UPDATE: This version includes two important fixes for
syndicatedpost.class.php CHANGED
@@ -1,6 +1,7 @@
1
  <?php
2
  require_once(dirname(__FILE__).'/feedtime.class.php');
3
  require_once(dirname(__FILE__).'/syndicatedpostterm.class.php');
 
4
 
5
  /**
6
  * class SyndicatedPost: FeedWordPress uses to manage the conversion of
@@ -42,7 +43,7 @@ class SyndicatedPost {
42
  * @param array $item The item syndicated from the feed.
43
  * @param SyndicatedLink $source The feed it was syndicated from.
44
  */
45
- function SyndicatedPost ($item, &$source) {
46
  global $wpdb;
47
 
48
  if ( empty($item) && empty($source) )
@@ -86,21 +87,6 @@ class SyndicatedPost {
86
  // Fucking SimplePie.
87
  $this->xmlns['reverse']['rss'][] = '';
88
 
89
- # These globals were originally an ugly kludge around a bug in
90
- # apply_filters from WordPress 1.5. The bug was fixed in 1.5.1,
91
- # and I sure hope at this point that nobody writing filters for
92
- # FeedWordPress is still relying on them.
93
- #
94
- # Anyway, I hereby declare them DEPRECATED as of 8 February
95
- # 2010. I'll probably remove the globals within 1-2 releases in
96
- # the interests of code hygiene and memory usage. If you
97
- # currently use them in your filters, I advise you switch off to
98
- # accessing the public members SyndicatedPost::feed and
99
- # SyndicatedPost::feedmeta.
100
-
101
- global $fwp_channel, $fwp_feedmeta;
102
- $fwp_channel = $this->feed; $fwp_feedmeta = $this->feedmeta;
103
-
104
  // Trigger global syndicated_item filter.
105
  $changed = apply_filters('syndicated_item', $this->item, $this);
106
  $this->item = $changed;
@@ -265,93 +251,15 @@ class SyndicatedPost {
265
  // Store a hash of the post content for checking whether something needs to be updated
266
  $this->post['meta']['syndication_item_hash'] = $this->update_hash();
267
 
268
- // Categories: start with default categories, if any.
269
- $cats = array();
270
- if ('no' != $this->link->setting('add/category', NULL, 'yes')) :
271
- $fc = get_option("feedwordpress_syndication_cats");
272
- if ($fc) :
273
- $cats = array_merge($cats, explode("\n", $fc));
274
- endif;
275
- endif;
276
-
277
- $fc = $this->link->setting('cats', NULL, array());
278
- if (is_array($fc)) :
279
- $cats = array_merge($cats, $fc);
280
- endif;
281
- $this->preset_terms['category'] = $cats;
282
-
283
- // Now add categories from the post, if we have 'em
284
- $cats = array();
285
- $post_cats = $this->entry->get_categories();
286
- if (is_array($post_cats)) : foreach ($post_cats as $cat) :
287
- $cat_name = $cat->get_term();
288
- if (!$cat_name) : $cat_name = $cat->get_label(); endif;
289
-
290
- if ($this->link->setting('cat_split', NULL, NULL)) :
291
- $pcre = "\007".$this->feedmeta['cat_split']."\007";
292
- $cats = array_merge(
293
- $cats,
294
- preg_split(
295
- $pcre,
296
- $cat_name,
297
- -1 /*=no limit*/,
298
- PREG_SPLIT_NO_EMPTY
299
- )
300
- );
301
- else :
302
- $cats[] = $cat_name;
303
- endif;
304
- endforeach; endif;
305
-
306
- $this->feed_terms['category'] = apply_filters('syndicated_item_categories', $cats, $this);
307
-
308
- // Tags: start with default tags, if any
309
- $tags = array();
310
- if ('no' != $this->link->setting('add/post_tag', NULL, 'yes')) :
311
- $ft = get_option("feedwordpress_syndication_tags", NULL);
312
- $tags = (is_null($ft) ? array() : explode(FEEDWORDPRESS_CAT_SEPARATOR, $ft));
313
- endif;
314
-
315
- $ft = $this->link->setting('tags', NULL, array());
316
- if (is_array($ft)) :
317
- $tags = array_merge($tags, $ft);
318
- endif;
319
- $this->preset_terms['post_tag'] = $tags;
320
-
321
- // Scan post for /a[@rel='tag'] and use as tags if present
322
- $tags = $this->inline_tags();
323
- $this->feed_terms['post_tag'] = apply_filters('syndicated_item_tags', $tags, $this);
324
-
325
- $taxonomies = $this->link->taxonomies();
326
- $feedTerms = $this->link->setting('terms', NULL, array());
327
- $globalTerms = get_option('feedwordpress_syndication_terms', array());
328
-
329
- $specials = array('category' => 'cats', 'post_tag' => 'tags');
330
- foreach ($taxonomies as $tax) :
331
- if (!isset($specials[$tax])) :
332
- $terms = array();
333
-
334
- // See if we should get the globals
335
- if ('no' != $this->link->setting("add/$tax", NULL, 'yes')) :
336
- if (isset($globalTerms[$tax])) :
337
- $terms = $globalTerms[$tax];
338
- endif;
339
- endif;
340
-
341
- // Now merge in the locals
342
- if (isset($feedTerms[$tax])) :
343
- $terms = array_merge($terms, $feedTerms[$tax]);
344
- endif;
345
-
346
- // That's all, folks.
347
- $this->preset_terms[$tax] = $terms;
348
- endif;
349
- endforeach;
350
 
351
  $this->post['post_type'] = apply_filters('syndicated_post_type', $this->link->setting('syndicated post type', 'syndicated_post_type', 'post'), $this);
352
  endif;
353
 
354
- } /* SyndicatedPost::SyndicatedPost() */
355
 
356
  #####################################
357
  #### EXTRACT DATA FROM FEED ITEM ####
@@ -383,97 +291,26 @@ class SyndicatedPost {
383
  * elements or attributes
384
  */
385
  function query ($path) {
386
- $urlHash = array();
387
-
388
- // Allow {url} notation for namespaces. URLs will contain : and /, so...
389
- preg_match_all('/{([^}]+)}/', $path, $match, PREG_SET_ORDER);
390
- foreach ($match as $ref) :
391
- $urlHash[md5($ref[1])] = $ref[1];
392
- endforeach;
393
-
394
- foreach ($urlHash as $hash => $url) :
395
- $path = str_replace('{'.$url.'}', '{#'.$hash.'}', $path);
396
- endforeach;
397
-
398
- $path = explode('/', $path);
399
- foreach ($path as $index => $node) :
400
- if (preg_match('/{#([^}]+)}/', $node, $ref)) :
401
- if (isset($urlHash[$ref[1]])) :
402
- $path[$index] = str_replace(
403
- '{#'.$ref[1].'}',
404
- '{'.$urlHash[$ref[1]].'}',
405
- $node
406
- );
407
- endif;
408
- endif;
409
- endforeach;
410
-
411
- // Start out with a get_item_tags query.
412
- $node = '';
413
- while (strlen($node)==0 and !is_null($node)) :
414
- $node = array_shift($path);
415
- endwhile;
416
 
417
- switch ($node) :
418
- case 'feed' :
419
- case 'channel' :
420
- $node = array_shift($path);
421
- $data = $this->get_feed_root_element();
422
- $data = array_merge($data, $this->get_feed_channel_elements());
423
- break;
424
- case 'item' :
425
- $node = array_shift($path);
426
- default :
427
- $data = array($this->entry->data);
428
- $method = NULL;
429
- endswitch;
430
-
431
- while (!is_null($node)) :
432
- if (strlen($node) > 0) :
433
- $matches = array();
434
-
435
- list($axis, $element) = $this->xpath_name_and_axis($node);
436
-
437
- foreach ($data as $datum) :
438
- if (!is_string($datum) and isset($datum[$axis])) :
439
- foreach ($datum[$axis] as $ns => $elements) :
440
- if (isset($elements[$element])) :
441
- // Potential match.
442
- // Check namespace.
443
- if (is_string($elements[$element])) : // Attribute
444
- $addenda = array($elements[$element]);
445
- $contexts = array($datum);
446
- else : // Element
447
- $addenda = $elements[$element];
448
- $contexts = $elements[$element];
449
- endif;
450
-
451
- foreach ($addenda as $index => $addendum) :
452
- $context = $contexts[$index];
453
-
454
- $namespaces = $this->xpath_possible_namespaces($node, $context);
455
- if (in_array($ns, $namespaces)) :
456
- $matches[] = $addendum;
457
- endif;
458
- endforeach;
459
- endif;
460
- endforeach;
461
- endif;
462
- endforeach;
463
 
464
- $data = $matches;
465
- endif;
466
- $node = array_shift($path);
467
- endwhile;
 
 
 
 
 
 
 
 
468
 
469
- $matches = array();
470
- foreach ($data as $datum) :
471
- if (is_string($datum)) :
472
- $matches[] = $datum;
473
- elseif (isset($datum['data'])) :
474
- $matches[] = $datum['data'];
475
- endif;
476
- endforeach;
477
  return $matches;
478
  } /* SyndicatedPost::query() */
479
 
@@ -505,84 +342,6 @@ class SyndicatedPost {
505
  return $matches;
506
  } /* SyndicatedPost::get_feed_channel_elements() */
507
 
508
- function xpath_default_namespace () {
509
- // Get the default namespace.
510
- $type = $this->link->simplepie->get_type();
511
- if ($type & SIMPLEPIE_TYPE_ATOM_10) :
512
- $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_10;
513
- elseif ($type & SIMPLEPIE_TYPE_ATOM_03) :
514
- $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_03;
515
- elseif ($type & SIMPLEPIE_TYPE_RSS_090) :
516
- $defaultNS = SIMPLEPIE_NAMESPACE_RSS_090;
517
- elseif ($type & SIMPLEPIE_TYPE_RSS_10) :
518
- $defaultNS = SIMPLEPIE_NAMESPACE_RSS_10;
519
- elseif ($type & SIMPLEPIE_TYPE_RSS_20) :
520
- $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20;
521
- else :
522
- $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20;
523
- endif;
524
- return $defaultNS;
525
- } /* SyndicatedPost::xpath_default_namespace() */
526
-
527
- function xpath_name_and_axis ($node) {
528
- $ns = NULL; $element = NULL;
529
-
530
- if (substr($node, 0, 1)=='@') :
531
- $axis = 'attribs'; $node = substr($node, 1);
532
- else :
533
- $axis = 'child';
534
- endif;
535
-
536
- if (preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) :
537
- $element = $ref[2];
538
- elseif (strpos($node, ':') !== FALSE) :
539
- list($xmlns, $element) = explode(':', $node, 2);
540
- else :
541
- $element = $node;
542
- endif;
543
- return array($axis, $element);
544
- } /* SyndicatedPost::xpath_local_name () */
545
-
546
- function xpath_possible_namespaces ($node, $datum = array()) {
547
- $ns = NULL; $element = NULL;
548
-
549
- if (substr($node, 0, 1)=='@') :
550
- $attr = '@'; $node = substr($node, 1);
551
- else :
552
- $attr = '';
553
- endif;
554
-
555
- if (preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) :
556
- $ns = array($ref[1]);
557
- elseif (strpos($node, ':') !== FALSE) :
558
- list($xmlns, $element) = explode(':', $node, 2);
559
-
560
- if (isset($this->xmlns['reverse'][$xmlns])) :
561
- $ns = $this->xmlns['reverse'][$xmlns];
562
- else :
563
- $ns = array($xmlns);
564
- endif;
565
-
566
- // Fucking SimplePie. For attributes in default xmlns.
567
- $defaultNS = $this->xpath_default_namespace();
568
- if (isset($this->xmlns['forward'][$defaultNS])
569
- and ($xmlns==$this->xmlns['forward'][$defaultNS])) :
570
- $ns[] = '';
571
- endif;
572
-
573
- if (isset($datum['xmlns'])) :
574
- if (isset($datum['xmlns'][$xmlns])) :
575
- $ns[] = $datum['xmlns'][$xmlns];
576
- endif;
577
- endif;
578
- else :
579
- // Often in SimplePie, the default namespace gets stored
580
- // as an empty string rather than a URL.
581
- $ns = array($this->xpath_default_namespace(), '');
582
- endif;
583
- return array_unique($ns);
584
- } /* SyndicatedPost::xpath_possible_namespaces() */
585
-
586
  function get_categories ($params = array()) {
587
  return $this->entry->get_categories();
588
  }
@@ -976,6 +735,119 @@ class SyndicatedPost {
976
  return $author;
977
  } /* SyndicatedPost::author() */
978
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
979
  /**
980
  * SyndicatedPost::inline_tags: Return a list of all the tags embedded
981
  * in post content using the a[@rel="tag"] microformat.
@@ -1568,13 +1440,17 @@ class SyndicatedPost {
1568
  // We have to check again in case post has been filtered during
1569
  // the author_id lookup
1570
  if ($this->has_fresh_content()) :
1571
- $consider = array(
1572
- 'category' => array('abbr' => 'cats', 'domain' => array('category', 'post_tag')),
1573
- 'post_tag' => array('abbr' => 'tags', 'domain' => array('post_tag')),
1574
- );
1575
 
1576
  $termSet = array(); $valid = null;
1577
- foreach ($consider as $what => $taxes) :
 
 
 
 
1578
  if (!is_null($this->post)) : // Not filtered out yet
1579
  # -- Look up, or create, numeric ID for categories
1580
  $taxonomies = $this->link->setting("match/".$taxes['abbr'], 'match_'.$taxes['abbr'], $taxes['domain']);
@@ -1582,9 +1458,24 @@ class SyndicatedPost {
1582
  // Eliminate dummy variables
1583
  $taxonomies = array_filter($taxonomies, 'remove_dummy_zero');
1584
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1585
  $terms = $this->category_ids (
1586
- $this->feed_terms[$what],
1587
- $this->link->setting("unfamiliar {$what}", "unfamiliar_{$what}", 'create:'.$what),
1588
  /*taxonomies=*/ $taxonomies,
1589
  array(
1590
  'singleton' => false, // I don't like surprises
@@ -1727,6 +1618,8 @@ class SyndicatedPost {
1727
 
1728
  $dbpost = $this->normalize_post(/*new=*/ true);
1729
 
 
 
1730
  if (!is_null($dbpost)) :
1731
  $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
1732
 
@@ -1791,28 +1684,34 @@ class SyndicatedPost {
1791
  $dbpost['ID'] = $this->_wp_id;
1792
  endif;
1793
 
1794
- // Now that we've made sure the original exists, insert
1795
- // this version here as a revision.
1796
- $revision_id = _wp_put_post_revision($dbpost, /*autosave=*/ false);
1797
-
1798
- if (!$this->this_revision_needs_original_post()) :
 
 
 
 
 
1799
 
1800
- if ($this->this_revision_is_current()) :
1801
 
1802
- wp_restore_post_revision($revision_id);
1803
 
1804
- else :
1805
 
1806
- // If we do not activate this revision, then the
1807
- // add_rss_meta will not be called, which is
1808
- // more or less as it should be, but that means
1809
- // we have to actively record this revision's
1810
- // update hash from here.
1811
- $postId = $this->post['ID'];
1812
- $key = 'syndication_item_hash';
1813
- $hash = $this->update_hash();
1814
- FeedWordPress::diagnostic('syndicated_posts:meta_data', "Adding post meta-datum to post [$postId]: [$key] = ".FeedWordPress::val($hash, /*no newlines=*/ true));
1815
- add_post_meta( $postId, $key, $hash, /*unique=*/ false );
 
1816
  endif;
1817
  endif;
1818
 
@@ -1837,7 +1736,10 @@ class SyndicatedPost {
1837
  endforeach;
1838
 
1839
  $this->validate_post_id($dbpost, $update, array(__CLASS__, __FUNCTION__));
 
 
1840
  endif;
 
1841
  } /* function SyndicatedPost::insert_post () */
1842
 
1843
  function insert_new () {
@@ -1884,11 +1786,46 @@ class SyndicatedPost {
1884
  return $out;
1885
  }
1886
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1887
  function db_sanitize_post ($out) {
1888
- // < 3.6. Core API, including wp_insert_post(), will expect
1889
- // properly slashed data. If wp_slash() exists, then this is
1890
- // after the big change-over, and wp_insert_post() etc. will
1891
- // expect *un*-slashed data.
 
 
 
 
1892
  if (!function_exists('wp_slash')) :
1893
 
1894
  foreach ($out as $key => $value) :
@@ -1898,7 +1835,21 @@ class SyndicatedPost {
1898
  $out[$key] = $value;
1899
  endif;
1900
  endforeach;
1901
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1902
  endif;
1903
 
1904
  return $out;
@@ -2338,4 +2289,3 @@ EOM;
2338
  } /* SyndicatedPost::category_ids () */
2339
 
2340
  } /* class SyndicatedPost */
2341
-
1
  <?php
2
  require_once(dirname(__FILE__).'/feedtime.class.php');
3
  require_once(dirname(__FILE__).'/syndicatedpostterm.class.php');
4
+ require_once(dirname(__FILE__).'/syndicatedpostxpathquery.class.php');
5
 
6
  /**
7
  * class SyndicatedPost: FeedWordPress uses to manage the conversion of
43
  * @param array $item The item syndicated from the feed.
44
  * @param SyndicatedLink $source The feed it was syndicated from.
45
  */
46
+ function __construct ($item, &$source) {
47
  global $wpdb;
48
 
49
  if ( empty($item) && empty($source) )
87
  // Fucking SimplePie.
88
  $this->xmlns['reverse']['rss'][] = '';
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  // Trigger global syndicated_item filter.
91
  $changed = apply_filters('syndicated_item', $this->item, $this);
92
  $this->item = $changed;
251
  // Store a hash of the post content for checking whether something needs to be updated
252
  $this->post['meta']['syndication_item_hash'] = $this->update_hash();
253
 
254
+ // Categories, Tags, and other Terms: from settings assignments (global settings, subscription settings),
255
+ // and from feed assignments (item metadata, post content)
256
+ $this->preset_terms = apply_filters('syndicated_item_preset_terms', $this->get_terms_from_settings(), $this);
257
+ $this->feed_terms = apply_filters('syndicated_item_feed_terms', $this->get_terms_from_feeds(), $this);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
  $this->post['post_type'] = apply_filters('syndicated_post_type', $this->link->setting('syndicated post type', 'syndicated_post_type', 'post'), $this);
260
  endif;
261
 
262
+ } /* SyndicatedPost::__construct() */
263
 
264
  #####################################
265
  #### EXTRACT DATA FROM FEED ITEM ####
291
  * elements or attributes
292
  */
293
  function query ($path) {
294
+ $xq = new SyndicatedPostXPathQuery(array("path" => $path));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
 
296
+ $feedChannel = array_merge(
297
+ $this->get_feed_root_element(),
298
+ $this->get_feed_channel_elements()
299
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
 
301
+ $matches = $xq->match(array(
302
+ "type" => $this->link->simplepie->get_type(),
303
+ "xmlns" => $this->xmlns,
304
+ "map" => array(
305
+ "/" => array($this->entry->data),
306
+ "item" => array($this->entry->data),
307
+ "feed" => $feedChannel,
308
+ "channel" => $feedChannel
309
+ ),
310
+ "context" => $this->entry->data,
311
+ "parent" => $feedChannel,
312
+ ));
313
 
 
 
 
 
 
 
 
 
314
  return $matches;
315
  } /* SyndicatedPost::query() */
316
 
342
  return $matches;
343
  } /* SyndicatedPost::get_feed_channel_elements() */
344
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  function get_categories ($params = array()) {
346
  return $this->entry->get_categories();
347
  }
735
  return $author;
736
  } /* SyndicatedPost::author() */
737
 
738
+ /**
739
+ * SyndicatedPost::get_terms_from_settings(): Return an array of terms to associate with the incoming
740
+ * post based on the Categories, Tags, and other terms associated with each new post by the user's
741
+ * settings (global and feed-specific).
742
+ *
743
+ * @since 2016.0331
744
+ * @return array of lists, each element has the taxonomy for a key ('category', 'post_tag', etc.),
745
+ * and a list of term codes (either alphanumeric names, or ID numbers encoded in a format that
746
+ * SyndicatedLink::category_ids() can understand) within that taxonomy
747
+ *
748
+ */
749
+ public function get_terms_from_settings () {
750
+ // Categories: start with default categories, if any.
751
+ $cats = array();
752
+ if ('no' != $this->link->setting('add/category', NULL, 'yes')) :
753
+ $fc = get_option("feedwordpress_syndication_cats");
754
+ if ($fc) :
755
+ $cats = array_merge($cats, explode("\n", $fc));
756
+ endif;
757
+ endif;
758
+
759
+ $fc = $this->link->setting('cats',NULL, array());
760
+ if (is_array($fc)) :
761
+ $cats = array_merge($cats, $fc);
762
+ endif;
763
+ $preset_terms['category'] = $cats;
764
+
765
+ // Tags: start with default tags, if any
766
+ $tags = array();
767
+ if ('no' != $this->link->setting('add/post_tag', NULL, 'yes')) :
768
+ $ft = get_option("feedwordpress_syndication_tags", NULL);
769
+ $tags = (is_null($ft) ? array() : explode(FEEDWORDPRESS_CAT_SEPARATOR, $ft));
770
+ endif;
771
+
772
+ $ft = $this->link->setting('tags', NULL, array());
773
+ if (is_array($ft)) :
774
+ $tags = array_merge($tags, $ft);
775
+ endif;
776
+ $preset_terms['post_tag'] = $tags;
777
+
778
+ $taxonomies = $this->link->taxonomies();
779
+ $feedTerms = $this->link->setting('terms', NULL, array());
780
+ $globalTerms = get_option('feedwordpress_syndication_terms', array());
781
+ $specials = array('category' => 'cats', 'post_tag' => 'tags');
782
+
783
+ foreach ($taxonomies as $tax) :
784
+ // category and tag settings have already previously been handled
785
+ // but if this is from another taxonomy, then...
786
+ if (!isset($specials[$tax])) :
787
+ $terms = array();
788
+
789
+ // See if we should get the globals
790
+ if ('no' != $this->link->setting("add/$tax", NULL, 'yes')) :
791
+ if (isset($globalTerms[$tax])) :
792
+ $terms = $globalTerms[$tax];
793
+ endif;
794
+ endif;
795
+
796
+ // Now merge in the locals
797
+ if (isset($feedTerms[$tax])) :
798
+ $terms = array_merge($terms, $feedTerms[$tax]);
799
+ endif;
800
+
801
+ // That's all, folks.
802
+ $preset_terms[$tax] = $terms;
803
+ endif;
804
+ endforeach;
805
+
806
+ return $preset_terms;
807
+ } /* SyndicatedPost::get_terms_from_settings () */
808
+
809
+ /**
810
+ * SyndicatedPost::get_terms_from_feeds(): Return an array of terms to associate with the incoming
811
+ * post based on the contents of the subscribed feed (atom:category and rss:category elements, dc:subject
812
+ * elements, tags embedded using microformats in the post content, etc.)
813
+ *
814
+ * @since 2016.0331
815
+ * @return array of lists, each element has the taxonomy for a key ('category', 'post_tag', etc.),
816
+ * and a list of alphanumeric term names
817
+ */
818
+ public function get_terms_from_feeds () {
819
+ // Now add categories from the post, if we have 'em
820
+ $cats = array();
821
+ $post_cats = $this->entry->get_categories();
822
+ if (is_array($post_cats)) : foreach ($post_cats as $cat) :
823
+ $cat_name = $cat->get_term();
824
+ if (!$cat_name) : $cat_name = $cat->get_label(); endif;
825
+
826
+ if ($this->link->setting('cat_split', NULL, NULL)) :
827
+ $pcre = "\007".$this->feedmeta['cat_split']."\007";
828
+ $cats = array_merge(
829
+ $cats,
830
+ preg_split(
831
+ $pcre,
832
+ $cat_name,
833
+ -1 /*=no limit*/,
834
+ PREG_SPLIT_NO_EMPTY
835
+ )
836
+ );
837
+ else :
838
+ $cats[] = $cat_name;
839
+ endif;
840
+ endforeach; endif;
841
+
842
+ $feed_terms['category'] = apply_filters('syndicated_item_categories', $cats, $this);
843
+
844
+ // Scan post for /a[@rel='tag'] and use as tags if present
845
+ $tags = $this->inline_tags();
846
+ $feed_terms['post_tag'] = apply_filters('syndicated_item_tags', $tags, $this);
847
+
848
+ return $feed_terms;
849
+ } /* SyndicatedPost::get_terms_from_feeds () */
850
+
851
  /**
852
  * SyndicatedPost::inline_tags: Return a list of all the tags embedded
853
  * in post content using the a[@rel="tag"] microformat.
1440
  // We have to check again in case post has been filtered during
1441
  // the author_id lookup
1442
  if ($this->has_fresh_content()) :
1443
+ $mapping = apply_filters('syndicated_post_terms_mapping', array(
1444
+ 'category' => array('abbr' => 'cats', 'unfamiliar' => 'category', 'domain' => array('category', 'post_tag')),
1445
+ 'post_tag' => array('abbr' => 'tags', 'unfamiliar' => 'post_tag', 'domain' => array('post_tag')),
1446
+ ), $this);
1447
 
1448
  $termSet = array(); $valid = null;
1449
+ foreach ($this->feed_terms as $what => $anTerms) :
1450
+ // Default to using the inclusive procedures (for cats) rather than exclusive (for inline tags)
1451
+ $taxes = (isset($mapping[$what]) ? $mapping[$what] : $mapping['category']);
1452
+ $unfamiliar = $taxes['unfamiliar'];
1453
+
1454
  if (!is_null($this->post)) : // Not filtered out yet
1455
  # -- Look up, or create, numeric ID for categories
1456
  $taxonomies = $this->link->setting("match/".$taxes['abbr'], 'match_'.$taxes['abbr'], $taxes['domain']);
1458
  // Eliminate dummy variables
1459
  $taxonomies = array_filter($taxonomies, 'remove_dummy_zero');
1460
 
1461
+ // Allow FWP add-on filters to control the taxonomies we use to search for a term
1462
+ $taxonomies = apply_filters("syndicated_post_terms_match", $taxonomies, $what, $this);
1463
+ $taxonomies = apply_filters("syndicated_post_terms_match_${what}", $taxonomies, $this);
1464
+
1465
+ // Allow FWP add-on filters to control with greater precision what happens on unmatched
1466
+ $unmatched = apply_filters("syndicated_post_terms_unfamiliar",
1467
+ $this->link->setting(
1468
+ "unfamiliar {$unfamiliar}",
1469
+ "unfamiliar_{$unfamiliar}",
1470
+ 'create:'.$unfamiliar
1471
+ ),
1472
+ $what,
1473
+ $this
1474
+ );
1475
+
1476
  $terms = $this->category_ids (
1477
+ $anTerms,
1478
+ $unmatched,
1479
  /*taxonomies=*/ $taxonomies,
1480
  array(
1481
  'singleton' => false, // I don't like surprises
1618
 
1619
  $dbpost = $this->normalize_post(/*new=*/ true);
1620
 
1621
+ $ret = null;
1622
+
1623
  if (!is_null($dbpost)) :
1624
  $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
1625
 
1684
  $dbpost['ID'] = $this->_wp_id;
1685
  endif;
1686
 
1687
+ // Sanity check: if the attempt to insert post
1688
+ // returned an error, then feeding that error
1689
+ // object in to _wp_put_post_revision() would
1690
+ // cause a fatal error. Better to break out.
1691
+ if (!is_wp_error($this->_wp_id)) :
1692
+ // Now that we've made sure the original exists, insert
1693
+ // this version here as a revision.
1694
+ $revision_id = _wp_put_post_revision($dbpost, /*autosave=*/ false);
1695
+
1696
+ if (!$this->this_revision_needs_original_post()) :
1697
 
1698
+ if ($this->this_revision_is_current()) :
1699
 
1700
+ wp_restore_post_revision($revision_id);
1701
 
1702
+ else :
1703
 
1704
+ // If we do not activate this revision, then the
1705
+ // add_rss_meta will not be called, which is
1706
+ // more or less as it should be, but that means
1707
+ // we have to actively record this revision's
1708
+ // update hash from here.
1709
+ $postId = $this->post['ID'];
1710
+ $key = 'syndication_item_hash';
1711
+ $hash = $this->update_hash();
1712
+ FeedWordPress::diagnostic('syndicated_posts:meta_data', "Adding post meta-datum to post [$postId]: [$key] = ".FeedWordPress::val($hash, /*no newlines=*/ true));
1713
+ add_post_meta( $postId, $key, $hash, /*unique=*/ false );
1714
+ endif;
1715
  endif;
1716
  endif;
1717
 
1736
  endforeach;
1737
 
1738
  $this->validate_post_id($dbpost, $update, array(__CLASS__, __FUNCTION__));
1739
+
1740
+ $ret = $this->_wp_id;
1741
  endif;
1742
+ return $ret;
1743
  } /* function SyndicatedPost::insert_post () */
1744
 
1745
  function insert_new () {
1786
  return $out;
1787
  }
1788
 
1789
+ public function db_sanitize_post_check_encoding ($out) {
1790
+ // Check encoding recursively: every string field needs to be checked
1791
+ // for character encoding issues. This is a bit problematic because we
1792
+ // *should* be using DB_CHARSET, but DB_CHARSET sometimes has values
1793
+ // that work for MySQL but not for PHP mb_check_encoding. So instead
1794
+ // we must rely on WordPress setting blog_charset and hope that the user
1795
+ // has got their database encoding set up to roughly match
1796
+ $charset = get_option('blog_charset', 'utf8');
1797
+
1798
+ foreach ($out as $key => $value) :
1799
+ if (is_string($value)) :
1800
+
1801
+ if (!function_exists('mb_check_encoding') or mb_check_encoding($value, $charset)) :
1802
+ $out[$key] = $value;
1803
+ else :
1804
+ $fromCharset = mb_detect_encoding($value, mb_detect_order(), /*strict=*/ true);
1805
+ $out[$key] = mb_convert_encoding($value, $charset, $fromCharset);
1806
+ endif;
1807
+
1808
+ elseif (is_array($value)) :
1809
+ $out[$key] = $this->db_sanitize_post_check_encoding($value);
1810
+
1811
+ else :
1812
+ $out[$key] = $value;
1813
+ endif;
1814
+
1815
+ endforeach;
1816
+
1817
+ return $out;
1818
+ } /* SyndicatedPost::db_sanitize_post_check_encoding () */
1819
+
1820
  function db_sanitize_post ($out) {
1821
+ global $wp_db_version;
1822
+
1823
+ $out = $this->db_sanitize_post_check_encoding($out);
1824
+
1825
+ // < 3.6. Core API, including `wp_insert_post()`, expects
1826
+ // properly slashed data. If `wp_slash()` exists, then
1827
+ // this is after the big change-over in how data slashing
1828
+ // was handled.
1829
  if (!function_exists('wp_slash')) :
1830
 
1831
  foreach ($out as $key => $value) :
1835
  $out[$key] = $value;
1836
  endif;
1837
  endforeach;
1838
+
1839
+ // For revisions [@23416,@23554), core API expects
1840
+ // unslashed data. Cf. <https://core.trac.wordpress.org/browser/trunk/wp-includes/post.php?rev=23416>
1841
+ // NOOP for those revisions.
1842
+
1843
+ // In revisions @23554 to present, `wp_insert_post()`
1844
+ // expects slashed data once again.
1845
+ // Cf. <https://core.trac.wordpress.org/changeset/23554/trunk/wp-includes/post.php?contextall=1>
1846
+ // But at least now we can use the wp_slash API function to do that.
1847
+ // Hooray.
1848
+
1849
+ elseif ($wp_db_version >= 23524) :
1850
+
1851
+ $out = wp_slash($out);
1852
+
1853
  endif;
1854
 
1855
  return $out;
2289
  } /* SyndicatedPost::category_ids () */
2290
 
2291
  } /* class SyndicatedPost */
 
syndicatedpostterm.class.php CHANGED
@@ -59,6 +59,9 @@ class SyndicatedPostTerm {
59
 
60
  protected function search () {
61
 
 
 
 
62
  // Either this is a numbered term code, which supplies the ID
63
  // and the taxonomy explicitly (e.g.: {category#2}; in which
64
  // case we have set $this->tax to a unit array containing only
@@ -90,7 +93,7 @@ class SyndicatedPostTerm {
90
  'CHECKED familiarity of term '
91
  .json_encode($this->term)
92
  .' across '.json_encode($this->tax)
93
- . ' with result: '.json_encode($record)
94
  );
95
 
96
  return $this->exists;
59
 
60
  protected function search () {
61
 
62
+ // Initialize
63
+ $found = null;
64
+
65
  // Either this is a numbered term code, which supplies the ID
66
  // and the taxonomy explicitly (e.g.: {category#2}; in which
67
  // case we have set $this->tax to a unit array containing only
93
  'CHECKED familiarity of term '
94
  .json_encode($this->term)
95
  .' across '.json_encode($this->tax)
96
+ . ' with result: '.json_encode($found)
97
  );
98
 
99
  return $this->exists;
syndicatedpostxpathquery.class.php ADDED
@@ -0,0 +1,462 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * class SyndicatedPostXPathQuery: implements an XPath-like syntax used to query
4
+ * arbitrary elements within the syndicated item.
5
+ *
6
+ */
7
+ class SyndicatedPostXPathQuery {
8
+ private $path;
9
+ private $parsedPath;
10
+ private $feed_type;
11
+ private $xmlns;
12
+ private $urlHash = array();
13
+
14
+ /**
15
+ * SyndicatedPostXPathQuery::__construct
16
+ *
17
+ * @param array $args
18
+ * @uses wp_parse_args
19
+ *
20
+ */
21
+ public function __construct ($args = array()) {
22
+ if (is_string($args)) :
23
+ $args = array("path" => $args);
24
+ endif;
25
+
26
+ $args = wp_parse_args($args, array(
27
+ "path" => "",
28
+ ));
29
+
30
+ $this->setPath($args['path']);
31
+ } /* SyndicatedPostXPathQuery::__construct() */
32
+
33
+ /**
34
+ * SyndicatedPostXPathQuery::getPath
35
+ *
36
+ * @param array $args
37
+ * @return mixed
38
+ */
39
+ public function getPath ($args = array()) {
40
+ $args = wp_parse_args($args, array(
41
+ "parsed" => false,
42
+ ));
43
+
44
+ return ($args['parsed'] ? $this->parsedPath : $this->path);
45
+ } /* SyndicatedPostXPathQuery::getPath () */
46
+
47
+ /**
48
+ * SyndicatedPostXPathQuery::setPath
49
+ *
50
+ * @param string $path
51
+ */
52
+ public function setPath ($path) {
53
+ $this->urlHash = array();
54
+
55
+ $this->path = $path;
56
+
57
+ // Allow {url} notation for namespaces. URLs will contain : and /, so...
58
+ preg_match_all('/{([^}]+)}/', $path, $match, PREG_SET_ORDER);
59
+ foreach ($match as $ref) :
60
+ $this->urlHash[md5($ref[1])] = $ref[1];
61
+ endforeach;
62
+
63
+ foreach ($this->urlHash as $hash => $url) :
64
+ $path = str_replace('{'.$url.'}', '{#'.$hash.'}', $path);
65
+ endforeach;
66
+
67
+ $path = $this->parsePath(/*cur=*/ $path, /*orig=*/ $path);
68
+
69
+ $this->parsedPath = $path;
70
+
71
+ } /* SyndicatedPostXPathQuery::setPath() */
72
+
73
+ /**
74
+ * SyndicatedPostXPathQuery::snipSlug
75
+ *
76
+ * @return string
77
+ */
78
+ protected function snipSlug ($path, $start, $n) {
79
+ $slug = substr($path, $start, ($n-$start));
80
+ if (strlen($slug) > 0) :
81
+ if (preg_match('/{#([^}]+)}/', $slug, $ref)) :
82
+ if (isset($this->urlHash[$ref[1]])) :
83
+ $slug = str_replace(
84
+ '{#'.$ref[1].'}',
85
+ '{'.$this->urlHash[$ref[1]].'}',
86
+ $slug
87
+ );
88
+ endif;
89
+ endif;
90
+ endif;
91
+ return $slug;
92
+ } /* SyndicatedPostXPathQuery::snipSlug () */
93
+
94
+ /**
95
+ * SyndicatedPostXPathQuery::parsePath ()
96
+ *
97
+ * @param mixed $path
98
+ * @param mixed $rootPath
99
+ * @return array|object
100
+ */
101
+ public function parsePath ($path, $rootPath) {
102
+ if (is_array($path)) :
103
+ // This looks like it's already been parsed.
104
+ $pp = $path;
105
+ else :
106
+ $pp = array();
107
+
108
+ // Okay let's parse this thing.
109
+ $n = 0; $start = 0; $state = 'slug';
110
+ while ($state != '$') :
111
+ switch ($state) :
112
+ case 'slash' :
113
+ $slug = $this->snipSlug($path, $start, $n);
114
+ if (strlen($slug) > 0) :
115
+ $pp[] = $slug;
116
+ endif;
117
+
118
+ $n++;
119
+ // don't include the slash in our next slug
120
+ $start = $n;
121
+
122
+ $state = (($n < strlen($path)) ? 'slug' : '$');
123
+
124
+ break;
125
+ case 'brackets' :
126
+
127
+ // first, snip off what we've consumed so far
128
+ $slug = $this->snipSlug($path, $start, $n);
129
+ if (strlen($slug) > 0) :
130
+ $pp[] = $slug;
131
+ endif;
132
+
133
+ // now, chase the ]
134
+ $depth = 1;
135
+ $n++; $start = $n;
136
+
137
+ // find the end of the [square-bracketed] expression
138
+ while ($depth > 0 and $n != '') :
139
+ $tok = ((strlen($path) > $n) ? $path[$n] : '');
140
+ switch ($tok) :
141
+ case '' :
142
+ // ERROR STATE: syntax error
143
+ $depth = -1;
144
+ $state = 'syntax-error';
145
+ break;
146
+ case '[' :
147
+ $depth++;
148
+ break;
149
+ case ']' :
150
+ $depth--;
151
+ break;
152
+ default :
153
+ // NOOP
154
+ endswitch;
155
+ $n++;
156
+ endwhile;
157
+
158
+ if ($state != 'syntax-error') :
159
+ $bracketed = substr($path, $start, ($n-$start)-1);
160
+
161
+ // recursive parsing
162
+ $oFilter = new stdClass;
163
+ $oFilter->verb = 'has';
164
+ $oFilter->query = $this->parsePath($bracketed, $rootPath);
165
+ $pp[] = $oFilter;
166
+
167
+ $start = $n;
168
+
169
+ $state = 'slash-expected';
170
+ endif;
171
+ break;
172
+
173
+ case 'slash-expected' :
174
+ $tok = ((strlen($path) > $n) ? $path[$n] : '');
175
+ if ($tok == '/' or $tok == '') :
176
+ $state = 'slash';
177
+ else :
178
+ $state = 'syntax-error';
179
+ endif;
180
+ break;
181
+ case 'syntax-error' :
182
+ $pp = new WP_Error('xpath', __("Syntax error", "feedwordpress"));
183
+ $state = '$';
184
+ break;
185
+ case 'slug' :
186
+ default :
187
+ $tok = ((strlen($path) > $n) ? $path[$n] : '');
188
+ switch ($tok) :
189
+ case '' :
190
+ case '/' :
191
+ $state = 'slash';
192
+ break;
193
+ case '[' :
194
+ $state = 'brackets';
195
+ break;
196
+ default :
197
+ $n++;
198
+ endswitch;
199
+ endswitch;
200
+ endwhile;
201
+ endif;
202
+ return $pp;
203
+ } /* SyndicatedPostXPathQuery::parsePath() */
204
+
205
+ /**
206
+ * SyndicatedPostXPathQuery::match
207
+ *
208
+ * @param string $path
209
+ * @return array
210
+ */
211
+ public function match ($r = array()) {
212
+ $path = $this->parsedPath;
213
+
214
+ $r = wp_parse_args($r, array(
215
+ "type" => SIMPLEPIE_TYPE_ATOM_10,
216
+ "xmlns" => array(),
217
+ "map" => array(),
218
+ "context" => array(),
219
+ "parent" => array(),
220
+ "format" => "string",
221
+ ));
222
+
223
+ $this->feed_type = $r['type'];
224
+ $this->xmlns = $r['xmlns'];
225
+
226
+ // Start out with a get_item_tags query.
227
+ $node = '';
228
+ while (strlen($node)==0 and !is_null($node)) :
229
+ $node = array_shift($path);
230
+ endwhile;
231
+
232
+ if (is_string($node) and isset($r['map'][$node])) :
233
+ $data = $r['map'][$node];
234
+ $node = array_shift($path);
235
+ else :
236
+ $data = $r['map']['/'];
237
+ endif;
238
+
239
+ $matches = $data;
240
+ while (!is_null($node)) :
241
+ if (is_object($node) OR strlen($node) > 0) :
242
+ list($axis, $element) = $this->xpath_name_and_axis($node);
243
+ if ('self'==$axis) :
244
+ if (is_object($element) and property_exists($element, 'verb')) :
245
+
246
+ $subq = new self(array("path" => $element->query));
247
+ $result = $subq->match(array(
248
+ "type" => $r['type'],
249
+ "xmlns" => $r['xmlns'],
250
+ "map" => array(
251
+ "/" => $matches,
252
+ ),
253
+ "context" => $matches,
254
+ "parent" => $r['parent'],
255
+ "format" => "object",
256
+ ));
257
+
258
+ // when format = 'object' we should get back
259
+ // a sparse array of arrays, with indices = indices
260
+ // from the input array, each element = an array of
261
+ // one or more matching elements
262
+
263
+ if ($element->verb = 'has' and is_array($result)) :
264
+
265
+ $results = array();
266
+ foreach (array_keys($result) as $a) :
267
+ $results[$a] = $matches[$a];
268
+ endforeach;
269
+
270
+ $matches = $results;
271
+ $data = $matches;
272
+ endif;
273
+
274
+ elseif (is_numeric($node)) :
275
+
276
+ // according to W3C, sequence starts at position 1, not 0
277
+ // so subtract 1 to line up with PHP array starting at 0
278
+ $idx = intval($element) - 1;
279
+ if (isset($matches[$idx])) :
280
+ $data = array($idx => $matches[$idx]);
281
+ else :
282
+ $data = array();
283
+ endif;
284
+
285
+ $matches = array($idx => $data);
286
+ endif;
287
+
288
+ else :
289
+ $matches = array();
290
+
291
+ foreach ($data as $idx => $datum) :
292
+ if (!is_string($datum) and isset($datum[$axis])) :
293
+ foreach ($datum[$axis] as $ns => $elements) :
294
+ if (isset($elements[$element])) :
295
+ // Potential match.
296
+ // Check namespace.
297
+ if (is_string($elements[$element])) : // Attribute
298
+ $addenda = array($elements[$element]);
299
+ $contexts = array($datum);
300
+
301
+ // Element
302
+ else :
303
+ $addenda = $elements[$element];
304
+ $contexts = $elements[$element];
305
+ endif;
306
+
307
+ foreach ($addenda as $index => $addendum) :
308
+ $context = $contexts[$index];
309
+
310
+ $namespaces = $this->xpath_possible_namespaces($node, $context);
311
+ if (in_array($ns, $namespaces)) :
312
+ $matches[] = $addendum;
313
+ endif;
314
+ endforeach;
315
+ endif;
316
+ endforeach;
317
+ endif;
318
+ endforeach;
319
+
320
+ $data = $matches;
321
+ endif;
322
+ endif;
323
+ $node = array_shift($path);
324
+ endwhile;
325
+
326
+ $matches = array();
327
+ foreach ($data as $idx => $datum) :
328
+ if ($r['format'] == 'string') :
329
+ if (is_string($datum)) :
330
+ $matches[] = $datum;
331
+ elseif (isset($datum['data'])) :
332
+ $matches[] = $datum['data'];
333
+ endif;
334
+ else :
335
+ $matches[$idx] = $datum;
336
+ endif;
337
+ endforeach;
338
+
339
+ return $matches;
340
+ } /* SyndicatedPostXPathQuery::match() */
341
+
342
+ public function xpath_default_namespace () {
343
+ // Get the default namespace.
344
+ $type = $this->feed_type;
345
+ if ($type & SIMPLEPIE_TYPE_ATOM_10) :
346
+ $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_10;
347
+ elseif ($type & SIMPLEPIE_TYPE_ATOM_03) :
348
+ $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_03;
349
+ elseif ($type & SIMPLEPIE_TYPE_RSS_090) :
350
+ $defaultNS = SIMPLEPIE_NAMESPACE_RSS_090;
351
+ elseif ($type & SIMPLEPIE_TYPE_RSS_10) :
352
+ $defaultNS = SIMPLEPIE_NAMESPACE_RSS_10;
353
+ elseif ($type & SIMPLEPIE_TYPE_RSS_20) :
354
+ $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20;
355
+ else :
356
+ $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20;
357
+ endif;
358
+ return $defaultNS;
359
+ } /* SyndicatedPostXPathQuery::xpath_default_namespace() */
360
+
361
+ public function xpath_name_and_axis ($node) {
362
+ $ns = NULL; $element = NULL;
363
+
364
+ $axis = 'child'; // "In effect, `child` is the default axis."
365
+ if (is_object($node) and property_exists($node, 'verb')):
366
+ if ('has'==$node->verb) :
367
+ $axis = 'self';
368
+ endif;
369
+ elseif (strpos($node, '::') !== false) :
370
+ list($axis, $node) = explode("::", $node, 2);
371
+ if ($axis=='attribute') :
372
+ $axis = 'attribs'; // map from W3C to SimplePie's idiosyncratic notation
373
+ endif;
374
+ elseif (substr($node, 0, 1)=='@') :
375
+ $axis = 'attribs'; $node = substr($node, 1);
376
+ elseif (is_numeric($node)) :
377
+ $axis = 'self';
378
+ elseif (substr($node, 0, 1)=='/') :
379
+ // FIXME: properly, we should check for // and if we have it,
380
+ // treat that as short for /descendent-or-self::node()/
381
+ $axis = 'child'; $node = substr($node, 1);
382
+ else :
383
+ // NOOP
384
+ endif;
385
+
386
+ if (is_string($node) and preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) :
387
+ $element = $ref[2];
388
+ elseif (is_string($node) and strpos($node, ':') !== FALSE) :
389
+ list($xmlns, $element) = explode(':', $node, 2);
390
+ else :
391
+ $element = $node;
392
+ endif;
393
+ return array($axis, $element);
394
+ } /* SyndicatedPostXPathQuery::xpath_name_and_axis () */
395
+
396
+ public function xpath_possible_namespaces ($node, $datum = array()) {
397
+ $ns = NULL; $element = NULL;
398
+
399
+ if (substr($node, 0, 1)=='@') :
400
+ $attr = '@'; $node = substr($node, 1);
401
+ else :
402
+ $attr = '';
403
+ endif;
404
+
405
+ if (preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) :
406
+ $ns = array($ref[1]);
407
+ elseif (strpos($node, ':') !== FALSE) :
408
+ list($xmlns, $element) = explode(':', $node, 2);
409
+
410
+ if (isset($this->xmlns['reverse'][$xmlns])) :
411
+ $ns = $this->xmlns['reverse'][$xmlns];
412
+ else :
413
+ $ns = array($xmlns);
414
+ endif;
415
+
416
+ // Fucking SimplePie. For attributes in default xmlns.
417
+ $defaultNS = $this->xpath_default_namespace();
418
+ if (isset($this->xmlns['forward'][$defaultNS])
419
+ and ($xmlns==$this->xmlns['forward'][$defaultNS])) :
420
+ $ns[] = '';
421
+ endif;
422
+
423
+ if (isset($datum['xmlns'])) :
424
+ if (isset($datum['xmlns'][$xmlns])) :
425
+ $ns[] = $datum['xmlns'][$xmlns];
426
+ endif;
427
+ endif;
428
+ else :
429
+ // Often in SimplePie, the default namespace gets stored
430
+ // as an empty string rather than a URL.
431
+ $ns = array($this->xpath_default_namespace(), '');
432
+ endif;
433
+ return array_unique($ns);
434
+ } /* SyndicatedPostXPathQuery::xpath_possible_namespaces() */
435
+
436
+ } /* class SyndicatedPostXPathQuery */
437
+
438
+ // When called directly, run through and perform some tests.
439
+ if (basename($_SERVER['SCRIPT_FILENAME'])==basename(__FILE__)) :
440
+ # some day when I am a grown-up developer I might include
441
+ # some test cases in this here section
442
+ # we need to implement wp_parse_args(), __(), and class WP_Error ...
443
+ #function wp_parse_args ($r, $defaults) {
444
+ # return array_merge($defaults, $r);
445
+ #}
446
+ #function __($text, $domain) {
447
+ # return $text;
448
+ #}
449
+ #class WP_Error {
450
+ # public function __construct ( $slug, $message ) {
451
+ # /*DBG*/ echo $slug;
452
+ # /*DBG*/ echo ": ";
453
+ # /*DBG*/ echo $message;
454
+ # }
455
+ #}
456
+ #
457
+ #header("Content-type: text/plain");
458
+ #
459
+ #$spxq = new SyndicatedPostXPathQuery(array("path" => $_REQUEST['p']));
460
+ #
461
+ #var_dump($spxq);
462
+ endif;