FeedWordPress - Version 2010.0528

Version Description

Download this release

Release Info

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

Code changes from version 2010.0127 to 2010.0528

ChangeLog.text CHANGED
@@ -2,13 +2,191 @@ FeedWordPress Change Log
2
  ========================
3
  Changes from 2009.0707 to Trunk
4
  -------------------------------
5
- * NEW INTERFACE HOOKS
6
 
7
- * FILTER API: SyndicatedPost::isTaggedAs
8
-
9
- * BUGIFX: SILENT FAILURE OF "SYNDICATE" BUTTON FIXED
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- * BUGFIX: MagpieRSS "ILLEGAL OFFSET TYPE" ERROR MESSAGE FIXED
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  Changes from 2009.0618 to 2009.0707
14
  -----------------------------------
2
  ========================
3
  Changes from 2009.0707 to Trunk
4
  -------------------------------
 
5
 
6
+ ### Compatibility ###
7
+
8
+ * SIMPLEPIE IS NOW USED TO PARSE FEEDS; NO MORE MAGPIERSS UPGRADES NEEDED:
9
+ One of the biggest changes in this release is that FeedWordPress no
10
+ longer depends on MagpieRSS to parse feeds, and has switched to the much
11
+ more up-to-date and flexible SimplePie feed parser, which is included as
12
+ a standard part of WordPress versions 2.8 and later. Using SimplePie will
13
+ hopefully allow for better handling of feeds going further, and will
14
+ allow me greater flexibility in determining how exactly the feed parser
15
+ will operate. It also means that FeedWordPress no longer requires
16
+ special upgrades to the WordPress core MagpieRSS files, and should
17
+ eliminate quite a bit of complexity.
18
+
19
+ * MAGPIERSS COMPATIBILITY LAYER FOR EXISTING FILTERS AND ADD-ONS: However,
20
+ I have also implemented a compatibility layer to ensure that existing
21
+ filters and add-ons for FeedWordPress which depended on the MagpieRSS
22
+ data format *should not be broken* by the switch to SimplePie. Going
23
+ forward, I recommend that new filters and add-ons be written to take
24
+ advantage of the SimplePie object representations of items, feeds, etc.,
25
+ rather than the MagpieRSS arrays, but the MagpieRSS arrays will still
26
+ be available and older filters should continue to work as they have in
27
+ the past.
28
+
29
+ * COMPATIBILITY WITH WORDPRESS 2.9.x and 3.0: This release has been tested
30
+ for the existing WordPress 2.9.x branch and with the upcoming release of
31
+ WordPress 3.0. Changes in the user interface JavaScript between WordPress
32
+ 2.8.x and WordPress 2.9 caused the tag box interface element to break in
33
+ the Syndication --> Categories & Tags settings page; changes in the API
34
+ functions for adding new authors caused fatal errors under certain
35
+ conditions in WordPress 3.0. These breakages have been fixed.
36
+
37
+ * DROPPED LEGACY SUPPORT FOR WORDPRESS PRIOR TO 2.8: Because SimplePie is
38
+ not included with versions of WordPress prior to 2.8, I have chosen to
39
+ drop legacy support for WordPress versions 1.5 through 2.7. If you are
40
+ using FeedWordPress with a version of WordPress before 2.8, you will
41
+ have to upgrade your installation of WordPress in order to take
42
+ advantage of this release.
43
+
44
+ * PHP 5.3 COMPATIBILITY: A couple of compatibility issues, which were
45
+ causing fatal errors amd ugly warnings for users of PHP 5.3,
46
+ have been eliminated.
47
 
48
+ ### Features and Processing ###
49
+
50
+ * INTERFACE REORGANIZATION: The interface restructuring, began with
51
+ Version 2009.0612, has been completed. Catch-all settings pages have
52
+ been eliminated entirely for pages that cover each aspect of handling
53
+ a feed: Feeds & Updates, Posts & Links, Authors, Categories & Tags,
54
+ and Back End handling of the database and diagnostic information.
55
+ Extensive new interface hooks allow add-on modules to significantly
56
+ change or extend the FeedWordPress admin interface and workflow.
57
+
58
+ * STORING INFORMATION FROM THE FEED IN CUSTOM FIELDS: Many users
59
+ have written to request the ability to store information from elements
60
+ in the feed in a custom field on each post. (So that, for example, if
61
+ post includes a `itunes:duration` element, you could store the contents
62
+ in a Custom Field called `duration` on the post (for a Theme to access
63
+ later). The Custom Post Settings under Syndication --> Posts & Links now
64
+ allow you to access any item or feed tag, using a syntax similar to
65
+ a much-simplified version of XPath. See Posts & Links settings for
66
+ details.
67
+
68
+ * UPDATE-FREEZING ON MANUALLY EDITED POSTS: FeedWordPress now allows you
69
+ to mark posts that have been manually edited, so that the changes you
70
+ make will not be overwritten by later updates from the feed. If you make
71
+ manual edits to a particular post, just check the "Manual editing"
72
+ checkbox in order to protect your changes from being overwritten. If you
73
+ want to block *all* posts from being updated after they are imported
74
+ for the first time, a new "Updated Posts" setting in Posts & Links
75
+ allows you to freeze all posts from a particular feed, or all syndicated
76
+ posts.
77
+
78
+ * SETTING: FEED-BY-FEED SETTINGS FOR WHERE PERMALINKS POINT TO: You've
79
+ always been able to tell FeedWordPress whether permalinks for posts
80
+ should point to the original source of the story or the local copy. Now
81
+ you can choose different policies for different feeds, instead of one
82
+ global policy for all feeds. (Of course, you can still use a global
83
+ default if you prefer.)
84
+
85
+ * SETTING: USER CONTROL OVER TIMING BASIS. You can now determine the
86
+ schedule on which feeds are considered ready to poll for updates --
87
+ by default feeds become ready for polling after about 1 hour. You can
88
+ now increase or decrease the time window under Syndication --> Feeds &
89
+ Updates. (However, please pay *CAREFUL ATTENTION* to the recommendations
90
+ and DO NOT set the scheduling lower than 60 minutes unless you are
91
+ ABSOLUTELY SURE that you have specific permission from webmaster who
92
+ provides that specific feed to poll more frequently than that. If you
93
+ set this too low (and about 60 minutes is the polite minimum if you
94
+ haven't been given a different figure), most webmasters will consider
95
+ the frequent hits on their server as rude, or even downright abusive.
96
+
97
+ * OTHER SETTINGS: New settings also include the ability to stop FWP from
98
+ resolving relative URLs within syndicated content, and the ability to
99
+ choose whether FeedWordPress should indicate the comment feed from the
100
+ original source, or the local comment feed, when providing the comment
101
+ feed URL for a syndicated post.
102
+
103
+ ### PARSING ###
104
+
105
+ * BETTER DATE HANDLING -- FEWER FLASHBACKS TO 1969 and 1970: FeedWordPress
106
+ has made some bugfixes and some improvements in the logic for parsing
107
+ dates. This should allow FeedWordPress to correctly parse more dates in
108
+ more feeds; and, in the last resort, when FeedWordPress fails to
109
+ correctly parse a date, to fall back to a more intelligent default. This
110
+ should hopefully avoid most or all error conditions that have resulted
111
+ in articles being erroneously dated to the dawn of the Unix epoch
112
+ (31 December 1969 or 1 January 1970).
113
+
114
+ * FULL-TEXT "EXCERPTS" NOW PROPERLY SHORTENED. Based on a straightforward
115
+ reading of the existing RSS specs, it's reasonable for the
116
+ rss:description element to be read as a plaintext summary or excerpt for
117
+ the item containing the description -- with the full text of the item,
118
+ if available, in another, better-suited element, such as the de facto
119
+ standard content:encoded extension element. The problem is that uses of
120
+ RSS rarely have much to do with anything like a straightforward reading
121
+ of the specs. As a result, many actual RSS producers in the wild put the
122
+ full text of the article in a description element. But since
123
+ FeedWordPress has treated this text as a summary, this produces
124
+ aggregated posts with lengthy "excerpts" containing the full text of the
125
+ article. This release of FeedWordPress fixes the problem by doing a
126
+ little digging before treating rss:description as a summary: if the
127
+ description element is used properly as a plain text summary, then
128
+ FeedWordPress will take the summary provided by the feed, rather than
129
+ recreating its own excerpt from the full text; but if an RSS item has no
130
+ full-text element other than description, FeedWordPress will treat the
131
+ description element as the full text of the article, and generate a
132
+ shortened excerpt automatically from that text.
133
+
134
+ ### API ###
135
+
136
+ * TEMPLATE API: new template tags `get_local_permalink()` and
137
+ `the_local_permalink()` allow you to access the permalink for a post on
138
+ your aggregator site, even when FeedWordPress is rewriting permalinks to
139
+ point to the original source site.
140
+
141
+ * NEW HOOKS FOR ADD-ONS AND FILTERS: I have added a number of new hooks
142
+ which allow add-on modules to filter more precisely, gather information
143
+ at more points, and to enhance the FeedWordPress admin interface. For
144
+ a list of new hooks and documentation, see the FeedWordPress
145
+ documentation wiki at
146
+ <http://feedwordpress.radgeek.com/wiki/add-ons-and-filters>
147
+
148
+ * FILTER API: A number of new utility methods have been added to the
149
+ SyndicatedPost class to make it easier for filters and add-ons to
150
+
151
+ * FILTER API: Globals $fwp_channel and $fwp_feedmeta DEPRECATED. These
152
+ global variables, originally introduced to allow filters access to
153
+ information about the source feed in `syndicated_item` filters (which
154
+ were passed in through global variables rather than as parameters
155
+ because of a bug in WP 1.5 which was then fixed in 1.5.1) have been
156
+ DEPRECATED. If you have any filters or add-ons which still depend on
157
+ these global variables, you should see about fixing them to access data
158
+ about the source feed using the SyndicatedPost::link element instead.
159
+ For documentation, see the FeedWordPress documentation wiki at
160
+ <http://feedwordpress.radgeek.com/wiki/syndicatedpost> and
161
+ <http://feedwordpress.radgeek.com/wiki/syndicatedlink>.
162
+
163
+ * DIAGNOSTICS: I've included a number of new diagnostic options and
164
+ messages, which should allow an experienced user to better investigate
165
+ any problems that may crop up.
166
+
167
+ ### Bug Fixes ###
168
+
169
+ * BUGFIX: & IN PERMALINKS NO LONGER CAUSING ATOM OR HTML VALIDATION
170
+ EFFORTS: Many users reported an issue in which syndicating a feed with
171
+ special XML characters in the URLs (& was the most common, since it is
172
+ used to separate HTTP GET parameters) would cause the aggregator's
173
+ feeds to produce invalid (malformed) XML. This update addresses the
174
+ issue in Atom feeds. Unfortunately, it has not been technically possible
175
+ to address the problem in RSS 2.0 feeds, due to limitations on
176
+ WordPress's internal templates for RSS feeds.
177
+
178
+ * BUGFIX: BROKEN URLS IN "POPULAR POSTS" AND SIMILAR PLUGINS SHOULD NO
179
+ LONGER BE BROKEN. A number of users noticed an issue where plugins and
180
+ templates that listed posts in locations outside of the post loop
181
+ (for example, "Popular Posts"-style plugins that listed posts in the
182
+ sidebar), often produced the wrong URL for post links. (Typically, all
183
+ the posts listed would get the same wrong URL.) This should now be
184
+ fixed. Thanks to Björn for sending in a quick fix!
185
+
186
+ * MINOR BUGFIXES: This release includes a number of fixes to minor bugs
187
+ and compatibility issues, including: silent failures of the "Syndicate"
188
+ button, "Illegal Offset Type" error messages from MagpieRSS,
189
+
190
 
191
  Changes from 2009.0618 to 2009.0707
192
  -----------------------------------
MagpieRSS-upgrade/rss-functions.php DELETED
@@ -1,4 +0,0 @@
1
- <?php
2
- // Deprecated. Use rss.php instead.
3
- require_once (ABSPATH . WPINC . '/rss.php');
4
- ?>
 
 
 
 
MagpieRSS-upgrade/rss.php DELETED
@@ -1,2045 +0,0 @@
1
- <?php
2
- /* Project: MagpieRSS: a simple RSS integration tool
3
- * File: A compiled file for RSS syndication
4
- * Author: Kellan Elliot-McCrea <kellan@protest.net>
5
- * WordPress development team <http://www.wordpress.org/>
6
- * Charles Johnson <technophilia@radgeek.com>
7
- * Version: 2010.0122
8
- * License: GPL
9
- *
10
- * Provenance:
11
- *
12
- * This is a drop-in replacement for the `rss-functions.php` provided with the
13
- * WordPress 1.5 distribution, which upgrades the version of MagpieRSS from 0.51
14
- * to 0.8a. The update improves handling of character encoding, supports
15
- * multiple categories for posts (using <dc:subject> or <category>), supports
16
- * Atom 1.0, and implements many other useful features. The file is derived from
17
- * a combination of (1) the WordPress development team's modifications to
18
- * MagpieRSS 0.51 and (2) the latest bleeding-edge updates to the "official"
19
- * MagpieRSS software, including Kellan's original work and some substantial
20
- * updates by Charles Johnson. All possible through the magic of the GPL. Yay
21
- * for free software!
22
- *
23
- * Differences from the main branch of MagpieRSS:
24
- *
25
- * 1. Everything in rss_parse.inc, rss_fetch.inc, rss_cache.inc, and
26
- * rss_utils.inc is included in one file.
27
- *
28
- * 2. MagpieRSS returns the WordPress version as the user agent, rather than
29
- * Magpie
30
- *
31
- * 3. class RSSCache is a modified version by WordPress developers, which
32
- * caches feeds in the WordPress database (in the options table), rather
33
- * than writing external files directly.
34
- *
35
- * 4. There are two WordPress-specific functions, get_rss() and wp_rss()
36
- *
37
- * Differences from the version of MagpieRSS packaged with WordPress:
38
- *
39
- * 1. Support for translation between multiple character encodings. Under
40
- * PHP 5 this is very nicely handled by the XML parsing library. Under PHP
41
- * 4 we need to do a little bit of work ourselves, using either iconv or
42
- * mb_convert_encoding if it is not one of the (extremely limited) number
43
- * of character sets that PHP 4's XML module can handle natively.
44
- *
45
- * 2. Numerous bug fixes.
46
- *
47
- * 3. The parser class MagpieRSS has been substantially revised to better
48
- * support popular features such as enclosures and multiple categories,
49
- * and to support the new Atom 1.0 IETF standard. (Atom feeds are
50
- * normalized so as to make the data available using terminology from
51
- * either Atom 0.3 or Atom 1.0. Atom 0.3 backward-compatibility is provided
52
- * to allow existing software to easily begin accepting Atom 1.0 data; new
53
- * software SHOULD NOT depend on the 0.3 terminology, but rather use the
54
- * normalization as a convenient way to keep supporting 0.3 feeds while
55
- * they linger in the world.)
56
- *
57
- * The upgraded MagpieRSS can also now handle some content constructs that
58
- * had not been handled well by previous versions of Magpie (such as the
59
- * use of namespaced XHTML in <xhtml:body> or <xhtml:div> elements to
60
- * provide the full content of posts in RSS 2.0 feeds).
61
- *
62
- * Unlike previous versions of MagpieRSS, this version can parse multiple
63
- * instances of the same child element in item/entry and channel/feed
64
- * containers. This is done using simple counters next to the element
65
- * names: the first <category> element on an RSS item, for example, can be
66
- * found in $item['category'] (thus preserving backward compatibility); the
67
- * second in $item['category#2'], the third in $item['category#3'], and so
68
- * on. The number of categories applied to the item can be found in
69
- * $item['category#']
70
- *
71
- * Also unlike previous versions of MagpieRSS, this version allows you to
72
- * access the values of elements' attributes as well as the content they
73
- * contain. This can be done using a simple syntax inspired by XPath: to
74
- * access the type attribute of an RSS 2.0 enclosure, for example, you
75
- * need only access `$item['enclosure@type']`. A comma-separated list of
76
- * attributes for the enclosure element is stored in `$item['enclosure@']`.
77
- * (This syntax interacts easily with the syntax for multiple categories;
78
- * for example, the value of the `scheme` attribute for the fourth category
79
- * element on a particular item is stored in `$item['category#4@scheme']`.)
80
- *
81
- * Note also that this implementation IS NOT backward-compatible with the
82
- * kludges that were used to hack in support for multiple categories and
83
- * for enclosures in upgraded versions of MagpieRSS distributed with
84
- * previous versions of FeedWordPress. If your hacks or filter plugins
85
- * depended on the old way of doing things... well, I warned you that they
86
- * might not be permanent. Sorry!
87
- */
88
-
89
- define('RSS', 'RSS');
90
- define('ATOM', 'Atom');
91
-
92
- ################################################################################
93
- ## WordPress: make some settings WordPress-appropriate #########################
94
- ################################################################################
95
-
96
- define('MAGPIE_USER_AGENT', 'WordPress/' . $wp_version . '(+http://www.wordpress.org)');
97
-
98
- $wp_encoding = get_option('blog_charset', /*default=*/ 'ISO-8859-1');
99
- define('MAGPIE_OUTPUT_ENCODING', ($wp_encoding?$wp_encoding:'ISO-8859-1'));
100
-
101
- ################################################################################
102
- ## rss_parse.inc: from MagpieRSS 0.85 ##########################################
103
- ################################################################################
104
-
105
- /**
106
- * Hybrid parser, and object, takes RSS as a string and returns a simple object.
107
- *
108
- * see: rss_fetch.inc for a simpler interface with integrated caching support
109
- *
110
- */
111
- class MagpieRSS {
112
- var $parser;
113
-
114
- var $current_item = array(); // item currently being parsed
115
- var $items = array(); // collection of parsed items
116
- var $channel = array(); // hash of channel fields
117
- var $textinput = array();
118
- var $image = array();
119
- var $feed_type;
120
- var $feed_version;
121
- var $encoding = ''; // output encoding of parsed rss
122
-
123
- var $_source_encoding = ''; // only set if we have to parse xml prolog
124
-
125
- var $ERROR = "";
126
- var $WARNING = "";
127
-
128
- // define some constants
129
- var $_XMLNS_FAMILIAR = array (
130
- 'http://www.w3.org/2005/Atom' => 'atom' /* 1.0 */,
131
- 'http://purl.org/atom/ns#' => 'atom' /* pre-1.0 */,
132
- 'http://purl.org/rss/1.0/' => 'rss' /* 1.0 */,
133
- 'http://backend.userland.com/RSS2' => 'rss' /* 2.0 */,
134
- 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' => 'rdf',
135
- 'http://www.w3.org/1999/xhtml' => 'xhtml',
136
- 'http://purl.org/dc/elements/1.1/' => 'dc',
137
- 'http://purl.org/dc/terms/' => 'dcterms',
138
- 'http://purl.org/rss/1.0/modules/content/' => 'content',
139
- 'http://purl.org/rss/1.0/modules/syndication/' => 'sy',
140
- 'http://purl.org/rss/1.0/modules/taxonomy/' => 'taxo',
141
- 'http://purl.org/rss/1.0/modules/dc/' => 'dc',
142
- 'http://wellformedweb.org/CommentAPI/' => 'wfw',
143
- 'http://webns.net/mvcb/' => 'admin',
144
- 'http://purl.org/rss/1.0/modules/annotate/' => 'annotate',
145
- 'http://xmlns.com/foaf/0.1/' => 'foaf',
146
- 'http://madskills.com/public/xml/rss/module/trackback/' => 'trackback',
147
- 'http://web.resource.org/cc/' => 'cc',
148
- 'http://search.yahoo.com/mrss' => 'media',
149
- 'http://search.yahoo.com/mrss/' => 'media',
150
- 'http://video.search.yahoo.com/mrss' => 'media',
151
- 'http://video.search.yahoo.com/mrss/' => 'media',
152
- );
153
-
154
- var $_XMLBASE_RESOLVE = array (
155
- // Atom 0.3 and 1.0 xml:base support
156
- 'atom' => array (
157
- 'link' => array ('href' => true),
158
- 'content' => array ('src' => true, '*xml' => true, '*html' => true),
159
- 'summary' => array ('*xml' => true, '*html' => true),
160
- 'title' => array ('*xml' => true, '*html' => true),
161
- 'rights' => array ('*xml' => true, '*html' => true),
162
- 'subtitle' => array ('*xml' => true, '*html' => true),
163
- 'info' => array('*xml' => true, '*html' => true),
164
- 'tagline' => array('*xml' => true, '*html' => true),
165
- 'copyright' => array ('*xml' => true, '*html' => true),
166
- 'generator' => array ('uri' => true, 'url' => true),
167
- 'uri' => array ('*content' => true),
168
- 'url' => array ('*content' => true),
169
- 'icon' => array ('*content' => true),
170
- 'logo' => array ('*content' => true),
171
- ),
172
-
173
- // for inline namespaced XHTML
174
- 'xhtml' => array (
175
- 'a' => array ('href' => true),
176
- 'applet' => array('codebase' => true),
177
- 'area' => array('href' => true),
178
- 'blockquote' => array('cite' => true),
179
- 'body' => array('background' => true),
180
- 'del' => array('cite' => true),
181
- 'form' => array('action' => true),
182
- 'frame' => array('longdesc' => true, 'src' => true),
183
- 'iframe' => array('longdesc' => true, 'iframe' => true, 'src' => true),
184
- 'head' => array('profile' => true),
185
- 'img' => array('longdesc' => true, 'src' => true, 'usemap' => true),
186
- 'input' => array('src' => true, 'usemap' => true),
187
- 'ins' => array('cite' => true),
188
- 'link' => array('href' => true),
189
- 'object' => array('classid' => true, 'codebase' => true, 'data' => true, 'usemap' => true),
190
- 'q' => array('cite' => true),
191
- 'script' => array('src' => true),
192
- ),
193
- );
194
-
195
- var $_ATOM_CONTENT_CONSTRUCTS = array(
196
- 'content', 'summary', 'title', /* common */
197
- 'info', 'tagline', 'copyright', /* Atom 0.3 */
198
- 'rights', 'subtitle', /* Atom 1.0 */
199
- );
200
- var $_XHTML_CONTENT_CONSTRUCTS = array('body', 'div');
201
- var $_KNOWN_ENCODINGS = array('UTF-8', 'US-ASCII', 'ISO-8859-1');
202
-
203
- // parser variables, useless if you're not a parser, treat as private
204
- var $stack = array('element' => array (), 'ns' => array (), 'xmlns' => array (), 'xml:base' => array ()); // stack of XML data
205
-
206
- var $inchannel = false;
207
- var $initem = false;
208
-
209
- var $incontent = array(); // non-empty if in namespaced XML content field
210
- var $xml_escape = false; // true when accepting namespaced XML
211
- var $exclude_top = false; // true when Atom 1.0 type="xhtml"
212
-
213
- var $intextinput = false;
214
- var $inimage = false;
215
- var $root_namespaces = array();
216
- var $current_namespace = false;
217
- var $working_namespace_table = array();
218
-
219
- /**
220
- * Set up XML parser, parse source, and return populated RSS object..
221
- *
222
- * @param string $source string containing the RSS to be parsed
223
- *
224
- * NOTE: Probably a good idea to leave the encoding options alone unless
225
- * you know what you're doing as PHP's character set support is
226
- * a little weird.
227
- *
228
- * NOTE: A lot of this is unnecessary but harmless with PHP5
229
- *
230
- *
231
- * @param string $output_encoding output the parsed RSS in this character
232
- * set defaults to ISO-8859-1 as this is PHP's
233
- * default.
234
- *
235
- * NOTE: might be changed to UTF-8 in future
236
- * versions.
237
- *
238
- * @param string $input_encoding the character set of the incoming RSS source.
239
- * Leave blank and Magpie will try to figure it
240
- * out.
241
- *
242
- *
243
- * @param bool $detect_encoding if false Magpie won't attempt to detect
244
- * source encoding. (caveat emptor)
245
- *
246
- */
247
- function MagpieRSS ($source, $output_encoding='ISO-8859-1',
248
- $input_encoding=null, $detect_encoding=true, $base_uri=null)
249
- {
250
- # if PHP xml isn't compiled in, die
251
- #
252
- if (!function_exists('xml_parser_create')) {
253
- $this->error( "Failed to load PHP's XML Extension. " .
254
- "http://www.php.net/manual/en/ref.xml.php",
255
- E_USER_ERROR );
256
- }
257
-
258
- list($parser, $source) = $this->create_parser($source,
259
- $output_encoding, $input_encoding, $detect_encoding);
260
-
261
-
262
- if (!is_resource($parser)) {
263
- $this->error( "Failed to create an instance of PHP's XML parser. " .
264
- "http://www.php.net/manual/en/ref.xml.php",
265
- E_USER_ERROR );
266
- }
267
-
268
-
269
- $this->parser = $parser;
270
-
271
- # pass in parser, and a reference to this object
272
- # setup handlers
273
- #
274
- xml_set_object( $this->parser, $this );
275
- xml_set_element_handler($this->parser,
276
- 'feed_start_element', 'feed_end_element' );
277
-
278
- xml_set_character_data_handler( $this->parser, 'feed_cdata' );
279
-
280
- $this->stack['xml:base'] = array($base_uri);
281
-
282
- $status = xml_parse( $this->parser, $source );
283
-
284
- if (! $status ) {
285
- $errorcode = xml_get_error_code( $this->parser );
286
- if ( $errorcode != XML_ERROR_NONE ) {
287
- $xml_error = xml_error_string( $errorcode );
288
- $error_line = xml_get_current_line_number($this->parser);
289
- $error_col = xml_get_current_column_number($this->parser);
290
- $errormsg = "$xml_error at line $error_line, column $error_col";
291
-
292
- $this->error( $errormsg );
293
- }
294
- }
295
-
296
- xml_parser_free( $this->parser );
297
-
298
- $this->normalize();
299
- }
300
-
301
- function feed_start_element($p, $element, &$attributes) {
302
- $el = strtolower($element);
303
-
304
- $namespaces = end($this->stack['xmlns']);
305
- $baseuri = end($this->stack['xml:base']);
306
-
307
- if (isset($attributes['xml:base'])) {
308
- $baseuri = Relative_URI::resolve($attributes['xml:base'], $baseuri);
309
- }
310
- array_push($this->stack['xml:base'], $baseuri);
311
-
312
- // scan for xml namespace declarations. ugly ugly ugly.
313
- // theoretically we could use xml_set_start_namespace_decl_handler and
314
- // xml_set_end_namespace_decl_handler to handle this more elegantly, but
315
- // support for these is buggy
316
- foreach ($attributes as $attr => $value) {
317
- if ( preg_match('/^xmlns(\:([A-Z_a-z].*))?$/', $attr, $match) ) {
318
- $ns = (isset($match[2]) ? $match[2] : '');
319
- $namespaces[$ns] = $value;
320
- }
321
- }
322
-
323
- array_push($this->stack['xmlns'], $namespaces);
324
-
325
- // check for a namespace, and split if found
326
- // Don't munge content tags
327
- $ns = $this->xmlns($element);
328
- if ( empty($this->incontent) ) {
329
- $el = strtolower($ns['element']);
330
- $this->current_namespace = $ns['effective'];
331
- array_push($this->stack['ns'], $ns['effective']);
332
- }
333
-
334
- $nsc = $ns['canonical']; $nse = $ns['element'];
335
- if ( isset($this->_XMLBASE_RESOLVE[$nsc][$nse]) ) {
336
- if (isset($this->_XMLBASE_RESOLVE[$nsc][$nse]['*xml'])) {
337
- $attributes['xml:base'] = $baseuri;
338
- }
339
- foreach ($attributes as $key => $value) {
340
- if (isset($this->_XMLBASE_RESOLVE[$nsc][$nse][strtolower($key)])) {
341
- $attributes[$key] = Relative_URI::resolve($attributes[$key], $baseuri);
342
- }
343
- }
344
- }
345
-
346
- $attrs = array_change_key_case($attributes, CASE_LOWER);
347
-
348
- # if feed type isn't set, then this is first element of feed
349
- # identify feed from root element
350
- #
351
- if (!isset($this->feed_type) ) {
352
- if ( $el == 'rdf' ) {
353
- $this->feed_type = RSS;
354
- $this->root_namespaces = array('rss', 'rdf');
355
- $this->feed_version = '1.0';
356
- }
357
- elseif ( $el == 'rss' ) {
358
- $this->feed_type = RSS;
359
- $this->root_namespaces = array('rss');
360
- $this->feed_version = $attrs['version'];
361
- }
362
- elseif ( $el == 'feed' ) {
363
- $this->feed_type = ATOM;
364
- $this->root_namespaces = array('atom');
365
- if ($ns['uri'] == 'http://www.w3.org/2005/Atom') { // Atom 1.0
366
- $this->feed_version = '1.0';
367
- }
368
- else { // Atom 0.3, probably.
369
- $this->feed_version = $attrs['version'];
370
- }
371
- $this->inchannel = true;
372
- }
373
- return;
374
- }
375
-
376
- // if we're inside a namespaced content construct, treat tags as text
377
- if ( !empty($this->incontent) )
378
- {
379
- if ((count($this->incontent) > 1) or !$this->exclude_top) {
380
- if ($ns['effective']=='xhtml') {
381
- $tag = $ns['element'];
382
- }
383
- else {
384
- $tag = $element;
385
- $xmlns = 'xmlns';
386
- if (strlen($ns['prefix'])>0) {
387
- $xmlns = $xmlns . ':' . $ns['prefix'];
388
- }
389
- $attributes[$xmlns] = $ns['uri']; // make sure it's visible
390
- }
391
-
392
- // if tags are inlined, then flatten
393
- $attrs_str = join(' ',
394
- array_map(array($this, 'map_attrs'),
395
- array_keys($attributes),
396
- array_values($attributes) )
397
- );
398
-
399
- if (strlen($attrs_str) > 0) { $attrs_str = ' '.$attrs_str; }
400
- $this->append_content( "<{$tag}{$attrs_str}>" );
401
- }
402
- array_push($this->incontent, $ns); // stack for parsing content XML
403
- }
404
-
405
- elseif ( $el == 'channel' ) {
406
- $this->inchannel = true;
407
- }
408
-
409
- elseif ($el == 'item' or $el == 'entry' )
410
- {
411
- $this->initem = true;
412
- if ( isset($attrs['rdf:about']) ) {
413
- $this->current_item['about'] = $attrs['rdf:about'];
414
- }
415
- }
416
-
417
- // if we're in the default namespace of an RSS feed,
418
- // record textinput or image fields
419
- elseif (
420
- $this->feed_type == RSS and
421
- $this->current_namespace == '' and
422
- $el == 'textinput' )
423
- {
424
- $this->intextinput = true;
425
- }
426
-
427
- elseif (
428
- $this->feed_type == RSS and
429
- $this->current_namespace == '' and
430
- $el == 'image' )
431
- {
432
- $this->inimage = true;
433
- }
434
-
435
- // set stack[0] to current element
436
- else {
437
- // Atom support many links per containing element.
438
- // Magpie treats link elements of type rel='alternate'
439
- // as being equivalent to RSS's simple link element.
440
-
441
- $atom_link = false;
442
- if ( ($ns['canonical']=='atom') and $el == 'link') {
443
- $atom_link = true;
444
- if (isset($attrs['rel']) and $attrs['rel'] != 'alternate') {
445
- $el = $el . "_" . $attrs['rel']; // pseudo-element names for Atom link elements
446
- }
447
- }
448
- # handle atom content constructs
449
- elseif ( ($ns['canonical']=='atom') and in_array($el, $this->_ATOM_CONTENT_CONSTRUCTS) )
450
- {
451
- // avoid clashing w/ RSS mod_content
452
- if ($el == 'content' ) {
453
- $el = 'atom_content';
454
- }
455
-
456
- // assume that everything accepts namespaced XML
457
- // (that will pass through some non-validating feeds;
458
- // but so what? this isn't a validating parser)
459
- $this->incontent = array();
460
- array_push($this->incontent, $ns); // start a stack
461
-
462
- $this->xml_escape = $this->accepts_namespaced_xml($attrs);
463
-
464
- if ( isset($attrs['type']) and trim(strtolower($attrs['type']))=='xhtml') {
465
- $this->exclude_top = true;
466
- } else {
467
- $this->exclude_top = false;
468
- }
469
- }
470
- # Handle inline XHTML body elements --CWJ
471
- elseif ($ns['effective']=='xhtml' and in_array($el, $this->_XHTML_CONTENT_CONSTRUCTS)) {
472
- $this->current_namespace = 'xhtml';
473
- $this->incontent = array();
474
- array_push($this->incontent, $ns); // start a stack
475
-
476
- $this->xml_escape = true;
477
- $this->exclude_top = false;
478
- }
479
-
480
- array_unshift($this->stack['element'], $el);
481
- $elpath = join('_', array_reverse($this->stack['element']));
482
-
483
- $n = $this->element_count($elpath);
484
- $this->element_count($elpath, $n+1);
485
-
486
- if ($n > 0) {
487
- array_shift($this->stack['element']);
488
- array_unshift($this->stack['element'], $el.'#'.($n+1));
489
- $elpath = join('_', array_reverse($this->stack['element']));
490
- }
491
-
492
- // this makes the baby Jesus cry, but we can't do it in normalize()
493
- // because we've made the element name for Atom links unpredictable
494
- // by tacking on the relation to the end. -CWJ
495
- if ($atom_link and isset($attrs['href'])) {
496
- $this->append($elpath, $attrs['href']);
497
- }
498
-
499
- // add attributes
500
- if (count($attrs) > 0) {
501
- $this->append($elpath.'@', join(',', array_keys($attrs)));
502
- foreach ($attrs as $attr => $value) {
503
- $this->append($elpath.'@'.$attr, $value);
504
- }
505
- }
506
- }
507
- }
508
-
509
- function feed_cdata ($p, $text) {
510
- if ($this->incontent) {
511
- if ($this->xml_escape) { $text = htmlspecialchars($text, ENT_COMPAT, $this->encoding); }
512
- $this->append_content( $text );
513
- } else {
514
- $current_el = join('_', array_reverse($this->stack['element']));
515
- $this->append($current_el, $text);
516
- }
517
- }
518
-
519
- function feed_end_element ($p, $el) {
520
- $closer = $this->xmlns($el);
521
-
522
- if ( $this->incontent ) {
523
- $opener = array_pop($this->incontent);
524
-
525
- // balance tags properly
526
- // note: i don't think this is actually neccessary
527
- if ($opener != $closer) {
528
- array_push($this->incontent, $opener);
529
- $this->append_content("<$el />");
530
- } elseif ($this->incontent) { // are we in the content construct still?
531
- if ((count($this->incontent) > 1) or !$this->exclude_top) {
532
- if ($closer['effective']=='xhtml') {
533
- $tag = $closer['element'];
534
- }
535
- else {
536
- $tag = $el;
537
- }
538
- $this->append_content("</$tag>");
539
- }
540
- } else { // if we're done with the content construct, shift the opening of the content construct off the normal stack
541
- array_shift( $this->stack['element'] );
542
- }
543
- }
544
- elseif ($closer['effective'] == '') {
545
- $el = strtolower($closer['element']);
546
- if ( $el == 'item' or $el == 'entry' ) {
547
- $this->items[] = $this->current_item;
548
- $this->current_item = array();
549
- $this->initem = false;
550
- $this->current_category = 0;
551
- }
552
- elseif ($this->feed_type == RSS and $el == 'textinput' ) {
553
- $this->intextinput = false;
554
- }
555
- elseif ($this->feed_type == RSS and $el == 'image' ) {
556
- $this->inimage = false;
557
- }
558
- elseif ($el == 'channel' or $el == 'feed' ) {
559
- $this->inchannel = false;
560
- } else {
561
- $nsc = $closer['canonical']; $nse = $closer['element'];
562
- if (isset($this->_XMLBASE_RESOLVE[$nsc][$nse]['*content'])) {
563
- // Resolve relative URI in content of tag
564
- $this->dereference_current_element();
565
- }
566
- array_shift( $this->stack['element'] );
567
- }
568
- } else {
569
- $nsc = $closer['canonical']; $nse = strtolower($closer['element']);
570
- if (isset($this->_XMLBASE_RESOLVE[$nsc][$nse]['*content'])) {
571
- // Resolve relative URI in content of tag
572
- $this->dereference_current_element();
573
- }
574
- array_shift( $this->stack['element'] );
575
- }
576
-
577
- if ( !$this->incontent ) { // Don't munge the namespace after finishing with elements in namespaced content constructs -CWJ
578
- $this->current_namespace = array_pop($this->stack['ns']);
579
- }
580
- array_pop($this->stack['xmlns']);
581
- array_pop($this->stack['xml:base']);
582
- }
583
-
584
- // Namespace handling functions
585
- function xmlns ($element) {
586
- $namespaces = end($this->stack['xmlns']);
587
- $ns = '';
588
- if ( strpos( $element, ':' ) ) {
589
- list($ns, $element) = split( ':', $element, 2);
590
- }
591
-
592
- $uri = (isset($namespaces[$ns]) ? $namespaces[$ns] : null);
593
-
594
- if (!is_null($uri)) {
595
- $canonical = (
596
- isset($this->_XMLNS_FAMILIAR[$uri])
597
- ? $this->_XMLNS_FAMILIAR[$uri]
598
- : $uri
599
- );
600
- } else {
601
- $canonical = $ns;
602
- }
603
-
604
- if (in_array($canonical, $this->root_namespaces)) {
605
- $effective = '';
606
- } else {
607
- $effective = $canonical;
608
- }
609
-
610
- return array('effective' => $effective, 'canonical' => $canonical, 'prefix' => $ns, 'uri' => $uri, 'element' => $element);
611
- }
612
-
613
- // Utility functions for accessing data structure
614
-
615
- // for smart, namespace-aware methods...
616
- function magpie_data ($el, $method, $text = NULL) {
617
- $ret = NULL;
618
- if ($el) {
619
- if (is_array($method)) {
620
- $el = $this->{$method['key']}($el);
621
- $method = $method['value'];
622
- }
623
-
624
- if ( $this->current_namespace ) {
625
- if ( $this->initem ) {
626
- $ret = $this->{$method} (
627
- $this->current_item[ $this->current_namespace ][ $el ],
628
- $text
629
- );
630
- }
631
- elseif ($this->inchannel) {
632
- $ret = $this->{$method} (
633
- $this->channel[ $this->current_namespace][ $el ],
634
- $text
635
- );
636
- }
637
- elseif ($this->intextinput) {
638
- $ret = $this->{$method} (
639
- $this->textinput[ $this->current_namespace][ $el ],
640
- $text
641
- );
642
- }
643
- elseif ($this->inimage) {
644
- $ret = $this->{$method} (
645
- $this->image[ $this->current_namespace ][ $el ], $text );
646
- }
647
- }
648
- else {
649
- if ( $this->initem ) {
650
- $ret = $this->{$method} (
651
- $this->current_item[ $el ], $text);
652
- }
653
- elseif ($this->intextinput) {
654
- $ret = $this->{$method} (
655
- $this->textinput[ $el ], $text );
656
- }
657
- elseif ($this->inimage) {
658
- $ret = $this->{$method} (
659
- $this->image[ $el ], $text );
660
- }
661
- elseif ($this->inchannel) {
662
- $ret = $this->{$method} (
663
- $this->channel[ $el ], $text );
664
- }
665
- }
666
- }
667
- return $ret;
668
- }
669
-
670
- function concat (&$str1, $str2="") {
671
- if (!isset($str1) ) {
672
- $str1="";
673
- }
674
- $str1 .= $str2;
675
- }
676
-
677
- function retrieve_value (&$el, $text /*ignore*/) {
678
- return $el;
679
- }
680
- function replace_value (&$el, $text) {
681
- $el = $text;
682
- }
683
- function counter_key ($el) {
684
- return $el.'#';
685
- }
686
-
687
-
688
- function append_content($text) {
689
- $construct = reset($this->incontent);
690
- $ns = $construct['effective'];
691
-
692
- // Keeping data about parent elements is necessary to
693
- // properly handle atom:source and its children elements
694
- $tag = join('_', array_reverse($this->stack['element']));
695
-
696
- if ( $this->initem ) {
697
- if ($ns) {
698
- $this->concat( $this->current_item[$ns][$tag], $text );
699
- } else {
700
- $this->concat( $this->current_item[$tag], $text );
701
- }
702
- }
703
- elseif ( $this->inchannel ) {
704
- if ($this->current_namespace) {
705
- $this->concat( $this->channel[$ns][$tag], $text );
706
- } else {
707
- $this->concat( $this->channel[$tag], $text );
708
- }
709
- }
710
- }
711
-
712
- // smart append - field and namespace aware
713
- function append($el, $text) {
714
- $this->magpie_data($el, 'concat', $text);
715
- }
716
-
717
- function dereference_current_element () {
718
- $el = join('_', array_reverse($this->stack['element']));
719
- $base = end($this->stack['xml:base']);
720
- $uri = $this->magpie_data($el, 'retrieve_value');
721
- $this->magpie_data($el, 'replace_value', Relative_URI::resolve($uri, $base));
722
- }
723
-
724
- // smart count - field and namespace aware
725
- function element_count ($el, $set = NULL) {
726
- if (!is_null($set)) {
727
- $ret = $this->magpie_data($el, array('key' => 'counter_key', 'value' => 'replace_value'), $set);
728
- }
729
- $ret = $this->magpie_data($el, array('key' => 'counter_key', 'value' => 'retrieve_value'));
730
- return ($ret ? $ret : 0);
731
- }
732
-
733
- function normalize_enclosure (&$source, $from, &$dest, $to, $i) {
734
- $id_from = $this->element_id($from, $i);
735
- $id_to = $this->element_id($to, $i);
736
- if (isset($source["{$id_from}@"])) {
737
- foreach (explode(',', $source["{$id_from}@"]) as $attr) {
738
- if ($from=='link_enclosure' and $attr=='href') { // from Atom
739
- $dest["{$id_to}@url"] = $source["{$id_from}@{$attr}"];
740
- $dest["{$id_to}"] = $source["{$id_from}@{$attr}"];
741
- }
742
- elseif ($from=='enclosure' and $attr=='url') { // from RSS
743
- $dest["{$id_to}@href"] = $source["{$id_from}@{$attr}"];
744
- $dest["{$id_to}"] = $source["{$id_from}@{$attr}"];
745
- }
746
- else {
747
- $dest["{$id_to}@{$attr}"] = $source["{$id_from}@{$attr}"];
748
- }
749
- }
750
- }
751
- }
752
-
753
- function normalize_atom_person (&$source, $person, &$dest, $to, $i) {
754
- $id = $this->element_id($person, $i);
755
- $id_to = $this->element_id($to, $i);
756
-
757
- // Atom 0.3 <=> Atom 1.0
758
- if ($this->feed_version >= 1.0) { $used = 'uri'; $norm = 'url'; }
759
- else { $used = 'url'; $norm = 'uri'; }
760
-
761
- if (isset($source["{$id}_{$used}"])) {
762
- $dest["{$id_to}_{$norm}"] = $source["{$id}_{$used}"];
763
- }
764
-
765
- // Atom to RSS 2.0 and Dublin Core
766
- // RSS 2.0 person strings should be valid e-mail addresses if possible.
767
- if (isset($source["{$id}_email"])) {
768
- $rss_author = $source["{$id}_email"];
769
- }
770
- if (isset($source["{$id}_name"])) {
771
- $rss_author = $source["{$id}_name"]
772
- . (isset($rss_author) ? " <$rss_author>" : '');
773
- }
774
- if (isset($rss_author)) {
775
- $source[$id] = $rss_author; // goes to top-level author or contributor
776
- $dest[$id_to] = $rss_author; // goes to dc:creator or dc:contributor
777
- }
778
- }
779
-
780
- // Normalize Atom 1.0 and RSS 2.0 categories to Dublin Core...
781
- function normalize_category (&$source, $from, &$dest, $to, $i) {
782
- $cat_id = $this->element_id($from, $i);
783
- $dc_id = $this->element_id($to, $i);
784
-
785
- // first normalize category elements: Atom 1.0 <=> RSS 2.0
786
- if ( isset($source["{$cat_id}@term"]) ) { // category identifier
787
- $source[$cat_id] = $source["{$cat_id}@term"];
788
- } elseif ( $this->feed_type == RSS ) {
789
- $source["{$cat_id}@term"] = $source[$cat_id];
790
- }
791
-
792
- if ( isset($source["{$cat_id}@scheme"]) ) { // URI to taxonomy
793
- $source["{$cat_id}@domain"] = $source["{$cat_id}@scheme"];
794
- } elseif ( isset($source["{$cat_id}@domain"]) ) {
795
- $source["{$cat_id}@scheme"] = $source["{$cat_id}@domain"];
796
- }
797
-
798
- // Now put the identifier into dc:subject
799
- $dest[$dc_id] = $source[$cat_id];
800
- }
801
-
802
- // ... or vice versa
803
- function normalize_dc_subject (&$source, $from, &$dest, $to, $i) {
804
- $dc_id = $this->element_id($from, $i);
805
- $cat_id = $this->element_id($to, $i);
806
-
807
- $dest[$cat_id] = $source[$dc_id]; // RSS 2.0
808
- $dest["{$cat_id}@term"] = $source[$dc_id]; // Atom 1.0
809
- }
810
-
811
- // simplify the logic for normalize(). Makes sure that count of elements and
812
- // each of multiple elements is normalized properly. If you need to mess
813
- // with things like attributes or change formats or the like, pass it a
814
- // callback to handle each element.
815
- function normalize_element (&$source, $from, &$dest, $to, $via = NULL) {
816
- if (isset($source[$from]) or isset($source["{$from}#"])) {
817
- if (isset($source["{$from}#"])) {
818
- $n = $source["{$from}#"];
819
- $dest["{$to}#"] = $source["{$from}#"];
820
- }
821
- else { $n = 1; }
822
-
823
- for ($i = 1; $i <= $n; $i++) {
824
- if (isset($via)) { // custom callback for ninja attacks
825
- $this->{$via}($source, $from, $dest, $to, $i);
826
- }
827
- else { // just make it the same
828
- $from_id = $this->element_id($from, $i);
829
- $to_id = $this->element_id($to, $i);
830
- $dest[$to_id] = $source[$from_id];
831
- }
832
- }
833
- }
834
- }
835
-
836
- function normalize () {
837
- // if atom populate rss fields and normalize 0.3 and 1.0 feeds
838
- if ( $this->is_atom() ) {
839
- // Atom 1.0 elements <=> Atom 0.3 elements (Thanks, o brilliant wordsmiths of the Atom 1.0 standard!)
840
- if ($this->feed_version < 1.0) {
841
- $this->normalize_element($this->channel, 'tagline', $this->channel, 'subtitle');
842
- $this->normalize_element($this->channel, 'copyright', $this->channel, 'rights');
843
- $this->normalize_element($this->channel, 'modified', $this->channel, 'updated');
844
- } else {
845
- $this->normalize_element($this->channel, 'subtitle', $this->channel, 'tagline');
846
- $this->normalize_element($this->channel, 'rights', $this->channel, 'copyright');
847
- $this->normalize_element($this->channel, 'updated', $this->channel, 'modified');
848
- }
849
- $this->normalize_element($this->channel, 'author', $this->channel['dc'], 'creator', 'normalize_atom_person');
850
- $this->normalize_element($this->channel, 'contributor', $this->channel['dc'], 'contributor', 'normalize_atom_person');
851
-
852
- // Atom elements to RSS elements
853
- $this->normalize_element($this->channel, 'subtitle', $this->channel, 'description');
854
-
855
- if ( isset($this->channel['logo']) ) {
856
- $this->normalize_element($this->channel, 'logo', $this->image, 'url');
857
- $this->normalize_element($this->channel, 'link', $this->image, 'link');
858
- $this->normalize_element($this->channel, 'title', $this->image, 'title');
859
- }
860
-
861
- for ( $i = 0; $i < count($this->items); $i++) {
862
- $item = $this->items[$i];
863
-
864
- // Atom 1.0 elements <=> Atom 0.3 elements
865
- if ($this->feed_version < 1.0) {
866
- $this->normalize_element($item, 'modified', $item, 'updated');
867
- $this->normalize_element($item, 'issued', $item, 'published');
868
- } else {
869
- $this->normalize_element($item, 'updated', $item, 'modified');
870
- $this->normalize_element($item, 'published', $item, 'issued');
871
- }
872
-
873
- // "If an atom:entry element does not contain
874
- // atom:author elements, then the atom:author elements
875
- // of the contained atom:source element are considered
876
- // to apply. In an Atom Feed Document, the atom:author
877
- // elements of the containing atom:feed element are
878
- // considered to apply to the entry if there are no
879
- // atom:author elements in the locations described
880
- // above." <http://atompub.org/2005/08/17/draft-ietf-atompub-format-11.html#rfc.section.4.2.1>
881
- if (!isset($item["author#"])) {
882
- if (isset($item["source_author#"])) { // from aggregation source
883
- $source = $item;
884
- $author = "source_author";
885
- } elseif (isset($this->channel["author#"])) { // from containing feed
886
- $source = $this->channel;
887
- $author = "author";
888
- } else {
889
- $author = null;
890
- }
891
-
892
- if (!is_null($author)) {
893
- $item["author#"] = $source["{$author}#"];
894
- for ($au = 1; $au <= $item["author#"]; $au++) {
895
- $id_to = $this->element_id('author', $au);
896
- $id_from = $this->element_id($author, $au);
897
-
898
- $item[$id_to] = $source[$id_from];
899
- foreach (array('name', 'email', 'uri', 'url') as $what) {
900
- if (isset($source["{$id_from}_{$what}"])) {
901
- $item["{$id_to}_{$what}"] = $source["{$id_from}_{$what}"];
902
- }
903
- }
904
- }
905
- }
906
- }
907
-
908
- // Atom elements to RSS elements
909
- $this->normalize_element($item, 'author', $item['dc'], 'creator', 'normalize_atom_person');
910
- $this->normalize_element($item, 'contributor', $item['dc'], 'contributor', 'normalize_atom_person');
911
- $this->normalize_element($item, 'summary', $item, 'description');
912
- $this->normalize_element($item, 'atom_content', $item['content'], 'encoded');
913
- $this->normalize_element($item, 'link_enclosure', $item, 'enclosure', 'normalize_enclosure');
914
-
915
- // Categories
916
- if ( isset($item['category#']) ) { // Atom 1.0 categories to dc:subject and RSS 2.0 categories
917
- $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category');
918
- }
919
- elseif ( isset($item['dc']['subject#']) ) { // dc:subject to Atom 1.0 and RSS 2.0 categories
920
- $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject');
921
- }
922
-
923
- // Normalized item timestamp
924
- $atom_date = (isset($item['published']) ) ? $item['published'] : $item['updated'];
925
- if ( $atom_date ) {
926
- $epoch = @parse_w3cdtf($atom_date);
927
- if ($epoch and $epoch > 0) {
928
- $item['date_timestamp'] = $epoch;
929
- }
930
- }
931
-
932
- $this->items[$i] = $item;
933
- }
934
- }
935
- elseif ( $this->is_rss() ) {
936
- // RSS elements to Atom elements
937
- $this->normalize_element($this->channel, 'description', $this->channel, 'tagline'); // Atom 0.3
938
- $this->normalize_element($this->channel, 'description', $this->channel, 'subtitle'); // Atom 1.0 (yay wordsmithing!)
939
- $this->normalize_element($this->image, 'url', $this->channel, 'logo');
940
-
941
- for ( $i = 0; $i < count($this->items); $i++) {
942
- $item = $this->items[$i];
943
-
944
- // RSS elements to Atom elements
945
- $this->normalize_element($item, 'description', $item, 'summary');
946
- $this->normalize_element($item, 'enclosure', $item, 'link_enclosure', 'normalize_enclosure');
947
-
948
- // Categories
949
- if ( isset($item['category#']) ) { // RSS 2.0 categories to dc:subject and Atom 1.0 categories
950
- $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category');
951
- }
952
- elseif ( isset($item['dc']['subject#']) ) { // dc:subject to Atom 1.0 and RSS 2.0 categories
953
- $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject');
954
- }
955
-
956
- // Normalized item timestamp
957
- if ( $this->is_rss() == '1.0' and isset($item['dc']['date']) ) {
958
- $epoch = @parse_w3cdtf($item['dc']['date']);
959
- if ($epoch and $epoch > 0) {
960
- $item['date_timestamp'] = $epoch;
961
- }
962
- }
963
- elseif ( isset($item['pubdate']) ) {
964
- $epoch = @strtotime($item['pubdate']);
965
- if ($epoch > 0) {
966
- $item['date_timestamp'] = $epoch;
967
- }
968
- }
969
-
970
- $this->items[$i] = $item;
971
- }
972
- }
973
- }
974
-
975
-
976
- function is_rss () {
977
- if ( $this->feed_type == RSS ) {
978
- return $this->feed_version;
979
- }
980
- else {
981
- return false;
982
- }
983
- }
984
-
985
- function is_atom() {
986
- if ( $this->feed_type == ATOM ) {
987
- return $this->feed_version;
988
- }
989
- else {
990
- return false;
991
- }
992
- }
993
-
994
- /**
995
- * return XML parser, and possibly re-encoded source
996
- *
997
- */
998
- function create_parser($source, $out_enc, $in_enc, $detect) {
999
- if ( substr(phpversion(),0,1) == 5) {
1000
- $parser = $this->php5_create_parser($in_enc, $detect);
1001
- }
1002
- else {
1003
- list($parser, $source) = $this->php4_create_parser($source, $in_enc, $detect);
1004
- }
1005
- if ($out_enc) {
1006
- $this->encoding = $out_enc;
1007
- xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $out_enc);
1008
- }
1009
- xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
1010
- return array($parser, $source);
1011
- }
1012
-
1013
- /**
1014
- * Instantiate an XML parser under PHP5
1015
- *
1016
- * PHP5 will do a fine job of detecting input encoding
1017
- * if passed an empty string as the encoding.
1018
- *
1019
- * All hail libxml2!
1020
- *
1021
- */
1022
- function php5_create_parser($in_enc, $detect) {
1023
- // by default php5 does a fine job of detecting input encodings
1024
- if(!$detect && $in_enc) {
1025
- return xml_parser_create($in_enc);
1026
- }
1027
- else {
1028
- return xml_parser_create('');
1029
- }
1030
- }
1031
-
1032
- /**
1033
- * Instaniate an XML parser under PHP4
1034
- *
1035
- * Unfortunately PHP4's support for character encodings
1036
- * and especially XML and character encodings sucks. As
1037
- * long as the documents you parse only contain characters
1038
- * from the ISO-8859-1 character set (a superset of ASCII,
1039
- * and a subset of UTF-8) you're fine. However once you
1040
- * step out of that comfy little world things get mad, bad,
1041
- * and dangerous to know.
1042
- *
1043
- * The following code is based on SJM's work with FoF
1044
- * @see http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
1045
- *
1046
- */
1047
- function php4_create_parser($source, $in_enc, $detect) {
1048
- if ( !$detect ) {
1049
- return array(xml_parser_create($in_enc), $source);
1050
- }
1051
-
1052
- if (!$in_enc) {
1053
- if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $source, $m)) {
1054
- $in_enc = strtoupper($m[1]);
1055
- $this->source_encoding = $in_enc;
1056
- }
1057
- else {
1058
- $in_enc = 'UTF-8';
1059
- }
1060
- }
1061
-
1062
- if ($this->known_encoding($in_enc)) {
1063
- return array(xml_parser_create($in_enc), $source);
1064
- }
1065
-
1066
- // the dectected encoding is not one of the simple encodings PHP knows
1067
-
1068
- // attempt to use the iconv extension to
1069
- // cast the XML to a known encoding
1070
- // @see http://php.net/iconv
1071
-
1072
- if (function_exists('iconv')) {
1073
- $encoded_source = iconv($in_enc,'UTF-8', $source);
1074
- if ($encoded_source) {
1075
- return array(xml_parser_create('UTF-8'), $encoded_source);
1076
- }
1077
- }
1078
-
1079
- // iconv didn't work, try mb_convert_encoding
1080
- // @see http://php.net/mbstring
1081
- if(function_exists('mb_convert_encoding')) {
1082
- $encoded_source = mb_convert_encoding($source, 'UTF-8', $in_enc );
1083
- if ($encoded_source) {
1084
- return array(xml_parser_create('UTF-8'), $encoded_source);
1085
- }
1086
- }
1087
-
1088
- // else
1089
- $this->error("Feed is in an unsupported character encoding. ($in_enc) " .
1090
- "You may see strange artifacts, and mangled characters.",
1091
- E_USER_NOTICE);
1092
-
1093
- return array(xml_parser_create(), $source);
1094
- }
1095
-
1096
- function known_encoding($enc) {
1097
- $enc = strtoupper($enc);
1098
- if ( in_array($enc, $this->_KNOWN_ENCODINGS) ) {
1099
- return $enc;
1100
- }
1101
- else {
1102
- return false;
1103
- }
1104
- }
1105
-
1106
- function error ($errormsg, $lvl=E_USER_WARNING) {
1107
- // append PHP's error message if track_errors enabled
1108
- if ( isset($php_errormsg) ) {
1109
- $errormsg .= " ($php_errormsg)";
1110
- }
1111
- if ( MAGPIE_DEBUG ) {
1112
- trigger_error( $errormsg, $lvl);
1113
- }
1114
- else {
1115
- error_log( $errormsg, 0);
1116
- }
1117
-
1118
- $notices = E_USER_NOTICE|E_NOTICE;
1119
- if ( $lvl&$notices ) {
1120
- $this->WARNING = $errormsg;
1121
- } else {
1122
- $this->ERROR = $errormsg;
1123
- }
1124
- }
1125
-
1126
- // magic ID function for multiple elemenets.
1127
- // can be called as static MagpieRSS::element_id()
1128
- function element_id ($el, $counter) {
1129
- return $el . (($counter > 1) ? '#'.$counter : '');
1130
- }
1131
-
1132
- function map_attrs($k, $v) {
1133
- return $k.'="'.htmlspecialchars($v, ENT_COMPAT, $this->encoding).'"';
1134
- }
1135
-
1136
- function accepts_namespaced_xml ($attrs) {
1137
- $mode = (isset($attrs['mode']) ? trim(strtolower($attrs['mode'])) : 'xml');
1138
- $type = (isset($attrs['type']) ? trim(strtolower($attrs['type'])) : null);
1139
- if ($this->feed_type == ATOM and $this->feed_version < 1.0) {
1140
- if ($mode=='xml' and preg_match(':[/+](html|xml)$:i', $type)) {
1141
- $ret = true;
1142
- } else {
1143
- $ret = false;
1144
- }
1145
- } elseif ($this->feed_type == ATOM and $this->feed_version >= 1.0) {
1146
- if ($type=='xhtml' or preg_match(':[/+]xml$:i', $type)) {
1147
- $ret = true;
1148
- } else {
1149
- $ret = false;
1150
- }
1151
- } else {
1152
- $ret = false; // Don't munge unless you're sure
1153
- }
1154
- return $ret;
1155
- }
1156
- } // end class RSS
1157
-
1158
-
1159
- // patch to support medieval versions of PHP4.1.x,
1160
- // courtesy, Ryan Currie, ryan@digibliss.com
1161
-
1162
- if (!function_exists('array_change_key_case')) {
1163
- define("CASE_UPPER",1);
1164
- define("CASE_LOWER",0);
1165
-
1166
-
1167
- function array_change_key_case($array,$case=CASE_LOWER) {
1168
- if ($case==CASE_LOWER) $cmd='strtolower';
1169
- elseif ($case==CASE_UPPER) $cmd='strtoupper';
1170
- foreach($array as $key=>$value) {
1171
- $output[$cmd($key)]=$value;
1172
- }
1173
- return $output;
1174
- }
1175
-
1176
- }
1177
-
1178
- ################################################################################
1179
- ## WordPress: Load in Snoopy from wp-includes ##################################
1180
- ################################################################################
1181
-
1182
- if (!function_exists('wp_remote_request')) :
1183
- require_once( dirname(__FILE__) . '/class-snoopy.php');
1184
- endif;
1185
-
1186
- ################################################################################
1187
- ## rss_fetch.inc: from MagpieRSS 0.8a ##########################################
1188
- ################################################################################
1189
-
1190
- /*=======================================================================*\
1191
- Function: fetch_rss:
1192
- Purpose: return RSS object for the give url
1193
- maintain the cache
1194
- Input: url of RSS file
1195
- Output: parsed RSS object (see rss_parse.inc)
1196
-
1197
- NOTES ON CACHEING:
1198
- If caching is on (MAGPIE_CACHE_ON) fetch_rss will first check the cache.
1199
-
1200
- NOTES ON RETRIEVING REMOTE FILES:
1201
- If conditional gets are on (MAGPIE_CONDITIONAL_GET_ON) fetch_rss will
1202
- return a cached object, and touch the cache object upon recieving a
1203
- 304.
1204
-
1205
- NOTES ON FAILED REQUESTS:
1206
- If there is an HTTP error while fetching an RSS object, the cached
1207
- version will be return, if it exists (and if MAGPIE_CACHE_FRESH_ONLY is off)
1208
- \*=======================================================================*/
1209
-
1210
- define('MAGPIE_VERSION', '2010.0122');
1211
-
1212
- $MAGPIE_ERROR = "";
1213
-
1214
- function fetch_rss ($url) {
1215
- // initialize constants
1216
- init();
1217
-
1218
- if ( !isset($url) ) {
1219
- error("fetch_rss called without a url");
1220
- return false;
1221
- }
1222
-
1223
- // if cache is disabled
1224
- if ( !MAGPIE_CACHE_ON ) {
1225
- // fetch file, and parse it
1226
- $resp = _fetch_remote_file( $url );
1227
- if ( is_success( $resp->status ) ) {
1228
- return _response_to_rss( $resp, $url );
1229
- }
1230
- else {
1231
- error("Failed to fetch $url and cache is off");
1232
- return false;
1233
- }
1234
- }
1235
- // else cache is ON
1236
- else {
1237
- // Flow
1238
- // 1. check cache
1239
- // 2. if there is a hit, make sure its fresh
1240
- // 3. if cached obj fails freshness check, fetch remote
1241
- // 4. if remote fails, return stale object, or error
1242
-
1243
- $cache = new RSSCache( MAGPIE_CACHE_DIR, MAGPIE_CACHE_AGE );
1244
-
1245
- if (MAGPIE_DEBUG and $cache->ERROR) {
1246
- debug($cache->ERROR, E_USER_WARNING);
1247
- }
1248
-
1249
-
1250
- $cache_status = 0; // response of check_cache
1251
- $request_headers = array(); // HTTP headers to send with fetch
1252
- $rss = 0; // parsed RSS object
1253
- $errormsg = 0; // errors, if any
1254
-
1255
- // store parsed XML by desired output encoding
1256
- // as character munging happens at parse time
1257
- $cache_key = $url . MAGPIE_OUTPUT_ENCODING;
1258
-
1259
- if (!$cache->ERROR) {
1260
- // return cache HIT, MISS, or STALE
1261
- $cache_status = $cache->check_cache( $cache_key);
1262
- }
1263
-
1264
- // if object cached, and cache is fresh, return cached obj
1265
- if ( $cache_status == 'HIT' ) {
1266
- $rss = $cache->get( $cache_key );
1267
- if ( isset($rss) and $rss ) {
1268
- // should be cache age
1269
- $rss->from_cache = 1;
1270
- if ( MAGPIE_DEBUG > 1) {
1271
- debug("MagpieRSS: Cache HIT", E_USER_NOTICE);
1272
- }
1273
- return $rss;
1274
- }
1275
- }
1276
-
1277
- // else attempt a conditional get
1278
-
1279
- // setup headers
1280
- if ( $cache_status == 'STALE' ) {
1281
- $rss = $cache->get( $cache_key );
1282
- if ( $rss and isset($rss->etag) and $rss->last_modified ) {
1283
- $request_headers['If-None-Match'] = $rss->etag;
1284
- $request_headers['If-Last-Modified'] = $rss->last_modified;
1285
- }
1286
- }
1287
-
1288
- $resp = _fetch_remote_file( $url, $request_headers );
1289
-
1290
- if (isset($resp) and $resp) {
1291
- if ($resp->status == '304' ) {
1292
- // we have the most current copy
1293
- if ( MAGPIE_DEBUG > 1) {
1294
- debug("Got 304 for $url");
1295
- }
1296
- // reset cache on 304 (at minutillo insistent prodding)
1297
- $cache->set($cache_key, $rss);
1298
- return $rss;
1299
- }
1300
- elseif ( is_success( $resp->status ) ) {
1301
- $rss = _response_to_rss( $resp, $url );
1302
- if ( $rss ) {
1303
- if (MAGPIE_DEBUG > 1) {
1304
- debug("Fetch successful");
1305
- }
1306
- // add object to cache
1307
- $cache->set( $cache_key, $rss );
1308
- return $rss;
1309
- }
1310
- }
1311
- else {
1312
- $errormsg = "Failed to fetch $url ";
1313
- if ( $resp->status == '-100' ) {
1314
- $errormsg .= "(Request timed out after " . MAGPIE_FETCH_TIME_OUT . " seconds)";
1315
- }
1316
- elseif ( $resp->error ) {
1317
- # compensate for Snoopy's annoying habbit to tacking
1318
- # on '\n'
1319
- $http_error = substr($resp->error, 0, -2);
1320
- $errormsg .= "(HTTP Error: $http_error)";
1321
- }
1322
- else {
1323
- $errormsg .= "(HTTP Response: " . $resp->response_code .')';
1324
- }
1325
- }
1326
- }
1327
- else {
1328
- $errormsg = "Unable to retrieve RSS file for unknown reasons.";
1329
- }
1330
-
1331
- // else fetch failed
1332
- debug("MagpieRSS fetch failed [$errormsg]");
1333
-
1334
- // attempt to return cached object
1335
- if ($rss) {
1336
- if ( MAGPIE_DEBUG ) {
1337
- debug("Returning STALE object for $url");
1338
- }
1339
- return $rss;
1340
- }
1341
-
1342
- // else we totally failed
1343
- error( $errormsg );
1344
-
1345
- return false;
1346
-
1347
- } // end if ( !MAGPIE_CACHE_ON ) {
1348
- } // end fetch_rss()
1349
-
1350
- /*=======================================================================*\
1351
- Function: error
1352
- Purpose: set MAGPIE_ERROR, and trigger error
1353
- \*=======================================================================*/
1354
-
1355
- function error ($errormsg, $lvl=E_USER_WARNING) {
1356
- global $MAGPIE_ERROR;
1357
-
1358
- // append PHP's error message if track_errors enabled
1359
- if ( isset($php_errormsg) ) {
1360
- $errormsg .= " ($php_errormsg)";
1361
- }
1362
- if ( $errormsg ) {
1363
- $errormsg = "MagpieRSS: $errormsg";
1364
- $MAGPIE_ERROR = $errormsg;
1365
- if ( MAGPIE_DEBUG ) {
1366
- trigger_error( $errormsg, $lvl);
1367
- } else {
1368
- error_log($errormsg, 0);
1369
- }
1370
- }
1371
- }
1372
-
1373
- function debug ($debugmsg, $lvl=E_USER_NOTICE) {
1374
- trigger_error("MagpieRSS [debug] $debugmsg", $lvl);
1375
- }
1376
-
1377
- /*=======================================================================*\
1378
- Function: magpie_error
1379
- Purpose: accessor for the magpie error variable
1380
- \*=======================================================================*/
1381
- function magpie_error ($errormsg="") {
1382
- global $MAGPIE_ERROR;
1383
-
1384
- if ( isset($errormsg) and $errormsg ) {
1385
- $MAGPIE_ERROR = $errormsg;
1386
- }
1387
-
1388
- return $MAGPIE_ERROR;
1389
- }
1390
-
1391
- /*=======================================================================*\
1392
- Function: _fetch_remote_file
1393
- Purpose: retrieve an arbitrary remote file
1394
- Input: url of the remote file
1395
- headers to send along with the request (optional)
1396
- Output: an HTTP response object (see Snoopy.class.inc)
1397
- \*=======================================================================*/
1398
- function _fetch_remote_file ($url, $headers = "" ) {
1399
- // Ensure that we have constants set up, since they are used below.
1400
- init();
1401
-
1402
- // WordPress 2.7 has deprecated Snoopy. It's still there, for now, but
1403
- // I'd rather not rely on it.
1404
- if (function_exists('wp_remote_request')) :
1405
- $resp = wp_remote_request($url, array(
1406
- 'headers' => $headers,
1407
- 'timeout' => MAGPIE_FETCH_TIME_OUT
1408
- ));
1409
-
1410
- if ( is_wp_error($resp) ) :
1411
- $error = $resp->get_error_messages();
1412
-
1413
- $client = new stdClass;
1414
- $client->status = 500;
1415
- $client->response_code = 500;
1416
- $client->error = implode(" / ", $error). "\n"; //\n = Snoopy compatibility
1417
- else :
1418
- $client = new stdClass;
1419
- $client->status = $resp['response']['code'];
1420
- $client->response_code = $resp['response']['code'];
1421
- $client->headers = $resp['headers'];
1422
- $client->results = $resp['body'];
1423
- endif;
1424
- else :
1425
- // Snoopy is an HTTP client in PHP
1426
- $client = new Snoopy();
1427
- $client->agent = MAGPIE_USER_AGENT;
1428
- $client->read_timeout = MAGPIE_FETCH_TIME_OUT;
1429
- $client->use_gzip = MAGPIE_USE_GZIP;
1430
- if (is_array($headers) ) {
1431
- $client->rawheaders = $headers;
1432
- }
1433
- @$client->fetch($url);
1434
- endif;
1435
- return $client;
1436
- }
1437
-
1438
- /*=======================================================================*\
1439
- Function: _response_to_rss
1440
- Purpose: parse an HTTP response object into an RSS object
1441
- Input: an HTTP response object (see Snoopy)
1442
- Output: parsed RSS object (see rss_parse)
1443
- \*=======================================================================*/
1444
- function _response_to_rss ($resp, $url = null) {
1445
- $rss = new MagpieRSS( $resp->results, MAGPIE_OUTPUT_ENCODING, MAGPIE_INPUT_ENCODING, MAGPIE_DETECT_ENCODING, $url );
1446
-
1447
- // if RSS parsed successfully
1448
- if ( $rss and !$rss->ERROR) {
1449
- $rss->http_status = $resp->status;
1450
-
1451
- // find Etag, and Last-Modified
1452
- foreach($resp->headers as $index => $h) {
1453
- if (is_string($index)) :
1454
- $field = $index;
1455
- $val = $h;
1456
- elseif (strpos($h, ": ")) :
1457
- list($field, $val) = explode(": ", $h, 2);
1458
- else :
1459
- $field = $h; $val = '';
1460
- endif;
1461
-
1462
- $rss->header[$field] = $val;
1463
-
1464
- if ( $field == 'ETag' ) :
1465
- $rss->etag = $val;
1466
- elseif ( $field == 'Last-Modified' ) :
1467
- $rss->last_modified = $val;
1468
- endif;
1469
- }
1470
-
1471
- return $rss;
1472
- } // else construct error message
1473
- else {
1474
- $errormsg = "Failed to parse RSS file.";
1475
-
1476
- if ($rss) {
1477
- $errormsg .= " (" . $rss->ERROR . ")";
1478
- }
1479
- error($errormsg);
1480
-
1481
- return false;
1482
- } // end if ($rss and !$rss->error)
1483
- }
1484
-
1485
- /*=======================================================================*\
1486
- Function: init
1487
- Purpose: setup constants with default values
1488
- check for user overrides
1489
- \*=======================================================================*/
1490
- function init () {
1491
- if ( defined('MAGPIE_INITALIZED') ) {
1492
- return;
1493
- }
1494
- else {
1495
- define('MAGPIE_INITALIZED', true);
1496
- }
1497
-
1498
- if ( !defined('MAGPIE_CACHE_ON') ) {
1499
- define('MAGPIE_CACHE_ON', true);
1500
- }
1501
-
1502
- if ( !defined('MAGPIE_CACHE_DIR') ) {
1503
- define('MAGPIE_CACHE_DIR', './cache');
1504
- }
1505
-
1506
- if ( !defined('MAGPIE_CACHE_AGE') ) {
1507
- define('MAGPIE_CACHE_AGE', 60*60); // one hour
1508
- }
1509
-
1510
- if ( !defined('MAGPIE_CACHE_FRESH_ONLY') ) {
1511
- define('MAGPIE_CACHE_FRESH_ONLY', false);
1512
- }
1513
-
1514
- if ( !defined('MAGPIE_OUTPUT_ENCODING') ) {
1515
- define('MAGPIE_OUTPUT_ENCODING', 'ISO-8859-1');
1516
- }
1517
-
1518
- if ( !defined('MAGPIE_INPUT_ENCODING') ) {
1519
- define('MAGPIE_INPUT_ENCODING', null);
1520
- }
1521
-
1522
- if ( !defined('MAGPIE_DETECT_ENCODING') ) {
1523
- define('MAGPIE_DETECT_ENCODING', true);
1524
- }
1525
-
1526
- if ( !defined('MAGPIE_DEBUG') ) {
1527
- define('MAGPIE_DEBUG', 0);
1528
- }
1529
-
1530
- if ( !defined('MAGPIE_USER_AGENT') ) {
1531
- $ua = 'MagpieRSS/'. MAGPIE_VERSION . ' (+http://magpierss.sf.net';
1532
-
1533
- if ( MAGPIE_CACHE_ON ) {
1534
- $ua = $ua . ')';
1535
- }
1536
- else {
1537
- $ua = $ua . '; No cache)';
1538
- }
1539
-
1540
- define('MAGPIE_USER_AGENT', $ua);
1541
- }
1542
-
1543
- if ( !defined('MAGPIE_FETCH_TIME_OUT') ) {
1544
- define('MAGPIE_FETCH_TIME_OUT', 5); // 5 second timeout
1545
- }
1546
-
1547
- // use gzip encoding to fetch rss files if supported?
1548
- if ( !defined('MAGPIE_USE_GZIP') ) {
1549
- define('MAGPIE_USE_GZIP', true);
1550
- }
1551
- }
1552
-
1553
- // NOTE: the following code should really be in Snoopy, or at least
1554
- // somewhere other then rss_fetch!
1555
-
1556
- /*=======================================================================*\
1557
- HTTP STATUS CODE PREDICATES
1558
- These functions attempt to classify an HTTP status code
1559
- based on RFC 2616 and RFC 2518.
1560
-
1561
- All of them take an HTTP status code as input, and return true or false
1562
-
1563
- All this code is adapted from LWP's HTTP::Status.
1564
- \*=======================================================================*/
1565
-
1566
-
1567
- /*=======================================================================*\
1568
- Function: is_info
1569
- Purpose: return true if Informational status code
1570
- \*=======================================================================*/
1571
- function is_info ($sc) {
1572
- return $sc >= 100 && $sc < 200;
1573
- }
1574
-
1575
- /*=======================================================================*\
1576
- Function: is_success
1577
- Purpose: return true if Successful status code
1578
- \*=======================================================================*/
1579
- function is_success ($sc) {
1580
- return $sc >= 200 && $sc < 300;
1581
- }
1582
-
1583
- /*=======================================================================*\
1584
- Function: is_redirect
1585
- Purpose: return true if Redirection status code
1586
- \*=======================================================================*/
1587
- function is_redirect ($sc) {
1588
- return $sc >= 300 && $sc < 400;
1589
- }
1590
-
1591
- /*=======================================================================*\
1592
- Function: is_error
1593
- Purpose: return true if Error status code
1594
- \*=======================================================================*/
1595
- function is_error ($sc) {
1596
- return $sc >= 400 && $sc < 600;
1597
- }
1598
-
1599
- /*=======================================================================*\
1600
- Function: is_client_error
1601
- Purpose: return true if Error status code, and its a client error
1602
- \*=======================================================================*/
1603
- function is_client_error ($sc) {
1604
- return $sc >= 400 && $sc < 500;
1605
- }
1606
-
1607
- /*=======================================================================*\
1608
- Function: is_client_error
1609
- Purpose: return true if Error status code, and its a server error
1610
- \*=======================================================================*/
1611
- function is_server_error ($sc) {
1612
- return $sc >= 500 && $sc < 600;
1613
- }
1614
-
1615
- ################################################################################
1616
- ## rss_cache.inc: from WordPress 1.5 ###########################################
1617
- ################################################################################
1618
-
1619
- class RSSCache {
1620
- var $BASE_CACHE = 'wp-content/cache'; // where the cache files are stored
1621
- var $MAX_AGE = 43200; // when are files stale, default twelve hours
1622
- var $ERROR = ''; // accumulate error messages
1623
-
1624
- function RSSCache ($base='', $age='') {
1625
- if ( $base ) {
1626
- $this->BASE_CACHE = $base;
1627
- }
1628
- if ( $age ) {
1629
- $this->MAX_AGE = $age;
1630
- }
1631
-
1632
- }
1633
-
1634
- /*=======================================================================*\
1635
- Function: set
1636
- Purpose: add an item to the cache, keyed on url
1637
- Input: url from wich the rss file was fetched
1638
- Output: true on sucess
1639
- \*=======================================================================*/
1640
- function set ($url, $rss) {
1641
- global $wpdb;
1642
- $cache_option = 'rss_' . $this->file_name( $url );
1643
- $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
1644
-
1645
- if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_option'") )
1646
- add_option($cache_option, '', '', 'no');
1647
- if ( !$wpdb->get_var("SELECT option_name FROM $wpdb->options WHERE option_name = '$cache_timestamp'") )
1648
- add_option($cache_timestamp, '', '', 'no');
1649
-
1650
- update_option($cache_option, $rss);
1651
- update_option($cache_timestamp, time() );
1652
-
1653
- return $cache_option;
1654
- }
1655
-
1656
- /*=======================================================================*\
1657
- Function: get
1658
- Purpose: fetch an item from the cache
1659
- Input: url from wich the rss file was fetched
1660
- Output: cached object on HIT, false on MISS
1661
- \*=======================================================================*/
1662
- function get ($url) {
1663
- $this->ERROR = "";
1664
- $cache_option = 'rss_' . $this->file_name( $url );
1665
-
1666
- if ( ! get_option( $cache_option ) ) {
1667
- $this->debug(
1668
- "Cache doesn't contain: $url (cache option: $cache_option)"
1669
- );
1670
- return 0;
1671
- }
1672
-
1673
- $rss = get_option( $cache_option );
1674
-
1675
- // failsafe; seems to break at odd points in WP MU
1676
- if (is_string($rss)) {
1677
- $rss = $this->unserialize($rss);
1678
- }
1679
-
1680
- return $rss;
1681
- }
1682
-
1683
- /*=======================================================================*\
1684
- Function: check_cache
1685
- Purpose: check a url for membership in the cache
1686
- and whether the object is older then MAX_AGE (ie. STALE)
1687
- Input: url from wich the rss file was fetched
1688
- Output: cached object on HIT, false on MISS
1689
- \*=======================================================================*/
1690
- function check_cache ( $url ) {
1691
- $this->ERROR = "";
1692
- $cache_option = $this->file_name( $url );
1693
- $cache_timestamp = 'rss_' . $this->file_name( $url ) . '_ts';
1694
-
1695
- if ( $mtime = get_option($cache_timestamp) ) {
1696
- // find how long ago the file was added to the cache
1697
- // and whether that is longer then MAX_AGE
1698
- $age = time() - $mtime;
1699
- if ( $this->MAX_AGE > $age ) {
1700
- // object exists and is current
1701
- return 'HIT';
1702
- }
1703
- else {
1704
- // object exists but is old
1705
- return 'STALE';
1706
- }
1707
- }
1708
- else {
1709
- // object does not exist
1710
- return 'MISS';
1711
- }
1712
- }
1713
-
1714
- /*=======================================================================*\
1715
- Function: serialize
1716
- \*=======================================================================*/
1717
- function serialize ( $rss ) {
1718
- return serialize( $rss );
1719
- }
1720
-
1721
- /*=======================================================================*\
1722
- Function: unserialize
1723
- \*=======================================================================*/
1724
- function unserialize ( $data ) {
1725
- return unserialize( $data );
1726
- }
1727
-
1728
- /*=======================================================================*\
1729
- Function: file_name
1730
- Purpose: map url to location in cache
1731
- Input: url from wich the rss file was fetched
1732
- Output: a file name
1733
- \*=======================================================================*/
1734
- function file_name ($url) {
1735
- return md5( $url );
1736
- }
1737
-
1738
- /*=======================================================================*\
1739
- Function: error
1740
- Purpose: register error
1741
- \*=======================================================================*/
1742
- function error ($errormsg, $lvl=E_USER_WARNING) {
1743
- // append PHP's error message if track_errors enabled
1744
- if ( isset($php_errormsg) ) {
1745
- $errormsg .= " ($php_errormsg)";
1746
- }
1747
- $this->ERROR = $errormsg;
1748
- if ( MAGPIE_DEBUG ) {
1749
- trigger_error( $errormsg, $lvl);
1750
- }
1751
- else {
1752
- error_log( $errormsg, 0);
1753
- }
1754
- }
1755
- function debug ($debugmsg, $lvl=E_USER_NOTICE) {
1756
- if ( MAGPIE_DEBUG ) {
1757
- $this->error("MagpieRSS [debug] $debugmsg", $lvl);
1758
- }
1759
- }
1760
- }
1761
-
1762
- ################################################################################
1763
- ## rss_utils.inc: from MagpieRSS 0.8a ##########################################
1764
- ################################################################################
1765
-
1766
- /*======================================================================*\
1767
- Function: parse_w3cdtf
1768
- Purpose: parse a W3CDTF date into unix epoch
1769
-
1770
- NOTE: http://www.w3.org/TR/NOTE-datetime
1771
- \*======================================================================*/
1772
-
1773
- function parse_w3cdtf ( $date_str ) {
1774
-
1775
- # regex to match wc3dtf
1776
- $pat = "/^\s*(\d{4})(-(\d{2})(-(\d{2})(T(\d{2}):(\d{2})(:(\d{2})(\.\d+)?)?(?:([-+])(\d{2}):?(\d{2})|(Z))?)?)?)?\s*\$/";
1777
-
1778
- if ( preg_match( $pat, $date_str, $match ) ) {
1779
- list( $year, $month, $day, $hours, $minutes, $seconds) =
1780
- array( $match[1], $match[3], $match[5], $match[7], $match[8], $match[10]);
1781
-
1782
- # W3C dates can omit the time, the day of the month, or even the month.
1783
- # Fill in any blanks using information from the present moment. --CWJ
1784
- $default['hr'] = (int) gmdate('H');
1785
- $default['day'] = (int) gmdate('d');
1786
- $default['month'] = (int) gmdate('m');
1787
-
1788
- if (is_null($hours)) : $hours = $default['hr']; $minutes = 0; $seconds = 0; endif;
1789
- if (is_null($day)) : $day = $default['day']; endif;
1790
- if (is_null($month)) : $month = $default['month']; endif;
1791
-
1792
- # calc epoch for current date assuming GMT
1793
- $epoch = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);
1794
-
1795
- $offset = 0;
1796
- if ( $match[15] == 'Z' ) {
1797
- # zulu time, aka GMT
1798
- }
1799
- else {
1800
- list( $tz_mod, $tz_hour, $tz_min ) =
1801
- array( $match[12], $match[13], $match[14]);
1802
-
1803
- # zero out the variables
1804
- if ( ! $tz_hour ) { $tz_hour = 0; }
1805
- if ( ! $tz_min ) { $tz_min = 0; }
1806
-
1807
- $offset_secs = (($tz_hour*60)+$tz_min)*60;
1808
-
1809
- # is timezone ahead of GMT? then subtract offset
1810
- #
1811
- if ( $tz_mod == '+' ) {
1812
- $offset_secs = $offset_secs * -1;
1813
- }
1814
-
1815
- $offset = $offset_secs;
1816
- }
1817
- $epoch = $epoch + $offset;
1818
- return $epoch;
1819
- }
1820
- else {
1821
- return -1;
1822
- }
1823
- }
1824
-
1825
- # Relative URI static class: PHP class for resolving relative URLs
1826
- #
1827
- # This class is derived (under the terms of the GPL) from URL Class 0.3 by
1828
- # Keyvan Minoukadeh <keyvan@k1m.com>, which is great but more than we need
1829
- # for MagpieRSS's purposes. The class has been stripped down to a single
1830
- # public method: Relative_URI::resolve($url, $base), which resolves the URI in
1831
- # $url relative to the URI in $base
1832
- #
1833
- # FeedWordPress also uses this class. So if we have it loaded in, don't load it
1834
- # again.
1835
- #
1836
- # -- Charles Johnson <technophilia@radgeek.com>
1837
- if (!class_exists('Relative_URI')) {
1838
- class Relative_URI
1839
- {
1840
- // Resolve relative URI in $url against the base URI in $base. If $base
1841
- // is not supplied, then we use the REQUEST_URI of this script.
1842
- //
1843
- // I'm hoping this method reflects RFC 2396 Section 5.2
1844
- function resolve ($url, $base = NULL)
1845
- {
1846
- if (is_null($base)):
1847
- $base = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
1848
- endif;
1849
-
1850
- $base = Relative_URI::_encode(trim($base));
1851
- $uri_parts = Relative_URI::_parse_url($base);
1852
-
1853
- $url = Relative_URI::_encode(trim($url));
1854
- $parts = Relative_URI::_parse_url($url);
1855
-
1856
- $uri_parts['fragment'] = (isset($parts['fragment']) ? $parts['fragment'] : null);
1857
- $uri_parts['query'] = (isset($parts['query']) ? $parts['query'] : null);
1858
-
1859
- // if path is empty, and scheme, host, and query are undefined,
1860
- // the URL is referring the base URL
1861
-
1862
- if (($parts['path'] == '') && !isset($parts['scheme']) && !isset($parts['host']) && !isset($parts['query'])) {
1863
- // If the URI is empty or only a fragment, return the base URI
1864
- return $base . (isset($parts['fragment']) ? '#'.$parts['fragment'] : '');
1865
- } elseif (isset($parts['scheme'])) {
1866
- // If the scheme is set, then the URI is absolute.
1867
- return $url;
1868
- } elseif (isset($parts['host'])) {
1869
- $uri_parts['host'] = $parts['host'];
1870
- $uri_parts['path'] = $parts['path'];
1871
- } else {
1872
- // We have a relative path but not a host.
1873
-
1874
- // start ugly fix:
1875
- // prepend slash to path if base host is set, base path is not set, and url path is not absolute
1876
- if ($uri_parts['host'] && ($uri_parts['path'] == '')
1877
- && (strlen($parts['path']) > 0)
1878
- && (substr($parts['path'], 0, 1) != '/')) {
1879
- $parts['path'] = '/'.$parts['path'];
1880
- } // end ugly fix
1881
-
1882
- if (substr($parts['path'], 0, 1) == '/') {
1883
- $uri_parts['path'] = $parts['path'];
1884
- } else {
1885
- // copy base path excluding any characters after the last (right-most) slash character
1886
- $buffer = substr($uri_parts['path'], 0, (int)strrpos($uri_parts['path'], '/')+1);
1887
- // append relative path
1888
- $buffer .= $parts['path'];
1889
- // remove "./" where "." is a complete path segment.
1890
- $buffer = str_replace('/./', '/', $buffer);
1891
- if (substr($buffer, 0, 2) == './') {
1892
- $buffer = substr($buffer, 2);
1893
- }
1894
- // if buffer ends with "." as a complete path segment, remove it
1895
- if (substr($buffer, -2) == '/.') {
1896
- $buffer = substr($buffer, 0, -1);
1897
- }
1898
- // remove "<segment>/../" where <segment> is a complete path segment not equal to ".."
1899
- $search_finished = false;
1900
- $segment = explode('/', $buffer);
1901
- while (!$search_finished) {
1902
- for ($x=0; $x+1 < count($segment);) {
1903
- if (($segment[$x] != '') && ($segment[$x] != '..') && ($segment[$x+1] == '..')) {
1904
- if ($x+2 == count($segment)) $segment[] = '';
1905
- unset($segment[$x], $segment[$x+1]);
1906
- $segment = array_values($segment);
1907
- continue 2;
1908
- } else {
1909
- $x++;
1910
- }
1911
- }
1912
- $search_finished = true;
1913
- }
1914
- $buffer = (count($segment) == 1) ? '/' : implode('/', $segment);
1915
- $uri_parts['path'] = $buffer;
1916
-
1917
- }
1918
- }
1919
-
1920
- // If we've gotten to this point, we can try to put the pieces
1921
- // back together.
1922
- $ret = '';
1923
- if (isset($uri_parts['scheme'])) $ret .= $uri_parts['scheme'].':';
1924
- if (isset($uri_parts['user'])) {
1925
- $ret .= $uri_parts['user'];
1926
- if (isset($uri_parts['pass'])) $ret .= ':'.$uri_parts['parts'];
1927
- $ret .= '@';
1928
- }
1929
- if (isset($uri_parts['host'])) {
1930
- $ret .= '//'.$uri_parts['host'];
1931
- if (isset($uri_parts['port'])) $ret .= ':'.$uri_parts['port'];
1932
- }
1933
- $ret .= $uri_parts['path'];
1934
- if (isset($uri_parts['query'])) $ret .= '?'.$uri_parts['query'];
1935
- if (isset($uri_parts['fragment'])) $ret .= '#'.$uri_parts['fragment'];
1936
-
1937
- return $ret;
1938
- }
1939
-
1940
- /**
1941
- * Parse URL
1942
- *
1943
- * Regular expression grabbed from RFC 2396 Appendix B.
1944
- * This is a replacement for PHPs builtin parse_url().
1945
- * @param string $url
1946
- * @access private
1947
- * @return array
1948
- */
1949
- function _parse_url($url)
1950
- {
1951
- // I'm using this pattern instead of parse_url() as there's a few strings where parse_url()
1952
- // generates a warning.
1953
- if (preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!', $url, $match)) {
1954
- $parts = array();
1955
- if ($match[1] != '') $parts['scheme'] = $match[2];
1956
- if ($match[3] != '') $parts['auth'] = $match[4];
1957
- // parse auth
1958
- if (isset($parts['auth'])) {
1959
- // store user info
1960
- if (($at_pos = strpos($parts['auth'], '@')) !== false) {
1961
- $userinfo = explode(':', substr($parts['auth'], 0, $at_pos), 2);
1962
- $parts['user'] = $userinfo[0];
1963
- if (isset($userinfo[1])) $parts['pass'] = $userinfo[1];
1964
- $parts['auth'] = substr($parts['auth'], $at_pos+1);
1965
- }
1966
- // get port number
1967
- if ($port_pos = strrpos($parts['auth'], ':')) {
1968
- $parts['host'] = substr($parts['auth'], 0, $port_pos);
1969
- $parts['port'] = (int)substr($parts['auth'], $port_pos+1);
1970
- if ($parts['port'] < 1) $parts['port'] = null;
1971
- } else {
1972
- $parts['host'] = $parts['auth'];
1973
- }
1974
- }
1975
- unset($parts['auth']);
1976
- $parts['path'] = $match[5];
1977
- if (isset($match[6]) && ($match[6] != '')) $parts['query'] = $match[7];
1978
- if (isset($match[8]) && ($match[8] != '')) $parts['fragment'] = $match[9];
1979
- return $parts;
1980
- }
1981
- // shouldn't reach here
1982
- return array('path'=>'');
1983
- }
1984
-
1985
- function _encode($string)
1986
- {
1987
- static $replace = array();
1988
- if (!count($replace)) {
1989
- $find = array(32, 34, 60, 62, 123, 124, 125, 91, 92, 93, 94, 96, 127);
1990
- $find = array_merge(range(0, 31), $find);
1991
- $find = array_map('chr', $find);
1992
- foreach ($find as $char) {
1993
- $replace[$char] = '%'.bin2hex($char);
1994
- }
1995
- }
1996
- // escape control characters and a few other characters
1997
- $encoded = strtr($string, $replace);
1998
- // remove any character outside the hex range: 21 - 7E (see www.asciitable.com)
1999
- return preg_replace('/[^\x21-\x7e]/', '', $encoded);
2000
- }
2001
- } // class Relative_URI
2002
- }
2003
-
2004
- ################################################################################
2005
- ## WordPress: wp_rss(), get_rss() ##############################################
2006
- ################################################################################
2007
-
2008
- function wp_rss ($url, $num) {
2009
- //ini_set("display_errors", false); uncomment to suppress php errors thrown if the feed is not returned.
2010
- $num_items = $num;
2011
- $rss = fetch_rss($url);
2012
- if ( $rss ) {
2013
- echo "<ul>";
2014
- $rss->items = array_slice($rss->items, 0, $num_items);
2015
- foreach ($rss->items as $item ) {
2016
- echo "<li>\n";
2017
- echo "<a href='$item[link]' title='$item[description]'>";
2018
- echo htmlentities($item['title']);
2019
- echo "</a><br />\n";
2020
- echo "</li>\n";
2021
- }
2022
- echo "</ul>";
2023
- }
2024
- else {
2025
- echo "an error has occured the feed is probably down, try again later.";
2026
- }
2027
- }
2028
-
2029
- function get_rss ($uri, $num = 5) { // Like get posts, but for RSS
2030
- $rss = fetch_rss($url);
2031
- if ( $rss ) {
2032
- $rss->items = array_slice($rss->items, 0, $num_items);
2033
- foreach ($rss->items as $item ) {
2034
- echo "<li>\n";
2035
- echo "<a href='$item[link]' title='$item[description]'>";
2036
- echo htmlentities($item['title']);
2037
- echo "</a><br />\n";
2038
- echo "</li>\n";
2039
- }
2040
- return $posts;
2041
- } else {
2042
- return false;
2043
- }
2044
- }
2045
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
admin-ui.php CHANGED
@@ -52,7 +52,7 @@ class FeedWordPressAdminPage {
52
  /*static*/ function submitted_link () {
53
  $link_id = FeedWordPressAdminPage::submitted_link_id();
54
  if (is_numeric($link_id) and $link_id) :
55
- $link =& new SyndicatedLink($link_id);
56
  else :
57
  $link = NULL;
58
  endif;
@@ -62,7 +62,7 @@ class FeedWordPressAdminPage {
62
  function stamp_link_id ($field = null) {
63
  if (is_null($field)) : $field = 'save_link_id'; endif;
64
  ?>
65
- <input type="hidden" name="<?php print wp_specialchars($field, 'both'); ?>" value="<?php print ($this->for_feed_settings() ? $this->link->id : '*'); ?>" />
66
  <?php
67
  } /* FeedWordPressAdminPage::stamp_link_id () */
68
 
@@ -133,7 +133,7 @@ class FeedWordPressAdminPage {
133
  position:relative;
134
  }
135
  .fwpfs {
136
- color: #dddddd;
137
  background:#797979 url(<?php bloginfo('home') ?>/wp-admin/images/fav.png) repeat-x scroll left center;
138
  border-color:#777777 #777777 #666666 !important; -moz-border-radius-bottomleft:12px;
139
  -moz-border-radius-bottomright:12px;
@@ -179,7 +179,7 @@ class FeedWordPressAdminPage {
179
  <select name="link_id" class="fwpfs" style="max-width: 20.0em;">
180
  <option value="*"<?php if ($this->for_default_settings()) : ?> selected="selected"<?php endif; ?>>- defaults for all feeds -</option>
181
  <?php if ($links) : foreach ($links as $ddlink) : ?>
182
- <option value="<?php print (int) $ddlink->link_id; ?>"<?php if (!is_null($this->link) and ($this->link->id==$ddlink->link_id)) : ?> selected="selected"<?php endif; ?>><?php print wp_specialchars($ddlink->link_name, 1); ?></option>
183
  <?php endforeach; endif; ?>
184
  </select>
185
  <input class="button" type="submit" name="go" value="<?php _e('Go') ?> &raquo;" />
@@ -190,12 +190,12 @@ class FeedWordPressAdminPage {
190
  function display_sheet_header ($pagename = 'Syndication', $all = false) {
191
  if (FeedWordPressCompatibility::test_version(FWP_SCHEMA_27)) :
192
  ?>
193
- <div class="icon32"><img src="<?php print wp_specialchars(WP_PLUGIN_URL.'/'.$GLOBALS['fwp_path'].'/feedwordpress.png', 1); ?>" alt="" /></div>
194
  <?php
195
  endif;
196
  ?>
197
 
198
- <h2><?php print wp_specialchars(__($pagename.($all ? '' : ' Settings')), 1); ?><?php if ($this->for_feed_settings()) : ?>: <?php echo wp_specialchars($this->link->link->link_name, 1); ?><?php endif; ?></h2>
199
  <?php
200
  }
201
 
@@ -211,7 +211,7 @@ class FeedWordPressAdminPage {
211
  if (!is_null($mesg)) :
212
  ?>
213
  <div class="updated">
214
- <p><?php print wp_specialchars($mesg, 1); ?></p>
215
  </div>
216
  <?php
217
  endif;
@@ -221,7 +221,7 @@ class FeedWordPressAdminPage {
221
  if ($this->for_feed_settings()) :
222
  ?>
223
  <p>These settings only affect posts syndicated from
224
- <strong><?php echo wp_specialchars($this->link->link->link_name, 1); ?></strong>.</p>
225
  <?php
226
  else :
227
  ?>
@@ -508,7 +508,15 @@ function update_feeds_mention ($feed) {
508
  flush();
509
  }
510
  function update_feeds_finish ($feed, $added, $dt) {
511
- echo " completed in $dt second".(($dt==1)?'':'s')."</li>\n";
 
 
 
 
 
 
 
 
512
  }
513
 
514
  function fwp_author_list () {
52
  /*static*/ function submitted_link () {
53
  $link_id = FeedWordPressAdminPage::submitted_link_id();
54
  if (is_numeric($link_id) and $link_id) :
55
+ $link = new SyndicatedLink($link_id);
56
  else :
57
  $link = NULL;
58
  endif;
62
  function stamp_link_id ($field = null) {
63
  if (is_null($field)) : $field = 'save_link_id'; endif;
64
  ?>
65
+ <input type="hidden" name="<?php print esc_html($field); ?>" value="<?php print ($this->for_feed_settings() ? $this->link->id : '*'); ?>" />
66
  <?php
67
  } /* FeedWordPressAdminPage::stamp_link_id () */
68
 
133
  position:relative;
134
  }
135
  .fwpfs {
136
+ color: #dddddd !important; background-color: #333 !important;
137
  background:#797979 url(<?php bloginfo('home') ?>/wp-admin/images/fav.png) repeat-x scroll left center;
138
  border-color:#777777 #777777 #666666 !important; -moz-border-radius-bottomleft:12px;
139
  -moz-border-radius-bottomright:12px;
179
  <select name="link_id" class="fwpfs" style="max-width: 20.0em;">
180
  <option value="*"<?php if ($this->for_default_settings()) : ?> selected="selected"<?php endif; ?>>- defaults for all feeds -</option>
181
  <?php if ($links) : foreach ($links as $ddlink) : ?>
182
+ <option value="<?php print (int) $ddlink->link_id; ?>"<?php if (!is_null($this->link) and ($this->link->id==$ddlink->link_id)) : ?> selected="selected"<?php endif; ?>><?php print esc_html($ddlink->link_name); ?></option>
183
  <?php endforeach; endif; ?>
184
  </select>
185
  <input class="button" type="submit" name="go" value="<?php _e('Go') ?> &raquo;" />
190
  function display_sheet_header ($pagename = 'Syndication', $all = false) {
191
  if (FeedWordPressCompatibility::test_version(FWP_SCHEMA_27)) :
192
  ?>
193
+ <div class="icon32"><img src="<?php print esc_html(WP_PLUGIN_URL.'/'.$GLOBALS['fwp_path'].'/feedwordpress.png'); ?>" alt="" /></div>
194
  <?php
195
  endif;
196
  ?>
197
 
198
+ <h2><?php print esc_html(__($pagename.($all ? '' : ' Settings'))); ?><?php if ($this->for_feed_settings()) : ?>: <?php echo esc_html($this->link->link->link_name); ?><?php endif; ?></h2>
199
  <?php
200
  }
201
 
211
  if (!is_null($mesg)) :
212
  ?>
213
  <div class="updated">
214
+ <p><?php print esc_html($mesg); ?></p>
215
  </div>
216
  <?php
217
  endif;
221
  if ($this->for_feed_settings()) :
222
  ?>
223
  <p>These settings only affect posts syndicated from
224
+ <strong><?php echo esc_html($this->link->link->link_name); ?></strong>.</p>
225
  <?php
226
  else :
227
  ?>
508
  flush();
509
  }
510
  function update_feeds_finish ($feed, $added, $dt) {
511
+ if (is_wp_error($added)) :
512
+ $mesgs = $added->get_error_messages();
513
+ foreach ($mesgs as $mesg) :
514
+ echo "<br/><strong>Feed error:</strong> <code>$mesg</code>";
515
+ endforeach;
516
+ echo "</li>\n";
517
+ else :
518
+ echo " completed in $dt second".(($dt==1)?'':'s')."</li>\n";
519
+ endif;
520
  }
521
 
522
  function fwp_author_list () {
authors-page.php CHANGED
@@ -289,24 +289,14 @@ function fwp_authors_page () {
289
  endif;
290
  endif;
291
  endif;
292
-
293
- $alter[] = "link_notes = '".$wpdb->escape($link->settings_to_notes())."'";
294
-
295
- $alter_set = implode(", ", $alter);
296
-
297
- // issue update query
298
- $result = $wpdb->query("
299
- UPDATE $wpdb->links
300
- SET $alter_set
301
- WHERE link_id='$link_id'
302
- ");
303
  $updated_link = true;
304
-
305
- // reload link information from DB
306
- if (function_exists('clean_bookmark_cache')) :
307
- clean_bookmark_cache($link_id);
308
- endif;
309
- $link =& new SyndicatedLink($link_id);
310
  else :
311
  if ('newuser'==$GLOBALS['fwp_post']['unfamiliar_author']) :
312
  $newuser_name = trim($GLOBALS['fwp_post']['unfamiliar_author_newuser']);
@@ -361,7 +351,7 @@ function fwp_authors_page () {
361
  ?>
362
  <div class="updated"><p>Syndicated author settings updated.</p></div>
363
  <?php elseif (!is_null($mesg)) : ?>
364
- <div class="updated"><p><?php print wp_specialchars($mesg, 1); ?></p></div>
365
  <?php endif;
366
 
367
  if (function_exists('add_meta_box')) :
289
  endif;
290
  endif;
291
  endif;
292
+
293
+ // Save settings
294
+ $link->save_settings(/*reload=*/ true);
 
 
 
 
 
 
 
 
295
  $updated_link = true;
296
+
297
+ // Reset, reload
298
+ unset($link);
299
+ $link = new SyndicatedLink($link_id);
 
 
300
  else :
301
  if ('newuser'==$GLOBALS['fwp_post']['unfamiliar_author']) :
302
  $newuser_name = trim($GLOBALS['fwp_post']['unfamiliar_author_newuser']);
351
  ?>
352
  <div class="updated"><p>Syndicated author settings updated.</p></div>
353
  <?php elseif (!is_null($mesg)) : ?>
354
+ <div class="updated"><p><?php print esc_html($mesg); ?></p></div>
355
  <?php endif;
356
 
357
  if (function_exists('add_meta_box')) :
backend-page.php CHANGED
@@ -77,10 +77,14 @@ class FeedWordPressBackendPage extends FeedWordPressAdminPage {
77
 
78
  if (isset($post['create_index'])) :
79
  FeedWordPress::create_guid_index();
80
- $this->updated = __('Index created on database table.');
81
  endif;
82
-
83
- if (isset($_POST['clear_cache'])) :
 
 
 
 
84
  FeedWordPress::clear_cache();
85
  $this->updated = __("Cleared all cached feeds from WordPress database.");
86
  endif;
@@ -98,9 +102,21 @@ and force FeedWordPress to make a fresh scan for updates on syndicated feeds.</p
98
 
99
  <tr style="vertical-align: top">
100
  <th width="33%" scope="row">Guid index:</th>
101
- <td width="67%"><input class="button" type="submit" name="create_index" value="Create index on guid column in posts database table" />
 
102
  <p>Creating this index may significantly improve performance on some large
103
- FeedWordPress installations.</p></td>
 
 
 
 
 
 
 
 
 
 
 
104
  </tr>
105
  </table>
106
  <?php
77
 
78
  if (isset($post['create_index'])) :
79
  FeedWordPress::create_guid_index();
80
+ $this->updated = __('guid column index created on database table.');
81
  endif;
82
+ if (isset($post['remove_index'])) :
83
+ FeedWordPress::remove_guid_index();
84
+ $this->updated = __('guid column index removed from database table.');
85
+ endif;
86
+
87
+ if (isset($post['clear_cache'])) :
88
  FeedWordPress::clear_cache();
89
  $this->updated = __("Cleared all cached feeds from WordPress database.");
90
  endif;
102
 
103
  <tr style="vertical-align: top">
104
  <th width="33%" scope="row">Guid index:</th>
105
+ <td width="67%"><?php if (!FeedWordPress::has_guid_index()) : ?>
106
+ <input class="button" type="submit" name="create_index" value="Create index on guid column in posts database table" />
107
  <p>Creating this index may significantly improve performance on some large
108
+ FeedWordPress installations.</p>
109
+ <?php else : ?>
110
+
111
+ <p>You have already created an index on the guid column in the WordPress posts
112
+ table. If you'd like to remove the index for any reason, you can do so here.</p>
113
+
114
+ <input class="button" type="submit" name="remove_index" value="Remove index on guid column in posts database table" />
115
+
116
+ <?php endif; ?>
117
+
118
+
119
+ </td>
120
  </tr>
121
  </table>
122
  <?php
categories-page.php CHANGED
@@ -164,23 +164,13 @@ function fwp_categories_page () {
164
  endif;
165
  endif;
166
 
167
- $alter[] = "link_notes = '".$wpdb->escape($link->settings_to_notes())."'";
168
-
169
- $alter_set = implode(", ", $alter);
170
-
171
- // issue update query
172
- $result = $wpdb->query("
173
- UPDATE $wpdb->links
174
- SET $alter_set
175
- WHERE link_id='$link_id'
176
- ");
177
  $catsPage->updated = true;
178
-
179
- // reload link information from DB
180
- if (function_exists('clean_bookmark_cache')) :
181
- clean_bookmark_cache($link_id);
182
- endif;
183
- $link =& new SyndicatedLink($link_id);
184
  else :
185
  // Categories
186
  if (!empty($saveCats)) :
164
  endif;
165
  endif;
166
 
167
+ // Save settings
168
+ $link->save_settings(/*reload=*/ true);
 
 
 
 
 
 
 
 
169
  $catsPage->updated = true;
170
+
171
+ // Reset, reload
172
+ unset($link);
173
+ $link = new SyndicatedLink($link_id);
 
 
174
  else :
175
  // Categories
176
  if (!empty($saveCats)) :
compatability.php CHANGED
@@ -213,6 +213,30 @@ if (!function_exists('wp_die')) {
213
  } /* wp_die() */
214
  } /* if */
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  function fwp_category_checklist ($post_id = 0, $descendents_and_self = 0, $selected_cats = false) {
217
  if (function_exists('wp_category_checklist')) :
218
  wp_category_checklist($post_id, $descendents_and_self, $selected_cats);
@@ -259,7 +283,7 @@ function fwp_upgrade_page () {
259
  echo "<div class=\"wrap\">\n";
260
  echo "<h2>Upgrading FeedWordPress...</h2>";
261
 
262
- $feedwordpress =& new FeedWordPress;
263
  $feedwordpress->upgrade_database($ver);
264
  echo "<p><strong>Done!</strong> Upgraded database to version ".FEEDWORDPRESS_VERSION.".</p>\n";
265
  echo "<form action=\"\" method=\"get\">\n";
213
  } /* wp_die() */
214
  } /* if */
215
 
216
+ if (!function_exists('add_post_meta')) {
217
+ function add_post_meta ($postId, $key, $value, $unique) {
218
+ global $wpdb;
219
+
220
+ $postId = (int) $postId;
221
+ $key = $wpdb->escape($key);
222
+ $value = $wpdb->escape($value);
223
+
224
+ $result = $wpdb->query("
225
+ INSERT INTO $wpdb->postmeta
226
+ SET
227
+ post_id='$postId',
228
+ meta_key='$key',
229
+ meta_value='$value'
230
+ ");
231
+ if (!$result) :
232
+ $err = mysql_error();
233
+ if (FEEDWORDPRESS_DEBUG) :
234
+ echo "[DEBUG:".date('Y-m-d H:i:S')."][feedwordpress]: post metadata insertion FAILED for field '$key' := '$value': [$err]";
235
+ endif;
236
+ endif;
237
+ } /* add_post_meta() */
238
+ } /* if */
239
+
240
  function fwp_category_checklist ($post_id = 0, $descendents_and_self = 0, $selected_cats = false) {
241
  if (function_exists('wp_category_checklist')) :
242
  wp_category_checklist($post_id, $descendents_and_self, $selected_cats);
283
  echo "<div class=\"wrap\">\n";
284
  echo "<h2>Upgrading FeedWordPress...</h2>";
285
 
286
+ $feedwordpress = new FeedWordPress;
287
  $feedwordpress->upgrade_database($ver);
288
  echo "<p><strong>Done!</strong> Upgraded database to version ".FEEDWORDPRESS_VERSION.".</p>\n";
289
  echo "<form action=\"\" method=\"get\">\n";
feedfinder.class.php CHANGED
@@ -55,9 +55,9 @@ class FeedFinder {
55
  foreach ($href as $u) {
56
  $the_uri = Relative_URI::resolve($u, $this->uri);
57
  if ($this->verify) {
58
- $feed =& new FeedFinder($the_uri);
59
  if ($feed->is_feed()) $ret[] = $the_uri;
60
- $feed = NULL;
61
  } else {
62
  $ret[] = $the_uri;
63
  }
@@ -75,16 +75,24 @@ class FeedFinder {
75
  function status ($uri = NULL) {
76
  $this->_get($uri);
77
 
78
- if (isset($this->_response->status)) :
79
- $ret = $this->_response->status;
80
  else :
81
  $ret = NULL;
82
  endif;
83
  return $ret;
84
  }
85
 
86
- function error () {
87
- return $this->_error;
 
 
 
 
 
 
 
 
88
  }
89
 
90
  function is_feed ($uri = NULL) {
@@ -111,15 +119,20 @@ class FeedFinder {
111
  $headers['Accept'] = 'application/atom+xml application/rdf+xml application/rss+xml application/xml text/html */*';
112
  $headers['User-Agent'] = 'feedfinder/1.2 (compatible; PHP FeedFinder) +http://projects.radgeek.com/feedwordpress';
113
 
114
- // Use function provided by MagpieRSS package
115
- $client = _fetch_remote_file($this->uri, $headers);
116
- if (isset($client->error)) :
117
- $this->_error = $client->error;
 
 
 
 
 
 
118
  else :
 
119
  $this->_error = NULL;
120
  endif;
121
- $this->_response = $client;
122
- $this->_data = $client->results;
123
 
124
  // Kilroy was here
125
  $this->_cache_uri = $this->uri;
55
  foreach ($href as $u) {
56
  $the_uri = Relative_URI::resolve($u, $this->uri);
57
  if ($this->verify) {
58
+ $feed = new FeedFinder($the_uri);
59
  if ($feed->is_feed()) $ret[] = $the_uri;
60
+ unset($feed);
61
  } else {
62
  $ret[] = $the_uri;
63
  }
75
  function status ($uri = NULL) {
76
  $this->_get($uri);
77
 
78
+ if (!is_wp_error($this->_response) and isset($this->_response['response']['code'])) :
79
+ $ret = $this->_response['response']['code'];
80
  else :
81
  $ret = NULL;
82
  endif;
83
  return $ret;
84
  }
85
 
86
+ function error ($index = NULL) {
87
+ $message = NULL;
88
+ if (count($this->_error) > 0) :
89
+ if (is_scalar($index) and !is_null($index)) :
90
+ $message = $this->_error[$index];
91
+ else :
92
+ $message = implode(" / ", $this->_error)."\n";
93
+ endif;
94
+ endif;
95
+ return $message;
96
  }
97
 
98
  function is_feed ($uri = NULL) {
119
  $headers['Accept'] = 'application/atom+xml application/rdf+xml application/rss+xml application/xml text/html */*';
120
  $headers['User-Agent'] = 'feedfinder/1.2 (compatible; PHP FeedFinder) +http://projects.radgeek.com/feedwordpress';
121
 
122
+ // Use WordPress API function
123
+ $client = wp_remote_request($this->uri, array(
124
+ 'headers' => $headers,
125
+ 'timeout' => FEEDWORDPRESS_FETCH_TIME_OUT,
126
+ ));
127
+
128
+ $this->_response = $client;
129
+ if (is_wp_error($client)) :
130
+ $this->_data = NULL;
131
+ $this->_error = $client->get_error_messages();
132
  else :
133
+ $this->_data = $client['body'];
134
  $this->_error = NULL;
135
  endif;
 
 
136
 
137
  // Kilroy was here
138
  $this->_cache_uri = $this->uri;
feeds-page.php CHANGED
@@ -82,6 +82,7 @@ class FeedWordPressFeedsPage extends FeedWordPressAdminPage {
82
  'postmeta',
83
  'resolve relative',
84
  'freeze updates',
 
85
  'update/.*',
86
  'feed/.*',
87
  'link/.*',
@@ -330,9 +331,9 @@ contextual_appearance('time-limit', 'time-limit-box', null, 'yes');
330
  function feed_information_box ($page, $box = NULL) {
331
  global $wpdb;
332
  if ($page->for_feed_settings()) :
333
- $info['name'] = wp_specialchars($page->link->link->link_name, 1);
334
- $info['description'] = wp_specialchars($page->link->link->link_description, 'both');
335
- $info['url'] = wp_specialchars($page->link->link->link_url, 1);
336
  $rss_url = $page->link->link->link_rss;
337
 
338
  $hardcode['name'] = $page->link->hardcode('name');
@@ -383,9 +384,9 @@ contextual_appearance('time-limit', 'time-limit-box', null, 'yes');
383
 
384
  <tr>
385
  <th scope="row"><?php _e('Feed URL:') ?></th>
386
- <td><a href="<?php echo wp_specialchars($rss_url, 'both'); ?>"><?php echo wp_specialchars($rss_url, 'both'); ?></a>
387
  (<a href="<?php echo FEEDVALIDATOR_URI; ?>?url=<?php echo urlencode($rss_url); ?>"
388
- title="Check feed &lt;<?php echo wp_specialchars($rss_url, 'both'); ?>&gt; for validity">validate</a>)
389
  <input type="submit" name="feedfinder" value="switch &rarr;" style="font-size:smaller" /></td>
390
  </tr>
391
 
@@ -438,7 +439,7 @@ contextual_appearance('time-limit', 'time-limit-box', null, 'yes');
438
  if ($row->cat_id == $cat_id) :
439
  echo " selected='selected'";
440
  endif;
441
- echo ">$row->cat_id: ".wp_specialchars($row->cat_name);
442
  echo "</option>\n";
443
  endforeach;
444
  ?></select></p>
@@ -501,9 +502,9 @@ contextual_appearance('time-limit', 'time-limit-box', null, 'yes');
501
  if (!preg_match("\007^((".implode(')|(', $page->special_settings)."))$\007i", $key)) :
502
  ?>
503
  <tr style="vertical-align:top">
504
- <th width="30%" scope="row"><input type="hidden" name="notes[<?php echo $i; ?>][key0]" value="<?php echo wp_specialchars($key, 'both'); ?>" />
505
- <input id="notes-<?php echo $i; ?>-key" name="notes[<?php echo $i; ?>][key1]" value="<?php echo wp_specialchars($key, 'both'); ?>" /></th>
506
- <td width="60%"><textarea rows="2" cols="40" id="notes-<?php echo $i; ?>-value" name="notes[<?php echo $i; ?>][value]"><?php echo wp_specialchars($value, 'both'); ?></textarea></td>
507
  <td width="10%"><select name="notes[<?php echo $i; ?>][action]">
508
  <option value="update">save changes</option>
509
  <option value="delete">delete this setting</option>
@@ -531,9 +532,9 @@ contextual_appearance('time-limit', 'time-limit-box', null, 'yes');
531
 
532
  if ($this->for_feed_settings()) : // Existing feed?
533
  if (is_null($lookup)) : $lookup = $this->link->link->link_url; endif;
534
- $name = wp_specialchars($this->link->link->link_name, 'both');
535
  else: // Or a new subscription to add?
536
- $name = "Subscribe to <code>".wp_specialchars(feedwordpress_display_url($lookup))."</code>";
537
  endif;
538
  ?>
539
  <style type="text/css">
@@ -565,12 +566,14 @@ contextual_appearance('time-limit', 'time-limit-box', null, 'yes');
565
  <h2>Feed Finder: <?php echo $name; ?></h2>
566
 
567
  <?php
568
- $f =& new FeedFinder($lookup);
569
  $feeds = $f->find();
570
  if (count($feeds) > 0):
571
  foreach ($feeds as $key => $f):
572
- $rss = @fetch_rss($f);
573
- if ($rss):
 
 
574
  $feed_title = isset($rss->channel['title'])?$rss->channel['title']:$rss->channel['link'];
575
  $feed_link = isset($rss->channel['link'])?$rss->channel['link']:'';
576
  $feed_type = ($rss->feed_type ? $rss->feed_type : 'Unknown');
@@ -594,23 +597,24 @@ contextual_appearance('time-limit', 'time-limit-box', null, 'yes');
594
  // and homepage URL for the new Link.
595
  if (!$this->for_feed_settings()):
596
  ?>
597
- <input type="hidden" name="feed_title" value="<?php echo wp_specialchars($feed_title, 'both'); ?>" />
598
- <input type="hidden" name="feed_link" value="<?php echo wp_specialchars($feed_link, 'both'); ?>" />
599
  <?php
600
  endif;
601
  ?>
602
 
603
- <input type="hidden" name="feed" value="<?php echo wp_specialchars($f, 'both'); ?>" />
604
  <input type="hidden" name="action" value="switchfeed" />
605
 
606
  <div>
607
  <div class="feed-sample">
608
-
609
  <?php
610
- if (count($rss->items) > 0):
 
 
611
  // Prepare to display Sample Item
612
- $link =& new MagpieMockLink($rss, $f);
613
- $post =& new SyndicatedPost($rss->items[0], $link);
614
  ?>
615
  <h3>Sample Item</h3>
616
  <ul>
@@ -621,13 +625,14 @@ contextual_appearance('time-limit', 'time-limit-box', null, 'yes');
621
  <?php print $post->post['post_content']; ?>
622
  </div>
623
  <?php
 
624
  else:
625
- if (magpie_error()) :
626
  print '<div class="feed-problem">';
627
  print "<h3>Problem:</h3>\n";
628
  print "<p>FeedWordPress encountered the following error
629
  when trying to retrieve this feed:</p>";
630
- print '<p style="margin: 1.0em 3.0em"><code>'.magpie_error().'</code></p>';
631
  print "<p>If you think this is a temporary problem, you can still force FeedWordPress to add the subscription. FeedWordPress will not be able to find any syndicated posts until this problem is resolved.</p>";
632
  print "</div>";
633
  endif;
@@ -643,10 +648,11 @@ contextual_appearance('time-limit', 'time-limit-box', null, 'yes');
643
  <h3>Feed Information</h3>
644
  <ul>
645
  <li><strong>Homepage:</strong> <a href="<?php echo $feed_link; ?>"><?php echo is_null($feed_title)?'<em>Unknown</em>':$feed_title; ?></a></li>
646
- <li><strong>Feed URL:</strong> <a href="<?php echo wp_specialchars($f, 'both'); ?>"><?php echo wp_specialchars($f, 'both'); ?></a> (<a title="Check feed &lt;<?php echo wp_specialchars($f, 'both'); ?>&gt; for validity" href="http://feedvalidator.org/check.cgi?url=<?php echo urlencode($f); ?>">validate</a>)</li>
647
- <li><strong>Encoding:</strong> <?php echo isset($rss->encoding)?wp_specialchars($rss->encoding, 'both'):"<em>Unknown</em>"; ?></li>
648
- <li><strong>Description:</strong> <?php echo isset($rss->channel['description'])?wp_specialchars($rss->channel['description'], 'both'):"<em>Unknown</em>"; ?></li>
649
  </ul>
 
650
  <div class="submit"><input type="submit" name="Use" value="&laquo; Use this feed" /></div>
651
  <div class="submit"><input type="submit" name="Cancel" value="&laquo; Cancel" /></div>
652
  </div>
@@ -654,6 +660,8 @@ contextual_appearance('time-limit', 'time-limit-box', null, 'yes');
654
  </fieldset>
655
  </form>
656
  <?php
 
 
657
  endforeach;
658
  else:
659
  print "<p><strong>".__('Error').":</strong> ".__("FeedWordPress couldn't find any feeds at").' <code><a href="'.htmlspecialchars($lookup).'">'.htmlspecialchars($lookup).'</a></code>';
@@ -740,7 +748,7 @@ contextual_appearance('time-limit', 'time-limit-box', null, 'yes');
740
  // We have a checkbox for "No," so if it's unchecked, mark as "Yes."
741
  $this->link->settings["hardcode {$what}"] = (isset($post["hardcode_{$what}"]) ? $post["hardcode_{$what}"] : 'yes');
742
  if (FeedWordPress::affirmative($this->link->settings, "hardcode {$what}")) :
743
- $alter[] = "link_{$what} = '".$wpdb->escape($post['link'.$what])."'";
744
  endif;
745
  endforeach;
746
 
@@ -787,23 +795,24 @@ contextual_appearance('time-limit', 'time-limit-box', null, 'yes');
787
  $this->updatedPosts->accept_POST($post);
788
 
789
  if ($this->for_feed_settings()) :
790
- $alter[] = "link_notes = '".$wpdb->escape($this->link->settings_to_notes())."'";
791
-
792
- $alter_set = implode(", ", $alter);
793
 
794
  // issue update query
795
- $result = $wpdb->query("
796
- UPDATE $wpdb->links
797
- SET $alter_set
798
- WHERE link_id='{$this->link->id}'
799
- ");
 
 
 
 
800
  $this->updated = true;
801
 
802
- // reload link information from DB
803
- if (function_exists('clean_bookmark_cache')) :
804
- clean_bookmark_cache($this->link->id);
805
- endif;
806
- $this->link = new SyndicatedLink($this->link->id);
807
  endif;
808
 
809
  // Probably a "Go" button for the drop-down
82
  'postmeta',
83
  'resolve relative',
84
  'freeze updates',
85
+ 'munge permalink',
86
  'update/.*',
87
  'feed/.*',
88
  'link/.*',
331
  function feed_information_box ($page, $box = NULL) {
332
  global $wpdb;
333
  if ($page->for_feed_settings()) :
334
+ $info['name'] = esc_html($page->link->link->link_name);
335
+ $info['description'] = esc_html($page->link->link->link_description);
336
+ $info['url'] = esc_html($page->link->link->link_url);
337
  $rss_url = $page->link->link->link_rss;
338
 
339
  $hardcode['name'] = $page->link->hardcode('name');
384
 
385
  <tr>
386
  <th scope="row"><?php _e('Feed URL:') ?></th>
387
+ <td><a href="<?php echo esc_html($rss_url); ?>"><?php echo esc_html($rss_url); ?></a>
388
  (<a href="<?php echo FEEDVALIDATOR_URI; ?>?url=<?php echo urlencode($rss_url); ?>"
389
+ title="Check feed &lt;<?php echo esc_html($rss_url); ?>&gt; for validity">validate</a>)
390
  <input type="submit" name="feedfinder" value="switch &rarr;" style="font-size:smaller" /></td>
391
  </tr>
392
 
439
  if ($row->cat_id == $cat_id) :
440
  echo " selected='selected'";
441
  endif;
442
+ echo ">$row->cat_id: ".esc_html($row->cat_name);
443
  echo "</option>\n";
444
  endforeach;
445
  ?></select></p>
502
  if (!preg_match("\007^((".implode(')|(', $page->special_settings)."))$\007i", $key)) :
503
  ?>
504
  <tr style="vertical-align:top">
505
+ <th width="30%" scope="row"><input type="hidden" name="notes[<?php echo $i; ?>][key0]" value="<?php echo esc_html($key); ?>" />
506
+ <input id="notes-<?php echo $i; ?>-key" name="notes[<?php echo $i; ?>][key1]" value="<?php echo esc_html($key); ?>" /></th>
507
+ <td width="60%"><textarea rows="2" cols="40" id="notes-<?php echo $i; ?>-value" name="notes[<?php echo $i; ?>][value]"><?php echo esc_html($value); ?></textarea></td>
508
  <td width="10%"><select name="notes[<?php echo $i; ?>][action]">
509
  <option value="update">save changes</option>
510
  <option value="delete">delete this setting</option>
532
 
533
  if ($this->for_feed_settings()) : // Existing feed?
534
  if (is_null($lookup)) : $lookup = $this->link->link->link_url; endif;
535
+ $name = esc_html($this->link->link->link_name);
536
  else: // Or a new subscription to add?
537
+ $name = "Subscribe to <code>".esc_html(feedwordpress_display_url($lookup))."</code>";
538
  endif;
539
  ?>
540
  <style type="text/css">
566
  <h2>Feed Finder: <?php echo $name; ?></h2>
567
 
568
  <?php
569
+ $f = new FeedFinder($lookup);
570
  $feeds = $f->find();
571
  if (count($feeds) > 0):
572
  foreach ($feeds as $key => $f):
573
+ $pie = FeedWordPress::fetch($f);
574
+ $rss = (is_wp_error($pie) ? $pie : new MagpieFromSimplePie($pie));
575
+
576
+ if ($rss and !is_wp_error($rss)):
577
  $feed_title = isset($rss->channel['title'])?$rss->channel['title']:$rss->channel['link'];
578
  $feed_link = isset($rss->channel['link'])?$rss->channel['link']:'';
579
  $feed_type = ($rss->feed_type ? $rss->feed_type : 'Unknown');
597
  // and homepage URL for the new Link.
598
  if (!$this->for_feed_settings()):
599
  ?>
600
+ <input type="hidden" name="feed_title" value="<?php echo esc_html($feed_title); ?>" />
601
+ <input type="hidden" name="feed_link" value="<?php echo esc_html($feed_link); ?>" />
602
  <?php
603
  endif;
604
  ?>
605
 
606
+ <input type="hidden" name="feed" value="<?php echo esc_html($f); ?>" />
607
  <input type="hidden" name="action" value="switchfeed" />
608
 
609
  <div>
610
  <div class="feed-sample">
 
611
  <?php
612
+ $link = NULL;
613
+ $post = NULL;
614
+ if (!is_wp_error($rss) and count($rss->items) > 0):
615
  // Prepare to display Sample Item
616
+ $link = new MagpieMockLink(array('simplepie' => $pie, 'magpie' => $rss), $f);
617
+ $post = new SyndicatedPost(array('simplepie' => $rss->originals[0], 'magpie' => $rss->items[0]), $link);
618
  ?>
619
  <h3>Sample Item</h3>
620
  <ul>
625
  <?php print $post->post['post_content']; ?>
626
  </div>
627
  <?php
628
+ do_action('feedwordpress_feed_finder_sample_item', $f, $post, $link);
629
  else:
630
+ if (is_wp_error($rss)) :
631
  print '<div class="feed-problem">';
632
  print "<h3>Problem:</h3>\n";
633
  print "<p>FeedWordPress encountered the following error
634
  when trying to retrieve this feed:</p>";
635
+ print '<p style="margin: 1.0em 3.0em"><code>'.$rss->get_error_message().'</code></p>';
636
  print "<p>If you think this is a temporary problem, you can still force FeedWordPress to add the subscription. FeedWordPress will not be able to find any syndicated posts until this problem is resolved.</p>";
637
  print "</div>";
638
  endif;
648
  <h3>Feed Information</h3>
649
  <ul>
650
  <li><strong>Homepage:</strong> <a href="<?php echo $feed_link; ?>"><?php echo is_null($feed_title)?'<em>Unknown</em>':$feed_title; ?></a></li>
651
+ <li><strong>Feed URL:</strong> <a href="<?php echo esc_html($f); ?>"><?php echo esc_html($f); ?></a> (<a title="Check feed &lt;<?php echo esc_html($f); ?>&gt; for validity" href="http://feedvalidator.org/check.cgi?url=<?php echo urlencode($f); ?>">validate</a>)</li>
652
+ <li><strong>Encoding:</strong> <?php echo isset($rss->encoding)?esc_html($rss->encoding):"<em>Unknown</em>"; ?></li>
653
+ <li><strong>Description:</strong> <?php echo isset($rss->channel['description'])?esc_html($rss->channel['description']):"<em>Unknown</em>"; ?></li>
654
  </ul>
655
+ <?php do_action('feedwordpress_feedfinder_form', $f, $post, $link, $this->for_feed_settings()); ?>
656
  <div class="submit"><input type="submit" name="Use" value="&laquo; Use this feed" /></div>
657
  <div class="submit"><input type="submit" name="Cancel" value="&laquo; Cancel" /></div>
658
  </div>
660
  </fieldset>
661
  </form>
662
  <?php
663
+ unset($link);
664
+ unset($post);
665
  endforeach;
666
  else:
667
  print "<p><strong>".__('Error').":</strong> ".__("FeedWordPress couldn't find any feeds at").' <code><a href="'.htmlspecialchars($lookup).'">'.htmlspecialchars($lookup).'</a></code>';
748
  // We have a checkbox for "No," so if it's unchecked, mark as "Yes."
749
  $this->link->settings["hardcode {$what}"] = (isset($post["hardcode_{$what}"]) ? $post["hardcode_{$what}"] : 'yes');
750
  if (FeedWordPress::affirmative($this->link->settings, "hardcode {$what}")) :
751
+ $this->link->link->{'link_'.$what} = $post['link'.$what];
752
  endif;
753
  endforeach;
754
 
795
  $this->updatedPosts->accept_POST($post);
796
 
797
  if ($this->for_feed_settings()) :
798
+ // Save changes to channel-level meta-data
799
+ //$alter_set = implode(", ", $alter);
 
800
 
801
  // issue update query
802
+ //$result = $wpdb->query("
803
+ //UPDATE $wpdb->links
804
+ //SET $alter_set
805
+ //WHERE link_id='{$this->link->id}'
806
+ //");
807
+
808
+ // Save settings
809
+ $this->link->save_settings(/*reload=*/ true);
810
+
811
  $this->updated = true;
812
 
813
+ // Reset, reload
814
+ $link_id = $this->link->id; unset($this->link);
815
+ $this->link = new SyndicatedLink($link_id);
 
 
816
  endif;
817
 
818
  // Probably a "Go" button for the drop-down
feedtime.class.php ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * class FeedTime: handle common date-time formats used in feeds.
4
+ *
5
+ */
6
+ class FeedTime {
7
+ var $rep;
8
+ var $ts;
9
+
10
+ function FeedTime ($time) {
11
+ $this->set($time);
12
+ } /* FeedTime constructor */
13
+
14
+ function set ($time) {
15
+ $this->rep = $time;
16
+ $this->ts = NULL;
17
+ if (is_numeric($time)) : // Presumably a Unix-epoch timestamp
18
+ $this->ts = $this->rep;
19
+ elseif (is_string($time)) :
20
+ // First, try to parse it as a W3C date-time
21
+ $this->ts = $this->parse_w3cdtf();
22
+
23
+ if ($this->failed()) :
24
+ // In some versions of PHP, strtotime() does not support
25
+ // the UT timezone. Since UT is by definition within 1
26
+ // second of UTC, we'll just convert it here to avoid
27
+ // problems.
28
+ $time = preg_replace(
29
+ '/(\s)UT$/',
30
+ '$1UTC',
31
+ $time
32
+ );
33
+ $this->ts = strtotime($time);
34
+ endif;
35
+ endif;
36
+ } /* FeedTime::set() */
37
+
38
+ function timestamp () {
39
+ $unix = NULL;
40
+ if (!$this->failed()) :
41
+ $unix = $this->ts;
42
+ endif;
43
+ return $unix;
44
+ } /* FeedTime::timestamp() */
45
+
46
+ function failed () {
47
+ return (!is_numeric($this->ts) or !$this->ts or ($this->ts <= 0));
48
+ } /* FeedTime::failed() */
49
+
50
+ /**
51
+ * FeedTime::parse_w3cdtf() parses a W3C date-time format date into a
52
+ * Unix epoch timestamp. Derived from the parse_w3cdtf function included
53
+ * with MagpieRSS by Kellan Elliot-McCrea <kellan@protest.net>, with
54
+ * modifications and bugfixes by Charles Johnson
55
+ * <technophilia@radgeek.com>, under the terms of the GPL.
56
+ */
57
+ function parse_w3cdtf () {
58
+ $unix = NULL; // Failure
59
+
60
+ # regex to match wc3dtf
61
+ $pat = "/^\s*
62
+ (\d{4})
63
+ (-
64
+ (\d{2})
65
+ (-
66
+ (\d{2})
67
+ (T
68
+ (\d{2})
69
+ :(\d{2})
70
+ (:
71
+ (\d{2})
72
+ (\.\d+)?
73
+ )?
74
+ (?:([-+])(\d{2}):?(\d{2})|(Z))?
75
+ )?
76
+ )?
77
+ )?
78
+ \s*\$
79
+ /x";
80
+
81
+ if ( preg_match( $pat, $this->rep, $match ) ) :
82
+ $year = (isset($match[1]) ? $match[1] : NULL);
83
+ $month = (isset($match[3]) ? $match[3] : NULL);
84
+ $day = (isset($match[5]) ? $match[5] : NULL);
85
+ $hours = (isset($match[7]) ? $match[7] : NULL);
86
+ $minutes = (isset($match[8]) ? $match[8] : NULL);
87
+ $seconds = (isset($match[10]) ? $match[10] : NULL);
88
+
89
+ # W3C dates can omit the time, the day of the month, or even the month.
90
+ # Fill in any blanks using information from the present moment. --CWJ
91
+ $default['hr'] = (int) gmdate('H');
92
+ $default['day'] = (int) gmdate('d');
93
+ $default['month'] = (int) gmdate('m');
94
+
95
+ if (is_null($hours)) : $hours = $default['hr']; $minutes = 0; $seconds = 0; endif;
96
+ if (is_null($day)) : $day = $default['day']; endif;
97
+ if (is_null($month)) : $month = $default['month']; endif;
98
+
99
+ # calc epoch for current date assuming GMT
100
+ $unix = gmmktime( $hours, $minutes, $seconds, $month, $day, $year);
101
+
102
+ $offset = 0;
103
+ if ( isset($match[15]) and $match[15] == 'Z' ) :
104
+ # zulu time, aka GMT
105
+ else :
106
+ $tz_mod = $match[12];
107
+ $tz_hour = $match[13];
108
+ $tz_min = $match[14];
109
+
110
+ # zero out the variables
111
+ if ( ! $tz_hour ) { $tz_hour = 0; }
112
+ if ( ! $tz_min ) { $tz_min = 0; }
113
+
114
+ $offset_secs = (($tz_hour*60)+$tz_min)*60;
115
+
116
+ # is timezone ahead of GMT? then subtract offset
117
+ if ( $tz_mod == '+' ) :
118
+ $offset_secs = $offset_secs * -1;
119
+ endif;
120
+ $offset = $offset_secs;
121
+ endif;
122
+ $unix = $unix + $offset;
123
+ endif;
124
+ return $unix;
125
+ } /* FeedTime::parse_w3cdtf () */
126
+ } /* class FeedTime */
feedwordpress.php CHANGED
@@ -3,12 +3,17 @@
3
  Plugin Name: FeedWordPress
4
  Plugin URI: http://feedwordpress.radgeek.com/
5
  Description: simple and flexible Atom/RSS syndication for WordPress
6
- Version: 2010.0127
7
  Author: Charles Johnson
8
  Author URI: http://radgeek.com/
9
  License: GPL
10
  */
11
 
 
 
 
 
 
12
  # This uses code derived from:
13
  # - wp-rss-aggregate.php by Kellan Elliot-McCrea <kellan@protest.net>
14
  # - HTTP Navigator 2 by Keyvan Minoukadeh <keyvan@k1m.com>
@@ -28,7 +33,7 @@ License: GPL
28
 
29
  # -- Don't change these unless you know what you're doing...
30
 
31
- define ('FEEDWORDPRESS_VERSION', '2010.0127');
32
  define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'http://radgeek.com/contact');
33
 
34
  // Defaults
@@ -66,29 +71,29 @@ if (FEEDWORDPRESS_DEBUG) :
66
  // Help us to pick out errors, if any.
67
  ini_set('error_reporting', E_ALL & ~E_NOTICE);
68
  ini_set('display_errors', true);
69
- define('MAGPIE_DEBUG', true);
70
 
71
  // When testing we don't want cache issues to interfere. But this is
72
  // a VERY BAD SETTING for a production server. Webmasters will eat your
73
  // face for breakfast if you use it, and the baby Jesus will cry. So
74
  // make sure FEEDWORDPRESS_DEBUG is FALSE for any site that will be
75
  // used for more than testing purposes!
76
- define('MAGPIE_CACHE_AGE', 1);
 
 
77
  else :
78
- define('MAGPIE_DEBUG', false);
79
-
80
- define('MAGPIE_CACHE_AGE', 1*60);
 
 
81
  endif;
82
 
83
- // Note that the rss-functions.php that comes prepackaged with WordPress is
84
- // old & busted. For the new hotness, drop a copy of rss.php from
85
- // this archive into wp-includes/rss.php
86
 
87
- if (is_readable(ABSPATH . WPINC . '/rss.php')) :
88
- require_once (ABSPATH . WPINC . '/rss.php');
89
- else :
90
- require_once (ABSPATH . WPINC . '/rss-functions.php');
91
- endif;
92
 
93
  if (isset($wp_db_version)) :
94
  if ($wp_db_version >= FWP_SCHEMA_23) :
@@ -177,6 +182,13 @@ if (!FeedWordPress::needs_upgrade()) : // only work if the conditions are safe!
177
  # Filter in original permalinks if the user wants that
178
  add_filter('post_link', 'syndication_permalink', 1);
179
 
 
 
 
 
 
 
 
180
  # WTF? By default, wp_insert_link runs incoming link_url and link_rss
181
  # URIs through default filters that include `wp_kses()`. But `wp_kses()`
182
  # just happens to escape any occurrence of & to &amp; -- which just
@@ -187,8 +199,6 @@ if (!FeedWordPress::needs_upgrade()) : // only work if the conditions are safe!
187
  # Admin menu
188
  add_action('admin_menu', 'fwp_add_pages');
189
  add_action('admin_notices', 'fwp_check_debug');
190
- add_action('admin_notices', 'fwp_check_magpie');
191
- add_action('init', 'feedwordpress_check_for_magpie_fix');
192
 
193
  add_action('admin_menu', 'feedwordpress_add_post_edit_controls');
194
  add_action('save_post', 'feedwordpress_save_post_edit_controls');
@@ -216,12 +226,16 @@ if (!FeedWordPress::needs_upgrade()) : // only work if the conditions are safe!
216
  add_action('feedwordpress_update_complete', 'log_feedwordpress_update_complete', 100);
217
  endif;
218
 
219
- if (FeedWordPress::update_requested() and FEEDWORDPRESS_DEBUG) :
220
- add_action('post_syndicated_item', 'debug_out_feedwordpress_post', 100);
221
- add_action('update_syndicated_item', 'debug_out_feedwordpress_update_post', 100);
222
- add_action('feedwordpress_update', 'debug_out_feedwordpress_update_feeds', 100);
223
- add_action('feedwordpress_check_feed', 'debug_out_feedwordpress_check_feed', 100);
224
- add_action('feedwordpress_update_complete', 'debug_out_feedwordpress_update_complete', 100);
 
 
 
 
225
  endif;
226
 
227
  # Cron-less auto-update. Hooray!
@@ -242,42 +256,9 @@ else :
242
  add_action('admin_menu', 'fwp_add_pages');
243
  endif; // if (!FeedWordPress::needs_upgrade())
244
 
245
- function feedwordpress_check_for_magpie_fix () {
246
- if (isset($_POST['action']) and $_POST['action']=='fix_magpie_version') :
247
- FeedWordPressCompatibility::validate_http_request(/*action=*/ 'feedwordpress_fix_magpie', /*capability=*/ 'edit_files');
248
-
249
- $back_to = $_SERVER['REQUEST_URI'];
250
- if (isset($_POST['ignore'])) :
251
- // kill error message by telling it we ignored the upgrade request for this version
252
- update_option('feedwordpress_magpie_ignored_upgrade_to', EXPECTED_MAGPIE_VERSION);
253
- $ret = 'ignored';
254
- elseif (isset($_POST['upgrade'])) :
255
- $source = dirname(__FILE__)."/MagpieRSS-upgrade/rss.php";
256
- $destination = ABSPATH . WPINC . '/rss.php';
257
- $success = @copy($source, $destination);
258
-
259
- // Copy over rss-functions.php, too, to avoid collisions
260
- // on pre-lapsarian versions of WordPress.
261
- if ($success) :
262
- $source = dirname(__FILE__)."/MagpieRSS-upgrade/rss-functions.php";
263
- $destination = ABSPATH . WPINC . '/rss-functions.php';
264
- $success = @copy($source, $destination);
265
- endif;
266
- $ret = (int) $success;
267
- endif;
268
-
269
- if (strpos($back_to, '?')===false) : $sep = '?';
270
- else : $sep = '&';
271
- endif;
272
-
273
- header("Location: {$back_to}{$sep}feedwordpress_magpie_fix=".$ret);
274
- exit;
275
- endif;
276
- } /* feedwordpress_check_for_magpie_fix() */
277
-
278
  function feedwordpress_auto_update () {
279
  if (FeedWordPress::stale()) :
280
- $feedwordpress =& new FeedWordPress;
281
  $feedwordpress->update();
282
  endif;
283
  } /* feedwordpress_auto_update () */
@@ -287,7 +268,7 @@ function feedwordpress_update_magic_url () {
287
 
288
  // Explicit update request in the HTTP request (e.g. from a cron job)
289
  if (FeedWordPress::update_requested()) :
290
- $feedwordpress =& new FeedWordPress;
291
  $feedwordpress->update(FeedWordPress::update_requested_url());
292
 
293
  if (FEEDWORDPRESS_DEBUG and count($wpdb->queries) > 0) :
@@ -386,11 +367,31 @@ function debug_out_feedwordpress_update_complete ($delta) {
386
  : implode(' and ', $mesg))."\n");
387
  }
388
 
 
 
 
 
 
 
 
 
 
389
  ################################################################################
390
  ## TEMPLATE API: functions to make your templates syndication-aware ############
391
  ################################################################################
392
 
393
- function is_syndicated ($id = NULL) { return (strlen(get_syndication_feed_id($id)) > 0); }
 
 
 
 
 
 
 
 
 
 
 
394
 
395
  function get_syndication_source_link ($original = NULL, $id = NULL) {
396
  if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
@@ -422,6 +423,7 @@ function feedwordpress_display_url ($url, $before = 60, $after = 0) {
422
  $url = (isset($bits['user'])?$bits['user'].'@':'')
423
  .(isset($bits['host'])?$bits['host']:'')
424
  .(isset($bits['path'])?$bits['path']:'')
 
425
  .(isset($bits['query'])?'?'.$bits['query']:'');
426
 
427
  if (strlen($url) > ($before+$after)) :
@@ -502,20 +504,28 @@ function get_syndication_feed_id ($id = NULL) { list($u) = get_post_custom_value
502
  function the_syndication_feed_id ($id = NULL) { echo get_syndication_feed_id($id); }
503
 
504
  $feedwordpress_linkcache = array (); // only load links from database once
 
 
505
 
506
- function get_feed_meta ($key, $id = NULL) {
507
- global $wpdb, $feedwordpress_linkcache;
508
- $feed_id = get_syndication_feed_id($id);
509
 
510
- $ret = NULL;
511
  if (strlen($feed_id) > 0):
512
  if (isset($feedwordpress_linkcache[$feed_id])) :
513
  $link = $feedwordpress_linkcache[$feed_id];
514
  else :
515
- $link =& new SyndicatedLink($feed_id);
516
  $feedwordpress_linkcache[$feed_id] = $link;
517
  endif;
 
 
 
 
 
 
518
 
 
 
519
  $ret = $link->settings[$key];
520
  endif;
521
  return $ret;
@@ -528,11 +538,53 @@ function the_syndication_permalink ($id = NULL) {
528
  echo get_syndication_permalink($id);
529
  }
530
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
531
  ################################################################################
532
  ## FILTERS: syndication-aware handling of post data for templates and feeds ####
533
  ################################################################################
534
 
535
  $feedwordpress_the_syndicated_content = NULL;
 
536
 
537
  function feedwordpress_preserve_syndicated_content ($text) {
538
  global $feedwordpress_the_syndicated_content;
@@ -585,14 +637,128 @@ function feedwordpress_item_feed_data () {
585
  endif;
586
  }
587
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
588
  function syndication_permalink ($permalink = '') {
589
- if (get_option('feedwordpress_munge_permalink') != 'no'):
590
- $uri = get_syndication_permalink();
591
- return ((strlen($uri) > 0) ? $uri : $permalink);
592
- else:
593
- return $permalink;
 
 
 
 
 
 
 
 
594
  endif;
595
- } // function syndication_permalink ()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
 
597
  ################################################################################
598
  ## ADMIN MENU ADD-ONS: register Dashboard management pages #####################
@@ -638,95 +804,6 @@ for a production server.</p>
638
  endif;
639
  } /* function fwp_check_debug () */
640
 
641
- define('EXPECTED_MAGPIE_VERSION', '2010.0122');
642
- function fwp_check_magpie () {
643
- if (isset($_REQUEST['feedwordpress_magpie_fix'])) :
644
- if ($_REQUEST['feedwordpress_magpie_fix']=='ignored') :
645
- ?>
646
- <div class="updated fade">
647
- <p>O.K., we'll ignore the problem for now. FeedWordPress will not display any
648
- more error messages.</p>
649
- </div>
650
- <?php
651
- elseif ((bool) $_REQUEST['feedwordpress_magpie_fix']) :
652
- ?>
653
- <div class="updated fade">
654
- <p>Congratulations! Your MagpieRSS has been successfully upgraded to the version
655
- shipped with FeedWordPress.</p>
656
- </div>
657
- <?php
658
- else :
659
- $source = dirname(__FILE__)."/MagpieRSS-upgrade/rss.php";
660
- $destination = ABSPATH . WPINC . '/rss.php';
661
- $cmd = "cp '".htmlspecialchars(addslashes($source))."' '".htmlspecialchars(addslashes($destination))."'";
662
- $cmd = wordwrap($cmd, /*width=*/ 75, /*break=*/ " \\\n\t");
663
-
664
- ?>
665
- <div class="error">
666
- <p><strong>FeedWordPress was unable to automatically upgrade your copy of MagpieRSS.</strong></p>
667
- <p>It's likely that you need to change the file permissions on <code><?php print htmlspecialchars($source); ?></code>
668
- to allow FeedWordPress to overwrite it.</p>
669
- <p><strong>To perform the upgrade manually,</strong> you can
670
- use a SFTP or FTP client to upload a copy of <code>rss.php</code> from the
671
- <code>MagpieRSS-upgrades/</code> directory of your FeedWordPress archive so that
672
- it overwrites <code><?php print htmlspecialchars($destination); ?></code>. Or,
673
- if your web host provides shell access, you can issue the following command from
674
- a command prompt to perform the upgrade:</p>
675
- <pre>
676
- <samp>$</samp> <kbd><?php print $cmd; ?></kbd>
677
- </pre>
678
- <p><strong>If you've fixed the file permissions,</strong> you can try the
679
- automatic upgrade again.</p>
680
-
681
- <?php feedwordpress_upgrade_old_and_busted_buttons(); ?>
682
- </div>
683
- <?php
684
- endif;
685
- else :
686
- $magpie_version = FeedWordPress::magpie_version();
687
-
688
- $ignored = get_option('feedwordpress_magpie_ignored_upgrade_to');
689
- if (EXPECTED_MAGPIE_VERSION != $magpie_version and EXPECTED_MAGPIE_VERSION != $ignored) :
690
- if (current_user_can('edit_files')) :
691
- $youAre = 'you are';
692
- $itIsRecommendedThatYou = 'It is <strong>strongly recommended</strong> that you';
693
- else :
694
- $youAre = 'this site is';
695
- $itIsRecommendedThatYou = 'You may want to contact the administrator of the site; it is <strong>strongly recommended</strong> that they';
696
- endif;
697
- print '<div class="error">';
698
- ?>
699
- <p style="font-style: italic"><strong>FeedWordPress has detected that <?php print $youAre; ?> currently using a version of
700
- MagpieRSS other than the upgraded version that ships with this version of FeedWordPress.</strong></p>
701
- <ul>
702
- <li><strong>Currently running:</strong> MagpieRSS <?php print $magpie_version; ?></li>
703
- <li><strong>Version included with FeedWordPress <?php print FEEDWORDPRESS_VERSION; ?>:</strong> MagpieRSS <?php print EXPECTED_MAGPIE_VERSION; ?></li>
704
- </ul>
705
- <p><?php print $itIsRecommendedThatYou; ?> install the upgraded
706
- version of MagpieRSS supplied with FeedWordPress. The version of
707
- MagpieRSS that ships with WordPress is very old and buggy, and
708
- encounters a number of errors when trying to parse modern Atom
709
- and RSS feeds.</p>
710
- <?php
711
- feedwordpress_upgrade_old_and_busted_buttons();
712
- print '</div>';
713
- endif;
714
- endif;
715
- }
716
-
717
- function feedwordpress_upgrade_old_and_busted_buttons() {
718
- if (current_user_can('edit_files')) :
719
- ?>
720
- <form action="" method="post"><div>
721
- <?php FeedWordPressCompatibility::stamp_nonce('feedwordpress_fix_magpie'); ?>
722
- <input type="hidden" name="action" value="fix_magpie_version" />
723
- <input class="button-secondary" type="submit" name="ignore" value="<?php _e('Ignore this problem'); ?>" />
724
- <input class="button-primary" type="submit" name="upgrade" value="<?php _e('Upgrade'); ?>" />
725
- </div></form>
726
- <?php
727
- endif;
728
- }
729
-
730
  ################################################################################
731
  ## fwp_hold_pings() and fwp_release_pings(): Outbound XML-RPC ping reform ####
732
  ## ... 'coz it's rude to send 500 pings the first time your aggregator runs ####
@@ -797,8 +874,8 @@ function fwp_publish_post_hook ($post_id) {
797
  if (is_syndicated($post->ID)) :
798
  ?>
799
  <p>This is a syndicated post, which originally appeared at
800
- <cite><?php print wp_specialchars(get_syndication_source(NULL, $post->ID)); ?></cite>.
801
- <a href="<?php print wp_specialchars(get_syndication_permalink($post->ID)); ?>">View original post</a>.</p>
802
 
803
  <p><input type="hidden" name="feedwordpress_noncename" id="feedwordpress_noncename" value="<?php print wp_create_nonce(plugin_basename(__FILE__)); ?>" />
804
  <label><input type="checkbox" name="freeze_updates" value="yes" <?php if ($frozen_post) : ?>checked="checked"<?php endif; ?> /> <strong>Manual editing.</strong>
@@ -886,7 +963,7 @@ class FeedWordPress {
886
  $this->feeds = array ();
887
  $links = FeedWordPress::syndicated_links();
888
  if ($links): foreach ($links as $link):
889
- $this->feeds[] =& new SyndicatedLink($link);
890
  endforeach; endif;
891
  } // FeedWordPress::FeedWordPress ()
892
 
@@ -992,8 +1069,10 @@ class FeedWordPress {
992
  $added = $feed->poll($crash_ts);
993
  do_action('feedwordpress_check_feed_complete', $feed->settings, $added, time() - $start_ts);
994
 
995
- if (isset($added['new'])) : $delta['new'] += $added['new']; endif;
996
- if (isset($added['updated'])) : $delta['updated'] += $added['updated']; endif;
 
 
997
  endif;
998
  endforeach;
999
 
@@ -1149,6 +1228,17 @@ class FeedWordPress {
1149
  return apply_filters('syndicated_post_use_aggregator_source_data', ($ret=='yes'));
1150
  }
1151
 
 
 
 
 
 
 
 
 
 
 
 
1152
  function syndicated_links () {
1153
  $contributors = FeedWordPress::link_category_id();
1154
  if (function_exists('get_bookmarks')) :
@@ -1234,55 +1324,11 @@ class FeedWordPress {
1234
  if (is_null($from) or $from <= 0.96) : $from = 0.96; endif;
1235
 
1236
  switch ($from) :
1237
- case 0.96: // account for changes to syndication custom values and guid
1238
- echo "<p>Upgrading database from {$from} to ".FEEDWORDPRESS_VERSION."...</p>\n";
1239
-
1240
- $cat_id = FeedWordPress::link_category_id();
1241
-
1242
- // Avoid duplicates
1243
- $wpdb->query("DELETE FROM `{$wpdb->postmeta}` WHERE meta_key = 'syndication_feed_id'");
1244
-
1245
- // Look up all the link IDs
1246
- $wpdb->query("
1247
- CREATE TEMPORARY TABLE tmp_custom_values
1248
- SELECT
1249
- NULL AS meta_id,
1250
- post_id,
1251
- 'syndication_feed_id' AS meta_key,
1252
- link_id AS meta_value
1253
- FROM `{$wpdb->postmeta}`, `{$wpdb->links}`
1254
- WHERE
1255
- meta_key='syndication_feed'
1256
- AND meta_value=link_rss
1257
- AND link_category = {$cat_id}
1258
- ");
1259
-
1260
- // Now attach them to their posts
1261
- $wpdb->query("INSERT INTO `{$wpdb->postmeta}` SELECT * FROM tmp_custom_values");
1262
-
1263
- // And clean up after ourselves.
1264
- $wpdb->query("DROP TABLE tmp_custom_values");
1265
-
1266
- // Now fix the guids to avoid duplicate posts
1267
- echo "<ul>";
1268
- foreach ($this->feeds as $syndicatedLink) :
1269
- $feed = $syndicatedLink->settings;
1270
- echo "<li>Fixing post meta-data for <cite>".$feed['link/name']."</cite> &#8230; "; flush();
1271
- $rss = @fetch_rss($feed['link/uri']);
1272
- if (is_array($rss->items)) :
1273
- foreach ($rss->items as $item) :
1274
- $post = new SyndicatedPost($item, $syndicatedLink);
1275
- $guid = $wpdb->escape($post->guid()); // new GUID algorithm
1276
- $link = $wpdb->escape($item['link']);
1277
-
1278
- $wpdb->query("
1279
- UPDATE `{$wpdb->posts}` SET guid='{$guid}' WHERE guid='{$link}'
1280
- ");
1281
- endforeach;
1282
- endif;
1283
- echo "<strong>complete.</strong></li>\n";
1284
- endforeach;
1285
- echo "</ul>\n";
1286
 
1287
  // Mark the upgrade as successful.
1288
  update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
@@ -1290,6 +1336,25 @@ class FeedWordPress {
1290
  echo "<p>Upgrade complete. FeedWordPress is now ready to use again.</p>";
1291
  } /* FeedWordPress::upgrade_database() */
1292
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1293
  function create_guid_index () {
1294
  global $wpdb;
1295
 
@@ -1298,26 +1363,68 @@ class FeedWordPress {
1298
  ");
1299
  } /* FeedWordPress::create_guid_index () */
1300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1301
  function clear_cache () {
1302
  global $wpdb;
1303
 
1304
- // MagpieRSS stores its cached feeds in options table rows with
1305
- // name = `rss_{md5 of url}` and timestamps for cached feeds in
1306
- // table rows with name = `rss_{md5 of url}_ts`. The md5 is
1307
- // always 32 characters in length, so the total option_name is
1308
- // always over 32 characters.
 
 
1309
  $wpdb->query("
1310
  DELETE FROM {$wpdb->options}
1311
- WHERE LOCATE('rss_', option_name) AND LENGTH(option_name) > 32
1312
  ");
1313
  } /* FeedWordPress::clear_cache () */
1314
 
1315
- function magpie_version () {
1316
- if (!defined('MAGPIE_VERSION')) : $magpie_version = $GLOBALS['wp_version'].'-default';
1317
- else : $magpie_version = MAGPIE_VERSION;
 
1318
  endif;
1319
- return $magpie_version;
1320
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
1321
 
1322
  # Utility functions for handling text settings
1323
  function negative ($f, $setting) {
@@ -1335,15 +1442,10 @@ class FeedWordPress {
1335
  function critical_bug ($varname, $var, $line) {
1336
  global $wp_version;
1337
 
1338
- if (defined('MAGPIE_VERSION')) : $mv = MAGPIE_VERSION;
1339
- else : $mv = 'WordPress '.$wp_version.' default.';
1340
- endif;
1341
-
1342
  echo '<p>There may be a bug in FeedWordPress. Please <a href="'.FEEDWORDPRESS_AUTHOR_CONTACT.'">contact the author</a> and paste the following information into your e-mail:</p>';
1343
  echo "\n<plaintext>";
1344
  echo "Triggered at line # ".$line."\n";
1345
  echo "FeedWordPress version: ".FEEDWORDPRESS_VERSION."\n";
1346
- echo "MagpieRSS version: {$mv}\n";
1347
  echo "WordPress version: {$wp_version}\n";
1348
  echo "PHP version: ".phpversion()."\n";
1349
  echo "\n";
@@ -1371,7 +1473,7 @@ function feedwordpress_xmlrpc_hook ($args = array ()) {
1371
  }
1372
 
1373
  function feedwordpress_pong ($args) {
1374
- $feedwordpress =& new FeedWordPress;
1375
  $delta = @$feedwordpress->update($args[1]);
1376
  if (is_null($delta)):
1377
  return array('flerror' => true, 'message' => "Sorry. I don't syndicate <$args[1]>.");
@@ -1384,11 +1486,7 @@ function feedwordpress_pong ($args) {
1384
  endif;
1385
  }
1386
 
1387
- # The upgraded MagpieRSS also uses this class. So if we have it loaded
1388
- # in, don't load it again.
1389
- if (!class_exists('Relative_URI')) {
1390
- require_once(dirname(__FILE__) . '/relative_uri.class.php');
1391
- }
1392
 
1393
  // take your best guess at the realname and e-mail, given a string
1394
  define('FWP_REGEX_EMAIL_ADDY', '([^@"(<\s]+@[^"@(<\s]+\.[^"@(<\s]+)');
3
  Plugin Name: FeedWordPress
4
  Plugin URI: http://feedwordpress.radgeek.com/
5
  Description: simple and flexible Atom/RSS syndication for WordPress
6
+ Version: 2010.0528
7
  Author: Charles Johnson
8
  Author URI: http://radgeek.com/
9
  License: GPL
10
  */
11
 
12
+ /**
13
+ * @package FeedWordPress
14
+ * @version 2010.0528
15
+ */
16
+
17
  # This uses code derived from:
18
  # - wp-rss-aggregate.php by Kellan Elliot-McCrea <kellan@protest.net>
19
  # - HTTP Navigator 2 by Keyvan Minoukadeh <keyvan@k1m.com>
33
 
34
  # -- Don't change these unless you know what you're doing...
35
 
36
+ define ('FEEDWORDPRESS_VERSION', '2010.0528');
37
  define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'http://radgeek.com/contact');
38
 
39
  // Defaults
71
  // Help us to pick out errors, if any.
72
  ini_set('error_reporting', E_ALL & ~E_NOTICE);
73
  ini_set('display_errors', true);
 
74
 
75
  // When testing we don't want cache issues to interfere. But this is
76
  // a VERY BAD SETTING for a production server. Webmasters will eat your
77
  // face for breakfast if you use it, and the baby Jesus will cry. So
78
  // make sure FEEDWORDPRESS_DEBUG is FALSE for any site that will be
79
  // used for more than testing purposes!
80
+ define('FEEDWORDPRESS_CACHE_AGE', 1);
81
+ define('FEEDWORDPRESS_CACHE_LIFETIME', 1);
82
+ define('FEEDWORDPRESS_FETCH_TIME_OUT', 60);
83
  else :
84
+ // Hold onto data all day for conditional GET purposes,
85
+ // but consider it stale after 1 min (requiring a conditional GET)
86
+ define('FEEDWORDPRESS_CACHE_LIFETIME', 24*60*60);
87
+ define('FEEDWORDPRESS_CACHE_AGE', 1*60);
88
+ define('FEEDWORDPRESS_FETCH_TIME_OUT', 10);
89
  endif;
90
 
91
+ // Use our the cache settings that we want.
92
+ add_filter('wp_feed_cache_transient_lifetime', array('FeedWordPress', 'cache_lifetime'));
 
93
 
94
+ // Ensure that we have SimplePie loaded up and ready to go.
95
+ // We no longer need a MagpieRSS upgrade module. Hallelujah!
96
+ require_once(ABSPATH . WPINC . '/feed.php');
 
 
97
 
98
  if (isset($wp_db_version)) :
99
  if ($wp_db_version >= FWP_SCHEMA_23) :
182
  # Filter in original permalinks if the user wants that
183
  add_filter('post_link', 'syndication_permalink', 1);
184
 
185
+ # When foreign URLs are used for permalinks in feeds or display
186
+ # contexts, they need to be escaped properly.
187
+ add_filter('the_permalink', 'syndication_permalink_escaped');
188
+ add_filter('the_permalink_rss', 'syndication_permalink_escaped');
189
+
190
+ add_filter('post_comments_feed_link', 'syndication_comments_feed_link');
191
+
192
  # WTF? By default, wp_insert_link runs incoming link_url and link_rss
193
  # URIs through default filters that include `wp_kses()`. But `wp_kses()`
194
  # just happens to escape any occurrence of & to &amp; -- which just
199
  # Admin menu
200
  add_action('admin_menu', 'fwp_add_pages');
201
  add_action('admin_notices', 'fwp_check_debug');
 
 
202
 
203
  add_action('admin_menu', 'feedwordpress_add_post_edit_controls');
204
  add_action('save_post', 'feedwordpress_save_post_edit_controls');
226
  add_action('feedwordpress_update_complete', 'log_feedwordpress_update_complete', 100);
227
  endif;
228
 
229
+ if (FeedWordPress::update_requested()) :
230
+ if (FEEDWORDPRESS_DEBUG) :
231
+ add_action('post_syndicated_item', 'debug_out_feedwordpress_post', 100);
232
+ add_action('update_syndicated_item', 'debug_out_feedwordpress_update_post', 100);
233
+ add_action('feedwordpress_update', 'debug_out_feedwordpress_update_feeds', 100);
234
+ add_action('feedwordpress_check_feed', 'debug_out_feedwordpress_check_feed', 100);
235
+ add_action('feedwordpress_update_complete', 'debug_out_feedwordpress_update_complete', 100);
236
+ endif;
237
+
238
+ add_action('feedwordpress_check_feed_complete', 'debug_out_feedwordpress_feed_error', 100, 3);
239
  endif;
240
 
241
  # Cron-less auto-update. Hooray!
256
  add_action('admin_menu', 'fwp_add_pages');
257
  endif; // if (!FeedWordPress::needs_upgrade())
258
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  function feedwordpress_auto_update () {
260
  if (FeedWordPress::stale()) :
261
+ $feedwordpress = new FeedWordPress;
262
  $feedwordpress->update();
263
  endif;
264
  } /* feedwordpress_auto_update () */
268
 
269
  // Explicit update request in the HTTP request (e.g. from a cron job)
270
  if (FeedWordPress::update_requested()) :
271
+ $feedwordpress = new FeedWordPress;
272
  $feedwordpress->update(FeedWordPress::update_requested_url());
273
 
274
  if (FEEDWORDPRESS_DEBUG and count($wpdb->queries) > 0) :
367
  : implode(' and ', $mesg))."\n");
368
  }
369
 
370
+ function debug_out_feedwordpress_feed_error ($feed, $added, $dt) {
371
+ if (is_wp_error($added)) :
372
+ $mesgs = $added->get_error_messages();
373
+ foreach ($mesgs as $mesg) :
374
+ echo "[feedwordpress] Error updating [{$feed['link/uri']}]: $mesg\n";
375
+ endforeach;
376
+ endif;
377
+ }
378
+
379
  ################################################################################
380
  ## TEMPLATE API: functions to make your templates syndication-aware ############
381
  ################################################################################
382
 
383
+ /**
384
+ * is_syndicated: Tests whether the current post in a Loop context, or a post
385
+ * given by ID number, was syndicated by FeedWordPress. Useful for templates
386
+ * to determine whether or not to retrieve syndication-related meta-data in
387
+ * displaying a post.
388
+ *
389
+ * @param int $id The post to check for syndicated status. Defaults to the current post in a Loop context.
390
+ * @return bool TRUE if the post's meta-data indicates it was syndicated; FALSE otherwise
391
+ */
392
+ function is_syndicated ($id = NULL) {
393
+ return (strlen(get_syndication_feed_id($id)) > 0);
394
+ } /* function is_syndicated() */
395
 
396
  function get_syndication_source_link ($original = NULL, $id = NULL) {
397
  if (is_null($original)) : $original = FeedWordPress::use_aggregator_source_data();
423
  $url = (isset($bits['user'])?$bits['user'].'@':'')
424
  .(isset($bits['host'])?$bits['host']:'')
425
  .(isset($bits['path'])?$bits['path']:'')
426
+ .(isset($uri_bits['port'])?':'.$uri_bits['port']:'')
427
  .(isset($bits['query'])?'?'.$bits['query']:'');
428
 
429
  if (strlen($url) > ($before+$after)) :
504
  function the_syndication_feed_id ($id = NULL) { echo get_syndication_feed_id($id); }
505
 
506
  $feedwordpress_linkcache = array (); // only load links from database once
507
+ function get_syndication_feed_object ($id = NULL) {
508
+ global $feedwordpress_linkcache;
509
 
510
+ $link = NULL;
 
 
511
 
512
+ $feed_id = get_syndication_feed_id($id);
513
  if (strlen($feed_id) > 0):
514
  if (isset($feedwordpress_linkcache[$feed_id])) :
515
  $link = $feedwordpress_linkcache[$feed_id];
516
  else :
517
+ $link = new SyndicatedLink($feed_id);
518
  $feedwordpress_linkcache[$feed_id] = $link;
519
  endif;
520
+ endif;
521
+ return $link;
522
+ }
523
+
524
+ function get_feed_meta ($key, $id = NULL) {
525
+ $ret = NULL;
526
 
527
+ $link = get_syndication_feed_object($id);
528
+ if (is_object($link) and isset($link->settings[$key])) :
529
  $ret = $link->settings[$key];
530
  endif;
531
  return $ret;
538
  echo get_syndication_permalink($id);
539
  }
540
 
541
+ /**
542
+ * get_local_permalink: returns a string containing the internal permalink
543
+ * for a post (whether syndicated or not) on your local WordPress installation.
544
+ * This may be useful if you want permalinks to point to the original source of
545
+ * an article for most purposes, but want to retrieve a URL for the local
546
+ * representation of the post for one or two limited purposes (for example,
547
+ * linking to a comments page on your local aggregator site).
548
+ *
549
+ * @param $id The numerical ID of the post to get the permalink for. If empty,
550
+ * defaults to the current post in a Loop context.
551
+ * @return string The URL of the local permalink for this post.
552
+ *
553
+ * @uses get_permalink()
554
+ * @global $feedwordpress_the_original_permalink
555
+ *
556
+ * @since 2010.0217
557
+ */
558
+ function get_local_permalink ($id = NULL) {
559
+ global $feedwordpress_the_original_permalink;
560
+
561
+ // get permalink, and thus activate filter and force global to be filled
562
+ // with original URL.
563
+ $url = get_permalink($id);
564
+ return $feedwordpress_the_original_permalink;
565
+ } /* get_local_permalink() */
566
+
567
+ /**
568
+ * the_original_permalink: displays the contents of get_original_permalink()
569
+ *
570
+ * @param $id The numerical ID of the post to get the permalink for. If empty,
571
+ * defaults to the current post in a Loop context.
572
+ *
573
+ * @uses get_local_permalinks()
574
+ * @uses apply_filters
575
+ *
576
+ * @since 2010.0217
577
+ */
578
+ function the_local_permalink ($id = NULL) {
579
+ print apply_filters('the_permalink', get_local_permalink($id));
580
+ } /* function the_local_permalink() */
581
+
582
  ################################################################################
583
  ## FILTERS: syndication-aware handling of post data for templates and feeds ####
584
  ################################################################################
585
 
586
  $feedwordpress_the_syndicated_content = NULL;
587
+ $feedwordpress_the_original_permalink = NULL;
588
 
589
  function feedwordpress_preserve_syndicated_content ($text) {
590
  global $feedwordpress_the_syndicated_content;
637
  endif;
638
  }
639
 
640
+ /**
641
+ * syndication_permalink: Allow WordPress to use the original remote URL of
642
+ * syndicated posts as their permalink. Can be turned on or off by by setting in
643
+ * Syndication => Posts & Links. Saves the old internal permalink in a global
644
+ * variable for later use.
645
+ *
646
+ * @param string $permalink The internal permalink
647
+ * @return string The new permalink. Same as the old if the post is not
648
+ * syndicated, or if FWP is set to use internal permalinks, or if the post
649
+ * was syndicated, but didn't have a proper permalink recorded.
650
+ *
651
+ * @uses FeedWordPress::munge_permalinks()
652
+ * @uses get_syndication_permalink()
653
+ * @global $feedwordpress_the_original_permalink
654
+ */
655
  function syndication_permalink ($permalink = '') {
656
+ global $feedwordpress_the_original_permalink;
657
+
658
+ // Save the local permalink in case we need to retrieve it later.
659
+ $feedwordpress_the_original_permalink = $permalink;
660
+
661
+ // Map this permalink to a post ID so we can get the correct permalink
662
+ // even outside of the Post Loop. Props Björn.
663
+ $id = url_to_postid($permalink);
664
+
665
+ $munge = false;
666
+ $link = get_syndication_feed_object($id);
667
+ if (is_object($link)) :
668
+ $munge = ($link->setting('munge permalink', 'munge_permalink', 'yes') != 'no');
669
  endif;
670
+
671
+ if ($munge):
672
+ $uri = get_syndication_permalink($id);
673
+ $permalink = ((strlen($uri) > 0) ? $uri : $permalink);
674
+ endif;
675
+ return $permalink;
676
+ } /* function syndication_permalink () */
677
+
678
+ /**
679
+ * syndication_permalink_escaped: Escape XML special characters in syndicated
680
+ * permalinks when used in feed contexts and HTML display contexts.
681
+ *
682
+ * @param string $permalink
683
+ * @return string
684
+ *
685
+ * @uses is_syndicated()
686
+ * @uses FeedWordPress::munge_permalinks()
687
+ *
688
+ */
689
+ function syndication_permalink_escaped ($permalink) {
690
+ if (is_syndicated() and FeedWordPress::munge_permalinks()) :
691
+ // This is a foreign link; WordPress can't vouch for its not
692
+ // having any entities that need to be &-escaped. So we'll do
693
+ // it here.
694
+ $permalink = esc_html($permalink);
695
+ endif;
696
+ return $permalink;
697
+ } /* function syndication_permalink_escaped() */
698
+
699
+ /**
700
+ * syndication_comments_feed_link: Escape XML special characters in comments
701
+ * feed links
702
+ *
703
+ * @param string $link
704
+ * @return string
705
+ *
706
+ * @uses is_syndicated()
707
+ * @uses FeedWordPress::munge_permalinks()
708
+ */
709
+ function syndication_comments_feed_link ($link) {
710
+ global $feedwordpress_the_original_permalink, $id;
711
+
712
+ if (is_syndicated() and FeedWordPress::munge_permalinks()) :
713
+ // If the source post provided a comment feed URL using
714
+ // wfw:commentRss or atom:link/@rel="replies" we can make use of
715
+ // that value here.
716
+ $source = get_syndication_feed_object();
717
+ $replacement = NULL;
718
+ if ($source->setting('munge comments feed links', 'munge_comments_feed_links', 'yes') != 'no') :
719
+ $commentFeeds = get_post_custom_values('wfw:commentRSS');
720
+ if (
721
+ is_array($commentFeeds)
722
+ and (count($commentFeeds) > 0)
723
+ and (strlen($commentFeeds[0]) > 0)
724
+ ) :
725
+ $replacement = $commentFeeds[0];
726
+
727
+ // This is a foreign link; WordPress can't vouch for its not
728
+ // having any entities that need to be &-escaped. So we'll do it
729
+ // here.
730
+ $replacement = esc_html($replacement);
731
+ endif;
732
+ endif;
733
+
734
+ if (is_null($replacement)) :
735
+ // Q: How can we get the proper feed format, since the
736
+ // format is, stupidly, not passed to the filter?
737
+ // A: Kludge kludge kludge kludge!
738
+ $fancy_permalinks = ('' != get_option('permalink_structure'));
739
+ if ($fancy_permalinks) :
740
+ preg_match('|/feed(/([^/]+))?/?$|', $link, $ref);
741
+
742
+ $format = (isset($ref[2]) ? $ref[2] : '');
743
+ if (strlen($format) == 0) : $format = get_default_feed(); endif;
744
+
745
+ $replacement = trailingslashit($feedwordpress_the_original_permalink) . 'feed';
746
+ if ($format != get_default_feed()) :
747
+ $replacement .= '/'.$format;
748
+ endif;
749
+ $replacement = user_trailingslashit($replacement, 'single_feed');
750
+ else :
751
+ // No fancy permalinks = no problem
752
+ // WordPress doesn't call get_permalink() to
753
+ // generate the comment feed URL, so the
754
+ // comments feed link is never munged by FWP.
755
+ endif;
756
+ endif;
757
+
758
+ if (!is_null($replacement)) : $link = $replacement; endif;
759
+ endif;
760
+ return $link;
761
+ } /* function syndication_comments_feed_link() */
762
 
763
  ################################################################################
764
  ## ADMIN MENU ADD-ONS: register Dashboard management pages #####################
804
  endif;
805
  } /* function fwp_check_debug () */
806
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
807
  ################################################################################
808
  ## fwp_hold_pings() and fwp_release_pings(): Outbound XML-RPC ping reform ####
809
  ## ... 'coz it's rude to send 500 pings the first time your aggregator runs ####
874
  if (is_syndicated($post->ID)) :
875
  ?>
876
  <p>This is a syndicated post, which originally appeared at
877
+ <cite><?php print esc_html(get_syndication_source(NULL, $post->ID)); ?></cite>.
878
+ <a href="<?php print esc_html(get_syndication_permalink($post->ID)); ?>">View original post</a>.</p>
879
 
880
  <p><input type="hidden" name="feedwordpress_noncename" id="feedwordpress_noncename" value="<?php print wp_create_nonce(plugin_basename(__FILE__)); ?>" />
881
  <label><input type="checkbox" name="freeze_updates" value="yes" <?php if ($frozen_post) : ?>checked="checked"<?php endif; ?> /> <strong>Manual editing.</strong>
963
  $this->feeds = array ();
964
  $links = FeedWordPress::syndicated_links();
965
  if ($links): foreach ($links as $link):
966
+ $this->feeds[] = new SyndicatedLink($link);
967
  endforeach; endif;
968
  } // FeedWordPress::FeedWordPress ()
969
 
1069
  $added = $feed->poll($crash_ts);
1070
  do_action('feedwordpress_check_feed_complete', $feed->settings, $added, time() - $start_ts);
1071
 
1072
+ if (is_array($added)) : // Success
1073
+ if (isset($added['new'])) : $delta['new'] += $added['new']; endif;
1074
+ if (isset($added['updated'])) : $delta['updated'] += $added['updated']; endif;
1075
+ endif;
1076
  endif;
1077
  endforeach;
1078
 
1228
  return apply_filters('syndicated_post_use_aggregator_source_data', ($ret=='yes'));
1229
  }
1230
 
1231
+ /**
1232
+ * FeedWordPress::munge_permalinks: check whether or not FeedWordPress
1233
+ * should rewrite permalinks for syndicated items to reflect their
1234
+ * original location.
1235
+ *
1236
+ * @return bool TRUE if FeedWordPress SHOULD rewrite permalinks; FALSE otherwise
1237
+ */
1238
+ /*static*/ function munge_permalinks () {
1239
+ return (get_option('feedwordpress_munge_permalink', /*default=*/ 'yes') != 'no');
1240
+ } /* FeedWordPress::munge_permalinks() */
1241
+
1242
  function syndicated_links () {
1243
  $contributors = FeedWordPress::link_category_id();
1244
  if (function_exists('get_bookmarks')) :
1324
  if (is_null($from) or $from <= 0.96) : $from = 0.96; endif;
1325
 
1326
  switch ($from) :
1327
+ case 0.96:
1328
+ // Dropping legacy upgrade code. If anyone is still
1329
+ // using 0.96 and just now decided to upgrade, well, I'm
1330
+ // sorry about that. You'll just have to cope with a few
1331
+ // duplicate posts.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1332
 
1333
  // Mark the upgrade as successful.
1334
  update_option('feedwordpress_version', FEEDWORDPRESS_VERSION);
1336
  echo "<p>Upgrade complete. FeedWordPress is now ready to use again.</p>";
1337
  } /* FeedWordPress::upgrade_database() */
1338
 
1339
+ function has_guid_index () {
1340
+ global $wpdb;
1341
+
1342
+ $found = false; // Guilty until proven innocent.
1343
+
1344
+ $results = $wpdb->get_results("
1345
+ SHOW INDEXES FROM {$wpdb->posts}
1346
+ ");
1347
+ if ($results) :
1348
+ foreach ($results as $index) :
1349
+ if (isset($index->Column_name)
1350
+ and ('guid' == $index->Column_name)) :
1351
+ $found = true;
1352
+ endif;
1353
+ endforeach;
1354
+ endif;
1355
+ return $found;
1356
+ } /* FeedWordPress::has_guid_index () */
1357
+
1358
  function create_guid_index () {
1359
  global $wpdb;
1360
 
1363
  ");
1364
  } /* FeedWordPress::create_guid_index () */
1365
 
1366
+ function remove_guid_index () {
1367
+ global $wpdb;
1368
+
1369
+ $wpdb->query("
1370
+ DROP INDEX {$wpdb->posts}_guid_idx ON {$wpdb->posts}
1371
+ ");
1372
+ }
1373
+
1374
+ /*static*/ function fetch ($url) {
1375
+ require_once (ABSPATH . WPINC . '/class-feed.php');
1376
+ $feed = new SimplePie();
1377
+ $feed->set_feed_url($url);
1378
+ $feed->set_cache_class('WP_Feed_Cache');
1379
+ $feed->set_file_class('WP_SimplePie_File');
1380
+ $feed->set_cache_duration(FeedWordPress::cache_duration());
1381
+ $feed->init();
1382
+ $feed->handle_content_type();
1383
+
1384
+ if ($feed->error()) :
1385
+ $ret = new WP_Error('simplepie-error', $feed->error());
1386
+ else :
1387
+ $ret = $feed;
1388
+ endif;
1389
+ return $ret;
1390
+ } /* FeedWordPress::fetch () */
1391
+
1392
  function clear_cache () {
1393
  global $wpdb;
1394
 
1395
+ // The WordPress SimplePie module stores its cached feeds as
1396
+ // transient records in the options table. The data itself is
1397
+ // stored in `_transient_feed_{md5 of url}` and the last-modified
1398
+ // timestamp in `_transient_feed_mod_{md5 of url}`. Timeouts for
1399
+ // these records are stored in `_transient_timeout_feed_{md5}`.
1400
+ // Since the md5 is always 32 characters in length, the
1401
+ // option_name is always over 32 characters.
1402
  $wpdb->query("
1403
  DELETE FROM {$wpdb->options}
1404
+ WHERE option_name LIKE '_transient%_feed_%' AND LENGTH(option_name) > 32
1405
  ");
1406
  } /* FeedWordPress::clear_cache () */
1407
 
1408
+ function cache_duration () {
1409
+ $duration = NULL;
1410
+ if (defined('FEEDWORDPRESS_CACHE_AGE')) :
1411
+ $duration = FEEDWORDPRESS_CACHE_AGE;
1412
  endif;
1413
+ return $duration;
1414
  }
1415
+ function cache_lifetime ($duration) {
1416
+ // Check for explicit setting of a lifetime duration
1417
+ if (defined('FEEDWORDPRESS_CACHE_LIFETIME')) :
1418
+ $duration = FEEDWORDPRESS_CACHE_LIFETIME;
1419
+
1420
+ // Fall back to the cache freshness duration
1421
+ elseif (defined('FEEDWORDPRESS_CACHE_AGE')) :
1422
+ $duration = FEEDWORDPRESS_CACHE_AGE;
1423
+ endif;
1424
+
1425
+ // Fall back to WordPress default
1426
+ return $duration;
1427
+ } /* FeedWordPress::cache_lifetime () */
1428
 
1429
  # Utility functions for handling text settings
1430
  function negative ($f, $setting) {
1442
  function critical_bug ($varname, $var, $line) {
1443
  global $wp_version;
1444
 
 
 
 
 
1445
  echo '<p>There may be a bug in FeedWordPress. Please <a href="'.FEEDWORDPRESS_AUTHOR_CONTACT.'">contact the author</a> and paste the following information into your e-mail:</p>';
1446
  echo "\n<plaintext>";
1447
  echo "Triggered at line # ".$line."\n";
1448
  echo "FeedWordPress version: ".FEEDWORDPRESS_VERSION."\n";
 
1449
  echo "WordPress version: {$wp_version}\n";
1450
  echo "PHP version: ".phpversion()."\n";
1451
  echo "\n";
1473
  }
1474
 
1475
  function feedwordpress_pong ($args) {
1476
+ $feedwordpress = new FeedWordPress;
1477
  $delta = @$feedwordpress->update($args[1]);
1478
  if (is_null($delta)):
1479
  return array('flerror' => true, 'message' => "Sorry. I don't syndicate <$args[1]>.");
1486
  endif;
1487
  }
1488
 
1489
+ require_once(dirname(__FILE__) . '/relative_uri.class.php');
 
 
 
 
1490
 
1491
  // take your best guess at the realname and e-mail, given a string
1492
  define('FWP_REGEX_EMAIL_ADDY', '([^@"(<\s]+@[^"@(<\s]+\.[^"@(<\s]+)');
magpiefromsimplepie.class.php ADDED
@@ -0,0 +1,775 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ require_once(dirname(__FILE__).'/feedtime.class.php');
3
+
4
+ /**
5
+ * class MagpieFromSimplePie: compatibility layer to prevent existing filters
6
+ * from breaking.
7
+ *
8
+ * @since 2010.0203
9
+ *
10
+ */
11
+
12
+ class MagpieFromSimplePie {
13
+ var $pie;
14
+ var $originals;
15
+
16
+ var $channel;
17
+ var $items;
18
+ var $textinput = array ();
19
+ var $image = array();
20
+
21
+ var $feed_type;
22
+ var $feed_version;
23
+
24
+ var $_XMLNS_FAMILIAR = array (
25
+ 'http://www.w3.org/2005/Atom' => 'atom' /* 1.0 */,
26
+ 'http://purl.org/atom/ns#' => 'atom' /* pre-1.0 */,
27
+ 'http://purl.org/rss/1.0/' => 'rss' /* 1.0 */,
28
+ 'http://backend.userland.com/RSS2' => 'rss' /* 2.0 */,
29
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' => 'rdf',
30
+ 'http://www.w3.org/1999/xhtml' => 'xhtml',
31
+ 'http://purl.org/dc/elements/1.1/' => 'dc',
32
+ 'http://purl.org/dc/terms/' => 'dcterms',
33
+ 'http://purl.org/rss/1.0/modules/content/' => 'content',
34
+ 'http://purl.org/rss/1.0/modules/syndication/' => 'sy',
35
+ 'http://purl.org/rss/1.0/modules/taxonomy/' => 'taxo',
36
+ 'http://purl.org/rss/1.0/modules/dc/' => 'dc',
37
+ 'http://wellformedweb.org/CommentAPI/' => 'wfw',
38
+ 'http://webns.net/mvcb/' => 'admin',
39
+ 'http://purl.org/rss/1.0/modules/annotate/' => 'annotate',
40
+ 'http://xmlns.com/foaf/0.1/' => 'foaf',
41
+ 'http://madskills.com/public/xml/rss/module/trackback/' => 'trackback',
42
+ 'http://web.resource.org/cc/' => 'cc',
43
+ 'http://search.yahoo.com/mrss' => 'media',
44
+ 'http://search.yahoo.com/mrss/' => 'media',
45
+ 'http://video.search.yahoo.com/mrss' => 'media',
46
+ 'http://video.search.yahoo.com/mrss/' => 'media',
47
+ 'http://purl.org/syndication/thread/1.0' => 'thr',
48
+ 'http://purl.org/syndication/thread/1.0/' => 'thr',
49
+ 'http://www.w3.org/XML/1998/namespace' => 'xml',
50
+ 'http://www.itunes.com/dtds/podcast-1.0.dtd' => 'itunes',
51
+ 'http://a9.com/-/spec/opensearchrss/1.0/' => 'openSearch',
52
+ 'http://purl.org/rss/1.0/modules/slash/' => 'slash',
53
+ );
54
+
55
+ /**
56
+ * MagpieFromSimplePie constructor
57
+ *
58
+ * @param SimplePie $pie The feed to convert to MagpieRSS format.
59
+ *
60
+ * @uses MagpieFromSimplePie::processItemData
61
+ * @uses MagpieFromSimplePie::normalize
62
+ */
63
+ function MagpieFromSimplePie ($pie) {
64
+ $this->pie = $pie;
65
+ $this->originals = $this->pie->get_items();
66
+
67
+ $this->channel = $this->processFeedData($this->pie->data);
68
+ foreach ($this->originals as $key => $item) :
69
+ $this->items[$key] = $this->processItemData($item->data);
70
+ endforeach;
71
+
72
+ $this->normalize();
73
+
74
+ // In case anyone goes poking around our private members (uh...)
75
+ $this->feed_type = ($this->is_atom() ? 'Atom' : 'RSS');
76
+ $this->feed_version = $this->feed_version();
77
+ } /* MagpieFromSimplePie constructor */
78
+
79
+ /**
80
+ * MagpieFromSimplePie::get_item: returns a MagpieRSS format array
81
+ * which is equivalent to the SimplePie_Item object from which this
82
+ * object was constructed.
83
+ *
84
+ * @return array A MagpieRSS format array representing this feed item.
85
+ */
86
+ function get_items () {
87
+ return $this->items;
88
+ } /* MagpieFromSimplePie::get_item */
89
+
90
+ /**
91
+ * MagpieFromSimplePie::processFeedData
92
+ *
93
+ * @param array $data
94
+ * @return array
95
+ *
96
+ * @uses MagpieFromSimplePie::processChannelData()
97
+ */
98
+ function processFeedData ($data) {
99
+ $ret = array();
100
+ if (isset($data['child'])) : foreach ($data['child'] as $ns => $elements) :
101
+ foreach ($elements as $name => $multi) :
102
+ foreach ($multi as $element) :
103
+ if ($name=='feed' or $name=='channel') :
104
+ // Don't need to process these
105
+ foreach (array(
106
+ '',
107
+ 'http://www.w3.org/2005/Atom',
108
+ 'http://purl.org/rss/1.0/',
109
+ 'http://backend.userland.com/RSS2'
110
+ ) as $ns) :
111
+ if (isset($element['child'][$ns]['entry'])) : unset($element['child'][$ns]['entry']); endif;
112
+ if (isset($element['child'][$ns]['item'])) : unset($element['child'][$ns]['item']); endif;
113
+ endforeach;
114
+
115
+ $ret = $this->processChannelData($element) + $ret;
116
+ elseif (in_array(strtolower($name), array('rss', 'rdf'))) :
117
+ // Drop down to get to <channel> element
118
+ $ret = $this->processFeedData($element) + $ret;
119
+ endif;
120
+ endforeach;
121
+ endforeach;
122
+ endforeach; endif;
123
+ return $ret;
124
+ } /* MagpieFromSimplePie::processFeedData() */
125
+
126
+ /**
127
+ * MagpieFromSimplePie::processChannelData
128
+ *
129
+ * @param array $data
130
+ * @param array $path
131
+ * @return array
132
+ *
133
+ * @uses MagpieFromSimplePie::handleAttributes
134
+ * @uses MagpieFromSimplePie::handleChildren
135
+ */
136
+ function processChannelData ($data, $path = array()) {
137
+ $ret = array();
138
+ $tagPath = strtolower(implode('_', $path));
139
+
140
+ // Only process at the right level
141
+ if (strlen($tagPath) > 0
142
+ and isset($data['data'])
143
+ and strlen($data['data']) > 0) :
144
+ $ret[$tagPath] = $data['data'];
145
+ endif;
146
+
147
+ $ret = $this->handleAttributes($data, $path)
148
+ + $this->handleChildren($data, $path, 'processChannelData')
149
+ + $ret;
150
+
151
+ return $ret;
152
+ } /* MagpieFromSimplePie::processChannelData() */
153
+
154
+ /**
155
+ * MagpieFromSimplePie::processItemData
156
+ *
157
+ * @param array $data
158
+ * @param array $path
159
+ * @return array
160
+ *
161
+ * @uses MagpieFromSimplePie::handleAttributes
162
+ * @uses MagpieFromSimplePie::handleChildren
163
+ */
164
+ function processItemData ($data, $path = array()) {
165
+ $ret = array();
166
+ $tagPath = strtolower(implode('_', $path));
167
+
168
+ if (strlen($tagPath) > 0 and isset($data['data']) and strlen($data['data']) > 0) :
169
+ $ret[$tagPath] = $data['data'];
170
+ endif;
171
+
172
+ // Set up xml:base to be recorded in array
173
+ if (isset($data['xml_base_explicit']) and $data['xml_base_explicit']) :
174
+ $data['attribs']['']['xml:base'] = $data['xml_base'];
175
+ endif;
176
+
177
+ $ret = $this->handleAttributes($data, $path)
178
+ + $this->handleChildren($data, $path, 'processItemData')
179
+ + $ret;
180
+
181
+ return $ret;
182
+ } /* MagpieFromSimplePie::processItemData() */
183
+
184
+ /**
185
+ * MagpieFromSimplePie::handleAttributes
186
+ *
187
+ * @param array $data
188
+ * @param array $path
189
+ * @return array
190
+ */
191
+ function handleAttributes ($data, $path) {
192
+ $tagPath = strtolower(implode('_', $path));
193
+ $ret = array();
194
+ if (isset($data['attribs'])) : foreach ($data['attribs'] as $ns => $pairs) :
195
+ if (isset($this->_XMLNS_FAMILIAR[$ns])) :
196
+ $ns = $this->_XMLNS_FAMILIAR[$ns];
197
+ endif;
198
+
199
+ foreach ($pairs as $attr => $value) :
200
+ $attr = strtolower($attr);
201
+ if ($ns=='rdf' and $attr=='about') :
202
+ $ret['about'] = $value;
203
+ elseif (strlen($tagPath) > 0) :
204
+ if (strlen($ns) > 0 and $this->is_namespaced($ns, /*attrib=*/ true)) :
205
+ $attr = $ns.':'.$attr;
206
+ endif;
207
+
208
+ $ret[$tagPath.'@'.$attr] = $value;
209
+ if (isset($ret[$tagPath.'@']) and strlen($ret[$tagPath.'@'])>0) :
210
+ $ret[$tagPath.'@'] .= ',';
211
+ else :
212
+ $ret[$tagPath.'@'] = '';
213
+ endif;
214
+ $ret[$tagPath.'@'] .= $attr;
215
+ endif;
216
+ endforeach;
217
+ endforeach; endif;
218
+ return $ret;
219
+ } /* MagpieFromSimplePie::handleAttributes() */
220
+
221
+ var $inImage = false;
222
+ var $inTextInput = false;
223
+
224
+ /**
225
+ * MagpieFromSimplePie::handleChildren
226
+ *
227
+ * @param array $data
228
+ * @param array $path
229
+ * @return array
230
+ *
231
+ * @uses MagpieFromSimplePie::get_attrib
232
+ * @uses MagpieFromSimplePie::is_atom
233
+ * @uses MagpieFromSimplePie::increment_element
234
+ * @uses MagpieFromSimplePie::processItemData
235
+ */
236
+ function handleChildren ($data, $path = array(), $method = 'processItemData') {
237
+ $tagPath = strtolower(implode('_', $path));
238
+ $ret = array();
239
+ if (isset($data['child'])) : foreach ($data['child'] as $ns => $elements) :
240
+ if (isset($this->_XMLNS_FAMILIAR[$ns])) :
241
+ $ns = $this->_XMLNS_FAMILIAR[$ns];
242
+ endif;
243
+ if (''==$ns) :
244
+ $ns = ($this->is_atom() ? 'atom' : 'rss');
245
+ endif;
246
+
247
+ foreach ($elements as $tag => $multi) : foreach ($multi as $element) :
248
+ $copyOver = NULL;
249
+
250
+ if ('image'==$tag and 'rss'==$ns) :
251
+ $this->inImage = true;
252
+ $childPath = array();
253
+ $co = NULL;
254
+ elseif ('textinput'==strtolower($tag) and 'rss'==$ns) :
255
+ $this->inTextInput = true;
256
+ $childPath = array();
257
+ $co = NULL;
258
+ else :
259
+ // Determine tag name; check #; increment #
260
+ $childTag = strtolower($tag);
261
+ if ('link'==$tag and 'atom'==$ns) :
262
+ $rel = $this->get_attrib(
263
+ /*ns=*/ array('', 'http://www.w3.org/2005/Atom'),
264
+ /*attr=*/ 'rel',
265
+ $element
266
+ );
267
+ if ($rel != 'alternate') :
268
+ $childTag .= '_'.$rel;
269
+ endif;
270
+ $copyOver = $this->get_attrib(
271
+ /*ns=*/ array('', 'http://www.w3.org/2005/Atom'),
272
+ /*attr=*/ 'href',
273
+ $element
274
+ );
275
+ elseif ('content'==$tag and 'atom'==$ns) :
276
+ $childTag = 'atom_'.$tag;
277
+ endif;
278
+
279
+ $childTag = $this->increment_element($ret, $childTag, $ns, $path);
280
+ $childPath = $path; $childPath[] = strtolower($childTag);
281
+
282
+ if (!is_null($copyOver)) :
283
+ $co = array();
284
+ $co[implode('_', $childPath)] = $copyOver;
285
+ else :
286
+ $co = NULL;
287
+ endif;
288
+ endif;
289
+
290
+ $arr = $this->{$method}($element, $childPath);
291
+ if ($co) :
292
+ $arr = $co + $arr; // Left-hand overwrites right-hand
293
+ endif;
294
+
295
+ if ($this->inImage) :
296
+ $this->image = $arr + $this->image;
297
+
298
+ // Close tag
299
+ if ('image'==$tag and 'rss'==$ns) : $this->inImage = false; endif;
300
+ elseif ($this->inTextInput) :
301
+ $this->textinput = $arr + $this->textinput;
302
+
303
+ // Close tag
304
+ if ('textinput'==$tag and 'rss'==$ns) : $this->inTextInput = false; endif;
305
+ elseif ($this->is_namespaced($ns)) :
306
+ if (!isset($ret[$ns])) : $ret[$ns] = array(); endif;
307
+ $ret[$ns] = $arr + $ret[$ns];
308
+ else :
309
+ $ret = $arr + $ret;
310
+ endif;
311
+ endforeach; endforeach;
312
+
313
+ endforeach; endif;
314
+ return $ret;
315
+ } /* MagpieFromSimplePie::handleChildren() */
316
+
317
+ /**
318
+ * MagpieFromSimplePie::get_attrib
319
+ *
320
+ * @param array $namespaces
321
+ * @param string $attr
322
+ * @param array $element
323
+ * @param mixed $default
324
+ */
325
+ function get_attrib ($namespaces, $attr, $element, $default = NULL) {
326
+ $ret = $default;
327
+ if (isset($element['attribs'])) :
328
+ foreach ($namespaces as $ns) :
329
+ if (isset($element['attribs'][$ns])
330
+ and isset($element['attribs'][$ns][$attr])) :
331
+ $ret = $element['attribs'][$ns][$attr];
332
+ break;
333
+ endif;
334
+ endforeach;
335
+ endif;
336
+ return $ret;
337
+ } /* MagpieFromSimplePie::get_attrib */
338
+
339
+ /**
340
+ * MagpieFromSimplePie::normalize
341
+ *
342
+ * @uses MagpieFromSimplePie::is_atom
343
+ * @uses MagpieFromSimplePie::is_rss
344
+ * @uses MagpieFromSimplePie::normalize_element
345
+ * @uses MagpieFromSimplePie::normalize_author_inheritance
346
+ * @uses MagpieFromSimplePie::normalize_atom_person
347
+ * @uses MagpieFromSimplePie::normalize_enclosure
348
+ * @uses MagpieFromSimplePie::normalize_category
349
+ * @uses MagpieFromSimplePie::normalize_dc_subject
350
+ * @uses FeedTime
351
+ * @uses FeedTime::timestamp
352
+ */
353
+ function normalize () {
354
+ // Normalize channel data
355
+ if ( $this->is_atom() ) :
356
+ // Atom 1.0 elements <=> Atom 0.3 elements (Thanks, o brilliant wordsmiths of the Atom 1.0 standard!)
357
+ if ($this->feed_version() < 1.0) :
358
+ $this->normalize_element($this->channel, 'tagline', $this->channel, 'subtitle');
359
+ $this->normalize_element($this->channel, 'copyright', $this->channel, 'rights');
360
+ $this->normalize_element($this->channel, 'modified', $this->channel, 'updated');
361
+ else :
362
+ $this->normalize_element($this->channel, 'subtitle', $this->channel, 'tagline');
363
+ $this->normalize_element($this->channel, 'rights', $this->channel, 'copyright');
364
+ $this->normalize_element($this->channel, 'updated', $this->channel, 'modified');
365
+ endif;
366
+ $this->normalize_element($this->channel, 'author', $this->channel['dc'], 'creator', 'normalize_atom_person');
367
+ $this->normalize_element($this->channel, 'contributor', $this->channel['dc'], 'contributor', 'normalize_atom_person');
368
+
369
+ // Atom elements to RSS elements
370
+ $this->normalize_element($this->channel, 'subtitle', $this->channel, 'description');
371
+
372
+ if ( isset($this->channel['logo']) ) :
373
+ $this->normalize_element($this->channel, 'logo', $this->image, 'url');
374
+ $this->normalize_element($this->channel, 'link', $this->image, 'link');
375
+ $this->normalize_element($this->channel, 'title', $this->image, 'title');
376
+ endif;
377
+
378
+ elseif ( $this->is_rss() ) :
379
+ // Normalize image element from where stupid MagpieRSS puts it
380
+ //$this->normalize_element($this->channel, 'image_title', $this->image, 'title');
381
+ //$this->normalize_element($this->channel, 'image_link', $this->image, 'link');
382
+ //$this->normalize_element($this->channel, 'image_url', $this->image, 'url');
383
+
384
+ // ... and, gag, textInput
385
+ //$this->normalize_element($this->channel, 'textinput_title', $this->textinput, 'title');
386
+ //$this->normalize_element($this->channel, 'textinput_link', $this->textinput, 'link');
387
+ //$this->normalize_element($this->channel, 'textinput_name', $this->textinput, 'name');
388
+ //$this->normalize_element($this->channel, 'textinput_description', $this->textinput, 'description');
389
+
390
+ // RSS elements to Atom elements
391
+ $this->normalize_element($this->channel, 'description', $this->channel, 'tagline'); // Atom 0.3
392
+ $this->normalize_element($this->channel, 'description', $this->channel, 'subtitle'); // Atom 1.0 (yay wordsmithing!)
393
+ $this->normalize_element($this->image, 'url', $this->channel, 'logo');
394
+ endif;
395
+
396
+ // Now loop through and normalize item data
397
+ for ( $i = 0; $i < count($this->items); $i++) :
398
+ $item = $this->items[$i];
399
+
400
+ // if atom populate rss fields and normalize 0.3 and 1.0 feeds
401
+ if ( $this->is_atom() ) :
402
+ // Atom 1.0 elements <=> Atom 0.3 elements
403
+ if ($this->feed_version() < 1.0) :
404
+ $this->normalize_element($item, 'modified', $item, 'updated');
405
+ $this->normalize_element($item, 'issued', $item, 'published');
406
+ else :
407
+ $this->normalize_element($item, 'updated', $item, 'modified');
408
+ $this->normalize_element($item, 'published', $item, 'issued');
409
+ endif;
410
+
411
+ $this->normalize_author_inheritance($item, $this->originals[$i]);
412
+
413
+ // Atom elements to RSS elements
414
+ $this->normalize_element($item, 'author', $item['dc'], 'creator', 'normalize_atom_person');
415
+ $this->normalize_element($item, 'contributor', $item['dc'], 'contributor', 'normalize_atom_person');
416
+ $this->normalize_element($item, 'summary', $item, 'description');
417
+ $this->normalize_element($item, 'atom_content', $item['content'], 'encoded');
418
+ $this->normalize_element($item, 'link_enclosure', $item, 'enclosure', 'normalize_enclosure');
419
+
420
+ // Categories
421
+ if ( isset($item['category#']) ) : // Atom 1.0 categories to dc:subject and RSS 2.0 categories
422
+ $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category');
423
+ elseif ( isset($item['dc']['subject#']) ) : // dc:subject to Atom 1.0 and RSS 2.0 categories
424
+ $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject');
425
+ endif;
426
+
427
+ // Normalized item timestamp
428
+ $item_date = (isset($item['published']) ) ? $item['published'] : $item['updated'];
429
+ elseif ( $this->is_rss() ) :
430
+ // RSS elements to Atom elements
431
+ $this->normalize_element($item, 'description', $item, 'summary');
432
+ $this->normalize_element($item, 'enclosure', $item, 'link_enclosure', 'normalize_enclosure');
433
+
434
+ // Categories
435
+ if ( isset($item['category#']) ) : // RSS 2.0 categories to dc:subject and Atom 1.0 categories
436
+ $this->normalize_element($item, 'category', $item['dc'], 'subject', 'normalize_category');
437
+ elseif ( isset($item['dc']['subject#']) ) : // dc:subject to Atom 1.0 and RSS 2.0 categories
438
+ $this->normalize_element($item['dc'], 'subject', $item, 'category', 'normalize_dc_subject');
439
+ endif;
440
+
441
+ // Normalized item timestamp
442
+ if (isset($item['pubdate'])) :
443
+ $item_date = $item['pubdate'];
444
+ elseif (isset($item['dc']['date'])) :
445
+ $item_date = $item['dc']['date'];
446
+ else :
447
+ $item_date = null;
448
+ endif;
449
+ endif;
450
+
451
+ if ( $item_date ) :
452
+ $date_timestamp = new FeedTime($item_date);
453
+
454
+ if (!$date_timestamp->failed()) :
455
+ $item['date_timestamp'] = $date_timestamp->timestamp();
456
+ endif;
457
+ endif;
458
+
459
+ $this->items[$i] = $item;
460
+ endfor;
461
+ } /* MagpieFromSimplePie::normalize() */
462
+
463
+ /**
464
+ * MagpieFromSimplePie::normalize_author_inheritance
465
+ *
466
+ * @param SimplePie_Item $original
467
+ *
468
+ * @uses SimplePie_Item::get_authors
469
+ * @uses SimplePie_Author::get_name
470
+ * @uses SimplePie_Author::get_link
471
+ * @uses SimplePie_Author::get_email
472
+ * @uses MagpieFromSimplePie::increment_element
473
+ */
474
+ function normalize_author_inheritance (&$item, $original) {
475
+ // "If an atom:entry element does not contain
476
+ // atom:author elements, then the atom:author elements
477
+ // of the contained atom:source element are considered
478
+ // to apply. In an Atom Feed Document, the atom:author
479
+ // elements of the containing atom:feed element are
480
+ // considered to apply to the entry if there are no
481
+ // atom:author elements in the locations described
482
+ // above." <http://atompub.org/2005/08/17/draft-ietf-atompub-format-11.html#rfc.section.4.2.1>
483
+ if (!isset($item["author#"])) :
484
+ $authors = $original->get_authors();
485
+ foreach ($authors as $author) :
486
+ $tag = $this->increment_element($item, 'author', 'atom', array());
487
+ $item[$tag] = $item["{$tag}_name"] = $author->get_name();
488
+ if ($author->get_link()) : $item["{$tag}_uri"] = $item["{$tag}_url"] = $author->get_link(); endif;
489
+ if ($author->get_email()) : $item["{$tag}_email"] = $author->get_email(); endif;
490
+ endforeach;
491
+ endif;
492
+ } /* MagpieFromSimplePie::normalize_author_inheritance() */
493
+
494
+ /**
495
+ * MagpieFromSimplePie::normalize_element
496
+ * Simplify the logic for normalize(). Makes sure that count of elements
497
+ * and each of multiple elements is normalized properly. If you need to
498
+ * mess with things like attributes or change formats or the like, pass
499
+ * it a callback to handle each element.
500
+ *
501
+ * @param array &$source
502
+ * @param string $from
503
+ * @param array &$dest
504
+ * @param string $to
505
+ * @param mixed $via
506
+ */
507
+ function normalize_element (&$source, $from, &$dest, $to, $via = NULL) {
508
+ if (isset($source[$from]) or isset($source["{$from}#"])) :
509
+ if (isset($source["{$from}#"])) :
510
+ $n = $source["{$from}#"];
511
+ $dest["{$to}#"] = $source["{$from}#"];
512
+ else :
513
+ $n = 1;
514
+ endif;
515
+
516
+ for ($i = 1; $i <= $n; $i++) :
517
+ if (isset($via) and is_callable(array($this, $via))) : // custom callback for ninja attacks
518
+ $this->{$via}($source, $from, $dest, $to, $i);
519
+ else : // just make it the same
520
+ $from_id = $this->element_id($from, $i);
521
+ $to_id = $this->element_id($to, $i);
522
+
523
+ if (isset($source[$from_id])) : // Avoid PHP notice nastygrams
524
+ $dest[$to_id] = $source[$from_id];
525
+ endif;
526
+ endif;
527
+ endfor;
528
+ endif;
529
+ } /* MagpieFromSimplePie::normalize_element */
530
+
531
+ /**
532
+ * MagpieFromSimplePie::normalize_enclosure
533
+ *
534
+ * @param array &$source
535
+ * @param string $from
536
+ * @param array &$dest
537
+ * @param string $to
538
+ * @param int $i
539
+ *
540
+ * @uses MagpieFromSimplePie::element_id
541
+ */
542
+ function normalize_enclosure (&$source, $from, &$dest, $to, $i) {
543
+ $id_from = $this->element_id($from, $i);
544
+ $id_to = $this->element_id($to, $i);
545
+ if (isset($source["{$id_from}@"])) :
546
+ foreach (explode(',', $source["{$id_from}@"]) as $attr) :
547
+ if ($from=='link_enclosure' and $attr=='href') : // from Atom
548
+ $dest["{$id_to}@url"] = $source["{$id_from}@{$attr}"];
549
+ $dest["{$id_to}"] = $source["{$id_from}@{$attr}"];
550
+ elseif ($from=='enclosure' and $attr=='url') : // from RSS
551
+ $dest["{$id_to}@href"] = $source["{$id_from}@{$attr}"];
552
+ $dest["{$id_to}"] = $source["{$id_from}@{$attr}"];
553
+ else :
554
+ $dest["{$id_to}@{$attr}"] = $source["{$id_from}@{$attr}"];
555
+ endif;
556
+ endforeach;
557
+ endif;
558
+ } /* MagpieFromSimplePie::normalize_enclosure */
559
+
560
+ /**
561
+ * MagpieFromSimplePie::normalize_atom_person
562
+ *
563
+ * @param array &$source
564
+ * @param string $person
565
+ * @param array &$dest
566
+ * @param string $to
567
+ * @param int $i
568
+ *
569
+ *
570
+ * @uses MagpieFromSimplePie::element_id
571
+ */
572
+ function normalize_atom_person (&$source, $person, &$dest, $to, $i) {
573
+ $id = $this->element_id($person, $i);
574
+ $id_to = $this->element_id($to, $i);
575
+
576
+ // Atom 0.3 <=> Atom 1.0
577
+ if ($this->feed_version() >= 1.0) :
578
+ $used = 'uri'; $norm = 'url';
579
+ else :
580
+ $used = 'url'; $norm = 'uri';
581
+ endif;
582
+
583
+ if (isset($source["{$id}_{$used}"])) :
584
+ $dest["{$id_to}_{$norm}"] = $source["{$id}_{$used}"];
585
+ endif;
586
+
587
+ // Atom to RSS 2.0 and Dublin Core
588
+ // RSS 2.0 person strings should be valid e-mail addresses if possible.
589
+ if (isset($source["{$id}_email"])) :
590
+ $rss_author = $source["{$id}_email"];
591
+ endif;
592
+ if (isset($source["{$id}_name"])) :
593
+ $rss_author = $source["{$id}_name"]
594
+ . (isset($rss_author) ? " <$rss_author>" : '');
595
+ endif;
596
+ if (isset($rss_author)) :
597
+ $source[$id] = $rss_author; // goes to top-level author or contributor
598
+ $dest[$id_to] = $rss_author; // goes to dc:creator or dc:contributor
599
+ endif;
600
+ } /* MagpieFromSimplePie::normalize_atom_person */
601
+
602
+ /**
603
+ * MagpieFromSimplePie::normalize_category: Normalize Atom 1.0 and
604
+ * RSS 2.0 categories to Dublin Core...
605
+ *
606
+ * @param array &$source
607
+ * @param string $from
608
+ * @param array &$dest
609
+ * @param string $to
610
+ * @param int $i
611
+ *
612
+ * @uses MagpieFromSimplePie::element_id
613
+ * @uses MagpieFromSimplePie::is_rss
614
+ */
615
+ function normalize_category (&$source, $from, &$dest, $to, $i) {
616
+ $cat_id = $this->element_id($from, $i);
617
+ $dc_id = $this->element_id($to, $i);
618
+
619
+ // first normalize category elements: Atom 1.0 <=> RSS 2.0
620
+ if ( isset($source["{$cat_id}@term"]) ) : // category identifier
621
+ $source[$cat_id] = $source["{$cat_id}@term"];
622
+ elseif ( $this->is_rss() ) :
623
+ $source["{$cat_id}@term"] = $source[$cat_id];
624
+ endif;
625
+
626
+ if ( isset($source["{$cat_id}@scheme"]) ) : // URI to taxonomy
627
+ $source["{$cat_id}@domain"] = $source["{$cat_id}@scheme"];
628
+ elseif ( isset($source["{$cat_id}@domain"]) ) :
629
+ $source["{$cat_id}@scheme"] = $source["{$cat_id}@domain"];
630
+ endif;
631
+
632
+ // Now put the identifier into dc:subject
633
+ $dest[$dc_id] = $source[$cat_id];
634
+ } /* MagpieFromSimplePie::normalize_category */
635
+
636
+ /**
637
+ * MagpieFromSimplePie::normalize_dc_subject: Normalize Dublin Core
638
+ * "subject" elements to Atom 1.0 and RSS 2.0 categories.
639
+ *
640
+ * @param array &$source
641
+ * @param string $from
642
+ * @param array &$dest
643
+ * @param string $to
644
+ * @param int $i
645
+ *
646
+ * @uses MagpieFromSimplePie::element_id
647
+ */
648
+ function normalize_dc_subject (&$source, $from, &$dest, $to, $i) {
649
+ $dc_id = $this->element_id($from, $i);
650
+ $cat_id = $this->element_id($to, $i);
651
+
652
+ $dest[$cat_id] = $source[$dc_id]; // RSS 2.0
653
+ $dest["{$cat_id}@term"] = $source[$dc_id]; // Atom 1.0
654
+ }
655
+
656
+ /**
657
+ * MagpieFromSimplePie::element_id
658
+ * Magic ID function for multiple elemenets.
659
+ * Can be called as static MagpieRSS::element_id()
660
+ *
661
+ * @param string $el
662
+ * @param int $counter
663
+ * @return string
664
+ */
665
+ function element_id ($el, $counter) {
666
+ return $el . (($counter > 1) ? '#'.$counter : '');
667
+ } /* MagpieFromSimplePie::element_id */
668
+
669
+ /**
670
+ * MagpieFromSimplePie::increment_element
671
+ *
672
+ * @param array &$data
673
+ * @param string $childTag
674
+ * @param string $ns
675
+ * @param array $path
676
+ */
677
+ function increment_element (&$data, $childTag, $ns, $path) {
678
+ $counterIndex = strtolower(implode('_', array_merge($path, array($childTag.'#'))));
679
+ if ($this->is_namespaced($ns)) :
680
+ if (!isset($data[$ns])) : $data[$ns] = array(); endif;
681
+ if (!isset($data[$ns][$counterIndex])) : $data[$ns][$counterIndex] = 0; endif;
682
+ $data[$ns][$counterIndex] += 1;
683
+ $N = $data[$ns][$counterIndex];
684
+ else :
685
+ if (!isset($data[$counterIndex])) : $data[$counterIndex] = 0; endif;
686
+ $data[$counterIndex] += 1;
687
+ $N = $data[$counterIndex];
688
+ endif;
689
+
690
+ if ($N > 1) :
691
+ $childTag .= '#'.$N;
692
+ endif;
693
+ return $childTag;
694
+ } /* MagpieFromSimplePie::increment_element */
695
+
696
+ /**
697
+ * MagpieFromSimplePie::is_namespaced
698
+ *
699
+ * @param string $ns
700
+ * @return bool
701
+ *
702
+ * @uses MagpieFromSimplePie::is_atom
703
+ * @uses MagpieFromSimplePie::is_rdf
704
+ */
705
+ function is_namespaced ($ns, $attribute = false) {
706
+ // Atom vs. RSS
707
+ if ($this->is_atom()) : $root = array('', 'atom');
708
+ else : $root = array('', 'rss');
709
+ endif;
710
+
711
+ // RDF formats; namespaced in attribs but not in elements
712
+ if (!$attribute and $this->is_rdf()) :
713
+ $root[] = 'rdf';
714
+ endif;
715
+
716
+ return !in_array(strtolower($ns), $root);
717
+ } /* MagpieFromSimplePie::is_namespaced */
718
+
719
+ /**
720
+ * MagpieFromSimplePie::is_atom
721
+ *
722
+ * @return bool
723
+ */
724
+ function is_atom () {
725
+ return $this->pie->get_type() & SIMPLEPIE_TYPE_ATOM_ALL;
726
+ } /* MagpieFromSimplePie::increment_element */
727
+
728
+ /**
729
+ * MagpieFromSimplePie::is_rss
730
+ *
731
+ * @return bool
732
+ */
733
+ function is_rss () {
734
+ return $this->pie->get_type() & SIMPLEPIE_TYPE_RSS_ALL;
735
+ } /* MagpieFromSimplePie::is_rss */
736
+
737
+ /**
738
+ * MagpieFromSimplePie::is_rdf
739
+ *
740
+ * @return bool
741
+ */
742
+ function is_rdf () {
743
+ return $this->pie->get_type() & SIMPLEPIE_TYPE_RSS_RDF;
744
+ } /* MagpieFromSimplePie::is_rdf */
745
+
746
+ /**
747
+ * MagpieFromSimplePie::feed_version
748
+ *
749
+ * @return float
750
+ */
751
+ function feed_version () {
752
+ $map = array (
753
+ SIMPLEPIE_TYPE_ATOM_10 => 1.0,
754
+ SIMPLEPIE_TYPE_ATOM_03 => 0.3,
755
+ SIMPLEPIE_TYPE_RSS_090 => 0.90,
756
+ SIMPLEPIE_TYPE_RSS_091 => 0.91,
757
+ SIMPLEPIE_TYPE_RSS_092 => 0.92,
758
+ SIMPLEPIE_TYPE_RSS_093 => 0.93,
759
+ SIMPLEPIE_TYPE_RSS_094 => 0.94,
760
+ SIMPLEPIE_TYPE_RSS_10 => 1.0,
761
+ SIMPLEPIE_TYPE_RSS_20 => 2.0,
762
+ );
763
+
764
+ $ret = NULL; $type = $this->pie->get_type();
765
+ foreach ($map as $flag => $version) :
766
+ if ($type & $flag) :
767
+ $ret = $version;
768
+ break;
769
+ endif;
770
+ endforeach;
771
+ return $ret;
772
+ } /* MagpieFromSimplePie::feed_version */
773
+
774
+ } /* class MagpieFromSimplePie */
775
+
magpiemocklink.class.php CHANGED
@@ -6,7 +6,14 @@ class MagpieMockLink extends SyndicatedLink {
6
 
7
  function MagpieMockLink ($rss, $url) {
8
  $this->link = $rss;
9
- $this->magpie = $rss;
 
 
 
 
 
 
 
10
  $this->url = $url;
11
  $this->id = -1;
12
  $this->settings = array(
@@ -17,7 +24,9 @@ class MagpieMockLink extends SyndicatedLink {
17
 
18
  function poll ($crash_ts = NULL) {
19
  // Do nothing but update copy of feed
20
- $this->magpie = fetch_rss($this->url);
 
 
21
  $this->link = $this->magpie;
22
  } /* function MagpieMockLink::poll () */
23
 
@@ -26,8 +35,12 @@ class MagpieMockLink extends SyndicatedLink {
26
  } /* function MagpieMockLink::uri() */
27
 
28
  function homepage () {
29
- return (is_object($this->magpie) ? $this->magpie->channel['link'] : null);
30
  } /* function MagpieMockLink::homepage () */
 
 
 
 
31
  } /* class MagpieMockLink */
32
 
33
 
6
 
7
  function MagpieMockLink ($rss, $url) {
8
  $this->link = $rss;
9
+
10
+ if (is_array($rss) and isset($rss['simplepie']) and isset($rss['magpie'])) :
11
+ $this->simplepie = $rss['simplepie'];
12
+ $this->magpie = $rss['magpie'];
13
+ else :
14
+ $this->magpie = $rss;
15
+ endif;
16
+
17
  $this->url = $url;
18
  $this->id = -1;
19
  $this->settings = array(
24
 
25
  function poll ($crash_ts = NULL) {
26
  // Do nothing but update copy of feed
27
+ $this->simplepie = FeedWordPress::fetch($this->url);
28
+ $this->magpie = new MagpieFromSimplePie($this->simplepie);
29
+
30
  $this->link = $this->magpie;
31
  } /* function MagpieMockLink::poll () */
32
 
35
  } /* function MagpieMockLink::uri() */
36
 
37
  function homepage () {
38
+ return (!is_wp_error($this->simplepie) ? $this->simplepie->get_link() : null);
39
  } /* function MagpieMockLink::homepage () */
40
+
41
+ function save_settings ($reload = false) {
42
+ // NOOP.
43
+ }
44
  } /* class MagpieMockLink */
45
 
46
 
posts-page.php CHANGED
@@ -20,12 +20,12 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
20
 
21
  function accept_POST ($post) {
22
  global $wpdb;
23
-
24
- $link_id = $this->link->id;
25
 
26
  // User mashed a Save Changes button
27
  if (isset($post['save']) or isset($post['submit'])) :
28
  // custom post settings
 
 
29
  foreach ($post['notes'] as $mn) :
30
  $mn['key0'] = trim($mn['key0']);
31
  $mn['key1'] = trim($mn['key1']);
@@ -48,7 +48,13 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
48
  if (isset($post['resolve_relative'])) :
49
  $this->link->settings['resolve relative'] = $post['resolve_relative'];
50
  endif;
51
-
 
 
 
 
 
 
52
  // Post status, comment status, ping status
53
  foreach (array('post', 'comment', 'ping') as $what) :
54
  $sfield = "feed_{$what}_status";
@@ -61,22 +67,14 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
61
  endif;
62
  endforeach;
63
 
64
- $alter[] = "link_notes = '".$wpdb->escape($this->link->settings_to_notes())."'";
65
- $alter_set = implode(", ", $alter);
66
-
67
- // issue update query
68
- $result = $wpdb->query("
69
- UPDATE $wpdb->links
70
- SET $alter_set
71
- WHERE link_id='$link_id'
72
- ");
73
  $this->updated = true;
74
-
75
- // reload link information from DB
76
- if (function_exists('clean_bookmark_cache')) :
77
- clean_bookmark_cache($link_id);
78
- endif;
79
- $link =& new SyndicatedLink($link_id);
80
  else :
81
  // update_option ...
82
  if (isset($post['feed_post_status'])) :
@@ -92,6 +90,9 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
92
  if (isset($post['resolve_relative'])) :
93
  update_option('feedwordpress_resolve_relative', $post['resolve_relative']);
94
  endif;
 
 
 
95
  if (isset($_REQUEST['feed_comment_status']) and ($_REQUEST['feed_comment_status'] == 'open')) :
96
  update_option('feedwordpress_syndicated_comment_status', 'open');
97
  else :
@@ -125,8 +126,6 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
125
  * @uses FeedWordPress::syndicated_status()
126
  * @uses SyndicatedLink::syndicated_status()
127
  * @uses SyndicatedPost::use_api()
128
- * @uses fwp_option_box_opener()
129
- * @uses fwp_option_box_closer()
130
  */
131
  /*static*/ function publication_box ($page, $box = NULL) {
132
  global $fwp_path;
@@ -204,8 +203,6 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
204
  * a page for one feed's settings or for global defaults
205
  * @param array $box
206
  *
207
- * @uses fwp_option_box_opener()
208
- * @uses fwp_option_box_closer()
209
  */
210
  function formatting_box ($page, $box = NULL) {
211
  global $fwp_path;
@@ -271,21 +268,36 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
271
  * a page for one feed's settings or for global defaults
272
  * @param array $box
273
  *
274
- * @uses fwp_option_box_opener()
275
- * @uses fwp_option_box_closer()
276
  */
277
  /*static*/ function links_box ($page, $box = NULL) {
278
- $munge_permalink = get_option('feedwordpress_munge_permalink');
279
- $use_aggregator_source_data = get_option('feedwordpress_use_aggregator_source_data');
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  ?>
281
  <table class="form-table" cellspacing="2" cellpadding="5">
282
- <tr><th scope="row">Permalinks:</th>
283
- <td><select name="munge_permalink" size="1">
284
- <option value="yes"<?php echo ($munge_permalink=='yes')?' selected="selected"':''; ?>>point to the copy on the original website</option>
285
- <option value="no"<?php echo ($munge_permalink=='no')?' selected="selected"':''; ?>>point to the local copy on this website</option>
286
- </select></td>
 
 
 
287
  </tr>
288
 
 
289
  <tr><th scope="row">Posts from aggregator feeds:</th>
290
  <td><ul class="options">
291
  <li><label><input type="radio" name="use_aggregator_source_data" value="no"<?php echo ($use_aggregator_source_data!="yes")?' checked="checked"':''; ?>> Give the aggregator itself as the source of posts from an aggregator feed.</label></li>
@@ -295,6 +307,7 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
295
  This setting controls what FeedWordPress will give as the source of posts from
296
  such an aggregator feed.</p>
297
  </td></tr>
 
298
  </table>
299
 
300
  <?php
@@ -308,10 +321,10 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
308
  * a page for one feed's settings or for global defaults
309
  * @param array $box
310
  *
311
- * @uses fwp_option_box_opener()
312
- * @uses fwp_option_box_closer()
313
  */
314
  /*static*/ function comments_and_pings_box ($page, $box = NULL) {
 
 
315
  $setting = array();
316
  $selector = array();
317
 
@@ -321,6 +334,24 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
321
  );
322
  $onThesePosts = 'on '.$page->these_posts_phrase();
323
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  foreach ($whatsits as $what => $how) :
325
  $whatsits[$what]['default'] = FeedWordPress::syndicated_status($what, /*default=*/ 'closed');
326
 
@@ -368,11 +399,44 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
368
  <?php endforeach; ?>
369
  </ul></td></tr>
370
  <?php endforeach; ?>
 
 
 
 
 
 
 
 
 
 
 
371
  </table>
372
 
373
  <?php
374
  } /* FeedWordPressPostsPage::comments_and_pings_box() */
375
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  /**
377
  * Output "Custom Post Settings" settings box
378
  *
@@ -380,33 +444,12 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
380
  * @param object $page of class FeedWordPressPostsPage tells us whether this is
381
  * a page for one feed's settings or for global defaults
382
  * @param array $box
383
- *
384
- * @uses fwp_option_box_opener()
385
- * @uses fwp_option_box_closer()
386
  */
387
  /*static*/ function custom_post_settings_box ($page, $box = NULL) {
388
- if ($page->for_feed_settings()) :
389
- $custom_settings = $page->link->settings["postmeta"];
390
- if ($custom_settings and !is_array($custom_settings)) :
391
- $custom_settings = unserialize($custom_settings);
392
- endif;
393
-
394
- if (!is_array($custom_settings)) :
395
- $custom_settings = array();
396
- endif;
397
- else :
398
- $custom_settings = get_option('feedwordpress_custom_settings');
399
- if ($custom_settings and !is_array($custom_settings)) :
400
- $custom_settings = unserialize($custom_settings);
401
- endif;
402
-
403
- if (!is_array($custom_settings)) :
404
- $custom_settings = array();
405
- endif;
406
- endif;
407
-
408
  ?>
409
  <div id="postcustomstuff">
 
410
  <table id="meta-list" cellpadding="3">
411
  <tr>
412
  <th>Key</th>
@@ -419,9 +462,9 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
419
  foreach ($custom_settings as $key => $value) :
420
  ?>
421
  <tr style="vertical-align:top">
422
- <th width="30%" scope="row"><input type="hidden" name="notes[<?php echo $i; ?>][key0]" value="<?php echo wp_specialchars($key, 'both'); ?>" />
423
- <input id="notes-<?php echo $i; ?>-key" name="notes[<?php echo $i; ?>][key1]" value="<?php echo wp_specialchars($key, 'both'); ?>" /></th>
424
- <td width="60%"><textarea rows="2" cols="40" id="notes-<?php echo $i; ?>-value" name="notes[<?php echo $i; ?>][value]"><?php echo wp_specialchars($value, 'both'); ?></textarea></td>
425
  <td width="10%"><select name="notes[<?php echo $i; ?>][action]">
426
  <option value="update">save changes</option>
427
  <option value="delete">delete this setting</option>
@@ -433,9 +476,15 @@ class FeedWordPressPostsPage extends FeedWordPressAdminPage {
433
  endforeach;
434
  ?>
435
 
436
- <tr>
437
  <th scope="row"><input type="text" size="10" name="notes[<?php echo $i; ?>][key1]" value="" /></th>
438
- <td><textarea name="notes[<?php echo $i; ?>][value]" rows="2" cols="40"></textarea></td>
 
 
 
 
 
 
439
  <td><em>add new setting...</em><input type="hidden" name="notes[<?php echo $i; ?>][action]" value="update" /></td>
440
  </tr>
441
  </table>
@@ -458,7 +507,7 @@ function fwp_posts_page () {
458
  FeedWordPressCompatibility::validate_http_request(/*action=*/ 'feedwordpress_posts_settings', /*capability=*/ 'manage_links');
459
 
460
  $link = FeedWordPressAdminPage::submitted_link();
461
- $link_id = $link->id;
462
  $postsPage = new FeedWordPressPostsPage($link);
463
 
464
  $mesg = null;
@@ -476,7 +525,7 @@ function fwp_posts_page () {
476
  if ($postsPage->updated) : ?>
477
  <div class="updated"><p>Syndicated posts settings updated.</p></div>
478
  <?php elseif (!is_null($mesg)) : ?>
479
- <div class="updated"><p><?php print wp_specialchars($mesg, 1); ?></p></div>
480
  <?php endif; ?>
481
 
482
  <?php
@@ -498,11 +547,6 @@ $boxes_by_methods = array(
498
  'custom_post_settings_box' => __('Custom Post Settings (to apply to each syndicated post)'),
499
  );
500
 
501
- // Feed-level settings don't exist for these.
502
- if ($postsPage->for_feed_settings()) :
503
- unset($boxes_by_methods['links_box']);
504
- endif;
505
-
506
  foreach ($boxes_by_methods as $method => $title) :
507
  fwp_add_meta_box(
508
  /*id=*/ 'feedwordpress_'.$method,
20
 
21
  function accept_POST ($post) {
22
  global $wpdb;
 
 
23
 
24
  // User mashed a Save Changes button
25
  if (isset($post['save']) or isset($post['submit'])) :
26
  // custom post settings
27
+ $custom_settings = $this->custom_post_settings();
28
+
29
  foreach ($post['notes'] as $mn) :
30
  $mn['key0'] = trim($mn['key0']);
31
  $mn['key1'] = trim($mn['key1']);
48
  if (isset($post['resolve_relative'])) :
49
  $this->link->settings['resolve relative'] = $post['resolve_relative'];
50
  endif;
51
+ if (isset($post['munge_permalink'])) :
52
+ $this->link->settings['munge permalink'] = $post['munge_permalink'];
53
+ endif;
54
+ if (isset($post['munge_comments_feed_links'])) :
55
+ $this->link->settings['munge comments feed links'] = $post['munge_comments_feed_links'];
56
+ endif;
57
+
58
  // Post status, comment status, ping status
59
  foreach (array('post', 'comment', 'ping') as $what) :
60
  $sfield = "feed_{$what}_status";
67
  endif;
68
  endforeach;
69
 
70
+ // Save settings
71
+ $this->link->save_settings(/*reload=*/ true);
 
 
 
 
 
 
 
72
  $this->updated = true;
73
+
74
+ // Reset, reload
75
+ $link_id = $this->link->id;
76
+ unset($this->link);
77
+ $this->link = new SyndicatedLink($link_id);
 
78
  else :
79
  // update_option ...
80
  if (isset($post['feed_post_status'])) :
90
  if (isset($post['resolve_relative'])) :
91
  update_option('feedwordpress_resolve_relative', $post['resolve_relative']);
92
  endif;
93
+ if (isset($post['munge_comments_feed_links'])) :
94
+ update_option('feedwordpress_munge_comments_feed_links', $post['munge_comments_feed_links']);
95
+ endif;
96
  if (isset($_REQUEST['feed_comment_status']) and ($_REQUEST['feed_comment_status'] == 'open')) :
97
  update_option('feedwordpress_syndicated_comment_status', 'open');
98
  else :
126
  * @uses FeedWordPress::syndicated_status()
127
  * @uses SyndicatedLink::syndicated_status()
128
  * @uses SyndicatedPost::use_api()
 
 
129
  */
130
  /*static*/ function publication_box ($page, $box = NULL) {
131
  global $fwp_path;
203
  * a page for one feed's settings or for global defaults
204
  * @param array $box
205
  *
 
 
206
  */
207
  function formatting_box ($page, $box = NULL) {
208
  global $fwp_path;
268
  * a page for one feed's settings or for global defaults
269
  * @param array $box
270
  *
 
 
271
  */
272
  /*static*/ function links_box ($page, $box = NULL) {
273
+ $setting = array(
274
+ 'munge_permalink' => array(
275
+ 'yes' => 'the copy on the original website',
276
+ 'no' => 'the local copy on this website',
277
+ ),
278
+ );
279
+
280
+ $global_munge_permalink = get_option('feedwordpress_munge_permalink');
281
+ if ($page->for_feed_settings()) :
282
+ $munge_permalink = $page->link->setting('munge permalink', NULL);
283
+ else :
284
+ $munge_permalink = $global_munge_permalink;
285
+ $use_aggregator_source_data = get_option('feedwordpress_use_aggregator_source_data');
286
+ endif;
287
+
288
  ?>
289
  <table class="form-table" cellspacing="2" cellpadding="5">
290
+ <tr><th scope="row">Permalinks point to:</th>
291
+ <td><ul class="options">
292
+ <?php if ($page->for_feed_settings()) : ?>
293
+ <li><label><input type="radio" name="munge_permalink" value="default"<?php echo ($munge_permalink!='yes' and $munge_permalink != 'no')?' checked="checked"':''; ?>/> use site-wide setting (currently <?php print $setting['munge_permalink'][$global_munge_permalink]; ?>)</label></li>
294
+ <?php endif; ?>
295
+ <li><label><input type="radio" name="munge_permalink" value="yes"<?php echo ($munge_permalink=='yes')?' checked="checked"':''; ?>><?php print $setting['munge_permalink']['yes']; ?></label></li>
296
+ <li><label><input type="radio" name="munge_permalink" value="no"<?php echo ($munge_permalink=='no')?' checked="checked"':''; ?>><?php print $setting['munge_permalink']['no']; ?></label></li>
297
+ </ul></td>
298
  </tr>
299
 
300
+ <?php if (!$page->for_feed_settings()) : ?>
301
  <tr><th scope="row">Posts from aggregator feeds:</th>
302
  <td><ul class="options">
303
  <li><label><input type="radio" name="use_aggregator_source_data" value="no"<?php echo ($use_aggregator_source_data!="yes")?' checked="checked"':''; ?>> Give the aggregator itself as the source of posts from an aggregator feed.</label></li>
307
  This setting controls what FeedWordPress will give as the source of posts from
308
  such an aggregator feed.</p>
309
  </td></tr>
310
+ <?php endif; ?>
311
  </table>
312
 
313
  <?php
321
  * a page for one feed's settings or for global defaults
322
  * @param array $box
323
  *
 
 
324
  */
325
  /*static*/ function comments_and_pings_box ($page, $box = NULL) {
326
+ global $fwp_path;
327
+
328
  $setting = array();
329
  $selector = array();
330
 
334
  );
335
  $onThesePosts = 'on '.$page->these_posts_phrase();
336
 
337
+ $selected = array(
338
+ 'munge_comments_feed_links' => array('yes' => '', 'no' => '')
339
+ );
340
+
341
+ $globalMungeCommentsFeedLinks = get_option('feedwordpress_munge_comments_feed_links', 'yes');
342
+ if ($page->for_feed_settings()) :
343
+ $selected['munge_comments_feed_links']['default'] = '';
344
+
345
+ $sel = $page->link->setting('munge comments feed links', NULL, 'default');
346
+ else :
347
+ $sel = $globalMungeCommentsFeedLinks;
348
+ endif;
349
+ $selected['munge_comments_feed_links'][$sel] = ' checked="checked"';
350
+
351
+ if ($globalMungeCommentsFeedLinks != 'no') : $siteWide = __('comment feeds from the original website');
352
+ else : $siteWide = __('local comment feeds on this website');
353
+ endif;
354
+
355
  foreach ($whatsits as $what => $how) :
356
  $whatsits[$what]['default'] = FeedWordPress::syndicated_status($what, /*default=*/ 'closed');
357
 
399
  <?php endforeach; ?>
400
  </ul></td></tr>
401
  <?php endforeach; ?>
402
+ <tr><th scope="row"><?php _e('Comment feeds'); ?></th>
403
+ <td><p>When WordPress feeds and templates link to comments
404
+ feeds for <?php print $page->these_posts_phrase(); ?>, the
405
+ URLs for the feeds should...</p>
406
+ <ul class="options">
407
+ <?php if ($page->for_feed_settings()) : ?>
408
+ <li><label><input type="radio" name="munge_comments_feed_links" value="default"<?php print $selected['munge_comments_feed_links']['default']; ?> /> Use <a href="admin.php?page=<?php print $href; ?>">site-wide setting</a> (currently: <strong><?php _e($siteWide); ?></strong>)</label></li>
409
+ <?php endif; ?>
410
+ <li><label><input type="radio" name="munge_comments_feed_links" value="yes"<?php print $selected['munge_comments_feed_links']['yes']; ?> /> <?php _e('Point to comment feeds from the original website (when provided by the syndicated feed)'); ?></label></li>
411
+ <li><label><input type="radio" name="munge_comments_feed_links" value="no"<?php print $selected['munge_comments_feed_links']['no']; ?> /> <?php _e('Point to local comment feeds on this website'); ?></label></li>
412
+ </ul></td></tr>
413
  </table>
414
 
415
  <?php
416
  } /* FeedWordPressPostsPage::comments_and_pings_box() */
417
 
418
+ /*static*/ function custom_post_settings ($page = NULL) {
419
+ if (is_null($page)) :
420
+ $page = $this;
421
+ endif;
422
+
423
+ if ($page->for_feed_settings()) :
424
+ $custom_settings = $page->link->settings["postmeta"];
425
+ else :
426
+ $custom_settings = get_option('feedwordpress_custom_settings');
427
+ endif;
428
+
429
+ if ($custom_settings and !is_array($custom_settings)) :
430
+ $custom_settings = unserialize($custom_settings);
431
+ endif;
432
+
433
+ if (!is_array($custom_settings)) :
434
+ $custom_settings = array();
435
+ endif;
436
+
437
+ return $custom_settings;
438
+ } /* FeedWordPressPostsPage::custom_post_settings() */
439
+
440
  /**
441
  * Output "Custom Post Settings" settings box
442
  *
444
  * @param object $page of class FeedWordPressPostsPage tells us whether this is
445
  * a page for one feed's settings or for global defaults
446
  * @param array $box
 
 
 
447
  */
448
  /*static*/ function custom_post_settings_box ($page, $box = NULL) {
449
+ $custom_settings = FeedWordPressPostsPage::custom_post_settings($page);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
  ?>
451
  <div id="postcustomstuff">
452
+ <p>Custom fields can be used to add extra metadata to a post that you can <a href="http://codex.wordpress.org/Using_Custom_Fields">use in your theme</a>.</p>
453
  <table id="meta-list" cellpadding="3">
454
  <tr>
455
  <th>Key</th>
462
  foreach ($custom_settings as $key => $value) :
463
  ?>
464
  <tr style="vertical-align:top">
465
+ <th width="30%" scope="row"><input type="hidden" name="notes[<?php echo $i; ?>][key0]" value="<?php echo esc_html($key); ?>" />
466
+ <input id="notes-<?php echo $i; ?>-key" name="notes[<?php echo $i; ?>][key1]" value="<?php echo esc_html($key); ?>" /></th>
467
+ <td width="60%"><textarea rows="2" cols="40" id="notes-<?php echo $i; ?>-value" name="notes[<?php echo $i; ?>][value]"><?php echo esc_html($value); ?></textarea></td>
468
  <td width="10%"><select name="notes[<?php echo $i; ?>][action]">
469
  <option value="update">save changes</option>
470
  <option value="delete">delete this setting</option>
476
  endforeach;
477
  ?>
478
 
479
+ <tr style="vertical-align: top">
480
  <th scope="row"><input type="text" size="10" name="notes[<?php echo $i; ?>][key1]" value="" /></th>
481
+ <td><textarea name="notes[<?php echo $i; ?>][value]" rows="2" cols="40"></textarea>
482
+ <p>Enter a text value, or a path to a data element from the syndicated item.<br/>
483
+ For data elements, you can use an XPath-like syntax wrapped in <code>$( ... )</code>.<br/>
484
+ <code>hello</code> = the text value <code><span style="background-color: #30FFA0;">hello</span></code><br/>
485
+ <code>$(author/email)</code> = the contents of <code>&lt;author&gt;&lt;email&gt;<span style="background-color: #30FFA0">...</span>&lt;/email&gt;&lt;/author&gt;</code><br/>
486
+ <code>$(media:content/@url)</code> = the contents of <code>&lt;media:content url="<span style="background-color: #30FFA0">...</span>"&gt;...&lt;/media:content&gt;</code></p>
487
+ </td>
488
  <td><em>add new setting...</em><input type="hidden" name="notes[<?php echo $i; ?>][action]" value="update" /></td>
489
  </tr>
490
  </table>
507
  FeedWordPressCompatibility::validate_http_request(/*action=*/ 'feedwordpress_posts_settings', /*capability=*/ 'manage_links');
508
 
509
  $link = FeedWordPressAdminPage::submitted_link();
510
+
511
  $postsPage = new FeedWordPressPostsPage($link);
512
 
513
  $mesg = null;
525
  if ($postsPage->updated) : ?>
526
  <div class="updated"><p>Syndicated posts settings updated.</p></div>
527
  <?php elseif (!is_null($mesg)) : ?>
528
+ <div class="updated"><p><?php print esc_html($mesg); ?></p></div>
529
  <?php endif; ?>
530
 
531
  <?php
547
  'custom_post_settings_box' => __('Custom Post Settings (to apply to each syndicated post)'),
548
  );
549
 
 
 
 
 
 
550
  foreach ($boxes_by_methods as $method => $title) :
551
  fwp_add_meta_box(
552
  /*id=*/ 'feedwordpress_'.$method,
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  Contributors: Charles Johnson
3
  Donate link: http://feedwordpress.radgeek.com/
4
  Tags: syndication, aggregation, feed, atom, rss
5
- Requires at least: 1.5
6
- Tested up to: 2.9.1
7
- Stable tag: 2010.0127
8
 
9
  FeedWordPress syndicates content from feeds you choose into your WordPress weblog.
10
 
@@ -18,107 +18,69 @@ FeedWordPress is an Atom/RSS aggregator for WordPress. It syndicates content
18
  from feeds that you choose into your WordPress weblog; if you syndicate several
19
  feeds then you can use WordPress's posts database and templating engine as the
20
  back-end of an aggregation ("planet") website. It was developed, originally,
21
- because I needed a more flexible replacement for [Planet](http://www.planetplanet.org/)
22
- to use at [Feminist Blogs](http://feministblogs.org/).
 
 
 
23
 
24
  FeedWordPress is designed with flexibility, ease of use, and ease of
25
  configuration in mind. You'll need a working installation of WordPress or
26
- WordPress MU (versions [2.8][], [2.7][], [2.6][], [2.5][], [2.3][], [2.2][],
27
- [2.1][], [2.0][] or [1.5][]), and also FTP or SFTP access to your web host. The
28
- ability to create cron jobs on your web host is helpful but not absolutely
29
- necessary. You *don't* need to tweak any plain-text configuration files and you
30
  *don't* need shell access to your web host to make it work. (Although, I should
31
  point out, web hosts that *don't* offer shell access are *bad web hosts*.)
32
 
 
 
33
  [2.8]: http://codex.wordpress.org/Version_2.8
34
- [2.7]: http://codex.wordpress.org/Version_2.7
35
- [2.6]: http://codex.wordpress.org/Version_2.6
36
- [2.5]: http://codex.wordpress.org/Version_2.5
37
- [2.3]: http://codex.wordpress.org/Version_2.3
38
- [2.2]: http://codex.wordpress.org/Version_2.2
39
- [2.1]: http://codex.wordpress.org/Version_2.1
40
- [2.0]: http://codex.wordpress.org/Version_2.0
41
- [1.5]: http://codex.wordpress.org/Version_1.5
42
 
43
  == Installation ==
44
 
45
  To use FeedWordPress, you will need:
46
 
47
- * an installed and configured copy of WordPress version 2.x, or 1.5.x.
48
- (FeedWordPress will also work with the equivalent versions of WordPress
49
- MU.)
50
 
51
- * FTP or SFTP access to your web host
52
 
53
  = New Installations =
54
 
55
- 1. Download the FeedWordPress archive and extract the files on your computer.
 
56
 
57
  2. Create a new directory named `feedwordpress` in the `wp-content/plugins`
58
  directory of your WordPress installation. Use an FTP or SFTP client to
59
  upload the contents of your FeedWordPress archive to the new directory
60
  that you just created on your web host.
61
 
62
- 3. Upgrade the copy of MagpieRSS packaged with WordPress by installing the
63
- new copies of `rss.php` and `rss-functions.php` into the `wp-includes`
64
- directory of your FeedWordPress installation. These files are stored in
65
- the `MagpieRSS-upgrade` directory of your FeedWordPress archive. Strictly
66
- speaking, upgrading MagpieRSS is optional; FeedWordPress will run
67
- correctly without the upgrade. But if you hope to take advantage of
68
- numerous bug fixes, or support for Atom 1.0, multiple post categories,
69
- RSS enclosures, or multiple character encodings, then you need to
70
- install the upgrade.
71
 
72
- 4. Log in to the WordPress Dashboard and activate the FeedWordPress plugin.
 
 
 
 
 
 
73
 
74
- 5. Once the plugin is activated, you can go to **Syndication --> Options**
75
- and set (1) the link category that FeedWordPress will syndicate links
76
- from (by default, "Contributors"), and (2) whether FeedWordPress will
77
- use automatic updates or only manual updates.
78
 
79
- 5. Go to the main **Syndication** page to set up the list of sites that
80
- you want FeedWordPress to syndicate onto your blog.
81
 
82
- = Upgrades =
 
83
 
84
- To *upgrade* an existing installation of FeedWordPress to version 2008.1030:
85
-
86
- 1. Download the FeedWordPress archive in zip or gzipped tar format and
87
- extract the files on your computer.
88
-
89
- 2. If you are upgrading from version 0.98 or earlier, then you need to
90
- create a new directory named `feedwordpress` in the `wp-content/plugins`
91
- directory of your WordPress installation, and you also need to *delete*
92
- your existing `wp-content/update-feeds.php` and
93
- `wp-content/plugins/feedwordpress.php` files. The file structure for
94
- FeedWordPress has changed and the files from your old version will not
95
- be overwritten, which could cause conflicts if you leave them in place.
96
-
97
- 3. Upload the new PHP files to `wp-content/plugins/feedwordpress`,
98
- overwriting any existing FeedWordPress files that are there. Also be
99
- sure to upgrade the MagpieRSS module by uploading `rss.php` and
100
- `rss-functions.php` from the `MagpieRSS-upgrade` directory in your
101
- archive to the `wp-includes` directory of your WordPress installation.
102
-
103
- 3. If you are upgrading from version 0.96 or earlier, **immediately** log
104
- in to the WordPress Dashboard, and go to **Options --> Syndicated**.
105
- Follow the directions to launch the database upgrade procedure. The new
106
- versions of FeedWordPress incorporate some long-needed improvements, but
107
- old meta-data needs to be updated to prevent duplicate posts and other
108
- possible maladies. If you're upgrading an existing installation, updates
109
- and FeedWordPress template functions *will not work* until you've done
110
- the upgrade. Then take a coffee break while the upgrade runs. It should,
111
- hopefully, finish within a few minutes even on relatively large
112
- databases.
113
-
114
- 4. If you are upgrading from version 0.98 or earlier, note that the old
115
- `update-feeds.php` has been eliminated in favor of a (hopefully) more
116
- humane method for automatic updating. If you used a cron job for
117
- scheduled updates, it will not work anymore, but there is another,
118
- simpler method which will. See [Setting Up Feed Updates](http://projects.radgeek.com/feedwordpress/install/#setting-up-feed-updates)
119
- to get scheduled updates back on track.
120
-
121
- 5. Enjoy your new installation of FeedWordPress.
122
 
123
  == Using and Customizing FeedWordPress ==
124
 
@@ -127,11 +89,11 @@ Dashboard, and a lot of functionality accessible programmatically through
127
  WordPress templates or plugins. For further documentation of the ins and
128
  outs, see the documentation at the [FeedWordPress project homepage][].
129
 
130
- [FeedWordPress project homepage]: http://projects.radgeek.com/feedwordpress/
131
 
132
  == License ==
133
 
134
- The FeedWordPress plugin is copyright © 2005-2007 by Charles Johnson. It uses
135
  code derived or translated from:
136
 
137
  - [wp-rss-aggregate.php][] by [Kellan Elliot-McCrea](kellan@protest.net)
@@ -154,6 +116,5 @@ PARTICULAR PURPOSE. See the GNU General Public License for more details.
154
  [MagpieRSS]: http://magpierss.sourceforge.net/
155
  [HTTP Navigator 2]: http://www.keyvan.net/2004/11/16/http-navigator/
156
  [Ultra-Liberal Feed Finder]: http://diveintomark.org/projects/feed_finder/
157
-
158
  [GNU General Public License]: http://www.gnu.org/copyleft/gpl.html
159
 
2
  Contributors: Charles Johnson
3
  Donate link: http://feedwordpress.radgeek.com/
4
  Tags: syndication, aggregation, feed, atom, rss
5
+ Requires at least: 2.8
6
+ Tested up to: 3.0
7
+ Stable tag: 2010.0528
8
 
9
  FeedWordPress syndicates content from feeds you choose into your WordPress weblog.
10
 
18
  from feeds that you choose into your WordPress weblog; if you syndicate several
19
  feeds then you can use WordPress's posts database and templating engine as the
20
  back-end of an aggregation ("planet") website. It was developed, originally,
21
+ because I needed a more flexible replacement for [Planet][]
22
+ to use at [Feminist Blogs][].
23
+
24
+ [Planet]: http://www.planetplanet.org/
25
+ [Feminist Blogs]: http://feministblogs.org/
26
 
27
  FeedWordPress is designed with flexibility, ease of use, and ease of
28
  configuration in mind. You'll need a working installation of WordPress or
29
+ WordPress MU (version [2.8] or later), and also FTP or SFTP access to your web
30
+ host. The ability to create cron jobs on your web host is helpful but not
31
+ required. You *don't* need to tweak any plain-text configuration files and you
 
32
  *don't* need shell access to your web host to make it work. (Although, I should
33
  point out, web hosts that *don't* offer shell access are *bad web hosts*.)
34
 
35
+ [WordPress]: http://wordpress.org/
36
+ [WordPress MU]: http://mu.wordpress.org/
37
  [2.8]: http://codex.wordpress.org/Version_2.8
 
 
 
 
 
 
 
 
38
 
39
  == Installation ==
40
 
41
  To use FeedWordPress, you will need:
42
 
43
+ * an installed and configured copy of [WordPress][] or [WordPress MU][]
44
+ (version 2.8 or later).
 
45
 
46
+ * FTP, SFTP or shell access to your web host
47
 
48
  = New Installations =
49
 
50
+ 1. Download the FeedWordPress installation package and extract the files on
51
+ your computer.
52
 
53
  2. Create a new directory named `feedwordpress` in the `wp-content/plugins`
54
  directory of your WordPress installation. Use an FTP or SFTP client to
55
  upload the contents of your FeedWordPress archive to the new directory
56
  that you just created on your web host.
57
 
58
+ 3. Log in to the WordPress Dashboard and activate the FeedWordPress plugin.
 
 
 
 
 
 
 
 
59
 
60
+ 4. Once the plugin is activated, a new **Syndication** section should
61
+ appear in your WordPress admin menu. Click here to add new syndicated
62
+ feeds, set up configuration options, and determine how FeedWordPress
63
+ will check for updates. For help, see the [FeedWordPress Quick Start][]
64
+ page.
65
+
66
+ [FeedWordPress Quick Start]: http://feedwordpress.radgeek.com/wiki/quick-start
67
 
68
+ = Upgrades =
 
 
 
69
 
70
+ To *upgrade* an existing installation of FeedWordPress to the most recent
71
+ release:
72
 
73
+ 1. Download the FeedWordPress installation package and extract the files on
74
+ your computer.
75
 
76
+ 2. Upload the new PHP files to `wp-content/plugins/feedwordpress`,
77
+ overwriting any existing FeedWordPress files that are there.
78
+
79
+ 3. Log in to your WordPress administrative interface immediately in order
80
+ to see whether there are any further tasks that you need to perform
81
+ to complete the upgrade.
82
+
83
+ 4. Enjoy your newer and hotter installation of FeedWordPress
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  == Using and Customizing FeedWordPress ==
86
 
89
  WordPress templates or plugins. For further documentation of the ins and
90
  outs, see the documentation at the [FeedWordPress project homepage][].
91
 
92
+ [FeedWordPress project homepage]: http://feedwordpress.radgeek.com/
93
 
94
  == License ==
95
 
96
+ The FeedWordPress plugin is copyright © 2005-2010 by Charles Johnson. It uses
97
  code derived or translated from:
98
 
99
  - [wp-rss-aggregate.php][] by [Kellan Elliot-McCrea](kellan@protest.net)
116
  [MagpieRSS]: http://magpierss.sourceforge.net/
117
  [HTTP Navigator 2]: http://www.keyvan.net/2004/11/16/http-navigator/
118
  [Ultra-Liberal Feed Finder]: http://diveintomark.org/projects/feed_finder/
 
119
  [GNU General Public License]: http://www.gnu.org/copyleft/gpl.html
120
 
syndicatedlink.class.php CHANGED
@@ -33,10 +33,13 @@
33
  # Values of keys in link_notes are accessible from templates using
34
  # the function `get_feed_meta($key)` if this plugin is activated.
35
 
 
 
36
  class SyndicatedLink {
37
  var $id = null;
38
  var $link = null;
39
  var $settings = array ();
 
40
  var $magpie = null;
41
 
42
  function SyndicatedLink ($link) {
@@ -124,7 +127,7 @@ class SyndicatedLink {
124
  } /* SyndicatedLink::SyndicatedLink () */
125
 
126
  function found () {
127
- return is_object($this->link);
128
  } /* SyndicatedLink::found () */
129
 
130
  function stale () {
@@ -146,7 +149,19 @@ class SyndicatedLink {
146
  function poll ($crash_ts = NULL) {
147
  global $wpdb;
148
 
149
- $this->magpie = fetch_rss($this->link->link_rss);
 
 
 
 
 
 
 
 
 
 
 
 
150
  $new_count = NULL;
151
 
152
  $resume = FeedWordPress::affirmative($this->settings, 'update/unfinished');
@@ -158,7 +173,9 @@ class SyndicatedLink {
158
  $processed = array();
159
  endif;
160
 
161
- if (is_object($this->magpie)) :
 
 
162
  $new_count = array('new' => 0, 'updated' => 0);
163
 
164
  # -- Update Link metadata live from feed
@@ -193,12 +210,11 @@ class SyndicatedLink {
193
  $this->settings['update/timed'] = 'feed';
194
  else :
195
  // spread out over a time interval for staggered updates
196
- if (isset($this->settings['update/window'])) :
197
- $updateWindow = $this->settings['update/window'];
198
- else :
199
- $updateWindow = get_option('feedwordpress_update_window');
200
- endif;
201
-
202
  if (!is_numeric($updateWindow) or ($updateWindow < 1)) :
203
  $updateWindow = DEFAULT_UPDATE_PERIOD;
204
  endif;
@@ -225,13 +241,24 @@ class SyndicatedLink {
225
  SET $update_set
226
  WHERE link_id='$this->id'
227
  ");
 
228
 
229
  # -- Add new posts from feed and update any updated posts
230
  $crashed = false;
231
 
232
- if (is_array($this->magpie->items)) :
233
- foreach ($this->magpie->items as $item) :
234
- $post =& new SyndicatedPost($item, $this);
 
 
 
 
 
 
 
 
 
 
235
  if (!$resume or !in_array(trim($post->guid()), $processed)) :
236
  $processed[] = $post->guid();
237
  if (!$post->filtered()) :
@@ -244,9 +271,13 @@ class SyndicatedLink {
244
  break;
245
  endif;
246
  endif;
 
247
  endforeach;
248
  endif;
249
-
 
 
 
250
  // Copy back any changes to feed settings made in the course of updating (e.g. new author rules)
251
  $to_notes = $this->settings;
252
 
@@ -263,6 +294,8 @@ class SyndicatedLink {
263
  SET $update_set
264
  WHERE link_id='$this->id'
265
  ");
 
 
266
  endif;
267
 
268
  return $new_count;
@@ -365,9 +398,17 @@ class SyndicatedLink {
365
  function save_settings ($reload = false) {
366
  global $wpdb;
367
 
368
- $update_set = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
369
-
370
- // Update the properties of the link from the feed information
 
 
 
 
 
 
 
 
371
  $result = $wpdb->query("
372
  UPDATE $wpdb->links
373
  SET $update_set
@@ -375,7 +416,7 @@ class SyndicatedLink {
375
  ");
376
 
377
  if ($reload) :
378
- // reload link information from DB
379
  if (function_exists('clean_bookmark_cache')) :
380
  clean_bookmark_cache($this->id);
381
  endif;
@@ -397,11 +438,21 @@ class SyndicatedLink {
397
  $ret = $this->settings[$name];
398
  endif;
399
 
400
- if ((is_null($ret) or strtolower($ret)=='default') and !is_null($fallback_global)) :
 
 
 
 
 
401
  $ret = get_option('feedwordpress_'.$fallback_global, /*default=*/ NULL);
402
  endif;
403
 
404
- if ((is_null($ret) or strtolower($ret)=='default') and !is_null($fallback_value)) :
 
 
 
 
 
405
  $ret = $fallback_value;
406
  endif;
407
  return $ret;
33
  # Values of keys in link_notes are accessible from templates using
34
  # the function `get_feed_meta($key)` if this plugin is activated.
35
 
36
+ require_once(dirname(__FILE__).'/magpiefromsimplepie.class.php');
37
+
38
  class SyndicatedLink {
39
  var $id = null;
40
  var $link = null;
41
  var $settings = array ();
42
+ var $simplepie = null;
43
  var $magpie = null;
44
 
45
  function SyndicatedLink ($link) {
127
  } /* SyndicatedLink::SyndicatedLink () */
128
 
129
  function found () {
130
+ return is_object($this->link) and !is_wp_error($this->link);
131
  } /* SyndicatedLink::found () */
132
 
133
  function stale () {
149
  function poll ($crash_ts = NULL) {
150
  global $wpdb;
151
 
152
+ $this->simplepie = apply_filters(
153
+ 'syndicated_feed',
154
+ FeedWordPress::fetch($this->link->link_rss),
155
+ $this
156
+ );
157
+
158
+ // Filter compatibility mode
159
+ if (is_wp_error($this->simplepie)) :
160
+ $this->magpie = $this->simplepie;
161
+ else :
162
+ $this->magpie = new MagpieFromSimplePie($this->simplepie);
163
+ endif;
164
+
165
  $new_count = NULL;
166
 
167
  $resume = FeedWordPress::affirmative($this->settings, 'update/unfinished');
173
  $processed = array();
174
  endif;
175
 
176
+ if (is_wp_error($this->simplepie)) :
177
+ $new_count = $this->simplepie;
178
+ elseif (is_object($this->simplepie)) :
179
  $new_count = array('new' => 0, 'updated' => 0);
180
 
181
  # -- Update Link metadata live from feed
210
  $this->settings['update/timed'] = 'feed';
211
  else :
212
  // spread out over a time interval for staggered updates
213
+ $updateWindow = $this->setting(
214
+ 'update/window',
215
+ 'update_window',
216
+ DEFAULT_UPDATE_PERIOD
217
+ );
 
218
  if (!is_numeric($updateWindow) or ($updateWindow < 1)) :
219
  $updateWindow = DEFAULT_UPDATE_PERIOD;
220
  endif;
241
  SET $update_set
242
  WHERE link_id='$this->id'
243
  ");
244
+ do_action('update_syndicated_feed', $this->id, $this);
245
 
246
  # -- Add new posts from feed and update any updated posts
247
  $crashed = false;
248
 
249
+ $posts = apply_filters(
250
+ 'syndicated_feed_items',
251
+ $this->magpie->originals,
252
+ $this
253
+ );
254
+ if (is_array($posts)) :
255
+ foreach ($posts as $key => $original) :
256
+ $item = $this->magpie->items[$key];
257
+ $post = new SyndicatedPost(array(
258
+ 'simplepie' => $original,
259
+ 'magpie' => $item,
260
+ ), $this);
261
+
262
  if (!$resume or !in_array(trim($post->guid()), $processed)) :
263
  $processed[] = $post->guid();
264
  if (!$post->filtered()) :
271
  break;
272
  endif;
273
  endif;
274
+ unset($post);
275
  endforeach;
276
  endif;
277
+ $suffix = ($crashed ? 'crashed' : 'completed');
278
+ do_action('update_syndicated_feed_items', $this->id, $this);
279
+ do_action("update_syndicated_feed_items_${suffix}", $this->id, $this);
280
+
281
  // Copy back any changes to feed settings made in the course of updating (e.g. new author rules)
282
  $to_notes = $this->settings;
283
 
294
  SET $update_set
295
  WHERE link_id='$this->id'
296
  ");
297
+
298
+ do_action("update_syndicated_feed_completed", $this->id, $this);
299
  endif;
300
 
301
  return $new_count;
398
  function save_settings ($reload = false) {
399
  global $wpdb;
400
 
401
+ // Save channel-level meta-data
402
+ foreach (array('link_name', 'link_description', 'link_url') as $what) :
403
+ $alter[] = "{$what} = '".$wpdb->escape($this->link->{$what})."'";
404
+ endforeach;
405
+
406
+ // Save settings to the notes field
407
+ $alter[] = "link_notes = '".$wpdb->escape($this->settings_to_notes())."'";
408
+
409
+ // Update the properties of the link from settings changes, etc.
410
+ $update_set = implode(", ", $alter);
411
+
412
  $result = $wpdb->query("
413
  UPDATE $wpdb->links
414
  SET $update_set
416
  ");
417
 
418
  if ($reload) :
419
+ // force reload of link information from DB
420
  if (function_exists('clean_bookmark_cache')) :
421
  clean_bookmark_cache($this->id);
422
  endif;
438
  $ret = $this->settings[$name];
439
  endif;
440
 
441
+ $no_value = (
442
+ is_null($ret)
443
+ or (is_string($ret) and strtolower($ret)=='default')
444
+ );
445
+
446
+ if ($no_value and !is_null($fallback_global)) :
447
  $ret = get_option('feedwordpress_'.$fallback_global, /*default=*/ NULL);
448
  endif;
449
 
450
+ $no_value = (
451
+ is_null($ret)
452
+ or (is_string($ret) and strtolower($ret)=='default')
453
+ );
454
+
455
+ if ($no_value and !is_null($fallback_value)) :
456
  $ret = $fallback_value;
457
  endif;
458
  return $ret;
syndicatedpost.class.php CHANGED
@@ -1,38 +1,95 @@
1
  <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  class SyndicatedPost {
3
- var $item = null;
4
-
 
5
  var $link = null;
6
  var $feed = null;
7
  var $feedmeta = null;
8
 
 
 
9
  var $post = array ();
10
 
11
  var $_freshness = null;
12
  var $_wp_id = null;
13
 
14
- function SyndicatedPost ($item, $link) {
 
 
 
 
 
 
 
 
 
15
  global $wpdb;
16
 
17
- $this->link = $link;
18
- $feedmeta = $link->settings;
19
- $feed = $link->magpie;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- # This is ugly as all hell. I'd like to use apply_filters()'s
22
- # alleged support for a variable argument count, but this seems
23
- # to have been broken in WordPress 1.5. It'll be fixed somehow
24
- # in WP 1.5.1, but I'm aiming at WP 1.5 compatibility across
25
- # the board here.
26
  #
27
- # Cf.: <http://mosquito.wordpress.org/view.php?id=901>
28
- global $fwp_channel, $fwp_feedmeta;
29
- $fwp_channel = $feed; $fwp_feedmeta = $feedmeta;
 
 
 
30
 
31
- $this->feed = $feed;
32
- $this->feedmeta = $feedmeta;
33
 
34
- $this->item = $item;
35
  $this->item = apply_filters('syndicated_item', $this->item, $this);
 
 
 
 
 
 
 
36
 
37
  # Filters can halt further processing by returning NULL
38
  if (is_null($this->item)) :
@@ -43,59 +100,39 @@ class SyndicatedPost {
43
  # of insertion, not here, to avoid double-escaping and
44
  # to avoid screwing with syndicated_post filters
45
 
46
- $this->post['post_title'] = apply_filters('syndicated_item_title', $this->item['title'], $this);
47
-
48
- // This just gives us an alphanumeric representation of
49
- // the author. We will look up (or create) the numeric
50
- // ID for the author in SyndicatedPost::add()
51
- $this->post['named']['author'] = apply_filters('syndicated_item_author', $this->author(), $this);
52
-
53
- # Identify content and sanitize it.
54
- # ---------------------------------
55
- if (isset($this->item['atom_content'])) :
56
- $content = $this->item['atom_content'];
57
- elseif (isset($this->item['xhtml']['body'])) :
58
- $content = $this->item['xhtml']['body'];
59
- elseif (isset($this->item['xhtml']['div'])) :
60
- $content = $this->item['xhtml']['div'];
61
- elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']):
62
- $content = $this->item['content']['encoded'];
63
- else:
64
- $content = $this->item['description'];
65
- endif;
66
- $this->post['post_content'] = apply_filters('syndicated_item_content', $content, $this);
67
 
68
- # Identify and sanitize excerpt
69
- $excerpt = NULL;
70
- if ( isset($this->item['description']) and $this->item['description'] ) :
71
- $excerpt = $this->item['description'];
72
- elseif ( isset($content) and $content ) :
73
- $excerpt = strip_tags($content);
74
- if (strlen($excerpt) > 255) :
75
- $excerpt = substr($excerpt,0,252).'...';
76
- endif;
77
- endif;
78
- $excerpt = apply_filters('syndicated_item_excerpt', $excerpt, $this);
79
 
 
 
 
 
 
 
80
  if (!is_null($excerpt)):
81
  $this->post['post_excerpt'] = $excerpt;
82
  endif;
83
 
84
- // This is unnecessary if we use wp_insert_post
85
- if (!$this->use_api('wp_insert_post')) :
86
- $this->post['post_name'] = sanitize_title($this->post['post_title']);
87
- endif;
88
-
89
  $this->post['epoch']['issued'] = apply_filters('syndicated_item_published', $this->published(), $this);
90
  $this->post['epoch']['created'] = apply_filters('syndicated_item_created', $this->created(), $this);
91
  $this->post['epoch']['modified'] = apply_filters('syndicated_item_updated', $this->updated(), $this);
92
 
93
  // Dealing with timestamps in WordPress is so fucking fucked.
94
  $offset = (int) get_option('gmt_offset') * 60 * 60;
95
- $this->post['post_date'] = gmdate('Y-m-d H:i:s', $this->published() + $offset);
96
- $this->post['post_modified'] = gmdate('Y-m-d H:i:s', $this->updated() + $offset);
97
- $this->post['post_date_gmt'] = gmdate('Y-m-d H:i:s', $this->published());
98
- $this->post['post_modified_gmt'] = gmdate('Y-m-d H:i:s', $this->updated());
99
 
100
  // Use feed-level preferences or the global default.
101
  $this->post['post_status'] = $this->link->syndicated_status('post', 'publish');
@@ -121,18 +158,47 @@ class SyndicatedPost {
121
  if (!is_array($custom_settings)) :
122
  $custom_settings = array();
123
  endif;
124
- $this->post['meta'] = array_merge($default_custom_settings, $custom_settings);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
 
 
 
 
 
 
 
126
  // RSS 2.0 / Atom 1.0 enclosure support
127
- if ( isset($this->item['enclosure#']) ) :
128
- for ($i = 1; $i <= $this->item['enclosure#']; $i++) :
129
- $eid = (($i > 1) ? "#{$id}" : "");
130
- $this->post['meta']['enclosure'][] =
131
- apply_filters('syndicated_item_enclosure_url', $this->item["enclosure{$eid}@url"], $this)."\n".
132
- apply_filters('syndicated_item_enclosure_length', $this->item["enclosure{$eid}@length"], $this)."\n".
133
- apply_filters('syndicated_item_enclosure_type', $this->item["enclosure{$eid}@type"], $this);
134
- endfor;
135
- endif;
136
 
137
  // In case you want to point back to the blog this was syndicated from
138
  if (isset($this->feed->channel['title'])) :
@@ -157,27 +223,16 @@ class SyndicatedPost {
157
  endif;
158
 
159
  // Store information on human-readable and machine-readable comment URIs
160
- if (isset($this->item['comments'])) :
161
- $this->post['meta']['rss:comments'] = apply_filters('syndicated_item_comments', $this->item['comments']);
162
- endif;
163
 
164
- // RSS 2.0 comment feeds extension
165
- if (isset($this->item['wfw']['commentrss'])) :
166
- $this->post['meta']['wfw:commentRSS'] = apply_filters('syndicated_item_commentrss', $this->item['wfw']['commentrss']);
167
- endif;
168
 
169
- // Atom 1.0 comment feeds link-rel
170
- if (isset($this->item['link_replies'])) :
171
- // There may be multiple <link rel="replies"> elements; feeds have a feed MIME type
172
- $N = isset($this->item['link_replies#']) ? $this->item['link_replies#'] : 1;
173
- for ($i = 1; $i <= $N; $i++) :
174
- $currentElement = 'link_replies'.(($i > 1) ? '#'.$i : '');
175
- if (isset($this->item[$currentElement.'@type'])
176
- and preg_match("\007application/(atom|rss|rdf)\+xml\007i", $this->item[$currentElement.'@type'])) :
177
- $this->post['meta']['wfw:commentRSS'] = apply_filters('syndicated_item_commentrss', $this->item[$currentElement]);
178
- endif;
179
- endfor;
180
- endif;
181
 
182
  // Store information to identify the feed that this came from
183
  if (isset($this->feedmeta['link/uri'])) :
@@ -192,17 +247,7 @@ class SyndicatedPost {
192
  endif;
193
 
194
  // In case you want to know the external permalink...
195
- if (isset($this->item['link'])) :
196
- $permalink = $this->item['link'];
197
-
198
- // No <link> element. See if this feed has <guid isPermalink="true"> ....
199
- elseif (isset($this->item['guid'])) :
200
- if (isset($this->item['guid@ispermalink']) and strtolower(trim($this->item['guid@ispermalink'])) != 'false') :
201
- $permalink = $this->item['guid'];
202
- endif;
203
- endif;
204
-
205
- $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $permalink);
206
 
207
  // Store a hash of the post content for checking whether something needs to be updated
208
  $this->post['meta']['syndication_item_hash'] = $this->update_hash();
@@ -253,1050 +298,1397 @@ class SyndicatedPost {
253
  endif;
254
  $this->post['tags_input'] = apply_filters('syndicated_item_tags', $this->post['tags_input'], $this);
255
  endif;
256
- } // SyndicatedPost::SyndicatedPost()
257
 
258
- function filtered () {
259
- return is_null($this->post);
260
- }
 
 
 
 
 
 
 
 
 
 
 
261
 
262
- function freshness () {
263
- global $wpdb;
 
 
 
 
 
 
 
264
 
265
- if ($this->filtered()) : // This should never happen.
266
- FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
267
- endif;
268
-
269
- if (is_null($this->_freshness)) :
270
- $guid = $wpdb->escape($this->guid());
 
 
 
 
 
 
271
 
272
- $result = $wpdb->get_row("
273
- SELECT id, guid, post_modified_gmt
274
- FROM $wpdb->posts WHERE guid='$guid'
275
- ");
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
- if (!$result) :
278
- $this->_freshness = 2; // New content
279
- else:
280
- $stored_update_hashes = get_post_custom_values('syndication_item_hash', $result->id);
281
- if (count($stored_update_hashes) > 0) :
282
- $stored_update_hash = $stored_update_hashes[0];
283
- $update_hash_changed = ($stored_update_hash != $this->update_hash());
 
284
  else :
285
- $update_hash_changed = false;
286
  endif;
287
 
288
- preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref);
 
 
 
 
 
 
 
289
 
290
- $last_rev_ts = gmmktime($backref[4], $backref[5], $backref[6], $backref[2], $backref[3], $backref[1]);
291
- $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
292
-
293
- $frozen_values = get_post_custom_values('_syndication_freeze_updates', $result->id);
294
- $frozen_post = (count($frozen_values) > 0 and 'yes' == $frozen_values[0]);
295
- $frozen_feed = ('yes' == $this->link->setting('freeze updates', 'freeze_updates', NULL));
 
 
 
 
 
 
296
 
297
- // Check timestamps...
298
- $updated = (
299
- !is_null($updated_ts)
300
- and ($updated_ts > $last_rev_ts)
301
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
 
 
 
 
 
 
 
 
 
303
 
304
- // Or the hash...
305
- $updated = ($updated or $update_hash_changed);
306
-
307
- // But only if the post is not frozen.
308
- $updated = (
309
- $updated
310
- and !$frozen_post
311
- and !$frozen_feed
312
- );
313
-
314
- if ($updated) :
315
- $this->_freshness = 1; // Updated content
316
- $this->_wp_id = $result->id;
317
- else :
318
- $this->_freshness = 0; // Same old, same old
319
- $this->_wp_id = $result->id;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  endif;
321
  endif;
322
  endif;
323
- return $this->_freshness;
 
 
 
 
 
 
 
324
  }
325
 
326
- function wp_id () {
327
- if ($this->filtered()) : // This should never happen.
328
- FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
329
- endif;
330
-
331
- if (is_null($this->_wp_id) and is_null($this->_freshness)) :
332
- $fresh = $this->freshness(); // sets WP DB id in the process
 
333
  endif;
334
- return $this->_wp_id;
335
- }
336
 
337
- function store () {
338
- global $wpdb;
 
339
 
340
- if ($this->filtered()) : // This should never happen.
341
- FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  endif;
343
 
344
- $freshness = $this->freshness();
345
- if ($freshness > 0) :
346
- # -- Look up, or create, numeric ID for author
347
- $this->post['post_author'] = $this->author_id (
348
- FeedWordPress::on_unfamiliar('author', $this->post['named']['unfamiliar']['author'])
349
- );
350
-
351
- if (is_null($this->post['post_author'])) :
352
- $this->post = NULL;
353
- endif;
354
  endif;
355
 
356
- if (!$this->filtered() and $freshness > 0) :
357
- # -- Look up, or create, numeric ID for categories
358
- list($pcats, $ptags) = $this->category_ids (
359
- $this->post['named']['category'],
360
- FeedWordPress::on_unfamiliar('category', $this->post['named']['unfamiliar']['category']),
361
- /*tags_too=*/ true
362
- );
363
-
364
- $this->post['post_category'] = $pcats;
365
- $this->post['tags_input'] = array_merge($this->post['tags_input'], $ptags);
366
-
367
- if (is_null($this->post['post_category'])) :
368
- // filter mode on, no matching categories; drop the post
369
- $this->post = NULL;
370
  else :
371
- // filter mode off or at least one match; now add on the feed and global presets
372
- $this->post['post_category'] = array_merge (
373
- $this->post['post_category'],
374
- $this->category_ids (
375
- $this->post['named']['preset/category'],
376
- 'default'
377
- )
378
- );
379
-
380
- if (count($this->post['post_category']) < 1) :
381
- $this->post['post_category'][] = 1; // Default to category 1 ("Uncategorized" / "General") if nothing else
382
- endif;
383
  endif;
384
  endif;
385
 
386
- if (!$this->filtered() and $freshness > 0) :
387
- unset($this->post['named']);
388
- $this->post = apply_filters('syndicated_post', $this->post, $this);
389
- endif;
390
-
391
- if (!$this->filtered() and $freshness == 2) :
392
- // The item has not yet been added. So let's add it.
393
- $this->insert_new();
394
- $this->add_rss_meta();
395
- do_action('post_syndicated_item', $this->wp_id(), $this);
396
 
397
- $ret = 'new';
398
- elseif (!$this->filtered() and $freshness == 1) :
399
- $this->post['ID'] = $this->wp_id();
400
- $this->update_existing();
401
- $this->add_rss_meta();
402
- do_action('update_syndicated_item', $this->wp_id(), $this);
403
 
404
- $ret = 'updated';
405
- else :
406
- $ret = false;
 
 
 
 
 
 
 
 
407
  endif;
408
 
409
- return $ret;
410
- } // function SyndicatedPost::store ()
411
-
412
- function insert_new () {
413
- global $wpdb, $wp_db_version;
414
-
415
- $dbpost = $this->normalize_post(/*new=*/ true);
416
- if (!is_null($dbpost)) :
417
- if ($this->use_api('wp_insert_post')) :
418
- $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
419
-
420
- // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
421
- add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
422
-
423
- // Kludge to prevent kses filters from stripping the
424
- // content of posts when updating without a logged in
425
- // user who has `unfiltered_html` capability.
426
- add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
427
-
428
- $this->_wp_id = wp_insert_post($dbpost);
429
-
430
- // Turn off ridiculous fucking kludges #1 and #2
431
- remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
432
- remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
433
-
434
- $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
435
-
436
- // Unfortunately, as of WordPress 2.3, wp_insert_post()
437
- // *still* offers no way to use a guid of your choice,
438
- // and munges your post modified timestamp, too.
439
- $result = $wpdb->query("
440
- UPDATE $wpdb->posts
441
- SET
442
- guid='{$dbpost['guid']}',
443
- post_modified='{$dbpost['post_modified']}',
444
- post_modified_gmt='{$dbpost['post_modified_gmt']}'
445
- WHERE ID='{$this->_wp_id}'
446
- ");
447
- else :
448
- # The right way to do this is the above. But, alas,
449
- # in earlier versions of WordPress, wp_insert_post has
450
- # too much behavior (mainly related to pings) that can't
451
- # be overridden. In WordPress 1.5, it's enough of a
452
- # resource hog to make PHP segfault after inserting
453
- # 50-100 posts. This can get pretty annoying, especially
454
- # if you are trying to update your feeds for the first
455
- # time.
456
-
457
- $result = $wpdb->query("
458
- INSERT INTO $wpdb->posts
459
- SET
460
- guid = '{$dbpost['guid']}',
461
- post_author = '{$dbpost['post_author']}',
462
- post_date = '{$dbpost['post_date']}',
463
- post_date_gmt = '{$dbpost['post_date_gmt']}',
464
- post_content = '{$dbpost['post_content']}',"
465
- .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
466
- post_title = '{$dbpost['post_title']}',
467
- post_name = '{$dbpost['post_name']}',
468
- post_modified = '{$dbpost['post_modified']}',
469
- post_modified_gmt = '{$dbpost['post_modified_gmt']}',
470
- comment_status = '{$dbpost['comment_status']}',
471
- ping_status = '{$dbpost['ping_status']}',
472
- post_status = '{$dbpost['post_status']}'
473
- ");
474
- $this->_wp_id = $wpdb->insert_id;
475
-
476
- $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
477
-
478
- // WordPress 1.5.x - 2.0.x
479
- wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
480
 
481
- // Since we are not going through official channels, we need to
482
- // manually tell WordPress that we've published a new post.
483
- // We need to make sure to do this in order for FeedWordPress
484
- // to play well with the staticize-reloaded plugin (something
485
- // that a large aggregator website is going to *want* to be
486
- // able to use).
487
- do_action('publish_post', $this->_wp_id);
488
  endif;
489
  endif;
490
- } /* SyndicatedPost::insert_new() */
491
 
492
- function update_existing () {
493
- global $wpdb;
494
 
495
- // Why the fuck doesn't wp_insert_post already do this?
496
- $dbpost = $this->normalize_post(/*new=*/ false);
497
- if (!is_null($dbpost)) :
498
- if ($this->use_api('wp_insert_post')) :
499
- $dbpost['post_pingback'] = false; // Tell WP 2.1 and 2.2 not to process for pingbacks
500
-
501
- // This is a ridiculous fucking kludge necessitated by WordPress 2.6 munging authorship meta-data
502
- add_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
503
-
504
- // Kludge to prevent kses filters from stripping the
505
- // content of posts when updating without a logged in
506
- // user who has `unfiltered_html` capability.
507
- add_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
508
 
509
- // Don't munge status fields that the user may have reset manually
510
- if (function_exists('get_post_field')) :
511
- $doNotMunge = array('post_status', 'comment_status', 'ping_status');
512
- foreach ($doNotMunge as $field) :
513
- $dbpost[$field] = get_post_field($field, $this->wp_id());
514
- endforeach;
515
- endif;
 
 
 
 
 
 
 
 
 
 
516
 
517
- $this->_wp_id = wp_insert_post($dbpost);
518
-
519
- // Turn off ridiculous fucking kludges #1 and #2
520
- remove_action('_wp_put_post_revision', array($this, 'fix_revision_meta'));
521
- remove_filter('content_save_pre', array($this, 'avoid_kses_munge'), 11);
522
-
523
- $this->validate_post_id($dbpost, array(__CLASS__, __FUNCTION__));
524
-
525
- // Unfortunately, as of WordPress 2.3, wp_insert_post()
526
- // munges your post modified timestamp.
527
- $result = $wpdb->query("
528
- UPDATE $wpdb->posts
529
- SET
530
- post_modified='{$dbpost['post_modified']}',
531
- post_modified_gmt='{$dbpost['post_modified_gmt']}'
532
- WHERE ID='{$this->_wp_id}'
533
- ");
534
  else :
 
 
 
 
 
535
 
536
- $result = $wpdb->query("
537
- UPDATE $wpdb->posts
538
- SET
539
- post_author = '{$dbpost['post_author']}',
540
- post_content = '{$dbpost['post_content']}',"
541
- .(isset($dbpost['post_excerpt']) ? "post_excerpt = '{$dbpost['post_excerpt']}'," : "")."
542
- post_title = '{$dbpost['post_title']}',
543
- post_name = '{$dbpost['post_name']}',
544
- post_modified = '{$dbpost['post_modified']}',
545
- post_modified_gmt = '{$dbpost['post_modified_gmt']}'
546
- WHERE guid='{$dbpost['guid']}'
547
- ");
548
 
549
- // WordPress 2.1.x and up
550
- if (function_exists('wp_set_post_categories')) :
551
- wp_set_post_categories($this->wp_id(), $this->post['post_category']);
552
- // WordPress 1.5.x - 2.0.x
553
- elseif (function_exists('wp_set_post_cats')) :
554
- wp_set_post_cats('1', $this->wp_id(), $this->post['post_category']);
555
- // This should never happen.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
556
  else :
557
- FeedWordPress::critical_bug(__CLASS__.'::'.__FUNCTION.'(): no post categorizing function', array("dbpost" => $dbpost, "this" => $this), __LINE__);
558
  endif;
559
-
560
- // Since we are not going through official channels, we need to
561
- // manually tell WordPress that we've published a new post.
562
- // We need to make sure to do this in order for FeedWordPress
563
- // to play well with the staticize-reloaded plugin (something
564
- // that a large aggregator website is going to *want* to be
565
- // able to use).
566
- do_action('edit_post', $this->post['ID']);
567
  endif;
 
 
568
  endif;
569
- } /* SyndicatedPost::update_existing() */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
 
571
  /**
572
- * SyndicatedPost::normalize_post()
 
 
573
  *
574
- * @param bool $new If true, this post is to be inserted anew. If false, it is an update of an existing post.
575
- * @return array A normalized representation of the post ready to be inserted into the database or sent to the WordPress API functions
 
 
 
576
  */
577
- function normalize_post ($new = true) {
578
- global $wpdb;
579
 
580
- $out = array();
 
 
581
 
582
- // Why the fuck doesn't wp_insert_post already do this?
583
- foreach ($this->post as $key => $value) :
584
- if (is_string($value)) :
585
- $out[$key] = $wpdb->escape($value);
586
- else :
587
- $out[$key] = $value;
588
- endif;
589
- endforeach;
 
 
 
 
 
 
590
 
591
- if (strlen($out['post_title'].$out['post_content'].$out['post_excerpt']) == 0) :
592
- // FIXME: Option for filtering out empty posts
593
- endif;
594
- if (strlen($out['post_title'])==0) :
595
- $offset = (int) get_option('gmt_offset') * 60 * 60;
596
- $out['post_title'] =
597
- $this->post['meta']['syndication_source']
598
- .' '.gmdate('Y-m-d H:i:s', $this->published() + $offset);
599
- // FIXME: Option for what to fill a blank title with...
600
  endif;
601
 
602
- return $out;
603
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
604
 
605
  /**
606
- * SyndicatedPost::validate_post_id()
 
607
  *
608
- * @param array $dbpost An array representing the post we attempted to insert or update
609
- * @param mixed $ns A string or array representing the namespace (class, method) whence this method was called.
 
 
 
610
  */
611
- function validate_post_id ($dbpost, $ns) {
612
- if (is_array($ns)) : $ns = implode('::', $ns);
613
- else : $ns = (string) $ns; endif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
614
 
615
- // This should never happen.
616
- if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
617
- FeedWordPress::critical_bug(
618
- /*name=*/ $ns.'::_wp_id',
619
- /*var =*/ array(
620
- "\$this->_wp_id" => $this->_wp_id,
621
- "\$dbpost" => $dbpost,
622
- "\$this" => $this
623
- ),
624
- /*line # =*/ __LINE__
625
- );
626
  endif;
627
- } /* SyndicatedPost::validate_post_id() */
628
-
629
- /**
630
- * SyndicatedPost::fix_revision_meta() - Fixes the way WP 2.6+ fucks up
631
- * meta-data (authorship, etc.) when storing revisions of an updated
632
- * syndicated post.
633
- *
634
- * In their infinite wisdom, the WordPress coders have made it completely
635
- * impossible for a plugin that uses wp_insert_post() to set certain
636
- * meta-data (such as the author) when you store an old revision of an
637
- * updated post. Instead, it uses the WordPress defaults (= currently
638
- * active user ID if the process is running with a user logged in, or
639
- * = #0 if there is no user logged in). This results in bogus authorship
640
- * data for revisions that are syndicated from off the feed, unless we
641
- * use a ridiculous kludge like this to end-run the munging of meta-data
642
- * by _wp_put_post_revision.
643
- *
644
- * @param int $revision_id The revision ID to fix up meta-data
645
- */
646
- function fix_revision_meta ($revision_id) {
647
- global $wpdb;
648
 
649
- $post_author = (int) $this->post['post_author'];
 
650
 
651
- $revision_id = (int) $revision_id;
652
- $wpdb->query("
653
- UPDATE $wpdb->posts
654
- SET post_author={$this->post['post_author']}
655
- WHERE post_type = 'revision' AND ID='$revision_id'
656
- ");
657
- } /* SyndicatedPost::fix_revision_meta () */
658
-
659
- /**
660
- * SyndicatedPost::avoid_kses_munge() -- If FeedWordPress is processing
661
- * an automatic update, that generally means that wp_insert_post() is
662
- * being called under the user credentials of whoever is viewing the
663
- * blog at the time -- usually meaning no user at all. But if WordPress
664
- * gets a wp_insert_post() when current_user_can('unfiltered_html') is
665
- * false, it will run the content of the post through a kses function
666
- * that strips out lots of HTML tags -- notably <object> and some others.
667
- * This causes problems for syndicating (for example) feeds that contain
668
- * YouTube videos. It also produces an unexpected asymmetry between
669
- * automatically-initiated updates and updates initiated manually from
670
- * the WordPress Dashboard (which are usually initiated under the
671
- * credentials of a logged-in admin, and so don't get run through the
672
- * kses function). So, to avoid the whole mess, what we do here is
673
- * just forcibly disable the kses munging for a single syndicated post,
674
- * by restoring the contents of the `post_content` field.
675
- *
676
- * @param string $content The content of the post, after other filters have gotten to it
677
- * @return string The original content of the post, before other filters had a chance to munge it.
678
- */
679
- function avoid_kses_munge ($content) {
680
- global $wpdb;
681
- return $wpdb->escape($this->post['post_content']);
682
- }
683
-
684
- // SyndicatedPost::add_rss_meta: adds interesting meta-data to each entry
685
- // using the space for custom keys. The set of keys and values to add is
686
- // specified by the keys and values of $post['meta']. This is used to
687
- // store anything that the WordPress user might want to access from a
688
- // template concerning the post's original source that isn't provided
689
- // for by standard WP meta-data (i.e., any interesting data about the
690
- // syndicated post other than author, title, timestamp, categories, and
691
- // guid). It's also used to hook into WordPress's support for
692
- // enclosures.
693
- function add_rss_meta () {
694
- global $wpdb;
695
- if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
696
- $postId = $this->wp_id();
697
-
698
- // Aggregated posts should NOT send out pingbacks.
699
- // WordPress 2.1-2.2 claim you can tell them not to
700
- // using $post_pingback, but they don't listen, so we
701
- // make sure here.
702
- $result = $wpdb->query("
703
- DELETE FROM $wpdb->postmeta
704
- WHERE post_id='$postId' AND meta_key='_pingme'
705
- ");
706
-
707
- foreach ( $this->post['meta'] as $key => $values ) :
708
 
709
- $key = $wpdb->escape($key);
 
710
 
711
- // If this is an update, clear out the old
712
- // values to avoid duplication.
713
- $result = $wpdb->query("
714
- DELETE FROM $wpdb->postmeta
715
- WHERE post_id='$postId' AND meta_key='$key'
716
- ");
 
 
 
 
 
 
 
717
 
718
- // Allow for either a single value or an array
719
- if (!is_array($values)) $values = array($values);
720
- foreach ( $values as $value ) :
721
- $value = $wpdb->escape($value);
722
- $result = $wpdb->query("
723
- INSERT INTO $wpdb->postmeta
724
- SET
725
- post_id='$postId',
726
- meta_key='$key',
727
- meta_value='$value'
728
- ");
729
- if (!$result) :
730
- $err = mysql_error();
731
- if (FEEDWORDPRESS_DEBUG) :
732
- echo "[DEBUG:".date('Y-m-d H:i:S')."][feedwordpress]: post metadata insertion FAILED for field '$key' := '$value': [$err]";
733
- endif;
734
- endif;
735
- endforeach;
736
- endforeach;
737
  endif;
738
- } /* SyndicatedPost::add_rss_meta () */
 
739
 
740
- // SyndicatedPost::author_id (): get the ID for an author name from
741
- // the feed. Create the author if necessary.
742
- function author_id ($unfamiliar_author = 'create') {
743
- global $wpdb;
744
 
745
- $a = $this->author();
746
- $author = $a['name'];
747
- $email = (isset($a['email']) ? $a['email'] : NULL);
748
- $url = (isset($a['uri']) ? $a['uri'] : NULL);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
749
 
750
- $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
751
- if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
752
- $test_email = $email;
753
- else :
754
- $test_email = NULL;
755
- endif;
756
 
757
- // Never can be too careful...
758
- $login = sanitize_user($author, /*strict=*/ true);
759
- $login = apply_filters('pre_user_login', $login);
 
 
760
 
761
- $nice_author = sanitize_title($author);
762
- $nice_author = apply_filters('pre_user_nicename', $nice_author);
 
 
 
 
763
 
764
- $reg_author = $wpdb->escape(preg_quote($author));
765
- $author = $wpdb->escape($author);
766
- $email = $wpdb->escape($email);
767
- $test_email = $wpdb->escape($test_email);
768
- $url = $wpdb->escape($url);
 
769
 
770
- // Check for an existing author rule....
771
- if (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) :
772
- $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))];
773
- else :
774
- $author_rule = NULL;
 
 
 
 
775
  endif;
776
-
777
- // User name is mapped to a particular author. If that author ID exists, use it.
778
- if (is_numeric($author_rule) and get_userdata((int) $author_rule)) :
779
- $id = (int) $author_rule;
780
-
781
- // User name is filtered out
782
- elseif ('filter' == $author_rule) :
783
- $id = NULL;
784
 
785
- else :
786
- // Check the database for an existing author record that might fit
787
 
788
- #-- WordPress 2.0+
789
- if (fwp_test_wp_version(FWP_SCHEMA_HAS_USERMETA)) :
 
 
 
790
 
791
- // First try the user core data table.
792
- $id = $wpdb->get_var(
793
- "SELECT ID FROM $wpdb->users
794
- WHERE
795
- TRIM(LCASE(user_login)) = TRIM(LCASE('$login'))
796
- OR (
797
- LENGTH(TRIM(LCASE(user_email))) > 0
798
- AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
799
- )
800
- OR TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author'))
801
- ");
802
-
803
- // If that fails, look for aliases in the user meta data table
804
- if (is_null($id)) :
805
- $id = $wpdb->get_var(
806
- "SELECT user_id FROM $wpdb->usermeta
807
- WHERE
808
- (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
809
- OR (
810
- meta_key = 'description'
811
- AND TRIM(LCASE(meta_value))
812
- RLIKE CONCAT(
813
- '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
814
- TRIM(LCASE('$reg_author')),
815
- '( |\\t|\\r)*(\\n|\$)'
816
- )
817
- )
818
- ");
819
- endif;
820
-
821
- #-- WordPress 1.5.x
822
- else :
823
- $id = $wpdb->get_var(
824
- "SELECT ID from $wpdb->users
825
- WHERE
826
- TRIM(LCASE(user_login)) = TRIM(LCASE('$login')) OR
827
- (
828
- LENGTH(TRIM(LCASE(user_email))) > 0
829
- AND TRIM(LCASE(user_email)) = TRIM(LCASE('$test_email'))
830
- ) OR
831
- TRIM(LCASE(user_firstname)) = TRIM(LCASE('$author')) OR
832
- TRIM(LCASE(user_nickname)) = TRIM(LCASE('$author')) OR
833
- TRIM(LCASE(user_nicename)) = TRIM(LCASE('$nice_author')) OR
834
- TRIM(LCASE(user_description)) = TRIM(LCASE('$author')) OR
835
- (
836
- LOWER(user_description)
837
- RLIKE CONCAT(
838
- '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
839
- LCASE('$reg_author'),
840
- '( |\\t|\\r)*(\\n|\$)'
841
- )
842
- )
843
- ");
844
 
845
- endif;
 
 
 
 
 
846
 
847
- // ... if you don't find one, then do what you need to do
848
- if (is_null($id)) :
849
- if ($unfamiliar_author === 'create') :
850
- $userdata = array();
 
 
 
 
851
 
852
- #-- user table data
853
- $userdata['ID'] = NULL; // new user
854
- $userdata['user_login'] = $login;
855
- $userdata['user_nicename'] = $nice_author;
856
- $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
857
- $userdata['user_email'] = $email;
858
- $userdata['user_url'] = $url;
859
- $userdata['display_name'] = $author;
860
-
861
- $id = wp_insert_user($userdata);
862
- elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
863
- $id = (int) $unfamiliar_author;
864
- elseif ($unfamiliar_author === 'default') :
865
- $id = 1;
866
- endif;
867
- endif;
868
- endif;
869
 
870
- if ($id) :
871
- $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id;
872
- endif;
873
- return $id;
874
- } // function SyndicatedPost::author_id ()
 
 
 
 
875
 
876
- // look up (and create) category ids from a list of categories
877
- function category_ids ($cats, $unfamiliar_category = 'create', $tags_too = false) {
 
 
 
 
 
 
 
 
 
 
878
  global $wpdb;
879
 
880
- // We need to normalize whitespace because (1) trailing
881
- // whitespace can cause PHP and MySQL not to see eye to eye on
882
- // VARCHAR comparisons for some versions of MySQL (cf.
883
- // <http://dev.mysql.com/doc/mysql/en/char.html>), and (2)
884
- // because I doubt most people want to make a semantic
885
- // distinction between 'Computers' and 'Computers '
886
- $cats = array_map('trim', $cats);
887
 
888
- $tags = array();
 
 
 
889
 
890
- $cat_ids = array ();
891
- foreach ($cats as $cat_name) :
892
- if (preg_match('/^{#([0-9]+)}$/', $cat_name, $backref)) :
893
- $cat_id = (int) $backref[1];
894
- if (function_exists('is_term') and is_term($cat_id, 'category')) :
895
- $cat_ids[] = $cat_id;
896
- elseif (get_category($cat_id)) :
897
- $cat_ids[] = $cat_id;
 
898
  endif;
899
- elseif (strlen($cat_name) > 0) :
900
- $esc = $wpdb->escape($cat_name);
901
- $resc = $wpdb->escape(preg_quote($cat_name));
 
 
902
 
903
- // WordPress 2.3+
904
- if (function_exists('is_term')) :
905
- $cat_id = is_term($cat_name, 'category');
906
- if ($cat_id) :
907
- $cat_ids[] = $cat_id['term_id'];
908
- // There must be a better way to do this...
909
- elseif ($results = $wpdb->get_results(
910
- "SELECT term_id
911
- FROM $wpdb->term_taxonomy
912
- WHERE
913
- LOWER(description) RLIKE
914
- CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)')"
915
- )) :
916
- foreach ($results AS $term) :
917
- $cat_ids[] = (int) $term->term_id;
918
- endforeach;
919
- elseif ('tag'==$unfamiliar_category) :
920
- $tags[] = $cat_name;
921
- elseif ('create'===$unfamiliar_category) :
922
- $term = wp_insert_term($cat_name, 'category');
923
- if (is_wp_error($term)) :
924
- FeedWordPress::noncritical_bug('term insertion problem', array('cat_name' => $cat_name, 'term' => $term, 'this' => $this), __LINE__);
925
- else :
926
- $cat_ids[] = $term['term_id'];
927
- endif;
928
- endif;
929
 
930
- // WordPress 1.5.x - 2.2.x
 
 
 
 
 
 
 
 
 
 
 
 
 
931
  else :
932
- $results = $wpdb->get_results(
933
- "SELECT cat_ID
934
- FROM $wpdb->categories
935
- WHERE
936
- (LOWER(cat_name) = LOWER('$esc'))
937
- OR (LOWER(category_description)
938
- RLIKE CONCAT('(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*', LOWER('{$resc}'), '( |\\t|\\r)*(\\n|\$)'))
939
- ");
940
- if ($results) :
941
- foreach ($results as $term) :
942
- $cat_ids[] = (int) $term->cat_ID;
943
- endforeach;
944
- elseif ('create'===$unfamiliar_category) :
945
- if (function_exists('wp_insert_category')) :
946
- $cat_id = wp_insert_category(array('cat_name' => $esc));
947
- // And into the database we go.
948
- else :
949
- $nice_kitty = sanitize_title($cat_name);
950
- $wpdb->query(sprintf("
951
- INSERT INTO $wpdb->categories
952
- SET
953
- cat_name='%s',
954
- category_nicename='%s'
955
- ", $esc, $nice_kitty
956
- ));
957
- $cat_id = $wpdb->insert_id;
958
- endif;
959
- $cat_ids[] = $cat_id;
960
- endif;
961
  endif;
962
  endif;
963
- endforeach;
964
-
965
- if ((count($cat_ids) == 0) and ($unfamiliar_category === 'filter')) :
966
- $cat_ids = NULL; // Drop the post
967
- else :
968
- $cat_ids = array_unique($cat_ids);
969
- endif;
970
-
971
- if ($tags_too) : $ret = array($cat_ids, $tags);
972
- else : $ret = $cat_ids;
973
  endif;
 
 
974
 
975
- return $ret;
976
- } // function SyndicatedPost::category_ids ()
977
-
978
- function use_api ($tag) {
979
- global $wp_db_version;
980
- switch ($tag) :
981
- case 'wp_insert_post':
982
- // Before 2.2, wp_insert_post does too much of the wrong stuff to use it
983
- // In 1.5 it was such a resource hog it would make PHP segfault on big updates
984
- $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_21);
985
- break;
986
- case 'post_status_pending':
987
- $ret = (isset($wp_db_version) and $wp_db_version > FWP_SCHEMA_23);
988
- break;
989
- endswitch;
990
- return $ret;
991
- } // function SyndicatedPost::use_api ()
992
-
993
- #### EXTRACT DATA FROM FEED ITEM ####
994
 
995
- function created () {
996
- $epoch = null;
997
- if (isset($this->item['dc']['created'])) :
998
- $epoch = @parse_w3cdtf($this->item['dc']['created']);
999
- elseif (isset($this->item['dcterms']['created'])) :
1000
- $epoch = @parse_w3cdtf($this->item['dcterms']['created']);
1001
- elseif (isset($this->item['created'])): // Atom 0.3
1002
- $epoch = @parse_w3cdtf($this->item['created']);
1003
  endif;
1004
- return $epoch;
 
 
 
 
1005
  }
1006
- function published ($fallback = true) {
1007
- $epoch = null;
1008
 
1009
- # RSS is a fucking mess. Figure out whether we have a date in
1010
- # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix
1011
- # epoch format for reformatting. If we can't find anything,
1012
- # we'll use the last-updated time.
1013
- if (isset($this->item['dc']['date'])): // Dublin Core
1014
- $epoch = @parse_w3cdtf($this->item['dc']['date']);
1015
- elseif (isset($this->item['dcterms']['issued'])) : // Dublin Core extensions
1016
- $epoch = @parse_w3cdtf($this->item['dcterms']['issued']);
1017
- elseif (isset($this->item['published'])) : // Atom 1.0
1018
- $epoch = @parse_w3cdtf($this->item['published']);
1019
- elseif (isset($this->item['issued'])): // Atom 0.3
1020
- $epoch = @parse_w3cdtf($this->item['issued']);
1021
- elseif (isset($this->item['pubdate'])): // RSS 2.0
1022
- $epoch = strtotime($this->item['pubdate']);
1023
- elseif ($fallback) : // Fall back to <updated> / <modified> if present
1024
- $epoch = $this->updated(/*fallback=*/ false);
1025
  endif;
1026
 
1027
- # If everything failed, then default to the current time.
1028
- if (is_null($epoch)) :
1029
- if (-1 == $default) :
1030
- $epoch = time();
1031
- else :
1032
- $epoch = $default;
1033
- endif;
1034
- endif;
1035
-
1036
- return $epoch;
1037
- }
1038
- function updated ($fallback = true, $default = -1) {
1039
- $epoch = null;
1040
 
1041
- # As far as I know, only dcterms and Atom have reliable ways to
1042
- # specify when something was *modified* last. If neither is
1043
- # available, then we'll try to get the time of publication.
1044
- if (isset($this->item['dc']['modified'])) : // Not really correct
1045
- $epoch = @parse_w3cdtf($this->item['dc']['modified']);
1046
- elseif (isset($this->item['dcterms']['modified'])) : // Dublin Core extensions
1047
- $epoch = @parse_w3cdtf($this->item['dcterms']['modified']);
1048
- elseif (isset($this->item['modified'])): // Atom 0.3
1049
- $epoch = @parse_w3cdtf($this->item['modified']);
1050
- elseif (isset($this->item['updated'])): // Atom 1.0
1051
- $epoch = @parse_w3cdtf($this->item['updated']);
1052
- elseif ($fallback) : // Fall back to issued / dc:date
1053
- $epoch = $this->published(/*fallback=*/ false, /*default=*/ $default);
1054
- endif;
1055
-
1056
- # If everything failed, then default to the current time.
1057
- if (is_null($epoch)) :
1058
- if (-1 == $default) :
1059
- $epoch = time();
1060
- else :
1061
- $epoch = $default;
1062
  endif;
1063
  endif;
 
 
 
 
 
 
 
 
1064
 
1065
- return $epoch;
1066
- }
1067
-
1068
- function update_hash () {
1069
- return md5(serialize($this->item));
1070
- }
1071
-
1072
- function guid () {
1073
- $guid = null;
1074
- if (isset($this->item['id'])): // Atom 0.3 / 1.0
1075
- $guid = $this->item['id'];
1076
- elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
1077
- $guid = $this->item['atom']['id'];
1078
- elseif (isset($this->item['guid'])) : // RSS 2.0
1079
- $guid = $this->item['guid'];
1080
- elseif (isset($this->item['dc']['identifier'])) :// yeah, right
1081
- $guid = $this->item['dc']['identifier'];
1082
- else :
1083
- // The feed does not seem to have provided us with a
1084
- // unique identifier, so we'll have to cobble together
1085
- // a tag: URI that might work for us. The base of the
1086
- // URI will be the host name of the feed source ...
1087
- $bits = parse_url($this->feedmeta['link/uri']);
1088
- $guid = 'tag:'.$bits['host'];
1089
 
1090
- // If we have a date of creation, then we can use that
1091
- // to uniquely identify the item. (On the other hand, if
1092
- // the feed producer was consicentious enough to
1093
- // generate dates of creation, she probably also was
1094
- // conscientious enough to generate unique identifiers.)
1095
- if (!is_null($this->created())) :
1096
- $guid .= '://post.'.date('YmdHis', $this->created());
1097
-
1098
- // Otherwise, use both the URI of the item, *and* the
1099
- // item's title. We have to use both because titles are
1100
- // often not unique, and sometimes links aren't unique
1101
- // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
1102
- // some podcasts). But it's rare to have *both* the same
1103
- // title *and* the same link for two different items. So
1104
- // this is about the best we can do.
1105
  else :
1106
- $guid .= '://'.md5($this->item['link'].'/'.$this->item['title']);
1107
- endif;
1108
- endif;
1109
- return $guid;
1110
- }
1111
-
1112
- function author () {
1113
- $author = array ();
1114
-
1115
- if (isset($this->item['author_name'])):
1116
- $author['name'] = $this->item['author_name'];
1117
- elseif (isset($this->item['dc']['creator'])):
1118
- $author['name'] = $this->item['dc']['creator'];
1119
- elseif (isset($this->item['dc']['contributor'])):
1120
- $author['name'] = $this->item['dc']['contributor'];
1121
- elseif (isset($this->feed->channel['dc']['creator'])) :
1122
- $author['name'] = $this->feed->channel['dc']['creator'];
1123
- elseif (isset($this->feed->channel['dc']['contributor'])) :
1124
- $author['name'] = $this->feed->channel['dc']['contributor'];
1125
- elseif (isset($this->feed->channel['author_name'])) :
1126
- $author['name'] = $this->feed->channel['author_name'];
1127
- elseif ($this->feed->is_rss() and isset($this->item['author'])) :
1128
- // The author element in RSS is allegedly an
1129
- // e-mail address, but lots of people don't use
1130
- // it that way. So let's make of it what we can.
1131
- $author = parse_email_with_realname($this->item['author']);
1132
-
1133
- if (!isset($author['name'])) :
1134
- if (isset($author['email'])) :
1135
- $author['name'] = $author['email'];
1136
- else :
1137
- $author['name'] = $this->feed->channel['title'];
1138
  endif;
1139
  endif;
1140
- else :
1141
- $author['name'] = $this->feed->channel['title'];
1142
  endif;
1143
 
1144
- if (isset($this->item['author_email'])):
1145
- $author['email'] = $this->item['author_email'];
1146
- elseif (isset($this->feed->channel['author_email'])) :
1147
- $author['email'] = $this->feed->channel['author_email'];
 
 
 
 
 
 
1148
  endif;
1149
 
1150
- if (isset($this->item['author_url'])):
1151
- $author['uri'] = $this->item['author_url'];
1152
- elseif (isset($this->feed->channel['author_url'])) :
1153
- $author['uri'] = $this->item['author_url'];
1154
- else:
1155
- $author['uri'] = $this->feed->channel['link'];
1156
- endif;
1157
-
1158
- return $author;
1159
- } // SyndicatedPost::author()
1160
-
1161
- /**
1162
- * SyndicatedPost::isTaggedAs: Test whether a feed item is
1163
- * tagged / categorized with a given string. Case and leading and
1164
- * trailing whitespace are ignored.
1165
- *
1166
- * @param string $tag Tag to check for
1167
- *
1168
- * @return bool Whether or not at least one of the categories / tags on
1169
- * $this->item is set to $tag (modulo case and leading and trailing
1170
- * whitespace)
1171
- */
1172
- function isTaggedAs ($tag) {
1173
- $desiredTag = strtolower(trim($tag)); // Normalize case and whitespace
1174
 
1175
- // Check to see if this is tagged with $tag
1176
- $currentCategory = 'category';
1177
- $currentCategoryNumber = 1;
 
1178
 
1179
- // If we have the new MagpieRSS, the number of category elements
1180
- // on this item is stored under index "category#".
1181
- if (isset($this->item['category#'])) :
1182
- $numberOfCategories = (int) $this->item['category#'];
1183
-
1184
- // We REALLY shouldn't have the old and busted MagpieRSS, but in
1185
- // case we do, it doesn't support multiple categories, but there
1186
- // might still be a single value under the "category" index.
1187
- elseif (isset($this->item['category'])) :
1188
- $numberOfCategories = 1;
1189
 
1190
- // No standard category or tag elements on this feed item.
1191
  else :
1192
- $numberOfCategories = 0;
1193
-
1194
  endif;
1195
 
1196
- $isSoTagged = false; // Innocent until proven guilty
1197
-
1198
- // Loop through category elements; if there are multiple
1199
- // elements, they are indexed as category, category#2,
1200
- // category#3, ... category#N
1201
- while ($currentCategoryNumber <= $numberOfCategories) :
1202
- if ($desiredTag == strtolower(trim($this->item[$currentCategory]))) :
1203
- $isSoTagged = true; // Got it!
1204
- break;
1205
- endif;
1206
-
1207
- $currentCategoryNumber += 1;
1208
- $currentCategory = 'category#'.$currentCategoryNumber;
1209
- endwhile;
1210
-
1211
- return $isSoTagged;
1212
- } /* SyndicatedPost::isTaggedAs() */
1213
-
1214
- var $uri_attrs = array (
1215
- array('a', 'href'),
1216
- array('applet', 'codebase'),
1217
- array('area', 'href'),
1218
- array('blockquote', 'cite'),
1219
- array('body', 'background'),
1220
- array('del', 'cite'),
1221
- array('form', 'action'),
1222
- array('frame', 'longdesc'),
1223
- array('frame', 'src'),
1224
- array('iframe', 'longdesc'),
1225
- array('iframe', 'src'),
1226
- array('head', 'profile'),
1227
- array('img', 'longdesc'),
1228
- array('img', 'src'),
1229
- array('img', 'usemap'),
1230
- array('input', 'src'),
1231
- array('input', 'usemap'),
1232
- array('ins', 'cite'),
1233
- array('link', 'href'),
1234
- array('object', 'classid'),
1235
- array('object', 'codebase'),
1236
- array('object', 'data'),
1237
- array('object', 'usemap'),
1238
- array('q', 'cite'),
1239
- array('script', 'src')
1240
- ); /* var SyndicatedPost::$uri_attrs */
1241
-
1242
- var $_base = null;
1243
 
1244
- function resolve_single_relative_uri ($refs) {
1245
- $tag = FeedWordPressHTML::attributeMatch($refs);
1246
- $url = Relative_URI::resolve($tag['value'], $this->_base);
1247
- return $tag['prefix'] . $url . $tag['suffix'];
1248
- } /* function SyndicatedPost::resolve_single_relative_uri() */
1249
 
1250
- function resolve_relative_uris ($content, $obj) {
1251
- $set = $obj->link->setting('resolve relative', 'resolve_relative', 'yes');
1252
- if ($set and $set != 'no') :
1253
- # The MagpieRSS upgrade has some `xml:base` support baked in.
1254
- # However, sometimes people do silly things, like putting
1255
- # relative URIs out on a production RSS 2.0 feed or other feeds
1256
- # with no good support for `xml:base`. So we'll do our best to
1257
- # try to catch any remaining relative URIs and resolve them as
1258
- # best we can.
1259
- $obj->_base = $obj->item['link']; // Reset the base for resolving relative URIs
1260
 
1261
- foreach ($obj->uri_attrs as $pair) :
1262
- list($tag, $attr) = $pair;
1263
- $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
1264
- $content = preg_replace_callback (
1265
- $pattern,
1266
- array(&$obj, 'resolve_single_relative_uri'),
1267
- $content
1268
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1269
  endforeach;
1270
  endif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1271
 
1272
- return $content;
1273
- } /* function SyndicatedPost::resolve_relative_uris () */
1274
 
1275
- var $strip_attrs = array (
1276
- array('[a-z]+', 'target'),
1277
- // array('[a-z]+', 'style'),
1278
- // array('[a-z]+', 'on[a-z]+'),
1279
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1280
 
1281
- function strip_attribute_from_tag ($refs) {
1282
- $tag = FeedWordPressHTML::attributeMatch($refs);
1283
- return $tag['before_attribute'].$tag['after_attribute'];
1284
- }
1285
 
1286
- function sanitize_content ($content, $obj) {
1287
- # This kind of sucks. I intend to replace it with
1288
- # lib_filter sometime soon.
1289
- foreach ($obj->strip_attrs as $pair):
1290
- list($tag,$attr) = $pair;
1291
- $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
 
1292
 
1293
- $content = preg_replace_callback (
1294
- $pattern,
1295
- array(&$obj, 'strip_attribute_from_tag'),
1296
- $content
1297
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1298
  endforeach;
1299
- return $content;
1300
- }
1301
- } // class SyndicatedPost
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1302
 
1
  <?php
2
+ require_once(dirname(__FILE__).'/feedtime.class.php');
3
+
4
+ /**
5
+ * class SyndicatedPost: FeedWordPress uses to manage the conversion of
6
+ * incoming items from the feed parser into posts for the WordPress
7
+ * database. It contains several internal management methods primarily
8
+ * of interest to someone working on the FeedWordPress source, as well
9
+ * as some utility methods for extracting useful data from many
10
+ * different feed formats, which may be useful to FeedWordPress users
11
+ * who make use of feed data in PHP add-ons and filters.
12
+ *
13
+ * @version 2010.0528
14
+ */
15
  class SyndicatedPost {
16
+ var $item = null; // MagpieRSS representation
17
+ var $entry = null; // SimplePie_Item representation
18
+
19
  var $link = null;
20
  var $feed = null;
21
  var $feedmeta = null;
22
 
23
+ var $xmlns = array ();
24
+
25
  var $post = array ();
26
 
27
  var $_freshness = null;
28
  var $_wp_id = null;
29
 
30
+ /**
31
+ * SyndicatedPost constructor: Given a feed item and the source from
32
+ * which it was taken, prepare a post that can be inserted into the
33
+ * WordPress database on request, or updated in place if it has already
34
+ * been syndicated.
35
+ *
36
+ * @param array $item The item syndicated from the feed.
37
+ * @param SyndicatedLink $source The feed it was syndicated from.
38
+ */
39
+ function SyndicatedPost ($item, $source) {
40
  global $wpdb;
41
 
42
+ if (is_array($item)
43
+ and isset($item['simplepie'])
44
+ and isset($item['magpie'])) :
45
+ $this->entry = $item['simplepie'];
46
+ $this->item = $item['magpie'];
47
+ $item = $item['magpie'];
48
+ else :
49
+ $this->item = $item;
50
+ endif;
51
+
52
+ $this->link = $source;
53
+ $this->feed = $source->magpie;
54
+ $this->feedmeta = $source->settings;
55
+
56
+ # Dealing with namespaces can get so fucking fucked.
57
+ $this->xmlns['forward'] = $source->magpie->_XMLNS_FAMILIAR;
58
+ $this->xmlns['reverse'] = array();
59
+ foreach ($this->xmlns['forward'] as $url => $ns) :
60
+ if (!isset($this->xmlns['reverse'][$ns])) :
61
+ $this->xmlns['reverse'][$ns] = array();
62
+ endif;
63
+ $this->xmlns['reverse'][$ns][] = $url;
64
+ endforeach;
65
+
66
+ // Fucking SimplePie.
67
+ $this->xmlns['reverse']['rss'][] = '';
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
 
84
+ // Trigger global syndicated_item filter.
85
  $this->item = apply_filters('syndicated_item', $this->item, $this);
86
+
87
+ // Allow for feed-specific syndicated_item filters.
88
+ $this->item = apply_filters(
89
+ "syndicated_item_".$source->uri(),
90
+ $this->item,
91
+ $this
92
+ );
93
 
94
  # Filters can halt further processing by returning NULL
95
  if (is_null($this->item)) :
100
  # of insertion, not here, to avoid double-escaping and
101
  # to avoid screwing with syndicated_post filters
102
 
103
+ $this->post['post_title'] = apply_filters(
104
+ 'syndicated_item_title',
105
+ $this->entry->get_title(), $this
106
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
+ $this->post['named']['author'] = apply_filters(
109
+ 'syndicated_item_author',
110
+ $this->author(), $this
111
+ );
112
+ // This just gives us an alphanumeric name for the author.
113
+ // We look up (or create) the numeric ID for the author
114
+ // in SyndicatedPost::add().
 
 
 
 
115
 
116
+ $this->post['post_content'] = apply_filters(
117
+ 'syndicated_item_content',
118
+ $this->content(), $this
119
+ );
120
+
121
+ $excerpt = apply_filters('syndicated_item_excerpt', $this->excerpt(), $this);
122
  if (!is_null($excerpt)):
123
  $this->post['post_excerpt'] = $excerpt;
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);
129
 
130
  // Dealing with timestamps in WordPress is so fucking fucked.
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));
136
 
137
  // Use feed-level preferences or the global default.
138
  $this->post['post_status'] = $this->link->syndicated_status('post', 'publish');
158
  if (!is_array($custom_settings)) :
159
  $custom_settings = array();
160
  endif;
161
+
162
+ $postMetaIn = array_merge($default_custom_settings, $custom_settings);
163
+ $postMetaOut = array();
164
+
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;
185
+ endforeach;
186
 
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);
191
+ endforeach;
192
+ endforeach;
193
+
194
  // RSS 2.0 / Atom 1.0 enclosure support
195
+ $enclosures = $this->entry->get_enclosures();
196
+ if (is_array($enclosures)) : foreach ($enclosures as $enclosure) :
197
+ $this->post['meta']['enclosure'][] =
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);
201
+ endforeach; endif;
 
 
202
 
203
  // In case you want to point back to the blog this was syndicated from
204
  if (isset($this->feed->channel['title'])) :
223
  endif;
224
 
225
  // Store information on human-readable and machine-readable comment URIs
 
 
 
226
 
227
+ // Human-readable comment URI
228
+ $commentLink = apply_filters('syndicated_item_comments', $this->comment_link(), $this);
229
+ if (!is_null($commentLink)) : $this->post['meta']['rss:comments'] = $commentLink; endif;
 
230
 
231
+ // Machine-readable content feed URI
232
+ $commentFeed = apply_filters('syndicated_item_commentrss', $this->comment_feed(), $this);
233
+ if (!is_null($commentFeed)) : $this->post['meta']['wfw:commentRSS'] = $commentFeed; endif;
234
+ // Yeah, yeah, now I know that it's supposed to be
235
+ // wfw:commentRss. Oh well. Path dependence, sucka.
 
 
 
 
 
 
 
236
 
237
  // Store information to identify the feed that this came from
238
  if (isset($this->feedmeta['link/uri'])) :
247
  endif;
248
 
249
  // In case you want to know the external permalink...
250
+ $this->post['meta']['syndication_permalink'] = apply_filters('syndicated_item_link', $this->permalink());
 
 
 
 
 
 
 
 
 
 
251
 
252
  // Store a hash of the post content for checking whether something needs to be updated
253
  $this->post['meta']['syndication_item_hash'] = $this->update_hash();
298
  endif;
299
  $this->post['tags_input'] = apply_filters('syndicated_item_tags', $this->post['tags_input'], $this);
300
  endif;
301
+ } /* SyndicatedPost::SyndicatedPost() */
302
 
303
+ #####################################
304
+ #### EXTRACT DATA FROM FEED ITEM ####
305
+ #####################################
306
+
307
+ /**
308
+ * SyndicatedPost::query uses an XPath-like syntax to query arbitrary
309
+ * elements within the syndicated item.
310
+ *
311
+ * @param string $path
312
+ * @returns array of string values representing contents of matching
313
+ * elements or attributes
314
+ */
315
+ function query ($path) {
316
+ $urlHash = array();
317
 
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;
327
 
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;
340
 
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;
358
 
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;
374
+ endforeach;
375
+ $data = $matches;
376
+
377
+ $node = array_shift($path);
378
+ endif;
379
 
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;
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
+
411
+ $matches = array();
412
+ foreach ($data as $datum) :
413
+ if (is_string($datum)) :
414
+ $matches[] = $datum;
415
+ elseif (isset($datum['data'])) :
416
+ $matches[] = $datum['data'];
417
+ endif;
418
+ endforeach;
419
+ return $matches;
420
+ } /* SyndicatedPost::query() */
421
+
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 () */
472
+
473
+ function content () {
474
+ $content = NULL;
475
+ if (isset($this->item['atom_content'])) :
476
+ $content = $this->item['atom_content'];
477
+ elseif (isset($this->item['xhtml']['body'])) :
478
+ $content = $this->item['xhtml']['body'];
479
+ elseif (isset($this->item['xhtml']['div'])) :
480
+ $content = $this->item['xhtml']['div'];
481
+ elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']):
482
+ $content = $this->item['content']['encoded'];
483
+ else:
484
+ $content = $this->item['description'];
485
+ endif;
486
+ return $content;
487
+ } /* SyndicatedPost::content() */
488
+
489
+ function excerpt () {
490
+ # Identify and sanitize excerpt: atom:summary, or rss:description
491
+ $excerpt = $this->entry->get_description();
492
+
493
+ # Many RSS feeds use rss:description, inadvisably, to
494
+ # carry the entire post (typically with escaped HTML).
495
+ # If that's what happened, we don't want the full
496
+ # content for the excerpt.
497
+ $content = $this->content();
498
+ if ( is_null($excerpt) or $excerpt == $content ) :
499
+ # If content is available, generate an excerpt.
500
+ if ( strlen(trim($content)) > 0 ) :
501
+ $excerpt = strip_tags($content);
502
+ if (strlen($excerpt) > 255) :
503
+ $excerpt = substr($excerpt,0,252).'...';
504
  endif;
505
  endif;
506
  endif;
507
+ return $excerpt;
508
+ } /* SyndicatedPost::excerpt() */
509
+
510
+ function permalink () {
511
+ // Handles explicit <link> elements and also RSS 2.0 cases with
512
+ // <guid isPermaLink="true">, etc. Hooray!
513
+ $permalink = $this->entry->get_link();
514
+ return $permalink;
515
  }
516
 
517
+ function created () {
518
+ $date = '';
519
+ if (isset($this->item['dc']['created'])) :
520
+ $date = $this->item['dc']['created'];
521
+ elseif (isset($this->item['dcterms']['created'])) :
522
+ $date = $this->item['dcterms']['created'];
523
+ elseif (isset($this->item['created'])): // Atom 0.3
524
+ $date = $this->item['created'];
525
  endif;
 
 
526
 
527
+ $epoch = new FeedTime($date);
528
+ return $epoch->timestamp();
529
+ } /* SyndicatedPost::created() */
530
 
531
+ function published ($fallback = true, $default = NULL) {
532
+ $date = '';
533
+
534
+ # RSS is a fucking mess. Figure out whether we have a date in
535
+ # <dc:date>, <issued>, <pubDate>, etc., and get it into Unix
536
+ # epoch format for reformatting. If we can't find anything,
537
+ # we'll use the last-updated time.
538
+ if (isset($this->item['dc']['date'])): // Dublin Core
539
+ $date = $this->item['dc']['date'];
540
+ elseif (isset($this->item['dcterms']['issued'])) : // Dublin Core extensions
541
+ $date = $this->item['dcterms']['issued'];
542
+ elseif (isset($this->item['published'])) : // Atom 1.0
543
+ $date = $this->item['published'];
544
+ elseif (isset($this->item['issued'])): // Atom 0.3
545
+ $date = $this->item['issued'];
546
+ elseif (isset($this->item['pubdate'])): // RSS 2.0
547
+ $date = $this->item['pubdate'];
548
  endif;
549
 
550
+ if (strlen($date) > 0) :
551
+ $time = new FeedTime($date);
552
+ $epoch = $time->timestamp();
553
+ elseif ($fallback) : // Fall back to <updated> / <modified> if present
554
+ $epoch = $this->updated(/*fallback=*/ false, /*default=*/ $default);
 
 
 
 
 
555
  endif;
556
 
557
+ # If everything failed, then default to the current time.
558
+ if (is_null($epoch)) :
559
+ if (-1 == $default) :
560
+ $epoch = time();
 
 
 
 
 
 
 
 
 
 
561
  else :
562
+ $epoch = $default;
 
 
 
 
 
 
 
 
 
 
 
563
  endif;
564
  endif;
565
 
566
+ return $epoch;
567
+ } /* SyndicatedPost::published() */
 
 
 
 
 
 
 
 
568
 
569
+ function updated ($fallback = true, $default = -1) {
570
+ $date = '';
 
 
 
 
571
 
572
+ # As far as I know, only dcterms and Atom have reliable ways to
573
+ # specify when something was *modified* last. If neither is
574
+ # available, then we'll try to get the time of publication.
575
+ if (isset($this->item['dc']['modified'])) : // Not really correct
576
+ $date = $this->item['dc']['modified'];
577
+ elseif (isset($this->item['dcterms']['modified'])) : // Dublin Core extensions
578
+ $date = $this->item['dcterms']['modified'];
579
+ elseif (isset($this->item['modified'])): // Atom 0.3
580
+ $date = $this->item['modified'];
581
+ elseif (isset($this->item['updated'])): // Atom 1.0
582
+ $date = $this->item['updated'];
583
  endif;
584
 
585
+ if (strlen($date) > 0) :
586
+ $time = new FeedTime($date);
587
+ $epoch = $time->timestamp();
588
+ elseif ($fallback) : // Fall back to issued / dc:date
589
+ $epoch = $this->published(/*fallback=*/ false, /*default=*/ $default);
590
+ endif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
591
 
592
+ # If everything failed, then default to the current time.
593
+ if (is_null($epoch)) :
594
+ if (-1 == $default) :
595
+ $epoch = time();
596
+ else :
597
+ $epoch = $default;
 
598
  endif;
599
  endif;
 
600
 
601
+ return $epoch;
602
+ } /* SyndicatedPost::updated() */
603
 
604
+ function update_hash () {
605
+ return md5(serialize($this->item));
606
+ } /* SyndicatedPost::update_hash() */
 
 
 
 
 
 
 
 
 
 
607
 
608
+ function guid () {
609
+ $guid = null;
610
+ if (isset($this->item['id'])): // Atom 0.3 / 1.0
611
+ $guid = $this->item['id'];
612
+ elseif (isset($this->item['atom']['id'])) : // Namespaced Atom
613
+ $guid = $this->item['atom']['id'];
614
+ elseif (isset($this->item['guid'])) : // RSS 2.0
615
+ $guid = $this->item['guid'];
616
+ elseif (isset($this->item['dc']['identifier'])) :// yeah, right
617
+ $guid = $this->item['dc']['identifier'];
618
+ else :
619
+ // The feed does not seem to have provided us with a
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']);
624
+ $guid = 'tag:'.$bits['host'];
625
 
626
+ // If we have a date of creation, then we can use that
627
+ // to uniquely identify the item. (On the other hand, if
628
+ // the feed producer was consicentious enough to
629
+ // generate dates of creation, she probably also was
630
+ // conscientious enough to generate unique identifiers.)
631
+ if (!is_null($this->created())) :
632
+ $guid .= '://post.'.date('YmdHis', $this->created());
633
+
634
+ // Otherwise, use both the URI of the item, *and* the
635
+ // item's title. We have to use both because titles are
636
+ // often not unique, and sometimes links aren't unique
637
+ // either (e.g. Bitch (S)HITLIST, Mozilla Dot Org news,
638
+ // some podcasts). But it's rare to have *both* the same
639
+ // title *and* the same link for two different items. So
640
+ // this is about the best we can do.
 
 
641
  else :
642
+ $guid .= '://'.md5($this->item['link'].'/'.$this->item['title']);
643
+ endif;
644
+ endif;
645
+ return $guid;
646
+ } /* SyndicatedPost::guid() */
647
 
648
+ function author () {
649
+ $author = array ();
 
 
 
 
 
 
 
 
 
 
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'];
674
  endif;
 
 
 
 
 
 
 
 
675
  endif;
676
+ else :
677
+ $author['name'] = $this->feed->channel['title'];
678
  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'];
684
+ 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'];
692
+ endif;
693
+
694
+ return $author;
695
+ } /* SyndicatedPost::author() */
696
 
697
  /**
698
+ * SyndicatedPost::isTaggedAs: Test whether a feed item is
699
+ * tagged / categorized with a given string. Case and leading and
700
+ * trailing whitespace are ignored.
701
  *
702
+ * @param string $tag Tag to check for
703
+ *
704
+ * @return bool Whether or not at least one of the categories / tags on
705
+ * $this->item is set to $tag (modulo case and leading and trailing
706
+ * whitespace)
707
  */
708
+ function isTaggedAs ($tag) {
709
+ $desiredTag = strtolower(trim($tag)); // Normalize case and whitespace
710
 
711
+ // Check to see if this is tagged with $tag
712
+ $currentCategory = 'category';
713
+ $currentCategoryNumber = 1;
714
 
715
+ // If we have the new MagpieRSS, the number of category elements
716
+ // on this item is stored under index "category#".
717
+ if (isset($this->item['category#'])) :
718
+ $numberOfCategories = (int) $this->item['category#'];
719
+
720
+ // We REALLY shouldn't have the old and busted MagpieRSS, but in
721
+ // case we do, it doesn't support multiple categories, but there
722
+ // might still be a single value under the "category" index.
723
+ elseif (isset($this->item['category'])) :
724
+ $numberOfCategories = 1;
725
+
726
+ // No standard category or tag elements on this feed item.
727
+ else :
728
+ $numberOfCategories = 0;
729
 
 
 
 
 
 
 
 
 
 
730
  endif;
731
 
732
+ $isSoTagged = false; // Innocent until proven guilty
733
+
734
+ // Loop through category elements; if there are multiple
735
+ // elements, they are indexed as category, category#2,
736
+ // category#3, ... category#N
737
+ while ($currentCategoryNumber <= $numberOfCategories) :
738
+ if ($desiredTag == strtolower(trim($this->item[$currentCategory]))) :
739
+ $isSoTagged = true; // Got it!
740
+ break;
741
+ endif;
742
+
743
+ $currentCategoryNumber += 1;
744
+ $currentCategory = 'category#'.$currentCategoryNumber;
745
+ endwhile;
746
+
747
+ return $isSoTagged;
748
+ } /* SyndicatedPost::isTaggedAs() */
749
 
750
  /**
751
+ * SyndicatedPost::enclosures: returns an array with any enclosures
752
+ * that may be attached to this syndicated item.
753
  *
754
+ * @param string $type If you only want enclosures that match a certain
755
+ * MIME type or group of MIME types, you can limit the enclosures
756
+ * that will be returned to only those with a MIME type which
757
+ * matches this regular expression.
758
+ * @return array
759
  */
760
+ function enclosures ($type = '/.*/') {
761
+ $enclosures = array();
762
+
763
+ if (isset($this->item['enclosure#'])) :
764
+ // Loop through enclosure, enclosure#2, enclosure#3, ....
765
+ for ($i = 1; $i <= $this->item['enclosure#']; $i++) :
766
+ $eid = (($i > 1) ? "#{$id}" : "");
767
+
768
+ // Does it match the type we want?
769
+ if (preg_match($type, $this->item["enclosure{$eid}@type"])) :
770
+ $enclosures[] = array(
771
+ "url" => $this->item["enclosure{$eid}@url"],
772
+ "type" => $this->item["enclosure{$eid}@type"],
773
+ "length" => $this->item["enclosure{$eid}@length"],
774
+ );
775
+ endif;
776
+ endfor;
777
+ endif;
778
+ return $enclosures;
779
+ } /* SyndicatedPost::enclosures() */
780
+
781
+ function comment_link () {
782
+ $url = null;
783
 
784
+ // RSS 2.0 has a standard <comments> element:
785
+ // "<comments> is an optional sub-element of <item>. If present,
786
+ // it is the url of the comments page for the item."
787
+ // <http://cyber.law.harvard.edu/rss/rss.html#ltcommentsgtSubelementOfLtitemgt>
788
+ if (isset($this->item['comments'])) :
789
+ $url = $this->item['comments'];
 
 
 
 
 
790
  endif;
791
+
792
+ // The convention in Atom feeds is to use a standard <link>
793
+ // element with @rel="replies" and @type="text/html".
794
+ // Unfortunately, SimplePie_Item::get_links() allows us to filter
795
+ // by the value of @rel, but not by the value of @type. *sigh*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
796
 
797
+ // Try Atom 1.0 first
798
+ $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link');
799
 
800
+ // Fall back and try Atom 0.3
801
+ if (is_null($linkElements)) : $linkElements = $this->entry->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'); endif;
802
+
803
+ // Now loop through the elements, screening by @rel and @type
804
+ if (is_array($linkElements)) : foreach ($linkElements as $link) :
805
+ $rel = (isset($link['attribs']['']['rel']) ? $link['attribs']['']['rel'] : 'alternate');
806
+ $type = (isset($link['attribs']['']['type']) ? $link['attribs']['']['type'] : NULL);
807
+ $href = (isset($link['attribs']['']['href']) ? $link['attribs']['']['href'] : NULL);
808
+
809
+ if (strtolower($rel)=='replies' and $type=='text/html' and !is_null($href)) :
810
+ $url = $href;
811
+ endif;
812
+ endforeach; endif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
813
 
814
+ return $url;
815
+ }
816
 
817
+ function comment_feed () {
818
+ $feed = null;
819
+
820
+ // Well Formed Web comment feeds extension for RSS 2.0
821
+ // <http://www.sellsbrothers.com/spout/default.aspx?content=archive.htm#exposingRssComments>
822
+ //
823
+ // N.B.: Correct capitalization is wfw:commentRss, but
824
+ // wfw:commentRSS is common in the wild (partly due to a typo in
825
+ // the original spec). In any case, our item array is normalized
826
+ // to all lowercase anyways.
827
+ if (isset($this->item['wfw']['commentrss'])) :
828
+ $feed = $this->item['wfw']['commentrss'];
829
+ endif;
830
 
831
+ // In Atom 1.0, the convention is to use a standard link element
832
+ // with @rel="replies". Sometimes this is also used to pass a
833
+ // link to the human-readable comments page, so we also need to
834
+ // check link/@type for a feed MIME type.
835
+ //
836
+ // Which is why I'm not using the SimplePie_Item::get_links()
837
+ // method here, incidentally: it doesn't allow you to filter by
838
+ // @type. *sigh*
839
+ if (isset($this->item['link_replies'])) :
840
+ // There may be multiple <link rel="replies"> elements; feeds have a feed MIME type
841
+ $N = isset($this->item['link_replies#']) ? $this->item['link_replies#'] : 1;
842
+ for ($i = 1; $i <= $N; $i++) :
843
+ $currentElement = 'link_replies'.(($i > 1) ? '#'.$i : '');
844
+ if (isset($this->item[$currentElement.'@type'])
845
+ and preg_match("\007application/(atom|rss|rdf)\+xml\007i", $this->item[$currentElement.'@type'])) :
846
+ $feed = $this->item[$currentElement];
847
+ endif;
848
+ endfor;
 
849
  endif;
850
+ return $feed;
851
+ } /* SyndicatedPost::comment_feed() */
852
 
853
+ ##################################
854
+ #### BUILT-IN CONTENT FILTERS ####
855
+ ##################################
 
856
 
857
+ var $uri_attrs = array (
858
+ array('a', 'href'),
859
+ array('applet', 'codebase'),
860
+ array('area', 'href'),
861
+ array('blockquote', 'cite'),
862
+ array('body', 'background'),
863
+ array('del', 'cite'),
864
+ array('form', 'action'),
865
+ array('frame', 'longdesc'),
866
+ array('frame', 'src'),
867
+ array('iframe', 'longdesc'),
868
+ array('iframe', 'src'),
869
+ array('head', 'profile'),
870
+ array('img', 'longdesc'),
871
+ array('img', 'src'),
872
+ array('img', 'usemap'),
873
+ array('input', 'src'),
874
+ array('input', 'usemap'),
875
+ array('ins', 'cite'),
876
+ array('link', 'href'),
877
+ array('object', 'classid'),
878
+ array('object', 'codebase'),
879
+ array('object', 'data'),
880
+ array('object', 'usemap'),
881
+ array('q', 'cite'),
882
+ array('script', 'src')
883
+ ); /* var SyndicatedPost::$uri_attrs */
884
 
885
+ var $_base = null;
 
 
 
 
 
886
 
887
+ function resolve_single_relative_uri ($refs) {
888
+ $tag = FeedWordPressHTML::attributeMatch($refs);
889
+ $url = Relative_URI::resolve($tag['value'], $this->_base);
890
+ return $tag['prefix'] . $url . $tag['suffix'];
891
+ } /* function SyndicatedPost::resolve_single_relative_uri() */
892
 
893
+ function resolve_relative_uris ($content, $obj) {
894
+ $set = $obj->link->setting('resolve relative', 'resolve_relative', 'yes');
895
+ if ($set and $set != 'no') :
896
+ // Fallback: if we don't have anything better, use the
897
+ // item link from the feed
898
+ $obj->_base = $obj->item['link']; // Reset the base for resolving relative URIs
899
 
900
+ // What we should do here, properly, is to use
901
+ // SimplePie_Item::get_base() -- but that method is
902
+ // currently broken. Or getting down and dirty in the
903
+ // SimplePie representation of the content tags and
904
+ // grabbing the xml_base member for the content element.
905
+ // Maybe someday...
906
 
907
+ foreach ($obj->uri_attrs as $pair) :
908
+ list($tag, $attr) = $pair;
909
+ $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
910
+ $content = preg_replace_callback (
911
+ $pattern,
912
+ array(&$obj, 'resolve_single_relative_uri'),
913
+ $content
914
+ );
915
+ endforeach;
916
  endif;
 
 
 
 
 
 
 
 
917
 
918
+ return $content;
919
+ } /* function SyndicatedPost::resolve_relative_uris () */
920
 
921
+ var $strip_attrs = array (
922
+ array('[a-z]+', 'target'),
923
+ // array('[a-z]+', 'style'),
924
+ // array('[a-z]+', 'on[a-z]+'),
925
+ );
926
 
927
+ function strip_attribute_from_tag ($refs) {
928
+ $tag = FeedWordPressHTML::attributeMatch($refs);
929
+ return $tag['before_attribute'].$tag['after_attribute'];
930
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
931
 
932
+ function sanitize_content ($content, $obj) {
933
+ # This kind of sucks. I intend to replace it with
934
+ # lib_filter sometime soon.
935
+ foreach ($obj->strip_attrs as $pair):
936
+ list($tag,$attr) = $pair;
937
+ $pattern = FeedWordPressHTML::attributeRegex($tag, $attr);
938
 
939
+ $content = preg_replace_callback (
940
+ $pattern,
941
+ array(&$obj, 'strip_attribute_from_tag'),
942
+ $content
943
+ );
944
+ endforeach;
945
+ return $content;
946
+ } /* SyndicatedPost::sanitize() */
947
 
948
+ #####################
949
+ #### POST STATUS ####
950
+ #####################
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951
 
952
+ /**
953
+ * SyndicatedPost::filtered: check whether or not this post has been
954
+ * screened out by a registered filter.
955
+ *
956
+ * @return bool TRUE iff post has been filtered out by a previous filter
957
+ */
958
+ function filtered () {
959
+ return is_null($this->post);
960
+ } /* SyndicatedPost::filtered() */
961
 
962
+ /**
963
+ * SyndicatedPost::freshness: check whether post is a new post to be
964
+ * inserted, a previously syndicated post that needs to be updated to
965
+ * match the latest revision, or a previously syndicated post that is
966
+ * still up-to-date.
967
+ *
968
+ * @return int A status code representing the freshness of the post
969
+ * 0 = post already syndicated; no update needed
970
+ * 1 = post already syndicated, but needs to be updated to latest
971
+ * 2 = post has not yet been syndicated; needs to be created
972
+ */
973
+ function freshness () {
974
  global $wpdb;
975
 
976
+ if ($this->filtered()) : // This should never happen.
977
+ FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
978
+ endif;
979
+
980
+ if (is_null($this->_freshness)) :
981
+ $guid = $wpdb->escape($this->guid());
 
982
 
983
+ $result = $wpdb->get_row("
984
+ SELECT id, guid, post_modified_gmt
985
+ FROM $wpdb->posts WHERE guid='$guid'
986
+ ");
987
 
988
+ if (!$result) :
989
+ $this->_freshness = 2; // New content
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());
995
+ else :
996
+ $update_hash_changed = false;
997
  endif;
998
+
999
+ preg_match('/([0-9]+)-([0-9]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/', $result->post_modified_gmt, $backref);
1000
+
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));
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
+
1025
+ if ($updated) :
1026
+ $this->_freshness = 1; // Updated content
1027
+ $this->_wp_id = $result->id;
1028
  else :
1029
+ $this->_freshness = 0; // Same old, same old
1030
+ $this->_wp_id = $result->id;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1031
  endif;
1032
  endif;
 
 
 
 
 
 
 
 
 
 
1033
  endif;
1034
+ return $this->_freshness;
1035
+ }
1036
 
1037
+ #################################################
1038
+ #### INTERNAL STORAGE AND MANAGEMENT METHODS ####
1039
+ #################################################
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1040
 
1041
+ function wp_id () {
1042
+ if ($this->filtered()) : // This should never happen.
1043
+ FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
 
 
 
 
 
1044
  endif;
1045
+
1046
+ if (is_null($this->_wp_id) and is_null($this->_freshness)) :
1047
+ $fresh = $this->freshness(); // sets WP DB id in the process
1048
+ endif;
1049
+ return $this->_wp_id;
1050
  }
 
 
1051
 
1052
+ function store () {
1053
+ global $wpdb;
1054
+
1055
+ if ($this->filtered()) : // This should never happen.
1056
+ FeedWordPress::critical_bug('SyndicatedPost', $this, __LINE__);
 
 
 
 
 
 
 
 
 
 
 
1057
  endif;
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
+ );
 
 
 
 
 
 
 
1065
 
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
+ );
1078
 
1079
+ $this->post['post_category'] = $pcats;
1080
+ $this->post['tags_input'] = array_merge($this->post['tags_input'], $ptags);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1081
 
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'
1092
+ )
1093
+ );
1094
+
1095
+ if (count($this->post['post_category']) < 1) :
1096
+ $this->post['post_category'][] = 1; // Default to category 1 ("Uncategorized" / "General") if nothing else
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1097
  endif;
1098
  endif;
 
 
1099
  endif;
1100
 
1101
+ if (!$this->filtered() and $freshness > 0) :
1102
+ unset($this->post['named']);
1103
+ $this->post = apply_filters('syndicated_post', $this->post, $this);
1104
+
1105
+ // Allow for feed-specific syndicated_post filters.
1106
+ $this->post = apply_filters(
1107
+ "syndicated_post_".$this->link->uri(),
1108
+ $this->post,
1109
+ $this
1110
+ );
1111
  endif;
1112
 
1113
+ // Hook in early to make sure these get inserted if at all possible
1114
+ add_action(
1115
+ /*hook=*/ 'transition_post_status',
1116
+ /*callback=*/ array($this, 'add_rss_meta'),
1117
+ /*priority=*/ -10000, /* very early */
1118
+ /*arguments=*/ 3
1119
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1120
 
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);
1125
 
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);
 
 
 
 
 
1131
 
1132
+ $ret = 'updated';
1133
  else :
1134
+ $ret = false;
 
1135
  endif;
1136
 
1137
+ // Remove add_rss_meta hook
1138
+ remove_action(
1139
+ /*hook=*/ 'transition_post_status',
1140
+ /*callback=*/ array($this, 'add_rss_meta')
1141
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1142
 
1143
+ return $ret;
1144
+ } /* function SyndicatedPost::store () */
1145
+
1146
+ function insert_new () {
1147
+ global $wpdb, $wp_db_version;
1148
 
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() */
1225
+
1226
+ function update_existing () {
1227
+ global $wpdb;
1228
+
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);
1242
+
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;
1249
+ endif;
1250
+
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__);
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']);
1301
+ endif;
1302
+ endif;
1303
+ } /* SyndicatedPost::update_existing() */
1304
+
1305
+ /**
1306
+ * SyndicatedPost::normalize_post()
1307
+ *
1308
+ * @param bool $new If true, this post is to be inserted anew. If false, it is an update of an existing post.
1309
+ * @return array A normalized representation of the post ready to be inserted into the database or sent to the WordPress API functions
1310
+ */
1311
+ function normalize_post ($new = true) {
1312
+ global $wpdb;
1313
+
1314
+ $out = array();
1315
+
1316
+ // Why the fuck doesn't wp_insert_post already do this?
1317
+ foreach ($this->post as $key => $value) :
1318
+ if (is_string($value)) :
1319
+ $out[$key] = $wpdb->escape($value);
1320
+ else :
1321
+ $out[$key] = $value;
1322
+ endif;
1323
+ endforeach;
1324
+
1325
+ if (strlen($out['post_title'].$out['post_content'].$out['post_excerpt']) == 0) :
1326
+ // FIXME: Option for filtering out empty posts
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;
1335
+
1336
+ return $out;
1337
+ }
1338
+
1339
+ /**
1340
+ * SyndicatedPost::validate_post_id()
1341
+ *
1342
+ * @param array $dbpost An array representing the post we attempted to insert or update
1343
+ * @param mixed $ns A string or array representing the namespace (class, method) whence this method was called.
1344
+ */
1345
+ function validate_post_id ($dbpost, $ns) {
1346
+ if (is_array($ns)) : $ns = implode('::', $ns);
1347
+ else : $ns = (string) $ns; endif;
1348
+
1349
+ // This should never happen.
1350
+ if (!is_numeric($this->_wp_id) or ($this->_wp_id == 0)) :
1351
+ FeedWordPress::critical_bug(
1352
+ /*name=*/ $ns.'::_wp_id',
1353
+ /*var =*/ array(
1354
+ "\$this->_wp_id" => $this->_wp_id,
1355
+ "\$dbpost" => $dbpost,
1356
+ "\$this" => $this
1357
+ ),
1358
+ /*line # =*/ __LINE__
1359
+ );
1360
+ endif;
1361
+ } /* SyndicatedPost::validate_post_id() */
1362
+
1363
+ /**
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.
1367
+ *
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
1372
+ * active user ID if the process is running with a user logged in, or
1373
+ * = #0 if there is no user logged in). This results in bogus authorship
1374
+ * data for revisions that are syndicated from off the feed, unless we
1375
+ * use a ridiculous kludge like this to end-run the munging of meta-data
1376
+ * by _wp_put_post_revision.
1377
+ *
1378
+ * @param int $revision_id The revision ID to fix up meta-data
1379
+ */
1380
+ function fix_revision_meta ($revision_id) {
1381
+ global $wpdb;
1382
+
1383
+ $post_author = (int) $this->post['post_author'];
1384
+
1385
+ $revision_id = (int) $revision_id;
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
+ ");
1391
+ } /* SyndicatedPost::fix_revision_meta () */
1392
+
1393
+ /**
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.
1409
+ *
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.
1412
+ */
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) {
1428
+ global $wpdb;
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
+ ");
1440
+
1441
+ foreach ( $this->post['meta'] as $key => $values ) :
1442
+
1443
+ $eKey = $wpdb->escape($key);
1444
+
1445
+ // If this is an update, clear out the old
1446
+ // values to avoid duplication.
1447
+ $result = $wpdb->query("
1448
+ DELETE FROM $wpdb->postmeta
1449
+ WHERE post_id='$postId' AND meta_key='$eKey'
1450
+ ");
1451
+
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);
1456
+ endforeach;
1457
  endforeach;
1458
  endif;
1459
+ } /* SyndicatedPost::add_rss_meta () */
1460
+
1461
+ // SyndicatedPost::author_id (): get the ID for an author name from
1462
+ // the feed. Create the author if necessary.
1463
+ function author_id ($unfamiliar_author = 'create') {
1464
+ global $wpdb;
1465
+
1466
+ $a = $this->author();
1467
+ $author = $a['name'];
1468
+ $email = (isset($a['email']) ? $a['email'] : NULL);
1469
+ $url = (isset($a['uri']) ? $a['uri'] : NULL);
1470
+
1471
+ $match_author_by_email = !('yes' == get_option("feedwordpress_do_not_match_author_by_email"));
1472
+ if ($match_author_by_email and !FeedWordPress::is_null_email($email)) :
1473
+ $test_email = $email;
1474
+ else :
1475
+ $test_email = NULL;
1476
+ endif;
1477
+
1478
+ // Never can be too careful...
1479
+ $login = sanitize_user($author, /*strict=*/ true);
1480
+ $login = apply_filters('pre_user_login', $login);
1481
+
1482
+ $nice_author = sanitize_title($author);
1483
+ $nice_author = apply_filters('pre_user_nicename', $nice_author);
1484
+
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);
1490
+
1491
+ // Check for an existing author rule....
1492
+ if (isset($this->link->settings['map authors']['name'][strtolower(trim($author))])) :
1493
+ $author_rule = $this->link->settings['map authors']['name'][strtolower(trim($author))];
1494
+ else :
1495
+ $author_rule = NULL;
1496
+ endif;
1497
+
1498
+ // User name is mapped to a particular author. If that author ID exists, use it.
1499
+ if (is_numeric($author_rule) and get_userdata((int) $author_rule)) :
1500
+ $id = (int) $author_rule;
1501
+
1502
+ // User name is filtered out
1503
+ elseif ('filter' == $author_rule) :
1504
+ $id = NULL;
1505
 
1506
+ else :
1507
+ // Check the database for an existing author record that might fit
1508
 
1509
+ // First try the user core data table.
1510
+ $id = $wpdb->get_var(
1511
+ "SELECT ID FROM $wpdb->users
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
+
1521
+ // If that fails, look for aliases in the user meta data table
1522
+ if (is_null($id)) :
1523
+ $id = $wpdb->get_var(
1524
+ "SELECT user_id FROM $wpdb->usermeta
1525
+ WHERE
1526
+ (meta_key = 'description' AND TRIM(LCASE(meta_value)) = TRIM(LCASE('$author')))
1527
+ OR (
1528
+ meta_key = 'description'
1529
+ AND TRIM(LCASE(meta_value))
1530
+ RLIKE CONCAT(
1531
+ '(^|\\n)a\\.?k\\.?a\\.?( |\\t)*:?( |\\t)*',
1532
+ TRIM(LCASE('$reg_author')),
1533
+ '( |\\t|\\r)*(\\n|\$)'
1534
+ )
1535
+ )
1536
+ ");
1537
+ endif;
1538
 
1539
+ // ... if you don't find one, then do what you need to do
1540
+ if (is_null($id)) :
1541
+ if ($unfamiliar_author === 'create') :
1542
+ $userdata = array();
1543
 
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.
1547
+ if (strlen($email) == 0 or FeedWordPress::is_null_email($email)) :
1548
+ $url = parse_url($this->feed->channel['link']);
1549
+ $email = $nice_author.'@'.$url['host'];
1550
+ endif;
1551
 
1552
+ #-- user table data
1553
+ $userdata['ID'] = NULL; // new user
1554
+ $userdata['user_login'] = $login;
1555
+ $userdata['user_nicename'] = $nice_author;
1556
+ $userdata['user_pass'] = substr(md5(uniqid(microtime())), 0, 6); // just something random to lock it up
1557
+ $userdata['user_email'] = $email;
1558
+ $userdata['user_url'] = $url;
1559
+ $userdata['display_name'] = $author;
1560
+
1561
+ $id = wp_insert_user($userdata);
1562
+ elseif (is_numeric($unfamiliar_author) and get_userdata((int) $unfamiliar_author)) :
1563
+ $id = (int) $unfamiliar_author;
1564
+ elseif ($unfamiliar_author === 'default') :
1565
+ $id = 1;
1566
+ endif;
1567
+ endif;
1568
+ endif;
1569
+
1570
+ if ($id) :
1571
+ $this->link->settings['map authors']['name'][strtolower(trim($author))] = $id;
1572
+ endif;
1573
+ return $id;
1574
+ } // function SyndicatedPost::author_id ()
1575
+
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;
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);
1669
+ endif;
1670
+
1671
+ if ($tags_too) : $ret = array($cat_ids, $tags);
1672
+ else : $ret = $cat_ids;
1673
+ endif;
1674
+
1675
+ return $ret;
1676
+ } // function SyndicatedPost::category_ids ()
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
+
1693
+ } /* class SyndicatedPost */
1694
 
syndication.php CHANGED
@@ -229,7 +229,7 @@ function fwp_dashboard_update_if_requested () {
229
 
230
  shuffle($update_set); // randomize order for load balancing purposes...
231
  if ($fwp_update_invoke != 'get' and count($update_set) > 0) : // Only do things with side-effects for HTTP POST or command line
232
- $feedwordpress =& new FeedWordPress;
233
  add_action('feedwordpress_check_feed', 'update_feeds_mention');
234
  add_action('feedwordpress_check_feed_complete', 'update_feeds_finish', 10, 3);
235
 
@@ -409,7 +409,7 @@ function fwp_syndication_manage_page_links_box ($object = NULL, $box = NULL) {
409
  endif;
410
  ?>
411
  <td>
412
- <strong><a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/feeds-page.php&amp;link_id=<?php echo $link->link_id; ?>"><?php echo wp_specialchars($link->link_name, 'both'); ?></a></strong>
413
  <div class="row-actions"><div><strong>Settings &gt;</strong>
414
  <a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/feeds-page.php&amp;link_id=<?php echo $link->link_id; ?>"><?php _e('Feed'); ?></a>
415
  | <a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/posts-page.php&amp;link_id=<?php echo $link->link_id; ?>"><?php _e('Posts'); ?></a>
@@ -418,29 +418,18 @@ function fwp_syndication_manage_page_links_box ($object = NULL, $box = NULL) {
418
  <div><strong>Actions &gt;</strong>
419
  <a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/<?php echo basename(__FILE__); ?>&amp;link_id=<?php echo $link->link_id; ?>&amp;action=feedfinder"><?php echo $caption; ?></a>
420
  | <a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/<?php echo basename(__FILE__); ?>&amp;link_id=<?php echo $link->link_id; ?>&amp;action=Unsubscribe"><?php _e('Unsubscribe'); ?></a>
421
- | <a href="<?php echo wp_specialchars($link->link_url, 'both'); ?>"><?php _e('View')?></a></div>
422
  </div>
423
  </td>
424
- <?php
425
- if (strlen($link->link_rss) > 0):
426
- $uri_bits = parse_url($link->link_rss);
427
- $uri_bits['host'] = preg_replace('/^www\./i', '', $uri_bits['host']);
428
- $display_uri =
429
- (isset($uri_bits['user'])?$uri_bits['user'].'@':'')
430
- .(isset($uri_bits['host'])?$uri_bits['host']:'')
431
- .(isset($uri_bits['port'])?':'.$uri_bits['port']:'')
432
- .(isset($uri_bits['path'])?$uri_bits['path']:'')
433
- .(isset($uri_bits['query'])?'?'.$uri_bits['query']:'');
434
- if (strlen($display_uri) > 32) : $display_uri = substr($display_uri, 0, 32).'&#8230;'; endif;
435
- ?>
436
- <td><a href="<?php echo wp_specialchars($link->link_rss, 'both'); ?>"><?php echo wp_specialchars($display_uri, 'both'); ?></a></td>
437
  <?php else: ?>
438
  <td style="background-color:#FFFFD0"><p><strong>no
439
  feed assigned</strong></p></td>
440
  <?php endif; ?>
441
 
442
  <td><?php
443
- $sLink =& new SyndicatedLink($link->link_id);
444
  if (isset($sLink->settings['update/last'])) :
445
  print fwp_time_elapsed($sLink->settings['update/last']);
446
  else :
@@ -459,6 +448,7 @@ function fwp_syndication_manage_page_links_box ($object = NULL, $box = NULL) {
459
  else:
460
  echo "as soon as possible";
461
  endif;
 
462
  print "</div>";
463
  ?>
464
  </td>
@@ -506,13 +496,15 @@ function fwp_switchfeed_page () {
506
  if (isset($fwp_post['save_link_id']) and ($fwp_post['save_link_id']=='*')) :
507
  $changed = true;
508
  $link_id = FeedWordPress::syndicate_link($fwp_post['feed_title'], $fwp_post['feed_link'], $fwp_post['feed']);
509
- if ($link_id): ?>
510
- <div class="updated"><p><a href="<?php print $fwp_post['feed_link']; ?>"><?php print wp_specialchars($fwp_post['feed_title'], 'both'); ?></a>
 
 
511
  has been added as a contributing site, using the feed at
512
- &lt;<a href="<?php print $fwp_post['feed']; ?>"><?php print wp_specialchars($fwp_post['feed'], 'both'); ?></a>&gt;.
513
  | <a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/feeds-page.php&amp;link_id=<?php print $link_id; ?>">Configure settings</a>.</p></div>
514
  <?php else: ?>
515
- <div class="updated"><p>There was a problem adding the feed. [SQL: <?php echo wp_specialchars(mysql_error(), 'both'); ?>]</p></div>
516
  <?php endif;
517
  elseif (isset($fwp_post['save_link_id'])):
518
  $existingLink = new SyndicatedLink($fwp_post['save_link_id']);
@@ -522,13 +514,17 @@ has been added as a contributing site, using the feed at
522
  $home = $existingLink->homepage(/*from feed=*/ false);
523
  $name = $existingLink->name(/*from feed=*/ false);
524
  ?>
525
- <div class="updated"><p>Feed for <a href="<?php echo wp_specialchars($home, 'both'); ?>"><?php echo wp_specialchars($name, 'both'); ?></a>
526
- updated to &lt;<a href="<?php echo wp_specialchars($fwp_post['feed'], 'both'); ?>"><?php echo wp_specialchars($fwp_post['feed'], 'both'); ?></a>&gt;.</p></div>
527
  <?php
528
  endif;
529
  endif;
530
  endif;
531
 
 
 
 
 
532
  if (!$changed) :
533
  ?>
534
  <div class="updated"><p>Nothing was changed.</p></div>
@@ -641,10 +637,10 @@ function fwp_multidelete_page () {
641
 
642
  <h2>Unsubscribe from Syndicated Links:</h2>
643
  <?php foreach ($targets as $link) :
644
- $link_url = wp_specialchars($link->link_url, 1);
645
- $link_name = wp_specialchars($link->link_name, 1);
646
- $link_description = wp_specialchars($link->link_description, 'both');
647
- $link_rss = wp_specialchars($link->link_rss, 'both');
648
  ?>
649
  <fieldset>
650
  <legend><?php echo $link_name; ?></legend>
229
 
230
  shuffle($update_set); // randomize order for load balancing purposes...
231
  if ($fwp_update_invoke != 'get' and count($update_set) > 0) : // Only do things with side-effects for HTTP POST or command line
232
+ $feedwordpress = new FeedWordPress;
233
  add_action('feedwordpress_check_feed', 'update_feeds_mention');
234
  add_action('feedwordpress_check_feed_complete', 'update_feeds_finish', 10, 3);
235
 
409
  endif;
410
  ?>
411
  <td>
412
+ <strong><a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/feeds-page.php&amp;link_id=<?php echo $link->link_id; ?>"><?php echo esc_html($link->link_name); ?></a></strong>
413
  <div class="row-actions"><div><strong>Settings &gt;</strong>
414
  <a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/feeds-page.php&amp;link_id=<?php echo $link->link_id; ?>"><?php _e('Feed'); ?></a>
415
  | <a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/posts-page.php&amp;link_id=<?php echo $link->link_id; ?>"><?php _e('Posts'); ?></a>
418
  <div><strong>Actions &gt;</strong>
419
  <a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/<?php echo basename(__FILE__); ?>&amp;link_id=<?php echo $link->link_id; ?>&amp;action=feedfinder"><?php echo $caption; ?></a>
420
  | <a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/<?php echo basename(__FILE__); ?>&amp;link_id=<?php echo $link->link_id; ?>&amp;action=Unsubscribe"><?php _e('Unsubscribe'); ?></a>
421
+ | <a href="<?php echo esc_html($link->link_url); ?>"><?php _e('View')?></a></div>
422
  </div>
423
  </td>
424
+ <?php if (strlen($link->link_rss) > 0): ?>
425
+ <td><a href="<?php echo esc_html($link->link_rss); ?>"><?php echo esc_html(feedwordpress_display_url($link->link_rss, 32)); ?></a></td>
 
 
 
 
 
 
 
 
 
 
 
426
  <?php else: ?>
427
  <td style="background-color:#FFFFD0"><p><strong>no
428
  feed assigned</strong></p></td>
429
  <?php endif; ?>
430
 
431
  <td><?php
432
+ $sLink = new SyndicatedLink($link->link_id);
433
  if (isset($sLink->settings['update/last'])) :
434
  print fwp_time_elapsed($sLink->settings['update/last']);
435
  else :
448
  else:
449
  echo "as soon as possible";
450
  endif;
451
+ unset($sLink);
452
  print "</div>";
453
  ?>
454
  </td>
496
  if (isset($fwp_post['save_link_id']) and ($fwp_post['save_link_id']=='*')) :
497
  $changed = true;
498
  $link_id = FeedWordPress::syndicate_link($fwp_post['feed_title'], $fwp_post['feed_link'], $fwp_post['feed']);
499
+ if ($link_id):
500
+ $existingLink = new SyndicatedLink($link_id);
501
+ ?>
502
+ <div class="updated"><p><a href="<?php print $fwp_post['feed_link']; ?>"><?php print esc_html($fwp_post['feed_title']); ?></a>
503
  has been added as a contributing site, using the feed at
504
+ &lt;<a href="<?php print $fwp_post['feed']; ?>"><?php print esc_html($fwp_post['feed']); ?></a>&gt;.
505
  | <a href="admin.php?page=<?php print $GLOBALS['fwp_path'] ?>/feeds-page.php&amp;link_id=<?php print $link_id; ?>">Configure settings</a>.</p></div>
506
  <?php else: ?>
507
+ <div class="updated"><p>There was a problem adding the feed. [SQL: <?php echo esc_html(mysql_error()); ?>]</p></div>
508
  <?php endif;
509
  elseif (isset($fwp_post['save_link_id'])):
510
  $existingLink = new SyndicatedLink($fwp_post['save_link_id']);
514
  $home = $existingLink->homepage(/*from feed=*/ false);
515
  $name = $existingLink->name(/*from feed=*/ false);
516
  ?>
517
+ <div class="updated"><p>Feed for <a href="<?php echo esc_html($home); ?>"><?php echo esc_html($name); ?></a>
518
+ updated to &lt;<a href="<?php echo esc_html($fwp_post['feed']); ?>"><?php echo esc_html($fwp_post['feed']); ?></a>&gt;.</p></div>
519
  <?php
520
  endif;
521
  endif;
522
  endif;
523
 
524
+ if (isset($existingLink)) :
525
+ do_action('feedwordpress_admin_switchfeed', $fwp_post['feed'], $existingLink);
526
+ endif;
527
+
528
  if (!$changed) :
529
  ?>
530
  <div class="updated"><p>Nothing was changed.</p></div>
637
 
638
  <h2>Unsubscribe from Syndicated Links:</h2>
639
  <?php foreach ($targets as $link) :
640
+ $link_url = esc_html($link->link_url);
641
+ $link_name = esc_html($link->link_name);
642
+ $link_description = esc_html($link->link_description);
643
+ $link_rss = esc_html($link->link_rss);
644
  ?>
645
  <fieldset>
646
  <legend><?php echo $link_name; ?></legend>
updatedpostscontrol.class.php CHANGED
@@ -18,7 +18,7 @@ class UpdatedPostsControl {
18
  else :
19
  $aFeed = 'a syndicated feed';
20
  $freeze_updates = $global_freeze_updates;
21
- endif;
22
  ?>
23
  <tr>
24
  <th scope="row"><?php _e('Updated posts:') ?></th>
18
  else :
19
  $aFeed = 'a syndicated feed';
20
  $freeze_updates = $global_freeze_updates;
21
+ endif;
22
  ?>
23
  <tr>
24
  <th scope="row"><?php _e('Updated posts:') ?></th>