diff --git a/assets/css/feedwordpress-elements.css b/assets/css/feedwordpress-elements.css
index 9a0aa30..283bfd4 100644
--- a/assets/css/feedwordpress-elements.css
+++ b/assets/css/feedwordpress-elements.css
@@ -396,11 +396,16 @@ table.twofer td.secondary {
/* These icons have been deprecated since 2013 or so;
see https://core.trac.wordpress.org/ticket/26119.
Nevertheless, we need proper sizing for the 'new' SVG.
- (gwyneth 20210717) */
+ (gwyneth 20210717)
+ Also: added class for dashboard widget plus sign, which was missing.
+ (gwyneth 20250111)
+*/
.feedwordpress-admin .icon32,
-.feedwordpress-admin .icon32 img {
+.feedwordpress-admin .icon32 img,
+input.feedwordpress-admin.icon32 {
width: 32px;
height: 32px;
+ display: inline;
}
#feedwordpress-admin-syndication .heads-up {
diff --git a/assets/images/plus.png b/assets/images/plus.png
new file mode 100644
index 0000000..09d2c36
Binary files /dev/null and b/assets/images/plus.png differ
diff --git a/assets/images/plus.svg b/assets/images/plus.svg
new file mode 100644
index 0000000..826fb1f
--- /dev/null
+++ b/assets/images/plus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/plus@2x.png b/assets/images/plus@2x.png
new file mode 100644
index 0000000..5ce0e0c
Binary files /dev/null and b/assets/images/plus@2x.png differ
diff --git a/extend/SimplePie/default/feedwordpie_parser.class.php b/extend/SimplePie/default/feedwordpie_parser.class.php
index 0f32e36..33b413d 100644
--- a/extend/SimplePie/default/feedwordpie_parser.class.php
+++ b/extend/SimplePie/default/feedwordpie_parser.class.php
@@ -1,18 +1,569 @@
namespace = array('');
+ $this->element = array('');
+ $this->xml_base = array('');
+ $this->xml_base_explicit = array(false);
+ $this->xml_lang = array('');
+ $this->data = array();
+ $this->datas = array(array());
+ $this->current_xhtml_construct = -1;
+ $this->xmlns_stack = array();
+ $this->xmlns_current = array();
+
+ // reset libxml parser
+ xml_parser_free($xml);
+
+ $xml = xml_parser_create_ns($this->encoding, $this->separator);
+ xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1);
+ xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0);
+ xml_set_object($xml, $this);
+ xml_set_character_data_handler($xml, 'cdata');
+ xml_set_element_handler($xml, 'tag_open', 'tag_close');
+ xml_set_start_namespace_decl_handler($xml, 'start_xmlns');
+ }
+
+ /**
+ * Parses a SimplePie object.
+ *
+ * @param string $data Data to be encoded.
+ * @param string $encoding Encoding type (UTF-8 preferred).
+ * @param string $url URL to contact.
+ *
+ * @return boolean Parse succeeded or not.
+ */
+ public function parse (string &$data, string $encoding, string $url = ''')
+ {
+
$data = apply_filters('feedwordpress_parser_parse', $data, $encoding, $this, $url);
- return parent::parse( $data, $encoding, $url );
+ if (class_exists('DOMXpath') && function_exists('Mf2\parse')) {
+ $doc = new DOMDocument();
+ @$doc->loadHTML($data);
+ $xpath = new DOMXpath($doc);
+ // Check for both h-feed and h-entry, as both a feed with no entries
+ // and a list of entries without an h-feed wrapper are both valid.
+ $query = '//*[contains(concat(" ", @class, " "), " h-feed ") or '.
+ 'contains(concat(" ", @class, " "), " h-entry ")]';
+ $result = $xpath->query($query);
+ if ($result->length !== 0) {
+ return $this->parse_microformats($data, $url);
+ }
+ }
+
+ // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character
+ if (strtoupper($encoding) === 'US-ASCII')
+ {
+ $this->encoding = 'UTF-8';
+ }
+ else
+ {
+ $this->encoding = $encoding;
+ }
+
+ // Strip BOM:
+ // UTF-32 Big Endian BOM
+ if (substr($data, 0, 4) === "\x00\x00\xFE\xFF")
+ {
+ $data = substr($data, 4);
+ }
+ // UTF-32 Little Endian BOM
+ elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00")
+ {
+ $data = substr($data, 4);
+ }
+ // UTF-16 Big Endian BOM
+ elseif (substr($data, 0, 2) === "\xFE\xFF")
+ {
+ $data = substr($data, 2);
+ }
+ // UTF-16 Little Endian BOM
+ elseif (substr($data, 0, 2) === "\xFF\xFE")
+ {
+ $data = substr($data, 2);
+ }
+ // UTF-8 BOM
+ elseif (substr($data, 0, 3) === "\xEF\xBB\xBF")
+ {
+ $data = substr($data, 3);
+ }
+
+ if (substr($data, 0, 5) === '')) !== false)
+ {
+ $declaration = $this->registry->create('XML_Declaration_Parser', array(substr($data, 5, $pos - 5)));
+ if ($declaration->parse())
+ {
+ $data = substr($data, $pos + 2);
+ $data = 'version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' ."\n". $this->declare_html_entities() . $data;
+ }
+ else
+ {
+ $this->error_string = 'SimplePie bug! Please report this!';
+ return false;
+ }
+ }
+
+ $return = true;
+
+ static $xml_is_sane = null;
+ if ($xml_is_sane === null)
+ {
+ $parser_check = xml_parser_create();
+ // PHP 8.X now returns an object, not a resource, which is set
+ // to false if something went wrong. (gwyneth 20240208)
+ if (false === $parser_check)
+ {
+ return false;
+ }
+ xml_parse_into_struct($parser_check, '&', $values);
+ xml_parser_free($parser_check);
+ $xml_is_sane = isset($values[0]['value']);
+ }
+
+ // Create the parser
+ if ($xml_is_sane)
+ {
+ $xml = xml_parser_create_ns($this->encoding, $this->separator);
+ // See comment above.
+ if (false === $xml)
+ {
+ return false;
+ }
+ xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1);
+ xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0);
+ xml_set_object($xml, $this);
+ xml_set_character_data_handler($xml, 'cdata');
+ xml_set_element_handler($xml, 'tag_open', 'tag_close');
+
+ // Parse!
+ $results = $this->do_xml_parse_attempt($xml, $data);
+ $parseResults = $results[0];
+ $data = $results[1];
+
+ if ( ! $parseResults) {
+ $this->error_code = xml_get_error_code($xml);
+ $this->error_string = xml_error_string($this->error_code);
+ $return = false;
+ }
+ $this->current_line = xml_get_current_line_number($xml);
+ $this->current_column = xml_get_current_column_number($xml);
+ $this->current_byte = xml_get_current_byte_index($xml);
+ xml_parser_free($xml);
+ return $return;
+ }
+
+ libxml_clear_errors();
+ $xml = new XMLReader();
+ $xml->xml($data);
+ while (@$xml->read())
+ {
+ switch ($xml->nodeType)
+ {
+
+ case constant('XMLReader::END_ELEMENT'):
+ if ($xml->namespaceURI !== '')
+ {
+ $tagName = $xml->namespaceURI . $this->separator . $xml->localName;
+ }
+ else
+ {
+ $tagName = $xml->localName;
+ }
+ $this->tag_close(null, $tagName);
+ break;
+ case constant('XMLReader::ELEMENT'):
+ $empty = $xml->isEmptyElement;
+ if ($xml->namespaceURI !== '')
+ {
+ $tagName = $xml->namespaceURI . $this->separator . $xml->localName;
+ }
+ else
+ {
+ $tagName = $xml->localName;
+ }
+ $attributes = array();
+ while ($xml->moveToNextAttribute())
+ {
+ if ($xml->namespaceURI !== '')
+ {
+ $attrName = $xml->namespaceURI . $this->separator . $xml->localName;
+ }
+ else
+ {
+ $attrName = $xml->localName;
+ }
+ $attributes[$attrName] = $xml->value;
+ }
+
+ $this->do_scan_attributes_namespaces($attributes);
+
+ $this->tag_open(null, $tagName, $attributes);
+ if ($empty)
+ {
+ $this->tag_close(null, $tagName);
+ }
+ break;
+ case constant('XMLReader::TEXT'):
+ return parent::parse( $data, $encoding, $url );
} /* FeedWordPie_Parser::parse() */
-
+
+ public function do_xml_parse_attempt ($xml, $data) {
+ xml_set_start_namespace_decl_handler($xml, 'start_xmlns');
+
+ // Parse!
+ $parseResults = xml_parse($xml, $data, true);
+ $endOfJunk = strpos($data, ' 0) :
+ // There is some junk before the feed prolog. Try to get rid of it.
+ $data = substr($data, $endOfJunk);
+ $data = trim($data);
+ $this->reset_parser($xml);
+
+ $parseResults = xml_parse($xml, $data, true);
+ endif;
+
+ $badEntity = (xml_get_error_code($xml) == 26);
+ if ( ! $parseResults and $badEntity) :
+ // There was an entity that libxml couldn't understand.
+ // Chances are, it was a stray HTML entity. So let's try
+ // converting all the named HTML entities to numeric XML
+ // entities and starting over.
+ $data = $this->html_convert_entities($data);
+ $this->reset_parser($xml);
+
+ $parseResults = xml_parse($xml, $data, true);
+ endif;
+
+ $result = array(
+ $parseResults,
+ $data
+ );
+ return $result;
+
+ }
+
+ public function do_scan_attributes_namespaces ($attributes) {
+ foreach ($attributes as $attr => $value) :
+ list($ns, $local) = $this->split_ns($attr);
+ if ($ns=='http://www.w3.org/2000/xmlns/') :
+ if ('xmlns' == $local) : $local = false; endif;
+ $this->start_xmlns(null, $local, $value);
+ endif;
+ endforeach;
+ }
+
+ var $xmlns_stack = array();
+ var $xmlns_current = array();
+ function tag_open ($parser, $tag, $attributes) {
+ $ret = parent::tag_open($parser, $tag, $attributes);
+ if ($this->current_xhtml_construct < 0) :
+ $this->data['xmlns'] = $this->xmlns_current;
+ $this->xmlns_stack[] = $this->xmlns_current;
+ endif;
+ return $ret;
+ }
+
+ function tag_close($parser, $tag) {
+ if ($this->current_xhtml_construct < 0) :
+ $this->xmlns_current = array_pop($this->xmlns_stack);
+ endif;
+ $ret = parent::tag_close($parser, $tag);
+ return $ret;
+ }
+
+ function start_xmlns ($parser, $prefix, $uri) {
+ if ( ! $prefix) :
+ $prefix = '';
+ endif;
+ if ($this->current_xhtml_construct < 0) :
+ $this->xmlns_current[$prefix] = $uri;
+ endif;
+
+ return true;
+ } /* FeedWordPie_Parser::start_xmlns() */
+
+ /* html_convert_entities($string) -- convert named HTML entities to
+ * XML-compatible numeric entities. Adapted from code by @inanimatt:
+ * https://gist.github.com/inanimatt/879249
+ */
+ public function html_convert_entities($string) {
+ return preg_replace_callback('/&([a-zA-Z][a-zA-Z0-9]+);/S',
+ array($this, 'convert_entity'), $string);
+ }
+
+ /* Swap HTML named entity with its numeric equivalent. If the entity
+ * isn't in the lookup table, this function returns a blank, which
+ * destroys the character in the output - this is probably the
+ * desired behaviour when producing XML. Adapted from code by @inanimatt:
+ * https://gist.github.com/inanimatt/879249
+ */
+ public function convert_entity($matches) {
+ static $table = array(
+ 'quot' => '"',
+ 'amp' => '&',
+ 'lt' => '<',
+ 'gt' => '>',
+ 'OElig' => 'Œ',
+ 'oelig' => 'œ',
+ 'Scaron' => 'Š',
+ 'scaron' => 'š',
+ 'Yuml' => 'Ÿ',
+ 'circ' => 'ˆ',
+ 'tilde' => '˜',
+ 'ensp' => ' ',
+ 'emsp' => ' ',
+ 'thinsp' => ' ',
+ 'zwnj' => '',
+ 'zwj' => '',
+ 'lrm' => '',
+ 'rlm' => '',
+ 'ndash' => '–',
+ 'mdash' => '—',
+ 'lsquo' => '‘',
+ 'rsquo' => '’',
+ 'sbquo' => '‚',
+ 'ldquo' => '“',
+ 'rdquo' => '”',
+ 'bdquo' => '„',
+ 'dagger' => '†',
+ 'Dagger' => '‡',
+ 'permil' => '‰',
+ 'lsaquo' => '‹',
+ 'rsaquo' => '›',
+ 'euro' => '€',
+ 'fnof' => 'ƒ',
+ 'Alpha' => 'Α',
+ 'Beta' => 'Β',
+ 'Gamma' => 'Γ',
+ 'Delta' => 'Δ',
+ 'Epsilon' => 'Ε',
+ 'Zeta' => 'Ζ',
+ 'Eta' => 'Η',
+ 'Theta' => 'Θ',
+ 'Iota' => 'Ι',
+ 'Kappa' => 'Κ',
+ 'Lambda' => 'Λ',
+ 'Mu' => 'Μ',
+ 'Nu' => 'Ν',
+ 'Xi' => 'Ξ',
+ 'Omicron' => 'Ο',
+ 'Pi' => 'Π',
+ 'Rho' => 'Ρ',
+ 'Sigma' => 'Σ',
+ 'Tau' => 'Τ',
+ 'Upsilon' => 'Υ',
+ 'Phi' => 'Φ',
+ 'Chi' => 'Χ',
+ 'Psi' => 'Ψ',
+ 'Omega' => 'Ω',
+ 'alpha' => 'α',
+ 'beta' => 'β',
+ 'gamma' => 'γ',
+ 'delta' => 'δ',
+ 'epsilon' => 'ε',
+ 'zeta' => 'ζ',
+ 'eta' => 'η',
+ 'theta' => 'θ',
+ 'iota' => 'ι',
+ 'kappa' => 'κ',
+ 'lambda' => 'λ',
+ 'mu' => 'μ',
+ 'nu' => 'ν',
+ 'xi' => 'ξ',
+ 'omicron' => 'ο',
+ 'pi' => 'π',
+ 'rho' => 'ρ',
+ 'sigmaf' => 'ς',
+ 'sigma' => 'σ',
+ 'tau' => 'τ',
+ 'upsilon' => 'υ',
+ 'phi' => 'φ',
+ 'chi' => 'χ',
+ 'psi' => 'ψ',
+ 'omega' => 'ω',
+ 'thetasym' => 'ϑ',
+ 'upsih' => 'ϒ',
+ 'piv' => 'ϖ',
+ 'bull' => '•',
+ 'hellip' => '…',
+ 'prime' => '′',
+ 'Prime' => '″',
+ 'oline' => '‾',
+ 'frasl' => '⁄',
+ 'weierp' => '℘',
+ 'image' => 'ℑ',
+ 'real' => 'ℜ',
+ 'trade' => '™',
+ 'alefsym' => 'ℵ',
+ 'larr' => '←',
+ 'uarr' => '↑',
+ 'rarr' => '→',
+ 'darr' => '↓',
+ 'harr' => '↔',
+ 'crarr' => '↵',
+ 'lArr' => '⇐',
+ 'uArr' => '⇑',
+ 'rArr' => '⇒',
+ 'dArr' => '⇓',
+ 'hArr' => '⇔',
+ 'forall' => '∀',
+ 'part' => '∂',
+ 'exist' => '∃',
+ 'empty' => '∅',
+ 'nabla' => '∇',
+ 'isin' => '∈',
+ 'notin' => '∉',
+ 'ni' => '∋',
+ 'prod' => '∏',
+ 'sum' => '∑',
+ 'minus' => '−',
+ 'lowast' => '∗',
+ 'radic' => '√',
+ 'prop' => '∝',
+ 'infin' => '∞',
+ 'ang' => '∠',
+ 'and' => '∧',
+ 'or' => '∨',
+ 'cap' => '∩',
+ 'cup' => '∪',
+ 'int' => '∫',
+ 'there4' => '∴',
+ 'sim' => '∼',
+ 'cong' => '≅',
+ 'asymp' => '≈',
+ 'ne' => '≠',
+ 'equiv' => '≡',
+ 'le' => '≤',
+ 'ge' => '≥',
+ 'sub' => '⊂',
+ 'sup' => '⊃',
+ 'nsub' => '⊄',
+ 'sube' => '⊆',
+ 'supe' => '⊇',
+ 'oplus' => '⊕',
+ 'otimes' => '⊗',
+ 'perp' => '⊥',
+ 'sdot' => '⋅',
+ 'lceil' => '⌈',
+ 'rceil' => '⌉',
+ 'lfloor' => '⌊',
+ 'rfloor' => '⌋',
+ 'lang' => '〈',
+ 'rang' => '〉',
+ 'loz' => '◊',
+ 'spades' => '♠',
+ 'clubs' => '♣',
+ 'hearts' => '♥',
+ 'diams' => '♦',
+ 'nbsp' => ' ',
+ 'iexcl' => '¡',
+ 'cent' => '¢',
+ 'pound' => '£',
+ 'curren' => '¤',
+ 'yen' => '¥',
+ 'brvbar' => '¦',
+ 'sect' => '§',
+ 'uml' => '¨',
+ 'copy' => '©',
+ 'ordf' => 'ª',
+ 'laquo' => '«',
+ 'not' => '¬',
+ 'shy' => '',
+ 'reg' => '®',
+ 'macr' => '¯',
+ 'deg' => '°',
+ 'plusmn' => '±',
+ 'sup2' => '²',
+ 'sup3' => '³',
+ 'acute' => '´',
+ 'micro' => 'µ',
+ 'para' => '¶',
+ 'middot' => '·',
+ 'cedil' => '¸',
+ 'sup1' => '¹',
+ 'ordm' => 'º',
+ 'raquo' => '»',
+ 'frac14' => '¼',
+ 'frac12' => '½',
+ 'frac34' => '¾',
+ 'iquest' => '¿',
+ 'Agrave' => 'À',
+ 'Aacute' => 'Á',
+ 'Acirc' => 'Â',
+ 'Atilde' => 'Ã',
+ 'Auml' => 'Ä',
+ 'Aring' => 'Å',
+ 'AElig' => 'Æ',
+ 'Ccedil' => 'Ç',
+ 'Egrave' => 'È',
+ 'Eacute' => 'É',
+ 'Ecirc' => 'Ê',
+ 'Euml' => 'Ë',
+ 'Igrave' => 'Ì',
+ 'Iacute' => 'Í',
+ 'Icirc' => 'Î',
+ 'Iuml' => 'Ï',
+ 'ETH' => 'Ð',
+ 'Ntilde' => 'Ñ',
+ 'Ograve' => 'Ò',
+ 'Oacute' => 'Ó',
+ 'Ocirc' => 'Ô',
+ 'Otilde' => 'Õ',
+ 'Ouml' => 'Ö',
+ 'times' => '×',
+ 'Oslash' => 'Ø',
+ 'Ugrave' => 'Ù',
+ 'Uacute' => 'Ú',
+ 'Ucirc' => 'Û',
+ 'Uuml' => 'Ü',
+ 'Yacute' => 'Ý',
+ 'THORN' => 'Þ',
+ 'szlig' => 'ß',
+ 'agrave' => 'à',
+ 'aacute' => 'á',
+ 'acirc' => 'â',
+ 'atilde' => 'ã',
+ 'auml' => 'ä',
+ 'aring' => 'å',
+ 'aelig' => 'æ',
+ 'ccedil' => 'ç',
+ 'egrave' => 'è',
+ 'eacute' => 'é',
+ 'ecirc' => 'ê',
+ 'euml' => 'ë',
+ 'igrave' => 'ì',
+ 'iacute' => 'í',
+ 'icirc' => 'î',
+ 'iuml' => 'ï',
+ 'eth' => 'ð',
+ 'ntilde' => 'ñ',
+ 'ograve' => 'ò',
+ 'oacute' => 'ó',
+ 'ocirc' => 'ô',
+ 'otilde' => 'õ',
+ 'ouml' => 'ö',
+ 'divide' => '÷',
+ 'oslash' => 'ø',
+ 'ugrave' => 'ù',
+ 'uacute' => 'ú',
+ 'ucirc' => 'û',
+ 'uuml' => 'ü',
+ 'yacute' => 'ý',
+ 'thorn' => 'þ',
+ 'yuml' => 'ÿ'
+ );
+ // Entity not found? Destroy it.
+ return isset($table[$matches[1]]) ? $table[$matches[1]] : '';
+ } /* FeedWordPie_Parser::convert_entity() */
+
+ private function declare_html_entities() {
+ // This is required because the RSS specification says that entity-encoded
+ // html is allowed, but the xml specification says they must be declared.
+ return ' ]>';
+ }
} /* class FeedWordPie_Parser */
diff --git a/feedwordpress.php b/feedwordpress.php
index bcfd2d8..a2e584f 100644
--- a/feedwordpress.php
+++ b/feedwordpress.php
@@ -30,8 +30,8 @@
## CONSTANTS & DEFAULTS ############################################################
####################################################################################
-define ('FEEDWORDPRESS_VERSION', '2025.1211');
-define ('FEEDWORDPRESS_AUTHOR_CONTACT', 'https://fwpplugin.com/contact' );
+define ( 'FEEDWORDPRESS_VERSION', '2025.1211');
+define ( 'FEEDWORDPRESS_AUTHOR_CONTACT', 'https://fwpplugin.com/contact' );
if ( ! defined( 'FEEDWORDPRESS_BLEG' ) ) :
define ( 'FEEDWORDPRESS_BLEG', true );
@@ -826,7 +826,7 @@ public function subscription ($which) {
WordPress store.
@param string|null $uri Either the URI of the feed to poll, the URI of the (human-readable) website whose feed you want to poll, or null.
- @param mixed|null $crash_ts Unknown purpose.
+ @param mixed|null $crash_ts Unknown purpose (probably it's a timestamp).
@return array|null Associative array, with 'new' => # of new posts added during update, and 'updated' => # of old posts that were updated. If both are zero, there was no change since çast update.
*/
public function update( $uri = null, $crash_ts = null ) {
@@ -850,7 +850,7 @@ public function update( $uri = null, $crash_ts = null ) {
do_action( 'feedwordpress_update', $uri );
if ( is_null( $crash_ts ) ) :
- $crash_ts = $this->crash_ts();
+ $crash_ts = $this->crash_ts(); // the problem is that this can return null as well... (gwyneth 20230924)
endif;
// Randomize order for load balancing purposes
@@ -935,9 +935,9 @@ public function update( $uri = null, $crash_ts = null ) {
*
* @todo is returning null advisable? (gwyneth 20230916)
*
- * @param int|null $default Default value, called when the corresponding FWP option is not set.
+ * @param int|null $default Default value, called when the corresponding FWP option is not set.
*
- * @return int|null
+ * @return int|null Allegedly it's a timestamp, or possibly null.
*/
public function crash_ts( $default = null ) {
$crash_dt = (int) get_option( 'feedwordpress_update_time_limit', 0 );
@@ -1040,29 +1040,36 @@ public function force_update_all () {
return ($this->has_secret() and FeedWordPress::param( 'force_update_feeds' ));
} /* FeedWordPress::force_update_all () */
- public function stale () {
- if ( !is_null($this->automatic_update_hook())) :
+ /**
+ * Checks if the feed is stale, avoiding simultaneous updates.
+ *
+ * @return bool True if feed is stale, false otherwise,
+ */
+ public function stale() {
+ /** @var bool Set the default return value here, because of scoping issues; just to be sure something valid is returned. (gwyneth 20230924) */
+ $ret = false;
+
+ if ( ! is_null( $this->automatic_update_hook() ) ) :
// Do our best to avoid possible simultaneous
// updates by getting up-to-the-minute settings.
- $last = $this->last_update_all();
+ $last = $this->last_update_all() ?? false; // never trust these return values! (gwyneth 20230924)
// If we haven't updated all yet, give it a time window
- if (false === $last) :
- $ret = false;
- update_option('feedwordpress_last_update_all', time());
+ if ( false === $last ) :
+ update_option( 'feedwordpress_last_update_all', time() );
// Otherwise, check against freshness interval
- elseif (is_numeric($last)) : // Expect a timestamp
- $freshness = get_option('feedwordpress_freshness');
- if (false === $freshness) : // Use default
+ elseif ( is_numeric( $last ) ) : // Expect a timestamp
+ $freshness = get_option( 'feedwordpress_freshness' ) ?? false; // If we get a NULL, turn it into false. (gwyneth 20230924)
+ if ( false === $freshness ) : // Use default
$freshness = FEEDWORDPRESS_FRESHNESS_INTERVAL;
endif;
- $ret = ( (time() - $last) > $freshness );
+ $ret = ( ( time() - $last ) > $freshness );
// This should never happen.
else :
- FeedWordPressDiagnostic::critical_bug('FeedWordPress::stale::last', $last, __LINE__, __FILE__);
+ FeedWordPressDiagnostic::critical_bug( 'FeedWordPress::stale::last', $last, __LINE__, __FILE__ );
endif;
else :
@@ -1213,8 +1220,17 @@ public function all_admin_notices () {
endif;
} /* FeedWordPress::all_admin_notices () */
- public function process_retirements ($delta) {
- update_option('feedwordpress_process_zaps', 1);
+ /**
+ * Retires old posts in the absence of a non-incremental feed.
+ *
+ * TODO: Why is $delta returned unchanged? (gwyneth 20240210)
+ *
+ * @param mixed $delta Whatever this is, it's not used and is returned without change...
+ *
+ * @return mixed The value of $delta (unchanged?)
+ */
+ public function process_retirements( $delta ) {
+ update_option( 'feedwordpress_process_zaps', 1 );
return $delta;
}
@@ -1682,8 +1698,11 @@ public function update_requested_url () {
return $ret;
} /* FeedWordPress::update_requested_url() */
- public function auto_update () {
- if ($this->stale()) :
+ /**
+ * Checks if the feed is stale (i.e. no freshness) and requests an update.
+ */
+ public function auto_update() {
+ if ( $this->stale() ) :
$this->update();
endif;
} /* FeedWordPress::auto_update () */
@@ -2066,13 +2085,20 @@ static public function cache_duration ($params = array()) {
return $duration;
}
- static public function cache_lifetime ($duration) {
+ /**
+ * Tries to return our own defined cache lifetime, if set; if not.
+ * falls back to the WordPress default (which is passed to this function).
+ *
+ * @param int $duration WordPress default cache lifetime duration (as a fallback), in seconds.
+ * @return int Set duration, or the WordPress default (in seconds).
+ */
+ static public function cache_lifetime( $duration ) {
// Check for explicit setting of a lifetime duration
- if (defined('FEEDWORDPRESS_CACHE_LIFETIME')) :
+ if ( defined( 'FEEDWORDPRESS_CACHE_LIFETIME' ) ) :
$duration = FEEDWORDPRESS_CACHE_LIFETIME;
// Fall back to the cache freshness duration
- elseif (defined('FEEDWORDPRESS_CACHE_AGE')) :
+ elseif ( defined( 'FEEDWORDPRESS_CACHE_AGE' ) ) :
$duration = FEEDWORDPRESS_CACHE_AGE;
endif;
@@ -2080,9 +2106,25 @@ static public function cache_lifetime ($duration) {
return $duration;
} /* FeedWordPress::cache_lifetime () */
- # Utility functions for handling text settings
- static function get_field( $f, $setting = null ) {
+ /*
+ * Utility functions for handling text settings.
+ */
+ /**
+ * Returns the settings (value) for a field (key), if the first
+ * parameter is an array; if a string was passed, returns the
+ * string instead.
+ *
+ * @note This is a rather awkward way to do this; the function is
+ * only called on the two functions `negative()` and `affirmative()`,
+ * which could be totally rewritten in a much simpler way — unless
+ * I'm missing something! (gwyneth 20230922)
+ *
+ * @param array|string $f Associative array with key/value pairs _or_ a simple string.
+ * @param string|null $setting Either the field name (key) for a setting, or NULL.
+ * @return string|null Returns either a valid string (if a setting was found) or NULL otherwise.
+ */
+ static function get_field( $f, $setting = null ) {
$ret = $f;
if ( ! is_null( $setting ) ) :
$ret = null;
@@ -2091,30 +2133,56 @@ static function get_field( $f, $setting = null ) {
endif;
endif;
return $ret;
-
} /* FeedWordPress::get_field () */
- static function negative ($f, $setting = null) {
- $nego = array ('n', 'no', 'f', 'false');
+ /**
+ * Checks if the passed parameter is one of the "negative" values.
+ *
+ * @note This static method is never used.
+ *
+ * @param array|string $f Associative array with key/value pairs _or_ a simple string.
+ * @param string|null $setting Either the field name (key) for a setting, or NULL.
+ * @return bool Returns TRUE if the field being tested is negative, FALSE otherwise.
+ */
+ static function negative( $f, $setting = null ) {
+ $nego = array( 'n', 'no', 'f', 'false' ); // why not `0` as well? (gwyneth 20230922) @see affirmative()
$q = self::get_field( $f, $setting );
- return in_array( strtolower( trim( $q ) ), $nego );
+ // Check first if `$q` is empty or, worse, null, so that `trim()` below
+ // doesn't give an error (gwyneth 20230922).
+ if ( ! empty( $q ) ) :
+ return in_array( strtolower( trim( $q ) ), $nego );
+ endif;
+ // A null/empty check above is undefined; all we can say is that it is NOT negative,
+ // so we return FALSE! (gwyneth 20230922)
+ return FALSE;
} /* FeedWordPress::negative () */
- static function affirmative ($f, $setting = null) {
- // Defining possible affirmative values
- $affirmo = [ 'y', 'yes', 't', 'true', 1 ];
-
- // Get the field value (presumably from some form or other input)
- $q = self::get_field( $f, $setting );
-
- // Ensure $q is treated properly even if it's null or not set
- if ( null === $q ) {
- return false; // Or you can return false or other fallback as needed
- }
+ /**
+ * Checks if the passed parameter is one of the "affirmative" values.
+ *
+ * @note A sparsely used method which might benefit from some code refactoring.
+ * Most notably, it treats variable types _very_ loosely for my taste! (gwyneth 20230922)
+ *
+ * @param array|string $f Associative array with key/value pairs _or_ a simple string.
+ * @param string|null $setting Either the field name (key) for a setting, or NULL.
+ * @return bool Returns TRUE if the field being tested is affirmative, FALSE otherwise.
+ */
+ static function affirmative( $f, $setting = null) {
+ // Defining possible affirmative values
+ $affirmo = [ 'y', 'yes', 't', 'true', 1 ];
+
+ // Get the field value (presumably from some form or other input)
+ $q = self::get_field( $f, $setting );
- // Check if the value, after trimming and converting to lowercase, is in the affirmative array
- return in_array( strtolower( trim( $q ) ), $affirmo, true ); // The third argument `true` ensures strict type checking
- } /* FeedWordPress::affirmative () */
+ // Ensure $q is treated properly even if it's null or not set
+ if ( ! empty( $q ) ) {
+ return false; // Or you can return false or other fallback as needed
+ }
+
+ // Check if the value, after trimming and converting to lowercase, is in the affirmative array
+ return in_array( strtolower( trim( $q ) ), $affirmo, true ); // The third argument `true` ensures strict type checking
+}
+ /* FeedWordPress::affirmative () */
/**
* Internal debugging functions.
@@ -2122,9 +2190,22 @@ static function affirmative ($f, $setting = null) {
* @todo radgeek needs to document this better. What levels exist, and
* how/where are they defined? (gwyneth 20230919)
*
+ * @note Diagnostics can be sent out to different loggers (stderr, file-based
+ * logging, dialogue boxes, error sent by email, etc...) and this method
+ * will attempt to contact the correct one.
+ *
+ * @param string $level Error level, in a structured way (usually `class/method`).
+ * @param string $out Error message to output.
+ * @param mixed|null $persist Probably checks if the error is transitory or persistent.
+ * @param mixed|null $since Probably the date (or a timestamp?) of the first occurrence of this error.
+ * @param mixed|null $mostRecent Probably the date (or a timestamp?) of the most recent occurence of this error.
+ *
* @global $feedwordpress_admin_footer
+ * @uses error_log()
+ * @uses get_option()
+ * @uses update_option()
*/
- static function diagnostic( $level, $out, $persist = null, $since = null, $mostRecent = null ) {
+ static function diagnostic($level, $out, $persist = null, $since = null, $mostRecent = null) {
global $feedwordpress_admin_footer;
$output = get_option( 'feedwordpress_diagnostics_output', array() );
@@ -2152,7 +2233,6 @@ static function diagnostic( $level, $out, $persist = null, $since = null, $mostR
error_log(self::log_prefix() . ' ' . $out);
break;
case 'email' :
-
if (is_null($persist)) :
$sect = 'occurrent';
$hook = (isset($dlog['mesg'][$sect]) ? count($dlog['mesg'][$sect]) : 0);
@@ -2163,6 +2243,8 @@ static function diagnostic( $level, $out, $persist = null, $since = null, $mostR
$line = array("Since" => $since, "Message" => $out, "Most Recent" => $mostRecent);
endif;
+ // Is this the default case?! If not, this code will very likely _never_ run! (gwyneth 20230922)
+ // @see PHP Manual for switch()
if ( !isset($dlog['mesg'])) : $dlog['mesg'] = array(); endif;
if ( !isset($dlog['mesg'][$sect])) : $dlog['mesg'][$sect] = array(); endif;
diff --git a/feedwordpresssyndicationpage.class.php b/feedwordpresssyndicationpage.class.php
index 7ebeb6e..0bb05ad 100644
--- a/feedwordpresssyndicationpage.class.php
+++ b/feedwordpresssyndicationpage.class.php
@@ -685,21 +685,20 @@ function dashboard_box($page, $box = NULL)
diff --git a/magpiefromsimplepie.class.php b/magpiefromsimplepie.class.php
index 8143ae4..08b2bd1 100644
--- a/magpiefromsimplepie.class.php
+++ b/magpiefromsimplepie.class.php
@@ -655,6 +655,9 @@ function normalize_atom_person (&$source, $person, &$dest, $to, $i) {
* MagpieFromSimplePie::normalize_category: Normalize Atom 1.0 and
* RSS 2.0 categories to Dublin Core...
*
+ * Note: in some cases, there is no "category" taxonomy, and this fails
+ * upstream with an error of invalid category. (gwyneth 20231017)
+ *
* @param array &$source
* @param string $from
* @param array &$dest
@@ -663,26 +666,33 @@ function normalize_atom_person (&$source, $person, &$dest, $to, $i) {
*
* @uses MagpieFromSimplePie::element_id
* @uses MagpieFromSimplePie::is_rss
+ * @uses FeedWordPress::diagnostic
*/
- function normalize_category (&$source, $from, &$dest, $to, $i) {
- $cat_id = $this->element_id($from, $i);
- $dc_id = $this->element_id($to, $i);
+ function normalize_category( &$source, $from, &$dest, $to, $i ) {
+ $cat_id = $this->element_id( $from, $i ) ?? "Uncategorized"; // attempt to overcome an error (gwyneth 20231017)
+ $dc_id = $this->element_id( $to, $i );
// first normalize category elements: Atom 1.0 <=> RSS 2.0
- if ( isset($source["{$cat_id}@term"]) ) : // category identifier
- $source[$cat_id] = $source["{$cat_id}@term"];
- elseif ( $this->is_rss() ) :
- $source["{$cat_id}@term"] = $source[$cat_id];
+ if ( ! empty( $source["{$cat_id}@term"] ) ) : // category identifier
+ $source[ $cat_id ] = $source["{$cat_id}@term"];
+ elseif ( $this->is_rss() and ! empty( $source[ $cat_id ] ) ) :
+ $source["{$cat_id}@term"] = $source[ $cat_id ];
+ else :
+ FeedWordPress::diagnostic(
+ 'MagpieFromSimplePie::normalize_category',
+ "Error in normalizing category to Dublin Core. \$cat_id is '" . $cat_id
+ . "' and \$dc_id is '" . $dc_id . "'"
+ );
endif;
- if ( isset($source["{$cat_id}@scheme"]) ) : // URI to taxonomy
+ if ( ! empty( $source["{$cat_id}@scheme"] ) ) : // URI to taxonomy
$source["{$cat_id}@domain"] = $source["{$cat_id}@scheme"];
- elseif ( isset($source["{$cat_id}@domain"]) ) :
+ elseif ( ! empty( $source["{$cat_id}@domain"] ) ) :
$source["{$cat_id}@scheme"] = $source["{$cat_id}@domain"];
endif;
// Now put the identifier into dc:subject
- $dest[$dc_id] = $source[$cat_id];
+ $dest[ $dc_id ] = $source[ $cat_id ] ?? "Uncategorized";
} /* MagpieFromSimplePie::normalize_category */
/**
@@ -707,15 +717,15 @@ function normalize_dc_subject (&$source, $from, &$dest, $to, $i) {
/**
* MagpieFromSimplePie::element_id
- * Magic ID function for multiple elemenets.
+ * Magic ID function for multiple elements.
* Can be called as static MagpieRSS::element_id()
*
* @param string $el
* @param int $counter
* @return string
*/
- function element_id ($el, $counter) {
- return $el . (($counter > 1) ? '#'.$counter : '');
+ function element_id( $el, $counter ) {
+ return $el . ( ( $counter > 1 ) ? '#' . $counter : '' );
} /* MagpieFromSimplePie::element_id */
/**
diff --git a/posts-page.php b/posts-page.php
index 27f11f9..6ea61d4 100644
--- a/posts-page.php
+++ b/posts-page.php
@@ -416,17 +416,24 @@ function formatting_box( $page, $box = NULL ) {
$page = $this;
endif;
- if ($page->for_feed_settings()) :
+ if (!empty($page->for_feed_settings())) :
$custom_settings = $page->link->setting("postmeta", NULL, array());
else :
$custom_settings = get_option('feedwordpress_custom_settings');
endif;
- if ($custom_settings and !is_array($custom_settings)) :
- $custom_settings = unserialize($custom_settings);
+ if (!empty($custom_settings) and !is_array($custom_settings) and (strlen($custom_settings) > 1)) :
+ // Catch edge case: $custom_settings exists, but it has only one byte.
+ // In such cases, unserialize() will fail. (gwyneth 20250111)
+ try {
+ $custom_settings = unserialize($custom_settings);
+ }
+ catch (Exception $e) {
+ $custom_settings = array();
+ }
endif;
- if ( !is_array($custom_settings)) :
+ if (!is_array($custom_settings)) :
$custom_settings = array();
endif;
diff --git a/readme.txt b/readme.txt
index 439e738..a5fd07a 100644
--- a/readme.txt
+++ b/readme.txt
@@ -76,6 +76,10 @@ FeedWordPress has many options which can be accessed through the WordPress Dashb
* COMPATIBILITY FIXES FOR WORDPRESS 6.9 (Fixes "PHP Fatal error: Cannot make static method SimplePie\\Parser::declare_html_entities() non static in class FeedWordPie_Parser" error, "PHP Fatal error: Uncaught TypeError: Return value of SimplePie\\File::get_headers() must be of the type array, null returned" error, or "PHP Fatal error: Uncaught TypeError: Argument 1 passed to SimplePie\\File::SimplePie\\{closure}() must be of the type string, array given" error after WordPress 6.9 upgrade): This version includes fixes to critical compatibility issues with WordPress 6.9 and WP 6.9's upgrade to SimplePie 1.9.0+. If you recently upgraded to WordPress 6.9 and noticed problems with updating or accessing your site, or messages about a "Critical issue" or fatal PHP errors in the interface or your error logs then a quick upgrade to this point release should hopefully resolve that issue for you.
+= 2025.0112 =
+
+* ADDITIONAL CODE CLEANUP: This version does some additional code clean-up to remove some obsolete methods of generating output, and to do a better job of sanitizing input and escaping output in the FeedWordPress administrative dashboard.
+
= 2024.1119 =
* COMPATIBILITY FIX FOR WORDPRESS 6.7 (Fixes "Uncaught TypeError: Argument 1 passed to SimplePie\Cache\BaseDataCache::__construct() must implement interface SimplePie\Cache\Base" error after WordPress 6.7 upgrade): This version includes a fix to a critical compatibility issue with SimplePie 1.8.0+'s new requirements for namespaced classes and interfaces. (Updating to WordPress 6.7 also means that you get an update to a more recent version of SimplePie included with the platform; unfortunately, the new version of SimplePie introduces code changes which are no doubt a good idea in the long run, but which broke backward compatibility with the technique that FeedWordPress uses to extend SimplePie classes.) If you recently upgraded to WordPress 6.7 and noticed that FeedWordPress suddenly stopped updating, or that you started seeing messages about a "Critical issue" or fatal PHP errors in the interface or your error logs -- especially when attempting to check feeds for updates within the FeedWordPress admin interface -- then a quick upgrade to this point release should hopefully resolve that issue for you.
diff --git a/syndicatedlink.class.php b/syndicatedlink.class.php
index 9518c54..b03cde9 100644
--- a/syndicatedlink.class.php
+++ b/syndicatedlink.class.php
@@ -1,1292 +1,1342 @@
Syndication.
-#
-# Fields used are:
-#
-# * link_rss: the URI of the Atom/RSS feed to syndicate
-#
-# * link_notes: user-configurable options, with keys and values
-# like so:
-#
-# key: value
-# cats: computers\nweb
-# feed/key: value
-#
-# Keys that start with "feed/" are gleaned from the data supplied
-# by the feed itself, and will be overwritten with each update.
-#
-# Values have linebreak characters escaped with C-style
-# backslashes (so, for example, a newline becomes "\n").
-#
-# The value of `cats` is used as a newline-separated list of
-# default categories for any post coming from a particular feed.
-# (In the example above, any posts from this feed will be placed
-# in the "computers" and "web" categories--*in addition to* any
-# categories that may already be applied to the posts.)
-#
-# Values of keys in link_notes are accessible from templates using
-# the function `get_feed_meta($key)` if this plugin is activated.
+/**
+ * Class SyndicatedLink: represents a syndication feed stored within the
+ * WordPress database.
+ *
+ * To keep things compact and editable from within WordPress, we use all the
+ * links under a particular category in the WordPress "Blogroll" for the list of
+ * feeds to syndicate. "Contributors" is the category used by default; you can
+ * configure that under Options --> Syndication.
+ *
+ * Fields used are:
+ *
+ * - link_rss: the URI of the Atom/RSS feed to syndicate
+ *
+ * - link_notes: user-configurable options, with keys and values like so:
+ *
+ * key: value
+ * cats: computers\nweb
+ * feed/key: value
+ *
+ * Keys that start with "feed/" are gleaned from the data supplied
+ * by the feed itself, and will be overwritten with each update.
+ *
+ * Values have linebreak characters escaped with C-style
+ * backslashes (so, for example, a newline becomes "\n").
+ *
+ * The value of `cats` is used as a newline-separated list of
+ * default categories for any post coming from a particular feed.
+ * (In the example above, any posts from this feed will be placed
+ * in the "computers" and "web" categories--*in addition to* any
+ * categories that may already be applied to the posts.)
+ *
+ * Values of keys in link_notes are accessible from templates using
+ * the function `get_feed_meta($key)` if this plugin is activated.
+ *
+ * @author Charles Johnson
+ */
require_once dirname( __FILE__ ) . '/magpiefromsimplepie.class.php';
require_once dirname( __FILE__ ) . '/feedwordpressparsedpostmeta.class.php';
class SyndicatedLink {
- var $id = null;
- var $link = null;
- var $settings = array ();
- public $simplepie = null;
- var $magpie = null;
-
- public function __construct( $link ) {
- if (is_object($link)) :
- $this->link = $link;
- $this->id = $link->link_id;
- else :
- $this->id = $link;
- $this->link = get_bookmark($link);
- endif;
-
- if (is_object($this->link)) :
- if (strlen($this->link->link_rss) > 0) :
- $this->get_settings_from_notes();
- endif;
- endif;
-
- add_filter('feedwordpress_update_complete', array($this, 'process_retirements'), 1000, 1);
- } /* SyndicatedLink::__construct () */
-
- public function found () {
- return is_object($this->link) and !is_wp_error($this->link);
- } /* SyndicatedLink::found () */
-
- public function id () {
- return (is_object($this->link) ? $this->link->link_id : NULL);
- }
-
- public function stale () {
- global $feedwordpress;
-
- $stale = true;
- if ($this->setting('update/hold')=='ping') :
- $stale = false; // don't update on any timed updates; pings only
- elseif ($this->setting('update/hold')=='next') :
- $stale = true; // update on the next timed update
- elseif ( ! $this->setting('update/last') ) :
- $stale = true; // initial update
- elseif ( !empty($feedwordpress) and $feedwordpress->force_update_all()) :
- $stale = true; // forced general updating
- else :
- $after = (
- (int) $this->setting('update/last')
- + (int) $this->setting('update/fudge')
- + ((int) $this->setting('update/ttl') * 60)
- );
- $stale = (time() >= $after);
- endif;
- return $stale;
- } /* SyndicatedLink::stale () */
-
- public function fetch () {
- $timeout = $this->setting('fetch timeout', 'feedwordpress_fetch_timeout', FEEDWORDPRESS_FETCH_TIMEOUT_DEFAULT);
-
- $this->simplepie = apply_filters(
- 'syndicated_feed',
- FeedWordPress::fetch($this, array('timeout' => $timeout)),
- $this
- );
-
- // Filter compatibility mode
- if (is_wp_error($this->simplepie)) :
- $this->magpie = $this->simplepie;
- else :
- $this->magpie = new MagpieFromSimplePie($this->simplepie, NULL);
- endif;
- } /* SyndicatedLink::fetch () */
-
- public function live_posts () {
- if ( !is_object($this->simplepie)) :
- $this->fetch();
- endif;
-
- if (is_object($this->simplepie) and method_exists($this->simplepie, 'get_items')) :
- $ret = apply_filters(
- 'syndicated_feed_items',
- $this->simplepie->get_items(),
- $this
- );
- else :
- $ret = $this->simplepie;
- endif;
- return $ret;
- } /* SyndicatedLink::live_posts () */
-
- protected function pause_updates () {
- return ('yes'==$this->setting("update/pause", "update_pause", 'no'));
- } /* SyndicatedLink::pause_updates () */
-
- public function poll ($crash_ts = NULL) {
- global $wpdb;
-
- $url = $this->uri(array('add_params' => true, 'fetch' => true));
- FeedWordPress::diagnostic('updated_feeds', 'Polling feed ['.$url.']');
-
- $this->fetch();
-
- $new_count = NULL;
-
- $resume = ('yes'==$this->setting('update/unfinished'));
- if ($resume) :
- // pick up where we left off
- $processed = array_map('trim', explode("\n", $this->setting('update/processed')));
- else :
- // begin at the beginning
- $processed = array();
- endif;
-
- if (is_wp_error($this->simplepie)) :
- $new_count = $this->simplepie;
- // Error; establish an error setting.
- $theError = array();
- $theError['ts'] = time();
- $theError['since'] = time();
- $theError['object'] = $this->simplepie;
-
- $oldError = $this->setting('update/error', NULL, NULL);
- if (is_string($oldError)) :
- $oldError = unserialize($oldError);
- endif;
-
- if ( !is_null($oldError)) :
- // Copy over the in-error-since timestamp
- $theError['since'] = $oldError['since'];
-
- // If this is a repeat error, then we should
- // take a step back before we try to fetch it
- // again.
- $this->update_setting('update/last', time(), NULL);
- $ttl = $this->automatic_ttl();
- $ttl = apply_filters('syndicated_feed_ttl', $ttl, $this);
- $ttl = apply_filters('syndicated_feed_ttl_from_error', $ttl, $this);
- $this->update_setting('update/ttl', $ttl);
- $this->update_setting('update/timed', 'automatically');
- endif;
-
- do_action('syndicated_feed_error', $theError, $oldError, $this);
-
- $this->update_setting('update/error', serialize($theError));
- $this->save_settings(/*reload=*/ true);
-
- elseif (is_object($this->simplepie)) :
-
- // Success; clear out error setting, if any.
- $this->update_setting('update/error', NULL);
-
- $new_count = array('new' => 0, 'updated' => 0, 'stored' => 0);
-
- # -- Update Link metadata live from feed
- $channel = $this->magpie->channel;
-
- if ( !isset($channel['id'])) :
- $channel['id'] = $this->link->link_rss;
- endif;
-
- $update = array();
- if ( ! $this->hardcode('url') and isset($channel['link'])) :
- $update[] = "link_url = '".esc_sql($channel['link'])."'";
- endif;
-
- if ( ! $this->hardcode('name') and isset($channel['title'])) :
- $update[] = "link_name = '".esc_sql($channel['title'])."'";
- endif;
-
- if ( ! $this->hardcode('description')) :
- if (isset($channel['tagline'])) :
- $update[] = "link_description = '".esc_sql($channel['tagline'])."'";
- elseif (isset($channel['description'])) :
- $update[] = "link_description = '".esc_sql($channel['description'])."'";
- endif;
- endif;
-
- $this->update_setting('link/feed_type', $this->simplepie->get_type());
-
- $this->merge_settings($channel, 'feed/');
-
- $this->update_setting('update/last', time());
- $this->do_update_ttl();
-
- if ( ! $this->setting('update/hold') != 'ping') :
- $this->update_setting('update/hold', 'scheduled');
- endif;
-
- $this->update_setting('update/unfinished', 'yes');
-
- $update[] = "link_notes = '".esc_sql($this->settings_to_notes())."'";
-
- $update_set = implode(',', $update);
-
- // Update the properties of the link from the feed information
- $result = $wpdb->query("
- UPDATE $wpdb->links
- SET $update_set
- WHERE link_id='$this->id'
- ");
- do_action('update_syndicated_feed', $this->id, $this);
-
- # -- Add new posts from feed and update any updated posts
- $crashed = false;
-
- $posts = $this->live_posts();
-
- $this->magpie->originals = $posts;
-
- // If this is a complete feed, rather than an incremental feed, we
- // need to prepare to mark everything for presumptive retirement.
- if ($this->is_non_incremental()) :
- $q = new WP_Query(array(
- 'fields' => '_synfrom',
- 'post_status__not' => 'fwpretired',
- 'ignore_sticky_posts' => true,
- 'meta_key' => 'syndication_feed_id',
- 'meta_value' => $this->id,
- ));
- foreach ($q->posts as $p) :
- update_post_meta($p->ID, '_feedwordpress_retire_me_'.$this->id, '1');
- endforeach;
- endif;
-
- if (is_array($posts)) :
- foreach ($posts as $key => $item) :
- if ( ! $this->pause_updates()) :
- $post = new SyndicatedPost($item, $this);
-
- if ( ! $resume or !in_array(trim($post->guid()), $processed)) :
- $processed[] = $post->guid();
- if ( ! $post->filtered()) :
- $new = $post->store();
- if ( $new !== false ) $new_count[$new]++;
- endif;
-
- if ( !is_null($crash_ts) and (time() > $crash_ts)) :
- $crashed = true;
- break;
- endif;
- endif;
-
- unset($post);
- endif;
- endforeach;
- endif;
-
- if ('yes'==$this->setting('tombstones', 'tombstones', 'yes')) :
- // Check for use of Atom tombstones. Spec:
- //
- $tombstones = $this->simplepie->get_feed_tags('http://purl.org/atompub/tombstones/1.0', 'deleted-entry');
- if ( !is_null($tombstones) && count($tombstones) > 0) :
- foreach ($tombstones as $tombstone) :
- $ref = NULL;
- foreach (array('', 'http://purl.org/atompub/tombstones/1.0') as $ns) :
- if (isset($tombstone['attribs'][$ns])
- and isset($tombstone['attribs'][$ns]['ref'])) :
- $ref = $tombstone['attribs'][$ns]['ref'];
- endif;
- endforeach;
-
- $q = new WP_Query(array(
- 'ignore_sticky_posts' => true,
- 'guid' => $ref,
- 'meta_key' => 'syndication_feed_id',
- 'meta_value' => $this->id, // Only allow a feed to tombstone its own entries.
- ));
-
- foreach ($q->posts as $p) :
- $old_status = $p->post_status;
- FeedWordPress::diagnostic('syndicated_posts', 'Retiring existing post # '.$p->ID.' "'.$p->post_title.'" due to Atom tombstone element in feed.');
- set_post_field('post_status', 'fwpretired', $p->ID);
- wp_transition_post_status('fwpretired', $old_status, $p);
- endforeach;
-
- endforeach;
- endif;
- endif;
-
- $suffix = ($crashed ? 'crashed' : 'completed');
- do_action('update_syndicated_feed_items', $this->id, $this);
- do_action("update_syndicated_feed_items_{$suffix}", $this->id, $this);
-
- $this->update_setting('update/processed', $processed);
- if ( ! $crashed) :
- $this->update_setting('update/unfinished', 'no');
- endif;
- $this->update_setting('link/item count', count($posts));
-
- // Copy back any changes to feed settings made in the
- // course of updating (e.g. new author rules)
- $update_set = "link_notes = '" . esc_sql($this->settings_to_notes())."'";
-
- // Update the properties of the link from the feed information
- $result = $wpdb->query("
- UPDATE {$wpdb->links}
- SET {$update_set}
- WHERE link_id='{$this->id}'
- ");
- if ( FALSE === $result ) :
- FeedWordPress::diagnostic(
- 'SyndicatedLink/poll',
- 'Database update failed',
- );
- endif;
-
- do_action("update_syndicated_feed_completed", $this->id, $this);
- endif;
-
- // All done; let's clean up.
- $this->magpie = NULL;
-
- // Avoid circular-reference memory leak in PHP < 5.3.
- // Cf.
- if (method_exists($this->simplepie, '__destruct')) :
- $this->simplepie->__destruct();
- endif;
- $this->simplepie = NULL;
-
- return $new_count;
- } /* SyndicatedLink::poll() */
-
- /**
- * Update the time to live of this link.
- */
- public function do_update_ttl(): void {
- // Get ttl and xml elements, return them if available
- list( $ttl, $xml ) = $this->ttl( /*return element=*/ true);
-
- // Check if ttl is not null
- if ( !is_null( $ttl ) ) {
- $this->update_setting( 'update/ttl', $ttl );
- $this->update_setting( 'update/xml', $xml );
- $this->update_setting( 'update/timed', 'feed' );
- } else {
- // Fallback to automatic ttl if null
- $ttl = $this->automatic_ttl();
- $this->update_setting( 'update/ttl', $ttl );
- $this->update_setting( 'update/xml', null ); // Explicit null
- $this->update_setting( 'update/timed', 'automatically' );
- }
-
- // Adding a random fudge value (ensure it works across versions)
- $this->update_setting( 'update/fudge', rand( 0, (int) ( $ttl / 3 ) ) * 60 );
-
- // Apply filter to ttl (should be compatible with all PHP 8.x versions)
- $this->update_setting(
- 'update/ttl',
- apply_filters(
- 'syndicated_feed_ttl',
- $this->setting( 'update/ttl' ),
- $this
- )
- );
- } /* SyndicatedLink::do_update_ttl () */
-
- public function process_retirements ($delta) {
- $q = new WP_Query(array(
- 'fields' => '_synfrom',
- 'post_status__not' => 'fwpretired',
- 'ignore_sticky_posts' => true,
- 'meta_key' => '_feedwordpress_retire_me_'.$this->id,
- 'meta_value' => '1',
- ));
- if ($q->have_posts()) :
- foreach ($q->posts as $p) :
- $old_status = $p->post_status;
- FeedWordPress::diagnostic('syndicated_posts', 'Retiring existing post # '.$p->ID.' "'.$p->post_title.'" due to absence from a non-incremental feed.');
- set_post_field('post_status', 'fwpretired', $p->ID);
- wp_transition_post_status('fwpretired', $old_status, $p);
- delete_post_meta($p->ID, '_feedwordpress_retire_me_'.$this->id);
- endforeach;
- endif;
-
- return $delta;
- } /* SyndicatedLink::process_retirements () */
-
- /**
- * Updates the URL for the feed syndicated by this link.
- *
- * @param string $url The new feed URL to use for this source.
- * @return bool TRUE on success, FALSE on failure.
- */
- public function set_uri ($url) {
- global $wpdb;
-
- if ($this->found()) :
- // Update link_rss
- $result = $wpdb->query("
- UPDATE $wpdb->links
- SET
- link_rss = '".esc_sql($url)."'
- WHERE link_id = '".esc_sql($this->id)."'
- ");
-
- $ret = ($result ? true : false);
- else :
- $ret = false;
- endif;
- return $ret;
- } /* SyndicatedLink::set_uri () */
-
- public function deactivate () {
- global $wpdb;
-
- $wpdb->query($wpdb->prepare("
- UPDATE $wpdb->links SET link_visible = 'N' WHERE link_id = %d
- ", (int) $this->id));
- } /* SyndicatedLink::deactivate () */
-
- /**
- * SyndicatedLink::delete() deletes a subscription from the WordPress links
- * table. Any posts that were syndicated through that subscription will still
- * be present in the wp_posts table; but postmeta fields that refer to the
- * syndication feed's numeric id (which will no longer be valid) will be
- * deleted. For most purposes, the posts remaining will be treated as if they
- * were locally authored posts rather than syndicated posts.
- *
- * @global $wpdb
- * @uses wpdb::query
- */
- public function delete () {
- global $wpdb;
-
- $wpdb->query($wpdb->prepare("
- DELETE FROM $wpdb->postmeta WHERE meta_key='syndication_feed_id'
- AND meta_value = '%s'
- ", $this->id));
-
- $wpdb->query($wpdb->prepare("
- DELETE FROM $wpdb->links WHERE link_id = %d
- ", (int) $this->id));
-
- $this->id = NULL;
- } /* SyndicatedLink::delete () */
-
- /**
- * SyndicatedLink::nuke() deletes a subscription AND all of the
- * posts syndicated through that subscription.
- *
- * @global $wpdb
- * @uses wpdb::get_col
- * @uses wp_delete_post
- * @uses SyndicatedLink::delete
- */
- public function nuke () {
- global $wpdb;
-
- // Make a list of the items syndicated from this feed...
- $post_ids = $wpdb->get_col($wpdb->prepare("
- SELECT post_id FROM $wpdb->postmeta
- WHERE meta_key = 'syndication_feed_id'
- AND meta_value = '%s'
- ", $this->id));
-
- // ... and kill them all
- if (count($post_ids) > 0) :
- foreach ($post_ids as $post_id) :
- // Force scrubbing of deleted post
- // rather than sending to Trashcan
- wp_delete_post(
- /*postid=*/ $post_id,
- /*force_delete=*/ true
- );
- endforeach;
- endif;
-
- $this->delete();
- } /* SyndicatedLink::nuke () */
-
- public function map_name_to_new_user ($name, $newuser_name) {
- global $wpdb;
-
- if (strlen($newuser_name) > 0) :
- $newuser_id = fwp_insert_new_user($newuser_name);
- if (is_numeric($newuser_id)) :
- if (is_null($name)) : // Unfamiliar author
- $this->update_setting('unfamiliar author', $newuser_id);
- else :
- $map = $this->setting('map authors');
- $map['name'][$name] = $newuser_id;
- $this->update_setting('map authors', $map);
- endif;
- else :
- // TODO: Add some error detection and reporting
- endif;
- else :
- // TODO: Add some error reporting
- endif;
- } /* SyndicatedLink::map_name_to_new_user () */
-
- protected function imploded_settings () {
- return array('cats', 'tags', 'match/cats', 'match/tags', 'match/filter');
- } /* SyndicatedLink::imploded_settings () */
-
- protected function get_settings_from_notes () {
- // Read off feed settings from link_notes
- $notes = explode("\n", $this->link->link_notes);
- foreach ($notes as $note):
- $pair = explode(": ", $note, 2);
- $key = (isset($pair[0]) ? $pair[0] : null);
- $value = (isset($pair[1]) ? $pair[1] : null);
- if ( !is_null($key) and !is_null($value)) :
- // Unescape and trim() off the whitespace.
- // Thanks to Ray Lischner for pointing out the
- // need to trim off whitespace.
- $this->settings[$key] = stripcslashes (trim($value));
- endif;
- endforeach;
-
- // "Magic" feed settings
- $this->settings['link/uri'] = $this->link->link_rss;
- $this->settings['link/name'] = $this->link->link_name;
- $this->settings['link/id'] = $this->link->link_id;
-
- // `hardcode categories` and `unfamiliar categories` are
- // deprecated in favor of `unfamiliar category`
- if (
- isset($this->settings['unfamiliar categories'])
- and !isset($this->settings['unfamiliar category'])
- ) :
- $this->settings['unfamiliar category'] = $this->settings['unfamiliar categories'];
- endif;
- if (
- FeedWordPress::affirmative($this->settings, 'hardcode categories')
- and !isset($this->settings['unfamiliar category'])
- ) :
- $this->settings['unfamiliar category'] = 'default';
- endif;
-
- // Set this up automagically for del.icio.us
- $bits = parse_url($this->link->link_rss);
- $tagspacers = array('del.icio.us', 'feeds.delicious.com');
- if ( !isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) :
- $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
- endif;
-
- // Simple lists
- foreach ($this->imploded_settings() as $what) :
- if (isset($this->settings[$what])):
- $this->settings[$what] = explode(
- FEEDWORDPRESS_CAT_SEPARATOR,
- $this->settings[$what]
- );
- endif;
- endforeach;
-
- if (isset($this->settings['terms'])) :
- // Look for new format
- $this->settings['terms'] = maybe_unserialize($this->settings['terms']);
-
- if ( !is_array($this->settings['terms'])) :
- // Deal with old format instead. Ugh.
-
- // Split on two *or more* consecutive breaks
- // because in the old format, a taxonomy
- // without any associated terms would
- // produce tax_name#1\n\n\ntax_name#2\nterm,
- // and the naive split on the first \n\n
- // would screw up the tax_name#2 list.
- //
- // Props to David Morris for pointing this
- // out.
-
- $this->settings['terms'] = preg_split(
- "/".FEEDWORDPRESS_CAT_SEPARATOR."{2,}/",
- $this->settings['terms']
- );
- $terms = array();
- foreach ($this->settings['terms'] as $line) :
- $line = explode(FEEDWORDPRESS_CAT_SEPARATOR, $line);
- $tax = array_shift($line);
- $terms[$tax] = $line;
- endforeach;
- $this->settings['terms'] = $terms;
- endif;
- endif;
-
- if (isset($this->settings['map authors'])) :
- $author_rules = explode("\n\n", $this->settings['map authors']);
- $ma = array();
- foreach ($author_rules as $rule) :
- list($rule_type, $author_name, $author_action) = explode("\n", $rule);
-
- // Normalize for case and whitespace
- $rule_type = strtolower(trim($rule_type));
- $author_name = strtolower(trim($author_name));
- $author_action = strtolower(trim($author_action));
-
- $ma[$rule_type][$author_name] = $author_action;
- endforeach;
- $this->settings['map authors'] = $ma;
- endif;
-
- } /* SyndicatedLink::get_settings_from_notes () */
-
- protected function settings_to_notes () {
- $to_notes = $this->settings;
-
- unset($to_notes['link/id']); // Magic setting; don't save
- unset($to_notes['link/uri']); // Magic setting; don't save
- unset($to_notes['link/name']); // Magic setting; don't save
- unset($to_notes['hardcode categories']); // Deprecated
- unset($to_notes['unfamiliar categories']); // Deprecated
-
- // Collapse array settings
- if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
- $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
- endif;
-
- foreach ($this->imploded_settings() as $what) :
- if (isset($to_notes[$what]) and is_array($to_notes[$what])) :
- $to_notes[$what] = implode(
- FEEDWORDPRESS_CAT_SEPARATOR,
- $to_notes[$what]
- );
- endif;
- endforeach;
-
- if (isset($to_notes['terms']) and is_array($to_notes['terms'])) :
- // Serialize it.
- $to_notes['terms'] = serialize($to_notes['terms']);
- endif;
-
- // Collapse the author mapping rule structure back into a flat string
- if (isset($to_notes['map authors'])) :
- $ma = array();
- foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
- foreach ($author_rules as $author_name => $author_action) :
- $ma[] = $rule_type."\n".$author_name."\n".$author_action;
- endforeach;
- endforeach;
- $to_notes['map authors'] = implode("\n\n", $ma);
- endif;
-
- $notes = '';
- foreach ($to_notes as $key => $value) :
- $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
- endforeach;
- return $notes;
- } /* SyndicatedLink::settings_to_notes () */
-
- public function save_settings ($reload = false) {
- global $wpdb;
-
- // Save channel-level meta-data
- foreach (array('link_name', 'link_description', 'link_url') as $what) :
- $alter[] = "{$what} = '".esc_sql($this->link->{$what})."'";
- endforeach;
-
- // Save settings to the notes field
- $alter[] = "link_notes = '".esc_sql($this->settings_to_notes())."'";
-
- // Update the properties of the link from settings changes, etc.
- $update_set = implode(", ", $alter);
-
- $result = $wpdb->query("
- UPDATE $wpdb->links
- SET $update_set
- WHERE link_id='$this->id'
- ");
-
- if ( $result === false )
- {
- error_log("query failed: " . $wpdb->last_error);
- }
-
- if ($reload) :
- // force reload of link information from DB
- if (function_exists('clean_bookmark_cache')) :
- clean_bookmark_cache($this->id);
- endif;
- endif;
- } /* SyndicatedLink::save_settings () */
-
- /**
- * Retrieves the value of a setting, allowing for a global setting to be
- * used as a fallback, or a constant value, or both.
- *
- * @param string $name The link setting key.
- * @param mixed $fallback_global If the link setting is nonexistent or
- * marked as a use-default value, fall back
- * to the value of this global setting.
- * @param mixed $fallback_value If the link setting and the global setting are
- * nonexistent or marked as ause-default value,
- * fall back to this constant value.
- *
- * @return mixed|null Should *not* return a boolean!!
- */
- public function setting( $name, $fallback_global = NULL, $fallback_value = NULL, $default = 'default' ) {
- $ret = NULL;
- if ( isset( $this->settings[ $name ] ) ) :
- $ret = $this->settings[ $name ];
- endif;
-
- $no_value = (
- is_null( $ret )
- or ( is_string( $ret ) and strtolower( $ret ) == $default )
- );
-
- if ( $no_value and ! is_null( $fallback_global ) ) :
- // Avoid duplication of this correction
- $fallback_global = preg_replace( '/^feedwordpress_/', '', $fallback_global );
-
- // Occasionally we'll get an array back. Convert it to a string
- if ( is_array( $fallback_global ) && sizeof( $fallback_global ) ) :
- $fallback_global = reset( $fallback_global );
- endif;
-
- if ( ! empty( $fallback_global ) ) :
- $ret = get_option( 'feedwordpress_' . $fallback_global, /*default=*/ NULL );
- endif;
- endif;
-
- $no_value = (
- is_null( $ret )
- or ( is_string( $ret ) and strtolower( $ret ) == $default )
- );
-
- if ( $no_value and ! is_null( $fallback_value ) ) :
- $ret = $fallback_value;
- endif;
- return $ret;
- } /* SyndicatedLink::setting() */
-
- /**
- * Merge current settings with array of additional data.
- *
- * @param array $data Settings to merge with.
- * @param string $prefix Hierarchical prefix (e.g. "feed/" as in "feed/a/b/c").
- * @param string $separator Hierarchy separator (e.g., '/').
- *
- * @see SyndicatedLink::flatten_array()
- */
- public function merge_settings( $data, $prefix, $separator = '/' ) {
- $dd = $this->flatten_array( $data, $prefix, $separator );
- $this->settings = array_merge( $this->settings, $dd );
- } /* SyndicatedLink::merge_settings () */
-
- public function update_setting( $name, $value, $default = 'default' ) {
- if ( !is_null( $value ) and $value != $default ) :
- $this->settings[ $name ] = $value;
- else : // Zap it.
- unset( $this->settings[ $name ] );
- endif;
- } /* SyndicatedLink::update_setting () */
-
- public function is_non_incremental () {
- return ('complete'==$this->setting('update_incremental', 'update_incremental', 'incremental'));
- } /* SyndicatedLink::is_non_incremental () */
-
- public function get_feed_type () {
- $type_code = $this->setting('link/feed_type');
-
- // list derived from: , retrieved 2020/01/18
- $bitmasks = array(
- SIMPLEPIE_TYPE_RSS_090 => 'RSS 0.90',
- SIMPLEPIE_TYPE_RSS_091_NETSCAPE => 'RSS 0.91 (Netscape)',
- SIMPLEPIE_TYPE_RSS_091_USERLAND => 'RSS 0.91 (Userland)',
- SIMPLEPIE_TYPE_RSS_091 => 'RSS 0.91',
- SIMPLEPIE_TYPE_RSS_092 => 'RSS 0.92',
- SIMPLEPIE_TYPE_RSS_093 => 'RSS 0.93',
- SIMPLEPIE_TYPE_RSS_094 => 'RSS 0.94',
- SIMPLEPIE_TYPE_RSS_10 => 'RSS 1.0',
- SIMPLEPIE_TYPE_RSS_20 => 'RSS 2.0.x',
- SIMPLEPIE_TYPE_RSS_RDF => 'RDF-based RSS',
- SIMPLEPIE_TYPE_RSS_SYNDICATION => 'Non-RDF-based RSS',
- SIMPLEPIE_TYPE_RSS_ALL => 'Any version of RSS',
- SIMPLEPIE_TYPE_ATOM_03 => 'Atom 0.3',
- SIMPLEPIE_TYPE_ATOM_10 => 'Atom 1.0',
- SIMPLEPIE_TYPE_ATOM_ALL => 'Atom (any version)',
- SIMPLEPIE_TYPE_ALL => 'Supported Feed (unspecified format)',
- );
-
- $type = "Unknown or unsupported format";
- foreach ($bitmasks as $format_flag => $format_string) :
- if (is_numeric($format_flag)) : // Guard against failure of constants to be defined.
- if ($type_code & $format_flag) :
- $type = $format_string;
- break; // foreach
- endif;
- endif;
- endforeach;
-
- return $type;
- } /* SyndicatedLink::get_feed_type () */
-
- public function uri ($params = array()) {
- $params = wp_parse_args($params, array(
- 'add_params' => false,
- 'fetch' => false,
- ));
-
- // Initialize $qp (= array for added query parameters, if any)
- $qp = array();
-
- $link_rss = (is_object($this->link) ? $this->link->link_rss : NULL);
-
- // $link_rss stores the URI for the subscription as stored in the feed's record.
- // $uri stores the effective URI of the request including any/all added query parameters
- $uri = $link_rss;
- if ( !is_null($uri) and strlen($uri) > 0 and $params['add_params']) :
- $qp = maybe_unserialize($this->setting('query parameters', array()));
-
- // For high-tech HTTP feed request kung fu
- $qp = apply_filters('syndicated_feed_parameters', $qp, $uri, $this);
-
- // $qp is an array of key-value pairs stored as arrays of format [$key, $value]
- $q = array();
- if (is_array($qp) and count($qp) > 0) :
- foreach ($qp as $pair) :
- $q[] = urlencode($pair[0]).'='.urlencode($pair[1]);
- endforeach;
-
- // Are we appending to a URI that already has params?
- $sep = ((strpos($uri, "?")===false) ? '?' : '&');
-
- // Tack it on
- $uri .= $sep . implode("&", $q);
- endif;
- endif;
-
- // Do we have any filters that apply here?
- $uri = apply_filters('syndicated_link_uri', $uri, $link_rss, $qp, $params, $this);
-
- // Return the filtered link URI.
- return $uri;
- } /* SyndicatedLink::uri () */
-
- public function username () {
- return $this->setting('http username', 'http_username', NULL);
- } /* SyndicatedLink::username () */
-
- public function password () {
- return $this->setting('http password', 'http_password', NULL);
- } /* SyndicatedLink::password () */
-
- public function authentication_method () {
- // Retrieve the authentication method from settings
- $auth = $this->setting( 'http auth method', null );
-
- // If the value is '-' or empty, treat it as NULL
- if ( '-' === $auth || empty( $auth ) ) {
- $auth = null;
- }
-
- return $auth;
- } /* SyndicatedLink::authentication_method () */
-
- var $postmeta = array();
- public function postmeta ($params = array()) {
- $params = wp_parse_args($params, /*defaults=*/ array(
- "field" => NULL,
- "parsed" => false,
- "force" => false,
- ));
-
- if ($params['force'] or !isset($this->postmeta[/*parsed = */ false])) :
- // First, get the global settings.
- $default_custom_settings = get_option('feedwordpress_custom_settings');
- if ($default_custom_settings and !is_array($default_custom_settings)) :
- $default_custom_settings = unserialize($default_custom_settings);
- endif;
- if ( !is_array($default_custom_settings)) :
- $default_custom_settings = array();
- endif;
-
- // Next, get the settings for this particular feed.
- $custom_settings = $this->setting('postmeta', NULL, NULL);
- if ($custom_settings and !is_array($custom_settings)) :
- $custom_settings = unserialize($custom_settings);
- endif;
- if ( !is_array($custom_settings)) :
- $custom_settings = array();
- endif;
-
- $this->postmeta[/*parsed=*/ false] = array_merge($default_custom_settings, $custom_settings);
- $this->postmeta[/*parsed=*/ true] = array();
-
- // Now, run through and parse them all.
- foreach ($this->postmeta[/*parsed=*/ false] as $key => $meta) :
- $meta = apply_filters("syndicated_link_post_meta_{$key}_pre", $meta, $this);
- $this->postmeta[/*parsed=*/ false][$key] = $meta;
- $this->postmeta[/*parsed=*/ true][$key] = new FeedWordPressParsedPostMeta($meta);
- endforeach;
- endif;
-
- $ret = $this->postmeta[!! $params['parsed']];
- if (is_string($params['field'])) :
- $ret = $ret[$params['field']];
- endif;
- return $ret;
- } /* SyndicatedLink::postmeta () */
-
- public function property_cascade ($fromFeed, $link_field, $setting, $method) {
- $value = NULL;
- if ($fromFeed) :
- $value = $this->setting($setting, NULL, NULL, NULL);
-
- $simp_pie = $this->simplepie;
- $callable = ( is_object( $simp_pie ) and method_exists( $simp_pie, $method ) );
- if ( is_null( $value ) and $callable ) :
-// $fallback = $s->{$method}(); // what is fallback supposed to be here? (gwyneth 20230816)
- $value = $simp_pie->{$method}(); // makes more sense this way!
- endif;
- else :
- $value = $this->link->{$link_field};
- endif;
- return $value;
- } /* SyndicatedLink::property_cascade () */
-
- public function homepage ($fromFeed = true) {
- return $this->property_cascade($fromFeed, 'link_url', 'feed/link', 'get_link');
- } /* SyndicatedLink::homepage () */
-
- public function name ($fromFeed = true) {
- return $this->property_cascade($fromFeed, 'link_name', 'feed/title', 'get_title');
- } /* SyndicatedLink::name () */
-
- public function guid () {
- $ret = $this->setting('feed/id', NULL, $this->uri());
-
- // If we can get it live from the feed, do so.
- if (is_object($this->simplepie)) :
- $search = array(
- array('', 'id'),
- array(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'),
- array(SIMPLEPIE_NAMESPACE_ATOM_03, 'id'),
- array(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'),
- array(SIMPLEPIE_NAMESPACE_DC_11, 'identifier'),
- array(SIMPLEPIE_NAMESPACE_DC_10, 'identifier'),
- );
-
- foreach ($search as $pair) :
- if ($id_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
- $ret = $id_tags[0]['data'];
- break;
- elseif ($id_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
- $ret = $id_tags[0]['data'];
- break;
- endif;
- endforeach;
- endif;
- return $ret;
- }
-
- public function links ($params = array()) {
- $params = wp_parse_args($params, array(
- "rel" => NULL,
- ));
-
- $fLinks = array();
- $search = array(
- array('', 'link'),
- array(SIMPLEPIE_NAMESPACE_ATOM_10, 'link'),
- array(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'),
- );
-
- foreach ($search as $pair) :
- if ($link_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
- $fLinks = array_merge($fLinks, $link_tags);
- endif;
- if ($link_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
- $fLinks = array_merge($fLinks, $link_tags);
- endif;
- endforeach;
-
- $ret = array();
- foreach ($fLinks as $link) :
- $filter = false;
- if ( !is_null($params['rel'])) :
- $filter = true;
-
- if (isset($link['attribs'])) :
- // Get a list of NSes from the search
- foreach ($search as $pair) :
- $ns = $pair[0];
-
- if (isset($link['attribs'][$ns])
- and isset($link['attribs'][$ns]['rel'])
- ) :
- $rel = strtolower(trim($link['attribs'][$ns]['rel']));
- $fRel = strtolower(trim($params['rel']));
-
- if ($rel == $fRel) :
- $filter = false;
- endif;
- endif;
- endforeach;
- endif;
- endif;
-
- if ( ! $filter) :
- $ret[] = $link;
- endif;
- endforeach;
-
- return $ret;
- }
-
- public function ttl ($return_element = false) {
- if (is_object($this->magpie)) :
- $channel = $this->magpie->channel;
- else :
- $channel = array();
- endif;
-
- if (isset($channel['ttl'])) :
- // "ttl stands for time to live. It's a number of
- // minutes that indicates how long a channel can be
- // cached before refreshing from the source."
- //
- $xml = 'rss:ttl';
- $ret = $channel['ttl'];
- elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
- $period_minutes = array (
- 'hourly' => 60, /* minutes in an hour */
- 'daily' => 1440, /* minutes in a day */
- 'weekly' => 10080, /* minutes in a week */
- 'monthly' => 43200, /* minutes in a month */
- 'yearly' => 525600, /* minutes in a year */
- );
-
- // "sy:updatePeriod: Describes the period over which the
- // channel format is updated. Acceptable values are:
- // hourly, daily, weekly, monthly, yearly. If omitted,
- // daily is assumed."
- if (isset($channel['sy']['updateperiod'])) : $period = trim($channel['sy']['updateperiod']);
- else : $period = 'daily';
- endif;
-
- // "sy:updateFrequency: Used to describe the frequency
- // of updates in relation to the update period. A
- // positive integer indicates how many times in that
- // period the channel is updated. ... If omitted a value
- // of 1 is assumed."
- if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
- else : $freq = 1;
- endif;
-
- // normalize the period name...
- $period = strtolower(trim($period));
-
- // do we recognize the alphanumeric period name? if not, then guess
- // a responsible default, e.g. roughly hourly
- $mins = (isset($period_minutes[$period]) ? $period_minutes[$period] : 67);
-
- $xml = 'sy:updateFrequency';
- $ret = (int) ($mins / $freq);
-
- else :
- $xml = NULL;
- $ret = NULL;
- endif;
-
- if ('yes'==$this->setting('update/minimum', 'update_minimum', 'no')) :
- $min = (int) $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
-
- if ($min > $ret) :
- $ret = NULL;
- endif;
- endif;
- return ( $return_element ? array( $ret, $xml ) : $ret );
- } /* SyndicatedLink::ttl() */
-
- /**
- * Sets the automatic time interval for
- *
- * @return
- */
- public function automatic_ttl() {
- // spread out over a time interval for staggered updates
- $updateWindow = $this->setting( 'update/window', 'update_window', DEFAULT_UPDATE_PERIOD );
- if ( ! is_numeric( $updateWindow ) or ( $updateWindow < 1 ) ) :
- $updateWindow = DEFAULT_UPDATE_PERIOD;
- endif;
-
- // We get a fudge of 1/3 of window from elsewhere. We'll do some more
- // fudging here.
- $fudgedInterval = $updateWindow + rand( - ( $updateWindow / 6 ), 5 * ( $updateWindow / 12 ) );
- return apply_filters( 'syndicated_feed_automatic_ttl', $fudgedInterval, $this );
- } /* SyndicatedLink::automatic_ttl() */
-
- /**
- * SyndicatedLink::flatten_array (): flatten an array. Useful for
- * hierarchical and namespaced elements.
- *
- * Given an array which may contain array or object elements in it,
- * return a "flattened" array: a one-dimensional array of scalars
- * containing each of the scalar elements contained within the array
- * structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
- * returned array for FeedWordPress::flatten_array($a) will contain a key
- * $a['feed/b/c/d'] with value 'e'.
- *
- * @param array $arr Array to flatten.
- * @param string $prefix Hierarchical prefix (e.g. "feed/" as in "feed/a/b/c").
- * @param string $separator Hierarchy separator (e.g., '/').
- * @return array Flattened one-dimensional array.
- */
- public function flatten_array( $arr, $prefix = 'feed/', $separator = '/' ) {
- $ret = array();
- if ( is_array( $arr ) ) :
- foreach ( $arr as $key => $value ) :
- if ( is_scalar( $value ) ) :
- $ret[ $prefix.$key ] = $value;
- else :
- $ret = array_merge( $ret, $this->flatten_array( $value, $prefix.$key.$separator, $separator ) );
- endif;
- endforeach;
- endif;
- return $ret;
- } /* SyndicatedLink::flatten_array() */
-
- /**
- * Check if a setting is hardcoded or not.
- *
- * @param string $what The setting we're checking for.
- *
- * @return bool True if the setting's value is 'yes', false otherwise.
- */
- public function hardcode( $what ) {
- return 'yes' == $this->setting( 'hardcode ' . $what, 'hardcode_' . $what, NULL );
- // Simplified logic. (gwyneth 20230920)
- } /* SyndicatedLink::hardcode () */
-
- /**
- * Returns the syndicated status for one item.
- *
- * @param string $what Item's name.
- * @param mixed $default Default value for this item.
- * @param boolean $fallback If true, falls back to global setting.
- *
- * @return string SQL-ready escaped status.
- */
- public function syndicated_status( $what, $default, $fallback = true ) {
- $g_set = ( $fallback ? 'syndicated_' . $what . '_status' : NULL );
- $ret = $this->setting( $what . ' status', $g_set, $default );
-
- return esc_sql( trim( strtolower( $ret ) ) );
- } /* SyndicatedLink:syndicated_status () */
-
- public function taxonomies () {
- $post_type = $this->setting('syndicated post type', 'syndicated_post_type', 'post');
- return get_object_taxonomies(array('object_type' => $post_type), 'names');
- } /* SyndicatedLink::taxonomies () */
-
- /**
- * category_ids: look up (and create) category ids from a list of
- * categories
- *
- * @param array $cats
- * @param string $unfamiliar_category
- * @param array|null $taxonomies
- * @return array
- */
- public function category_ids ($post, $cats, $unfamiliar_category = 'create', $taxonomies = NULL, $params = array()) {
- $singleton = (isset($params['singleton']) ? $params['singleton'] : true);
- $allowFilters = (isset($params['filters']) ? $params['filters'] : false);
-
- $catTax = 'category';
-
- if (is_null($taxonomies)) :
- $taxonomies = array('category');
- endif;
-
- // We need to normalize whitespace because (1) trailing
- // whitespace can cause PHP and MySQL not to see eye to eye on
- // VARCHAR comparisons for some versions of MySQL (cf.
- // ), and (2)
- // because I doubt most people want to make a semantic
- // distinction between 'Computers' and 'Computers '
- $cats = array_map('trim', $cats);
-
- $terms = array();
- foreach ($taxonomies as $tax) :
- $terms[$tax] = array();
- endforeach;
-
- foreach ($cats as $cat_name) :
- if (strlen(trim($cat_name)) < 1) :
- continue;
- endif;
-
- $oTerm = new SyndicatedPostTerm($cat_name, $taxonomies, $post);
-
- if ($oTerm->is_familiar()) :
-
- $tax = $oTerm->taxonomy();
- if ( !isset($terms[$tax])) :
- $terms[$tax] = array();
- endif;
- $terms[$tax][] = $oTerm->id();
-
- else :
-
- if ('tag'==$unfamiliar_category) :
- $unfamiliar_category = 'create:post_tag';
- endif;
-
- if (preg_match('/^create(:(.*))?$/i', $unfamiliar_category, $ref)) :
- $tax = $catTax; // Default
-
- if (isset($ref[2])
- and strlen($ref[2]) > 2) :
- $tax = $ref[2];
- endif;
-
- $inserted = $oTerm->insert($tax);
- if ( !is_null($inserted)) :
- if ( !isset($terms[$tax])) :
- $terms[$tax] = array();
- endif;
- $terms[$tax][] = $inserted;
- else :
-
- endif; // !is_null($inserted)
- endif; // preg_match(...)
-
- endif; /* ($oTerm->is_familiar()) */
- endforeach;
-
- $filtersOn = $allowFilters;
- if ($allowFilters) :
- $filters = array_filter(
- $this->setting('match/filter', 'match_filter', array()),
- 'remove_dummy_zero'
- );
- $filtersOn = ($filtersOn and is_array($filters) and (count($filters) > 0));
- endif;
-
- // Check for filter conditions
- foreach ($terms as $tax => $term_ids) :
- if ($filtersOn
- and (count($term_ids)==0)
- and in_array($tax, $filters)) :
- $terms = NULL; // Drop the post
- break;
- else :
- $terms[$tax] = array_unique($term_ids);
- endif;
- endforeach;
-
- if ($singleton and count($terms)==1) : // If we only searched one, just return the term IDs
- $terms = end($terms);
- endif;
-
- FeedWordPress::diagnostic(
- 'syndicated_posts:categories',
- 'Category: MAPPED term names '.json_encode($cats).' to IDs: '.json_encode($terms)
- );
- return $terms;
- } /* SyndicatedLink::category_ids () */
-} /* class SyndicatedLink */
-
+ var $id = null;
+ var $link = null;
+ var $settings = array ();
+ public $simplepie = null;
+ var $magpie = null;
+
+ public function __construct( $link ) {
+ if (is_object($link)) :
+ $this->link = $link;
+ $this->id = $link->link_id;
+ else :
+ $this->id = $link;
+ $this->link = get_bookmark($link);
+ endif;
+
+ if (is_object($this->link)) :
+ if (strlen($this->link->link_rss) > 0) :
+ $this->get_settings_from_notes();
+ endif;
+ endif;
+
+ add_filter('feedwordpress_update_complete', array($this, 'process_retirements'), 1000, 1);
+ } /* SyndicatedLink::__construct () */
+
+ public function found () {
+ return is_object($this->link) and !is_wp_error($this->link);
+ } /* SyndicatedLink::found () */
+
+ public function id () {
+ return (is_object($this->link) ? $this->link->link_id : NULL);
+ }
+
+ public function stale () {
+ global $feedwordpress;
+
+ $stale = true;
+ if ($this->setting('update/hold')=='ping') :
+ $stale = false; // don't update on any timed updates; pings only
+ elseif ($this->setting('update/hold')=='next') :
+ $stale = true; // update on the next timed update
+ elseif ( ! $this->setting('update/last') ) :
+ $stale = true; // initial update
+ elseif ( !empty($feedwordpress) and $feedwordpress->force_update_all()) :
+ $stale = true; // forced general updating
+ else :
+ $after = (
+ (int) $this->setting('update/last')
+ + (int) $this->setting('update/fudge')
+ + ((int) $this->setting('update/ttl') * 60)
+ );
+ $stale = (time() >= $after);
+ endif;
+ return $stale;
+ } /* SyndicatedLink::stale () */
+
+ public function fetch () {
+ $timeout = $this->setting('fetch timeout', 'feedwordpress_fetch_timeout', FEEDWORDPRESS_FETCH_TIMEOUT_DEFAULT);
+
+ $this->simplepie = apply_filters(
+ 'syndicated_feed',
+ FeedWordPress::fetch($this, array('timeout' => $timeout)),
+ $this
+ );
+
+ // Filter compatibility mode
+ if (is_wp_error($this->simplepie)) :
+ $this->magpie = $this->simplepie;
+ else :
+ $this->magpie = new MagpieFromSimplePie($this->simplepie, NULL);
+ endif;
+ } /* SyndicatedLink::fetch () */
+
+ public function live_posts () {
+ if ( !is_object($this->simplepie)) :
+ $this->fetch();
+ endif;
+
+ if (is_object($this->simplepie) and method_exists($this->simplepie, 'get_items')) :
+ $ret = apply_filters(
+ 'syndicated_feed_items',
+ $this->simplepie->get_items(),
+ $this
+ );
+ else :
+ $ret = $this->simplepie;
+ endif;
+ return $ret;
+ } /* SyndicatedLink::live_posts () */
+
+ protected function pause_updates () {
+ return ('yes'==$this->setting("update/pause", "update_pause", 'no'));
+ } /* SyndicatedLink::pause_updates () */
+
+ /**
+ * Polls all feeds to see if they are ready for updating.
+ *
+ * @param int|null $crash_ts (Optional) timestamp for testing if feed should be updated.
+ *
+ * @return array Returns an associative array with keys 'new' (new posts), 'updated' (existing posts that were updated), and 'stored' (unknown).
+ */
+ public function poll( $crash_ts = NULL ) {
+ global $wpdb;
+
+ $url = $this->uri(array('add_params' => true, 'fetch' => true));
+ FeedWordPress::diagnostic('updated_feeds', 'Polling feed ['.$url.']');
+
+ $this->fetch();
+
+ $new_count = array(); // it's better to allocate as an empty array, saves some error-checking later on
+
+ $resume = ('yes'==$this->setting('update/unfinished'));
+ if ($resume) :
+ // pick up where we left off
+ $processed = array_map('trim', explode("\n", $this->setting('update/processed')));
+ else :
+ // begin at the beginning
+ $processed = array();
+ endif;
+
+ if (is_wp_error($this->simplepie)) :
+ $new_count = $this->simplepie;
+ // Error; establish an error setting.
+ $theError = array();
+ $theError['ts'] = time();
+ $theError['since'] = time();
+ $theError['object'] = $this->simplepie;
+
+ $oldError = $this->setting('update/error', NULL, NULL);
+ if (is_string($oldError)) :
+ $oldError = unserialize($oldError);
+ endif;
+
+ if ( !is_null($oldError)) :
+ // Copy over the in-error-since timestamp
+ $theError['since'] = $oldError['since'];
+
+ // If this is a repeat error, then we should
+ // take a step back before we try to fetch it
+ // again.
+ $this->update_setting('update/last', time(), NULL);
+ $ttl = $this->automatic_ttl();
+ $ttl = apply_filters('syndicated_feed_ttl', $ttl, $this);
+ $ttl = apply_filters('syndicated_feed_ttl_from_error', $ttl, $this);
+ $this->update_setting('update/ttl', $ttl);
+ $this->update_setting('update/timed', 'automatically');
+ endif;
+
+ do_action('syndicated_feed_error', $theError, $oldError, $this);
+
+ $this->update_setting('update/error', serialize($theError));
+ $this->save_settings(/*reload=*/ true);
+
+ elseif (is_object($this->simplepie)) :
+
+ // Success; clear out error setting, if any.
+ $this->update_setting('update/error', NULL);
+
+ $new_count = array('new' => 0, 'updated' => 0, 'stored' => 0);
+
+ # -- Update Link metadata live from feed
+ $channel = $this->magpie->channel;
+
+ if ( !isset($channel['id'])) :
+ $channel['id'] = $this->link->link_rss;
+ endif;
+
+ $update = array();
+ if ( ! $this->hardcode('url') and isset($channel['link'])) :
+ $update[] = "link_url = '".esc_sql($channel['link'])."'";
+ endif;
+
+ if ( ! $this->hardcode('name') and isset($channel['title'])) :
+ $update[] = "link_name = '".esc_sql($channel['title'])."'";
+ endif;
+
+ if ( ! $this->hardcode('description')) :
+ if (isset($channel['tagline'])) :
+ $update[] = "link_description = '".esc_sql($channel['tagline'])."'";
+ elseif (isset($channel['description'])) :
+ $update[] = "link_description = '".esc_sql($channel['description'])."'";
+ endif;
+ endif;
+
+ $this->update_setting('link/feed_type', $this->simplepie->get_type());
+
+ $this->merge_settings($channel, 'feed/');
+
+ $this->update_setting('update/last', time());
+ $this->do_update_ttl();
+
+ if ( ! $this->setting('update/hold') != 'ping') :
+ $this->update_setting('update/hold', 'scheduled');
+ endif;
+
+ $this->update_setting('update/unfinished', 'yes');
+
+ $update[] = "link_notes = '".esc_sql($this->settings_to_notes())."'";
+
+ $update_set = implode(',', $update);
+
+ // Update the properties of the link from the feed information
+ $result = $wpdb->query("
+ UPDATE $wpdb->links
+ SET $update_set
+ WHERE link_id='$this->id'
+ ");
+ do_action('update_syndicated_feed', $this->id, $this);
+
+ # -- Add new posts from feed and update any updated posts
+ $crashed = false;
+
+ $posts = $this->live_posts();
+
+ $this->magpie->originals = $posts;
+
+ // If this is a complete feed, rather than an incremental feed, we
+ // need to prepare to mark everything for presumptive retirement.
+ if ($this->is_non_incremental()) :
+ $q = new WP_Query(array(
+ 'fields' => '_synfrom',
+ 'post_status__not' => 'fwpretired',
+ 'ignore_sticky_posts' => true,
+ 'meta_key' => 'syndication_feed_id',
+ 'meta_value' => $this->id,
+ ));
+ foreach ($q->posts as $p) :
+ update_post_meta($p->ID, '_feedwordpress_retire_me_'.$this->id, '1');
+ endforeach;
+ endif;
+
+ if (is_array($posts)) :
+ foreach ($posts as $key => $item) :
+ if ( ! $this->pause_updates()) :
+ $post = new SyndicatedPost($item, $this);
+
+ if ( ! $resume or !in_array(trim($post->guid()), $processed)) :
+ $processed[] = $post->guid();
+ if ( ! $post->filtered()) :
+ $new = $post->store();
+ if ( $new !== false ) $new_count[$new]++;
+ endif;
+
+ if ( ! is_null( $crash_ts ) and ( time() > $crash_ts ) ) :
+ $crashed = true;
+ break;
+ endif;
+ // What if $crash_ts _is_ null? What happens then? (gwyneth 20230924)
+ endif;
+
+ unset($post);
+ endif;
+ endforeach;
+ endif;
+
+ if ('yes'==$this->setting('tombstones', 'tombstones', 'yes')) :
+ // Check for use of Atom tombstones. Spec:
+ //
+ $tombstones = $this->simplepie->get_feed_tags('http://purl.org/atompub/tombstones/1.0', 'deleted-entry');
+ if ( !is_null($tombstones) && count($tombstones) > 0) :
+ foreach ($tombstones as $tombstone) :
+ $ref = NULL;
+ foreach (array('', 'http://purl.org/atompub/tombstones/1.0') as $ns) :
+ if (isset($tombstone['attribs'][$ns])
+ and isset($tombstone['attribs'][$ns]['ref'])) :
+ $ref = $tombstone['attribs'][$ns]['ref'];
+ endif;
+ endforeach;
+
+ $q = new WP_Query(array(
+ 'ignore_sticky_posts' => true,
+ 'guid' => $ref,
+ 'meta_key' => 'syndication_feed_id',
+ 'meta_value' => $this->id, // Only allow a feed to tombstone its own entries.
+ ));
+
+ foreach ($q->posts as $p) :
+ $old_status = $p->post_status;
+ FeedWordPress::diagnostic('syndicated_posts', 'Retiring existing post # '.$p->ID.' "'.$p->post_title.'" due to Atom tombstone element in feed.');
+ set_post_field('post_status', 'fwpretired', $p->ID);
+ wp_transition_post_status('fwpretired', $old_status, $p);
+ endforeach;
+
+ endforeach;
+ endif;
+ endif;
+
+ $suffix = ($crashed ? 'crashed' : 'completed');
+ do_action('update_syndicated_feed_items', $this->id, $this);
+ do_action("update_syndicated_feed_items_{$suffix}", $this->id, $this);
+
+ $this->update_setting('update/processed', $processed);
+ if ( ! $crashed) :
+ $this->update_setting('update/unfinished', 'no');
+ endif;
+ $this->update_setting('link/item count', count($posts));
+
+ // Copy back any changes to feed settings made in the
+ // course of updating (e.g. new author rules)
+ $update_set = "link_notes = '" . esc_sql($this->settings_to_notes())."'";
+
+ // Update the properties of the link from the feed information
+ $result = $wpdb->query("
+ UPDATE {$wpdb->links}
+ SET {$update_set}
+ WHERE link_id='{$this->id}'
+ ");
+ if ( FALSE === $result ) :
+ FeedWordPress::diagnostic(
+ 'SyndicatedLink/poll',
+ 'Database update failed',
+ );
+ endif;
+
+ do_action("update_syndicated_feed_completed", $this->id, $this);
+ endif;
+
+ // All done; let's clean up.
+ $this->magpie = NULL;
+
+ // Avoid circular-reference memory leak in PHP < 5.3.
+ // Cf.
+ if (method_exists($this->simplepie, '__destruct')) :
+ $this->simplepie->__destruct();
+ endif;
+ $this->simplepie = NULL;
+
+ return $new_count;
+ } /* SyndicatedLink::poll() */
+
+ /**
+ * Update the time to live of this link.
+ */
+ public function do_update_ttl() {
+ list( $ttl, $xml ) = $this->ttl( /*return element=*/ true );
+
+ if ( ! is_null( $ttl ) ) :
+ $this->update_setting( 'update/ttl', $ttl );
+ $this->update_setting( 'update/xml', $xml );
+ $this->update_setting( 'update/timed', 'feed' );
+ else :
+ $ttl = $this->automatic_ttl();
+ $this->update_setting( 'update/ttl', $ttl );
+ $this->update_setting( 'update/xml', NULL );
+ $this->update_setting( 'update/timed', 'automatically' );
+ endif;
+
+ $this->update_setting( 'update/fudge', rand( 0, (int)( $ttl / 3 ) ) * 60 );
+
+ $this->update_setting(
+ 'update/ttl',
+ apply_filters(
+ 'syndicated_feed_ttl',
+ $this->setting( 'update/ttl' ),
+ $this
+ )
+ );
+ } /* SyndicatedLink::do_update_ttl () */
+
+ /**
+ * Retires old posts in the absence of a non-incremental feed.
+ *
+ * TODO: something here will sometimes cause WP_Query() to break (!). It seems to
+ * be related to an invalid/non-existent user id. The code, however, seems fine. Just
+ * added an extra diagnostic message in the case that $this->id seems incorrect.
+ * (gwyneth 20231017)
+ *
+ * @param mixed $delta Whatever this is, it's not used and is returned without change...
+ *
+ * @return mixed The value of $delta (unchanged?)
+ */
+ public function process_retirements( $delta ) {
+ FeedWordPress::diagnostic(
+ 'syndicated_posts:process_retirements',
+ '[DEBUG] \$this->id is now: ' . $this->id ?? '(most likely BROKEN)'
+ );
+
+ try {
+ $q = new WP_Query(
+ array(
+ 'fields' => '_synfrom',
+ 'post_status__not' => 'fwpretired',
+ 'ignore_sticky_posts' => true,
+ 'meta_key' => '_feedwordpress_retire_me_' . $this->id,
+ 'meta_value' => '1',
+ )
+ );
+ } catch ( Exception $e ) {
+ error_log( 'Creating a new WP_Query for processing retirements failed; is_user_logged_in() cannot be called at this stage; actual exception was: ', $e->getMessage(), "\n" );
+ return $delta; // I have no idea if this is the expected result or not... (gwyneth 20240210)
+ }
+ if ( $q->have_posts() ) :
+ foreach ( $q->posts as $p ) :
+ $old_status = $p->post_status;
+ FeedWordPress::diagnostic(
+ 'syndicated_posts:process_retirements',
+ 'Retiring existing post # ' . $p->ID . ' "' . $p->post_title
+ .' " due to absence from a non-incremental feed.' );
+ set_post_field( 'post_status', 'fwpretired', $p->ID );
+ wp_transition_post_status( 'fwpretired', $old_status, $p );
+ delete_post_meta( $p->ID, '_feedwordpress_retire_me_' . $this->id );
+ endforeach;
+ endif;
+
+ return $delta;
+ } /* SyndicatedLink::process_retirements() */
+
+ /**
+ * Updates the URL for the feed syndicated by this link.
+ *
+ * @param string $url The new feed URL to use for this source.
+ * @return bool TRUE on success, FALSE on failure.
+ */
+ public function set_uri ($url) {
+ global $wpdb;
+
+ if ($this->found()) :
+ // Update link_rss
+ $result = $wpdb->query("
+ UPDATE $wpdb->links
+ SET
+ link_rss = '".esc_sql($url)."'
+ WHERE link_id = '".esc_sql($this->id)."'
+ ");
+
+ $ret = ($result ? true : false);
+ else :
+ $ret = false;
+ endif;
+ return $ret;
+ } /* SyndicatedLink::set_uri () */
+
+ public function deactivate () {
+ global $wpdb;
+
+ $wpdb->query($wpdb->prepare("
+ UPDATE $wpdb->links SET link_visible = 'N' WHERE link_id = %d
+ ", (int) $this->id));
+ } /* SyndicatedLink::deactivate () */
+
+ /**
+ * SyndicatedLink::delete() deletes a subscription from the WordPress links
+ * table. Any posts that were syndicated through that subscription will still
+ * be present in the wp_posts table; but postmeta fields that refer to the
+ * syndication feed's numeric id (which will no longer be valid) will be
+ * deleted. For most purposes, the posts remaining will be treated as if they
+ * were locally authored posts rather than syndicated posts.
+ *
+ * @global $wpdb
+ * @uses wpdb::query
+ */
+ public function delete () {
+ global $wpdb;
+
+ $wpdb->query($wpdb->prepare("
+ DELETE FROM $wpdb->postmeta WHERE meta_key='syndication_feed_id'
+ AND meta_value = '%s'
+ ", $this->id));
+
+ $wpdb->query($wpdb->prepare("
+ DELETE FROM $wpdb->links WHERE link_id = %d
+ ", (int) $this->id));
+
+ $this->id = NULL;
+ } /* SyndicatedLink::delete () */
+
+ /**
+ * SyndicatedLink::nuke() deletes a subscription AND all of the
+ * posts syndicated through that subscription.
+ *
+ * @global $wpdb
+ * @uses wpdb::get_col
+ * @uses wp_delete_post
+ * @uses SyndicatedLink::delete
+ */
+ public function nuke () {
+ global $wpdb;
+
+ // Make a list of the items syndicated from this feed...
+ $post_ids = $wpdb->get_col($wpdb->prepare("
+ SELECT post_id FROM $wpdb->postmeta
+ WHERE meta_key = 'syndication_feed_id'
+ AND meta_value = '%s'
+ ", $this->id));
+
+ // ... and kill them all
+ if (count($post_ids) > 0) :
+ foreach ($post_ids as $post_id) :
+ // Force scrubbing of deleted post
+ // rather than sending to Trashcan
+ wp_delete_post(
+ /*postid=*/ $post_id,
+ /*force_delete=*/ true
+ );
+ endforeach;
+ endif;
+
+ $this->delete();
+ } /* SyndicatedLink::nuke () */
+
+ public function map_name_to_new_user( $name, $newuser_name ) {
+ global $wpdb;
+
+ if (strlen($newuser_name) > 0) :
+ $newuser_id = fwp_insert_new_user($newuser_name);
+ if (is_numeric($newuser_id)) :
+ if (is_null($name)) : // Unfamiliar author
+ $this->update_setting('unfamiliar author', $newuser_id);
+ else :
+ $map = $this->setting('map authors');
+ $map['name'][$name] = $newuser_id;
+ $this->update_setting('map authors', $map);
+ endif;
+ else :
+ // TODO: Add some error detection and reporting
+ endif;
+ else :
+ // TODO: Add some error reporting
+ endif;
+ } /* SyndicatedLink::map_name_to_new_user () */
+
+ protected function imploded_settings () {
+ return array('cats', 'tags', 'match/cats', 'match/tags', 'match/filter');
+ } /* SyndicatedLink::imploded_settings () */
+
+ protected function get_settings_from_notes () {
+ // Read off feed settings from link_notes
+ $notes = explode("\n", $this->link->link_notes);
+ foreach ($notes as $note):
+ $pair = explode(": ", $note, 2);
+ $key = (isset($pair[0]) ? $pair[0] : null);
+ $value = (isset($pair[1]) ? $pair[1] : null);
+ if ( !is_null($key) and !is_null($value)) :
+ // Unescape and trim() off the whitespace.
+ // Thanks to Ray Lischner for pointing out the
+ // need to trim off whitespace.
+ $this->settings[$key] = stripcslashes (trim($value));
+ endif;
+ endforeach;
+
+ // "Magic" feed settings
+ $this->settings['link/uri'] = $this->link->link_rss;
+ $this->settings['link/name'] = $this->link->link_name;
+ $this->settings['link/id'] = $this->link->link_id;
+
+ // `hardcode categories` and `unfamiliar categories` are
+ // deprecated in favor of `unfamiliar category`
+ if (
+ isset($this->settings['unfamiliar categories'])
+ and !isset($this->settings['unfamiliar category'])
+ ) :
+ $this->settings['unfamiliar category'] = $this->settings['unfamiliar categories'];
+ endif;
+ if (
+ FeedWordPress::affirmative($this->settings, 'hardcode categories')
+ and !isset($this->settings['unfamiliar category'])
+ ) :
+ $this->settings['unfamiliar category'] = 'default';
+ endif;
+
+ // Set this up automagically for del.icio.us
+ $bits = parse_url($this->link->link_rss);
+ $tagspacers = array('del.icio.us', 'feeds.delicious.com');
+ if ( !isset($this->settings['cat_split']) and in_array($bits['host'], $tagspacers)) :
+ $this->settings['cat_split'] = '\s'; // Whitespace separates multiple tags in del.icio.us RSS feeds
+ endif;
+
+ // Simple lists
+ foreach ($this->imploded_settings() as $what) :
+ if (isset($this->settings[$what])):
+ $this->settings[$what] = explode(
+ FEEDWORDPRESS_CAT_SEPARATOR,
+ $this->settings[$what]
+ );
+ endif;
+ endforeach;
+
+ if (isset($this->settings['terms'])) :
+ // Look for new format
+ $this->settings['terms'] = maybe_unserialize($this->settings['terms']);
+
+ if ( !is_array($this->settings['terms'])) :
+ // Deal with old format instead. Ugh.
+
+ // Split on two *or more* consecutive breaks
+ // because in the old format, a taxonomy
+ // without any associated terms would
+ // produce tax_name#1\n\n\ntax_name#2\nterm,
+ // and the naive split on the first \n\n
+ // would screw up the tax_name#2 list.
+ //
+ // Props to David Morris for pointing this
+ // out.
+
+ $this->settings['terms'] = preg_split(
+ "/".FEEDWORDPRESS_CAT_SEPARATOR."{2,}/",
+ $this->settings['terms']
+ );
+ $terms = array();
+ foreach ($this->settings['terms'] as $line) :
+ $line = explode(FEEDWORDPRESS_CAT_SEPARATOR, $line);
+ $tax = array_shift($line);
+ $terms[$tax] = $line;
+ endforeach;
+ $this->settings['terms'] = $terms;
+ endif;
+ endif;
+
+ if (isset($this->settings['map authors'])) :
+ $author_rules = explode("\n\n", $this->settings['map authors']);
+ $ma = array();
+ foreach ($author_rules as $rule) :
+ list($rule_type, $author_name, $author_action) = explode("\n", $rule);
+
+ // Normalize for case and whitespace
+ $rule_type = strtolower(trim($rule_type));
+ $author_name = strtolower(trim($author_name));
+ $author_action = strtolower(trim($author_action));
+
+ $ma[$rule_type][$author_name] = $author_action;
+ endforeach;
+ $this->settings['map authors'] = $ma;
+ endif;
+
+ } /* SyndicatedLink::get_settings_from_notes () */
+
+ protected function settings_to_notes () {
+ $to_notes = $this->settings;
+
+ unset($to_notes['link/id']); // Magic setting; don't save
+ unset($to_notes['link/uri']); // Magic setting; don't save
+ unset($to_notes['link/name']); // Magic setting; don't save
+ unset($to_notes['hardcode categories']); // Deprecated
+ unset($to_notes['unfamiliar categories']); // Deprecated
+
+ // Collapse array settings
+ if (isset($to_notes['update/processed']) and (is_array($to_notes['update/processed']))) :
+ $to_notes['update/processed'] = implode("\n", $to_notes['update/processed']);
+ endif;
+
+ foreach ($this->imploded_settings() as $what) :
+ if (isset($to_notes[$what]) and is_array($to_notes[$what])) :
+ $to_notes[$what] = implode(
+ FEEDWORDPRESS_CAT_SEPARATOR,
+ $to_notes[$what]
+ );
+ endif;
+ endforeach;
+
+ if (isset($to_notes['terms']) and is_array($to_notes['terms'])) :
+ // Serialize it.
+ $to_notes['terms'] = serialize($to_notes['terms']);
+ endif;
+
+ // Collapse the author mapping rule structure back into a flat string
+ if (isset($to_notes['map authors'])) :
+ $ma = array();
+ foreach ($to_notes['map authors'] as $rule_type => $author_rules) :
+ foreach ($author_rules as $author_name => $author_action) :
+ $ma[] = $rule_type."\n".$author_name."\n".$author_action;
+ endforeach;
+ endforeach;
+ $to_notes['map authors'] = implode("\n\n", $ma);
+ endif;
+
+ $notes = '';
+ foreach ($to_notes as $key => $value) :
+ $notes .= $key . ": ". addcslashes($value, "\0..\37".'\\') . "\n";
+ endforeach;
+ return $notes;
+ } /* SyndicatedLink::settings_to_notes () */
+
+ public function save_settings ($reload = false) {
+ global $wpdb;
+
+ // Save channel-level meta-data
+ foreach (array('link_name', 'link_description', 'link_url') as $what) :
+ $alter[] = "{$what} = '".esc_sql($this->link->{$what})."'";
+ endforeach;
+
+ // Save settings to the notes field
+ $alter[] = "link_notes = '".esc_sql($this->settings_to_notes())."'";
+
+ // Update the properties of the link from settings changes, etc.
+ $update_set = implode(", ", $alter);
+
+ $result = $wpdb->query("
+ UPDATE $wpdb->links
+ SET $update_set
+ WHERE link_id='$this->id'
+ ");
+
+ if ( $result === false )
+ {
+ error_log("query failed: " . $wpdb->last_error);
+ }
+
+ if ($reload) :
+ // force reload of link information from DB
+ if (function_exists('clean_bookmark_cache')) :
+ clean_bookmark_cache($this->id);
+ endif;
+ endif;
+ } /* SyndicatedLink::save_settings () */
+
+ /**
+ * Retrieves the value of a setting, allowing for a global setting to be
+ * used as a fallback, or a constant value, or both.
+ *
+ * @param string $name The link setting key.
+ * @param mixed $fallback_global If the link setting is nonexistent or
+ * marked as a use-default value, fall back
+ * to the value of this global setting.
+ * @param mixed $fallback_value If the link setting and the global setting are
+ * nonexistent or marked as ause-default value,
+ * fall back to this constant value.
+ *
+ * @return mixed|null Should *not* return a boolean!!
+ */
+ public function setting( $name, $fallback_global = NULL, $fallback_value = NULL, $default = 'default' ) {
+ $ret = NULL;
+ if ( isset( $this->settings[ $name ] ) ) :
+ $ret = $this->settings[ $name ];
+ endif;
+
+ $no_value = (
+ is_null( $ret )
+ or ( is_string( $ret ) and strtolower( $ret ) == $default )
+ );
+
+ if ( $no_value and ! is_null( $fallback_global ) ) :
+ // Avoid duplication of this correction
+ $fallback_global = preg_replace( '/^feedwordpress_/', '', $fallback_global );
+
+ // Occasionally we'll get an array back. Convert it to a string
+ if ( is_array( $fallback_global ) && sizeof( $fallback_global ) ) :
+ $fallback_global = reset( $fallback_global );
+ endif;
+
+ if ( ! empty( $fallback_global ) ) :
+ $ret = get_option( 'feedwordpress_' . $fallback_global, /*default=*/ NULL );
+ endif;
+ endif;
+
+ $no_value = (
+ is_null( $ret )
+ or ( is_string( $ret ) and strtolower( $ret ) == $default )
+ );
+
+ if ( $no_value and ! is_null( $fallback_value ) ) :
+ $ret = $fallback_value;
+ endif;
+ return $ret;
+ } /* SyndicatedLink::setting() */
+
+ /**
+ * Merge current settings with array of additional data.
+ *
+ * @param array $data Settings to merge with.
+ * @param string $prefix Hierarchical prefix (e.g. "feed/" as in "feed/a/b/c").
+ * @param string $separator Hierarchy separator (e.g., '/').
+ *
+ * @see SyndicatedLink::flatten_array()
+ */
+ public function merge_settings( $data, $prefix, $separator = '/' ) {
+ $dd = $this->flatten_array( $data, $prefix, $separator );
+ $this->settings = array_merge( $this->settings, $dd );
+ } /* SyndicatedLink::merge_settings () */
+
+ /**
+ * Update an existing setting.
+ *
+ * @param string $name Key (name) of an existing setting.
+ * @param string $value New value for this seting.
+ * @param string $default If set equal to the $value, unsets this setting,
+ * restoring it to the default.
+ */
+ public function update_setting( $name, $value, $default = 'default' ) {
+ if ( !is_null( $value ) and $value != $default ) :
+ $this->settings[ $name ] = $value;
+ else : // Zap it.
+ unset( $this->settings[ $name ] );
+ endif;
+ } /* SyndicatedLink::update_setting () */
+
+ /**
+ * Check if the method of updating this RSS feed is incremental or complete.
+ *
+ * @return bool TRUE if complete, FALSE if incremental (or option not set).
+ */
+ public function is_non_incremental() {
+ return ( 'complete' == $this->setting( 'update_incremental', 'update_incremental', 'incremental' ) );
+ } /* SyndicatedLink::is_non_incremental () */
+
+ /**
+ * Returns the type of the RSS feed, as seen by SimplePie.
+ *
+ * @return string Human-readable description of the type.
+ */
+ public function get_feed_type() {
+ $type_code = $this->setting( 'link/feed_type' );
+
+ // list derived from: , retrieved 2020/01/18
+ $bitmasks = array(
+ SIMPLEPIE_TYPE_RSS_090 => 'RSS 0.90',
+ SIMPLEPIE_TYPE_RSS_091_NETSCAPE => 'RSS 0.91 (Netscape)',
+ SIMPLEPIE_TYPE_RSS_091_USERLAND => 'RSS 0.91 (Userland)',
+ SIMPLEPIE_TYPE_RSS_091 => 'RSS 0.91',
+ SIMPLEPIE_TYPE_RSS_092 => 'RSS 0.92',
+ SIMPLEPIE_TYPE_RSS_093 => 'RSS 0.93',
+ SIMPLEPIE_TYPE_RSS_094 => 'RSS 0.94',
+ SIMPLEPIE_TYPE_RSS_10 => 'RSS 1.0',
+ SIMPLEPIE_TYPE_RSS_20 => 'RSS 2.0.x',
+ SIMPLEPIE_TYPE_RSS_RDF => 'RDF-based RSS',
+ SIMPLEPIE_TYPE_RSS_SYNDICATION => 'Non-RDF-based RSS',
+ SIMPLEPIE_TYPE_RSS_ALL => 'Any version of RSS',
+ SIMPLEPIE_TYPE_ATOM_03 => 'Atom 0.3',
+ SIMPLEPIE_TYPE_ATOM_10 => 'Atom 1.0',
+ SIMPLEPIE_TYPE_ATOM_ALL => 'Atom (any version)',
+ SIMPLEPIE_TYPE_ALL => 'Supported Feed (unspecified format)',
+ );
+
+ $type = "Unknown or unsupported format";
+ foreach ( $bitmasks as $format_flag => $format_string ) :
+ if ( is_numeric( $format_flag ) ) : // Guard against failure of constants to be defined.
+ if ( $type_code & $format_flag ) :
+ $type = $format_string;
+ break; // foreach
+ endif;
+ endif;
+ endforeach;
+
+ return $type;
+ } /* SyndicatedLink::get_feed_type () */
+
+ public function uri ( $params = array() ) {
+ $params = wp_parse_args( $params, array(
+ 'add_params' => false,
+ 'fetch' => false,
+ ));
+
+ // Initialize $qp (= array for added query parameters, if any)
+ $qp = array();
+
+ $link_rss = (is_object($this->link) ? $this->link->link_rss : NULL);
+
+ // $link_rss stores the URI for the subscription as stored in the feed's record.
+ // $uri stores the effective URI of the request including any/all added query parameters
+ $uri = $link_rss;
+ if ( !is_null($uri) and strlen($uri) > 0 and $params['add_params']) :
+ $qp = maybe_unserialize($this->setting('query parameters', array()));
+
+ // For high-tech HTTP feed request kung fu
+ $qp = apply_filters('syndicated_feed_parameters', $qp, $uri, $this);
+
+ // $qp is an array of key-value pairs stored as arrays of format [$key, $value]
+ $q = array();
+ if (is_array($qp) and count($qp) > 0) :
+ foreach ($qp as $pair) :
+ $q[] = urlencode($pair[0]).'='.urlencode($pair[1]);
+ endforeach;
+
+ // Are we appending to a URI that already has params?
+ $sep = ((strpos($uri, "?")===false) ? '?' : '&');
+
+ // Tack it on
+ $uri .= $sep . implode("&", $q);
+ endif;
+ endif;
+
+ // Do we have any filters that apply here?
+ $uri = apply_filters('syndicated_link_uri', $uri, $link_rss, $qp, $params, $this);
+
+ // Return the filtered link URI.
+ return $uri;
+ } /* SyndicatedLink::uri () */
+
+ public function username () {
+ return $this->setting('http username', 'http_username', NULL);
+ } /* SyndicatedLink::username () */
+
+ public function password () {
+ return $this->setting('http password', 'http_password', NULL);
+ } /* SyndicatedLink::password () */
+
+ public function authentication_method () {
+ // Retrieve the authentication method from settings
+ $auth = $this->setting( 'http auth method' , null );
+
+ // If the value is '-' or empty, treat it as NULL
+ if ( '-' === $auth || empty( $auth ) ) {
+ $auth = null;
+ }
+
+ return $auth;
+ } /* SyndicatedLink::authentication_method () */
+
+ var $postmeta = array();
+ public function postmeta ($params = array()) {
+ $params = wp_parse_args($params, /*defaults=*/ array(
+ "field" => NULL,
+ "parsed" => false,
+ "force" => false,
+ ));
+
+ if ($params['force'] or !isset($this->postmeta[/*parsed = */ false])) :
+ // First, get the global settings.
+ $default_custom_settings = get_option('feedwordpress_custom_settings');
+ if (!empty($default_custom_settings) and !is_array($default_custom_settings)) :
+ $default_custom_settings = unserialize($default_custom_settings);
+ endif;
+ if ( !is_array($default_custom_settings)) :
+ $default_custom_settings = array();
+ endif;
+
+ // Next, get the settings for this particular feed.
+ $custom_settings = $this->setting('postmeta', NULL, NULL);
+ if (!empty($custom_settings) and !is_array($custom_settings)) :
+ $custom_settings = unserialize($custom_settings);
+ endif;
+ if ( !is_array($custom_settings)) :
+ $custom_settings = array();
+ endif;
+
+ $this->postmeta[/*parsed=*/ false] = array_merge($default_custom_settings, $custom_settings);
+ $this->postmeta[/*parsed=*/ true] = array();
+
+ // Now, run through and parse them all.
+ foreach ($this->postmeta[/*parsed=*/ false] as $key => $meta) :
+ $meta = apply_filters("syndicated_link_post_meta_{$key}_pre", $meta, $this);
+ $this->postmeta[/*parsed=*/ false][$key] = $meta;
+ $this->postmeta[/*parsed=*/ true][$key] = new FeedWordPressParsedPostMeta($meta);
+ endforeach;
+ endif;
+
+ $ret = $this->postmeta[!! $params['parsed']];
+ if (is_string($params['field'])) :
+ $ret = $ret[$params['field']];
+ endif;
+ return $ret;
+ } /* SyndicatedLink::postmeta () */
+
+ public function property_cascade ($fromFeed, $link_field, $setting, $method) {
+ $value = NULL;
+ if ($fromFeed) :
+ $value = $this->setting($setting, NULL, NULL, NULL);
+
+ $simp_pie = $this->simplepie;
+ $callable = ( is_object( $simp_pie ) and method_exists( $simp_pie, $method ) );
+ if ( is_null( $value ) and $callable ) :
+// $fallback = $s->{$method}(); // what is fallback supposed to be here? (gwyneth 20230816)
+ $value = $simp_pie->{$method}(); // makes more sense this way!
+ endif;
+ else :
+ $value = $this->link->{$link_field};
+ endif;
+ return $value;
+ } /* SyndicatedLink::property_cascade () */
+
+ public function homepage ($fromFeed = true) {
+ return $this->property_cascade($fromFeed, 'link_url', 'feed/link', 'get_link');
+ } /* SyndicatedLink::homepage () */
+
+ public function name ($fromFeed = true) {
+ return $this->property_cascade($fromFeed, 'link_name', 'feed/title', 'get_title');
+ } /* SyndicatedLink::name () */
+
+ public function guid () {
+ $ret = $this->setting('feed/id', NULL, $this->uri());
+
+ // If we can get it live from the feed, do so.
+ if (is_object($this->simplepie)) :
+ $search = array(
+ array('', 'id'),
+ array(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'),
+ array(SIMPLEPIE_NAMESPACE_ATOM_03, 'id'),
+ array(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'),
+ array(SIMPLEPIE_NAMESPACE_DC_11, 'identifier'),
+ array(SIMPLEPIE_NAMESPACE_DC_10, 'identifier'),
+ );
+
+ foreach ($search as $pair) :
+ if ($id_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
+ $ret = $id_tags[0]['data'];
+ break;
+ elseif ($id_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
+ $ret = $id_tags[0]['data'];
+ break;
+ endif;
+ endforeach;
+ endif;
+ return $ret;
+ }
+
+ public function links ($params = array()) {
+ $params = wp_parse_args($params, array(
+ "rel" => NULL,
+ ));
+
+ $fLinks = array();
+ $search = array(
+ array('', 'link'),
+ array(SIMPLEPIE_NAMESPACE_ATOM_10, 'link'),
+ array(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'),
+ );
+
+ foreach ($search as $pair) :
+ if ($link_tags = $this->simplepie->get_feed_tags($pair[0], $pair[1])) :
+ $fLinks = array_merge($fLinks, $link_tags);
+ endif;
+ if ($link_tags = $this->simplepie->get_channel_tags($pair[0], $pair[1])) :
+ $fLinks = array_merge($fLinks, $link_tags);
+ endif;
+ endforeach;
+
+ $ret = array();
+ foreach ($fLinks as $link) :
+ $filter = false;
+ if ( !is_null($params['rel'])) :
+ $filter = true;
+
+ if (isset($link['attribs'])) :
+ // Get a list of NSes from the search
+ foreach ($search as $pair) :
+ $ns = $pair[0];
+
+ if (isset($link['attribs'][$ns])
+ and isset($link['attribs'][$ns]['rel'])
+ ) :
+ $rel = strtolower(trim($link['attribs'][$ns]['rel']));
+ $fRel = strtolower(trim($params['rel']));
+
+ if ($rel == $fRel) :
+ $filter = false;
+ endif;
+ endif;
+ endforeach;
+ endif;
+ endif;
+
+ if ( ! $filter) :
+ $ret[] = $link;
+ endif;
+ endforeach;
+
+ return $ret;
+ }
+
+public function ttl ($return_element = false) {
+ if (is_object($this->magpie)) :
+ $channel = $this->magpie->channel;
+ else :
+ $channel = array();
+ endif;
+
+ if (isset($channel['ttl'])) :
+ // "ttl stands for time to live. It's a number of
+ // minutes that indicates how long a channel can be
+ // cached before refreshing from the source."
+ //
+ $xml = 'rss:ttl';
+ $ret = $channel['ttl'];
+ elseif (isset($channel['sy']['updatefrequency']) or isset($channel['sy']['updateperiod'])) :
+ $period_minutes = array (
+ 'hourly' => 60, /* minutes in an hour */
+ 'daily' => 1440, /* minutes in a day */
+ 'weekly' => 10080, /* minutes in a week */
+ 'monthly' => 43200, /* minutes in a month */
+ 'yearly' => 525600, /* minutes in a year */
+ );
+
+ // "sy:updatePeriod: Describes the period over which the
+ // channel format is updated. Acceptable values are:
+ // hourly, daily, weekly, monthly, yearly. If omitted,
+ // daily is assumed."
+ if (isset($channel['sy']['updateperiod'])) : $period = trim($channel['sy']['updateperiod']);
+ else : $period = 'daily';
+ endif;
+
+ // "sy:updateFrequency: Used to describe the frequency
+ // of updates in relation to the update period. A
+ // positive integer indicates how many times in that
+ // period the channel is updated. ... If omitted a value
+ // of 1 is assumed."
+ if (isset($channel['sy']['updatefrequency'])) : $freq = (int) $channel['sy']['updatefrequency'];
+ else : $freq = 1;
+ endif;
+
+ // normalize the period name...
+ $period = strtolower(trim($period));
+
+ // do we recognize the alphanumeric period name? if not, then guess
+ // a responsible default, e.g. roughly hourly
+ $mins = (isset($period_minutes[$period]) ? $period_minutes[$period] : 67);
+
+ $xml = 'sy:updateFrequency';
+ $ret = (int) ($mins / $freq);
+
+ else :
+ $xml = NULL;
+ $ret = NULL;
+ endif;
+
+ if ('yes'==$this->setting('update/minimum', 'update_minimum', 'no')) :
+ $min = (int) $this->setting('update/window', 'update_window', DEFAULT_UPDATE_PERIOD);
+
+ if ($min > $ret) :
+ $ret = NULL;
+ endif;
+ endif;
+ return ( $return_element ? array( $ret, $xml ) : $ret );
+ } /* SyndicatedLink::ttl() */
+
+ /**
+ * Sets the automatic time interval for
+ *
+ * @return
+ */
+ public function automatic_ttl() {
+ // spread out over a time interval for staggered updates
+ $updateWindow = $this->setting( 'update/window', 'update_window', DEFAULT_UPDATE_PERIOD );
+ if ( ! is_numeric( $updateWindow ) or ( $updateWindow < 1 ) ) :
+ $updateWindow = DEFAULT_UPDATE_PERIOD;
+ endif;
+
+ // We get a fudge of 1/3 of window from elsewhere. We'll do some more
+ // fudging here.
+ $fudgedInterval = $updateWindow + rand( - ( $updateWindow / 6 ), 5 * ( $updateWindow / 12 ) );
+ return apply_filters( 'syndicated_feed_automatic_ttl', $fudgedInterval, $this );
+ } /* SyndicatedLink::automatic_ttl() */
+
+ /**
+ * SyndicatedLink::flatten_array (): flatten an array. Useful for
+ * hierarchical and namespaced elements.
+ *
+ * Given an array which may contain array or object elements in it,
+ * return a "flattened" array: a one-dimensional array of scalars
+ * containing each of the scalar elements contained within the array
+ * structure. Thus, for example, if $a['b']['c']['d'] == 'e', then the
+ * returned array for FeedWordPress::flatten_array($a) will contain a key
+ * $a['feed/b/c/d'] with value 'e'.
+ *
+ * @param array $arr Array to flatten.
+ * @param string $prefix Hierarchical prefix (e.g. "feed/" as in "feed/a/b/c").
+ * @param string $separator Hierarchy separator (e.g., '/').
+ * @return array Flattened one-dimensional array.
+ */
+ public function flatten_array( $arr, $prefix = 'feed/', $separator = '/' ) {
+ $ret = array();
+ if ( is_array( $arr ) ) :
+ foreach ( $arr as $key => $value ) :
+ if ( is_scalar( $value ) ) :
+ $ret[ $prefix.$key ] = $value;
+ else :
+ $ret = array_merge( $ret, $this->flatten_array( $value, $prefix.$key.$separator, $separator ) );
+ endif;
+ endforeach;
+ endif;
+ return $ret;
+ } /* SyndicatedLink::flatten_array() */
+
+ /**
+ * Check if a setting is hardcoded or not.
+ *
+ * @param string $what The setting we're checking for.
+ *
+ * @return bool True if the setting's value is 'yes', false otherwise.
+ */
+ public function hardcode( $what ) {
+ return 'yes' == $this->setting( 'hardcode ' . $what, 'hardcode_' . $what, NULL );
+ // Simplified logic. (gwyneth 20230920)
+ } /* SyndicatedLink::hardcode () */
+
+ /**
+ * Returns the syndicated status for one item.
+ *
+ * @param string $what Item's name.
+ * @param mixed $default Default value for this item.
+ * @param boolean $fallback If true, falls back to global setting.
+ *
+ * @return string SQL-ready escaped status.
+ */
+ public function syndicated_status( $what, $default, $fallback = true ) {
+ $g_set = ( $fallback ? 'syndicated_' . $what . '_status' : NULL );
+ $ret = $this->setting( $what . ' status', $g_set, $default );
+
+ return esc_sql( trim( strtolower( $ret ) ) );
+ } /* SyndicatedLink:syndicated_status () */
+
+ public function taxonomies () {
+ $post_type = $this->setting('syndicated post type', 'syndicated_post_type', 'post');
+ return get_object_taxonomies(array('object_type' => $post_type), 'names');
+ } /* SyndicatedLink::taxonomies () */
+
+ /**
+ * category_ids: look up (and create) category ids from a list of
+ * categories
+ *
+ * @param array $cats
+ * @param string $unfamiliar_category
+ * @param array|null $taxonomies
+ * @return array
+ */
+ public function category_ids ($post, $cats, $unfamiliar_category = 'create', $taxonomies = NULL, $params = array()) {
+ $singleton = (isset($params['singleton']) ? $params['singleton'] : true);
+ $allowFilters = (isset($params['filters']) ? $params['filters'] : false);
+
+ $catTax = 'category';
+
+ if (is_null($taxonomies)) :
+ $taxonomies = array('category');
+ endif;
+
+ // We need to normalize whitespace because (1) trailing
+ // whitespace can cause PHP and MySQL not to see eye to eye on
+ // VARCHAR comparisons for some versions of MySQL (cf.
+ // ), and (2)
+ // because I doubt most people want to make a semantic
+ // distinction between 'Computers' and 'Computers '
+ $cats = array_map('trim', $cats);
+
+ $terms = array();
+ foreach ($taxonomies as $tax) :
+ $terms[$tax] = array();
+ endforeach;
+
+ foreach ($cats as $cat_name) :
+ if (strlen(trim($cat_name)) < 1) :
+ continue;
+ endif;
+
+ $oTerm = new SyndicatedPostTerm($cat_name, $taxonomies, $post);
+
+ if ($oTerm->is_familiar()) :
+
+ $tax = $oTerm->taxonomy();
+ if ( !isset($terms[$tax])) :
+ $terms[$tax] = array();
+ endif;
+ $terms[$tax][] = $oTerm->id();
+
+ else :
+
+ if ('tag'==$unfamiliar_category) :
+ $unfamiliar_category = 'create:post_tag';
+ endif;
+
+ if (preg_match('/^create(:(.*))?$/i', $unfamiliar_category, $ref)) :
+ $tax = $catTax; // Default
+
+ if (isset($ref[2])
+ and strlen($ref[2]) > 2) :
+ $tax = $ref[2];
+ endif;
+
+ $inserted = $oTerm->insert($tax);
+ if ( !is_null($inserted)) :
+ if ( !isset($terms[$tax])) :
+ $terms[$tax] = array();
+ endif;
+ $terms[$tax][] = $inserted;
+ else :
+
+ endif; // !is_null($inserted)
+ endif; // preg_match(...)
+
+ endif; /* ($oTerm->is_familiar()) */
+ endforeach;
+
+ $filtersOn = $allowFilters;
+ if ($allowFilters) :
+ $filters = array_filter(
+ $this->setting('match/filter', 'match_filter', array()),
+ 'remove_dummy_zero'
+ );
+ $filtersOn = ($filtersOn and is_array($filters) and (count($filters) > 0));
+ endif;
+
+ // Check for filter conditions
+ foreach ($terms as $tax => $term_ids) :
+ if ($filtersOn
+ and (count($term_ids)==0)
+ and in_array($tax, $filters)) :
+ $terms = NULL; // Drop the post
+ break;
+ else :
+ $terms[$tax] = array_unique($term_ids);
+ endif;
+ endforeach;
+
+ if ($singleton and count($terms)==1) : // If we only searched one, just return the term IDs
+ $terms = end($terms);
+ endif;
+
+ FeedWordPress::diagnostic(
+ 'syndicated_posts:categories',
+ 'Category: MAPPED term names '.json_encode($cats).' to IDs: '.json_encode($terms)
+ );
+ return $terms;
+ } /* SyndicatedLink::category_ids () */
+} /* class SyndicatedLink */
\ No newline at end of file
diff --git a/syndicatedpost.class.php b/syndicatedpost.class.php
index 475586e..e23fd71 100644
--- a/syndicatedpost.class.php
+++ b/syndicatedpost.class.php
@@ -44,10 +44,25 @@ class SyndicatedPost {
*
* @param array $item The item syndicated from the feed.
* @param SyndicatedLink $source The feed it was syndicated from.
+ *
+ * @uses FeedWordPress
+ * @uses FeedWordPress::diagnostic()
+ * @uses MagpieFromSimplePie
*/
- public function __construct( $item, $source ) {
- if ( empty( $item ) and empty( $source ) )
+ public function __construct( $item, $source )
+ {
+ /* The following checked if both were simultaneously empty. The problem here is that if
+ * _either_ is empty, we'll be using expected array entries or methods from them,
+ * which will break execution. I think that "or" is more appropriate here, although I'm
+ * adding a debug entry here, just in case... (gwyneth 20231017)
+ */
+ if ( empty( $item ) or empty( $source ) ) :
+ FeedWordPress::diagnostic(
+ 'feed_items',
+ 'Either \$item or \$source was empty, returning from constructor without doing anything'
+ );
return;
+ endif;
if ( is_array( $item )
and isset( $item['simplepie'] )
@@ -274,22 +289,30 @@ public function __construct( $item, $source ) {
#### EXTRACT DATA FROM FEED ITEM ####
#####################################
- function substitution_function ($name) {
+ /**
+ * Checks if the given function is allowed.
+ *
+ * @param string|null $name Name of the function to check for.
+ *
+ * @return string|null Returns the name being checked if allowed, null otherwise.
+ */
+ function substitution_function( $name )
+ {
$ret = NULL;
- switch ($name) :
- // Allowed PHP string functions
- case 'trim':
- case 'ltrim':
- case 'rtrim':
- case 'strtoupper':
- case 'strtolower':
- case 'urlencode':
- case 'urldecode':
- $ret = $name;
- endswitch;
+ switch( $name ) :
+ // Allowed PHP string functions
+ case 'trim':
+ case 'ltrim':
+ case 'rtrim':
+ case 'strtoupper':
+ case 'strtolower':
+ case 'urlencode':
+ case 'urldecode':
+ $ret = $name;
+ endswitch;
return $ret;
- }
+ } /* SyndicatedPost::substitution_function() */
/**
* SyndicatedPost::query uses an XPath-like syntax to query arbitrary
@@ -298,16 +321,18 @@ function substitution_function ($name) {
* @param string $path
* @returns array of string values representing contents of matching
* elements or attributes
+ *
+ * @uses SyndicatedPostXPathQuery
*/
public function query ($path) {
- $xq = new SyndicatedPostXPathQuery(array("path" => $path));
+ $xpathQuery = new SyndicatedPostXPathQuery(array("path" => $path));
$feedChannel = array_merge(
$this->get_feed_root_element(),
$this->get_feed_channel_elements()
);
- $matches = $xq->match(array(
+ $matches = $xpathQuery->match(array(
"type" => $this->link->simplepie->get_type(),
"xmlns" => $this->xmlns,
"map" => array(
@@ -359,11 +384,21 @@ public function title ($params = array()) {
return $this->entry->get_title();
} /* SyndicatedPost::title () */
- public function content ($params = array())
+ /**
+ * Extracts content from Atom/RSS feeds and deals with tricky edge cases.
+ *
+ * @param Array $params Filter parameters to include everything.
+ *
+ * @return string|null The extracted content (which could be null if error).
+ */
+ public function content( $params = array() )
{
- $params = wp_parse_args($params, array(
- "full only" => false,
- ));
+ $params = wp_parse_args(
+ $params,
+ array(
+ "full only" => false,
+ )
+ );
$content = NULL;
@@ -374,38 +409,39 @@ public function content ($params = array())
// SimplePie_Item::get_content() with content-only set to TRUE
// and some sanitization in effect. -CJ 1jul14
+ // Changed isset() with ! empty(), because we have no idea if these are
+ // set but not empty (thus breaking class variable checks). (gwyneth 20231017)
+
// atom:content, standard method of providing full HTML content
// in Atom feeds.
- if (isset($this->item['atom_content'])) :
+ if ( ! empty( $this->item['atom_content'] ) ) :
$content = $this->item['atom_content'];
- elseif (isset($this->item['atom']['atom_content'])) :
+ elseif ( ! empty( $this->item['atom']['atom_content'] ) ) :
$content = $this->item['atom']['atom_content'];
// Some exotics: back in the day, before widespread convergence
// on content:encoding, some RSS feeds took advantage of XML
// namespaces to use an inline xhtml:body or xhtml:div element
// for full-content HTML. (E.g. Sam Ruby's feed, IIRC.)
- elseif (isset($this->item['xhtml']['body'])) :
+ elseif ( ! empty( $this->item['xhtml']['body'] ) ) :
$content = $this->item['xhtml']['body'];
- elseif (isset($this->item['xhtml']['div'])) :
+ elseif ( ! empty( $this->item['xhtml']['div'] ) ) :
$content = $this->item['xhtml']['div'];
// content:encoded, most common method of providing full HTML in
// RSS 2.0 feeds.
- elseif (isset($this->item['content']['encoded']) and $this->item['content']['encoded']):
+ elseif ( ! empty( $this->item['content']['encoded']) and $this->item['content']['encoded'] ) :
$content = $this->item['content']['encoded'];
// Fall back on elements that sometimes may contain full HTML
// but sometimes not.
- elseif ( ! $params['full only']) :
-
+ elseif ( ! $params['full only'] ) : // hopefully this won't crash here (gwyneth 20231017)
// description element is sometimes used for full HTML
// sometimes for summary text in RSS. (By the letter of
// the standard, it should just be for summary text.)
- if (isset($this->item['description'])) :
+ if ( ! empty($this->item['description'] ) ) :
$content = $this->item['description'];
endif;
-
endif;
return $content;
@@ -1219,8 +1255,9 @@ static function sanitize_content ($content, $obj) {
*
* @return bool TRUE iff post has been filtered out by a previous filter
*/
- function filtered () {
- return is_null($this->post);
+ function filtered ()
+ {
+ return is_null( $this->post );
} /* SyndicatedPost::filtered() */
/**
@@ -1229,39 +1266,54 @@ function filtered () {
* match the latest revision, or a previously syndicated post that is
* still up-to-date.
*
+ * @param string $format The required format, e.g. 'status' or 'number' (default).
+ *
* @return int A status code representing the freshness of the post
* -1 = post already syndicated; has a revision that needs to be stored, but not updated to
- * 0 = post already syndicated; no update needed
- * 1 = post already syndicated, but needs to be updated to latest
- * 2 = post has not yet been syndicated; needs to be created
+ * 0 = post already syndicated; no update needed
+ * 1 = post already syndicated, but needs to be updated to latest
+ * 2 = post has not yet been syndicated; needs to be created
*/
- function freshness ($format = 'number') {
-
- if ($this->filtered()) : // This should never happen.
- FeedWordPressDiagnostic::critical_bug('SyndicatedPost', $this, __LINE__, __FILE__);
+ function freshness( $format = 'number' )
+ {
+ if ( $this->filtered() ) : // This should never happen.
+ FeedWordPressDiagnostic::critical_bug( 'SyndicatedPost', $this, __LINE__, __FILE__ );
endif;
- if (is_null($this->_freshness)) : // Not yet checked and cached.
+ if ( is_null( $this->_freshness ) ) : // Not yet checked and cached.
$guid = $this->post['guid'];
- $eguid = esc_sql($this->post['guid']);
+ $eguid = esc_sql( $this->post['guid'] );
- $q = new WP_Query(array(
- 'fields' => '_synfresh', // id, guid, post_modified_gmt
+ $q = new WP_Query( array(
+ 'fields' => '_synfresh', // id, guid, post_modified_gmt
'ignore_sticky_posts' => true,
- 'guid' => $eguid, // it's always better to use the escaped version, right? (gwyneth 20230915)
+ 'guid' => $eguid, // it's always better to use the escaped version, right? (gwyneth 20230915)
));
$old_post = NULL;
- if ($q->have_posts()) :
- while ($q->have_posts()) : $q->the_post();
- if (get_post_type($q->post->ID) == $this->post['post_type']):
- $old_post = $q->post;
+ if ( $q->have_posts() ) :
+ while ( $q->have_posts() ) :
+ /** FIXME: Apparently, this can be called outside a hook; as such,
+ * some functionality might not be loaded, namely, get_userdata() on the returned post.
+ * This will invariably give a fatal error and abort the script.
+ * I have no idea how to fix this, so, for now, I'm just avoiding this case and
+ * logging it (gwyneth 20240208)
+ */
+ if ( function_exists( 'get_userdata' ) ) :
+ $q->the_post();
+ if ( get_post_type( $q->post->ID ) == $this->post['post_type'] ):
+ $old_post = $q->post;
+ endif;
+ else :
+ FeedWordPressDiagnostic::noncritical_bug( 'freshness: loop encountered outside hook', $this, __LINE__, __FILE__ );
+ // error_log( 'freshness: loop encountered outside hook on ' . __FILE__ . ' (' . __LINE__ . ')' ); // Spews out far too much garbage (gwyneth 20240209).
endif;
endwhile;
endif;
- if (is_null($old_post)) : // No post with this guid
- FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is a NEW POST.');
+ if ( is_null( $old_post ) ) : // No post with this guid
+ FeedWordPress::diagnostic( 'feed_items:freshness', 'Item [' . $guid . '] "'
+ . $this->entry->get_title() . '" is a NEW POST.' );
$this->_wp_id = NULL;
$this->_freshness = 2; // New content
else :
@@ -1272,36 +1324,36 @@ function freshness ($format = 'number') {
// Pull the list of existing revisions to get
// timestamps.
- $revisions = wp_get_post_revisions($old_post->ID);
- foreach ($revisions as $rev) :
- $revisions_ts[] = mysql2date('G', $rev->post_modified_gmt);
+ $revisions = wp_get_post_revisions( $old_post->ID );
+ foreach ( $revisions as $rev ) :
+ $revisions_ts[] = mysql2date( 'G', $rev->post_modified_gmt );
endforeach;
- $revisions_ts[] = mysql2date('G', $old_post->post_modified_gmt);
- $last_rev_ts = end($revisions_ts);
- $updated_ts = $this->updated(/*fallback=*/ true, /*default=*/ NULL);
+ $revisions_ts[] = mysql2date( 'G', $old_post->post_modified_gmt );
+ $last_rev_ts = end( $revisions_ts );
+ $updated_ts = $this->updated( /*fallback=*/ true, /*default=*/ NULL );
// If we have an explicit updated timestamp,
// check that against existing stamps.
- if ( !is_null($updated_ts)) :
- $updated = !in_array($updated_ts, $revisions_ts);
+ if ( ! is_null( $updated_ts ) ) :
+ $updated = ! in_array( $updated_ts, $revisions_ts );
// If this a newer revision, make it go
// live. If an older one, just record
// the contents.
- $live = ($updated and ($updated_ts > $last_rev_ts));
+ $live = ( $updated and ( $updated_ts > $last_rev_ts ) );
endif;
// This is a revision we haven't seen before, judging by the date.
$updatedReason = NULL;
- if ($updated) :
+ if ( $updated ) :
$updatedReason = preg_replace(
"/\s+/", " ",
'has been marked with a new timestamp ('
- .date('Y-m-d H:i:s', $updated_ts)
+ .date( 'Y-m-d H:i:s', $updated_ts )
." > "
- .date('Y-m-d H:i:s', $last_rev_ts)
+ .date( 'Y-m-d H:i:s', $last_rev_ts )
.')'
);
@@ -1310,14 +1362,14 @@ function freshness ($format = 'number') {
else :
// Or the hash...
$hash = $this->update_hash();
- $seen = $this->stored_hashes($old_post->ID);
- if (is_countable($seen) and count($seen) > 0) :
- $updated = !in_array($hash, $seen); // Not seen yet?
+ $seen = $this->stored_hashes( $old_post->ID );
+ if ( is_countable( $seen ) and count( $seen ) > 0 ) :
+ $updated = ! in_array( $hash, $seen ); // Not seen yet?
else :
$updated = true; // Can't find syndication meta-data
endif;
- if ($updated and FeedWordPressDiagnostic::is_on('feed_items:freshness:reasons')) :
+ if ( $updated and FeedWordPressDiagnostic::is_on( 'feed_items:freshness:reasons' ) ) :
// In the absence of definitive
// timestamp information, we
// just have to assume that a
@@ -1326,59 +1378,62 @@ function freshness ($format = 'number') {
$live = true;
$updatedReason = ' has a not-yet-seen update hash: '
- .MyPHP::val($hash)
+ .MyPHP::val( $hash )
.' not in {'
- .implode(", ", array_map(array('FeedWordPress', 'val'), $seen))
+ .implode( ", ", array_map( array( 'FeedWordPress', 'val' ), $seen ) )
.'}. Basis: '
- .MyPHP::val(array_keys($this->update_hash(false)));
+ .MyPHP::val( array_keys( $this->update_hash( false ) ) );
endif;
endif;
$frozen = false;
- if ($updated) : // Ignore if the post is frozen
- $frozen = ('yes' == $this->link->setting('freeze updates', 'freeze_updates', NULL));
+ if ( $updated ) : // Ignore if the post is frozen
+ $frozen = ( 'yes' == $this->link->setting( 'freeze updates', 'freeze_updates', NULL ) );
if ( ! $frozen) :
- $frozen_value = get_post_meta($old_post->ID, '_syndication_freeze_updates', /*single=*/ true);
- $frozen = ( !is_null($frozen_value) and ('yes' == $frozen_value));
+ $frozen_value = get_post_meta( $old_post->ID, '_syndication_freeze_updates', /*single=*/ true );
+ $frozen = ( ! is_null( $frozen_value ) and ( 'yes' == $frozen_value ) );
- if ($frozen) :
- $updatedReason = ' IS BLOCKED FROM BEING UPDATED BY A UPDATE LOCK ON THIS POST, EVEN THOUGH IT '.$updatedReason;
+ if ( $frozen ) :
+ $updatedReason = ' IS BLOCKED FROM BEING UPDATED BY A UPDATE LOCK ON THIS POST, EVEN THOUGH IT ' . $updatedReason;
endif;
else :
$updatedReason = ' IS BLOCKED FROM BEING UPDATED BY A FEEDWORDPRESS UPDATE LOCK, EVEN THOUGH IT '.$updatedReason;
endif;
endif;
- $live = ($live and ! $frozen);
-
- if ($updated) :
- FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is an update of an existing post.');
- if ( !is_null($updatedReason)) :
- $updatedReason = preg_replace('/\s+/', ' ', $updatedReason);
- FeedWordPress::diagnostic('feed_items:freshness:reasons', 'Item ['.$guid.'] "'.$this->entry->get_title().'" '.$updatedReason);
+ $live = ( $live and ! $frozen );
+
+ if ( $updated ) :
+ FeedWordPress::diagnostic( 'feed_items:freshness', 'Item [' . $guid . '] "' . $this->entry->get_title()
+ . '" is an update of an existing post.' );
+ if ( ! is_null( $updatedReason ) ) :
+ $updatedReason = preg_replace( '/\s+/', ' ', $updatedReason );
+ FeedWordPress::diagnostic( 'feed_items:freshness:reasons', 'Item [' . $guid . '] "'
+ . $this->entry->get_title() . '" ' . $updatedReason );
endif;
- $this->_freshness = apply_filters('syndicated_item_freshness', ($live ? 1 : -1), $updated, $frozen, $updated_ts, $last_rev_ts, $this);
+ $this->_freshness = apply_filters( 'syndicated_item_freshness', ( $live ? 1 : -1 ), $updated, $frozen, $updated_ts, $last_rev_ts, $this );
- $this->_wp_id = $old_post->ID;
+ $this->_wp_id = $old_post->ID;
$this->_wp_post = $old_post;
// We want this to keep a running list of all the
// processed update hashes.
$this->post['meta']['syndication_item_hash'] = array_merge(
$this->stored_hashes(),
- array($this->update_hash())
+ array( $this->update_hash() )
);
else :
- FeedWordPress::diagnostic('feed_items:freshness', 'Item ['.$guid.'] "'.$this->entry->get_title().'" is a duplicate of an existing post.');
+ FeedWordPress::diagnostic( 'feed_items:freshness', 'Item [' . $guid . '] "' . $this->entry->get_title()
+ . '" is a duplicate of an existing post.' );
$this->_freshness = 0; // Same old, same old
$this->_wp_id = $old_post->ID;
endif;
endif;
endif;
- switch ($format) :
+ switch ( $format ) :
case 'status' :
- switch ($this->_freshness) :
+ switch ( $this->_freshness ) :
case -1:
$ret = 'stored';
break;
@@ -1399,31 +1454,45 @@ function freshness ($format = 'number') {
$ret = $this->_freshness;
endswitch;
-
return $ret;
- } /* SyndicatedPost::freshness () */
+ } /* SyndicatedPost::freshness() */
- function has_fresh_content () {
+ /**
+ * Checks if the content is fresh and not filtered for any reason. *
+ */
+ function has_fresh_content()
+ {
return ( ! $this->filtered() and $this->freshness() != 0 );
- } /* SyndicatedPost::has_fresh_content () */
+ } /* SyndicatedPost::has_fresh_content() */
- function this_revision_needs_original_post ($freshness = NULL) {
- if (is_null($freshness)) :
+ /**
+ * This revision needs the original post.
+ *
+ * (duh!!)
+ *
+ * @param integer|null $freshness Freshness level, or null if not set.
+ *
+ * @return bool Returns true if the freshness level is 2 or more, false otherwise.
+ */
+ function this_revision_needs_original_post( $freshness = NULL )
+ {
+ if ( is_null( $freshness ) ) :
$freshness = $this->freshness();
endif;
return ( $freshness >= 2 );
- }
+ } /* SyndicatedPost::this_revision_needs_original_post() */
- function this_revision_is_current ($freshness = NULL) {
- if (is_null($freshness)) :
+ function this_revision_is_current( $freshness = NULL )
+ {
+ if ( is_null( $freshness ) ) :
$freshness = $this->freshness();
endif;
return ( $freshness >= 1 );
- } /* SyndicatedPost::this_revision_is_current () */
+ } /* SyndicatedPost::this_revision_is_current() */
- function fresh_content_is_update () {
+ function fresh_content_is_update() {
return ($this->freshness() < 2);
- } /* SyndicatedPost::fresh_content_is_update () */
+ } /* SyndicatedPost::fresh_content_is_update() */
function fresh_storage_diagnostic () {
$ret = NULL;
@@ -1470,7 +1539,7 @@ function wp_id () {
$fresh = $this->freshness(); // sets WP DB id in the process
endif;
return $this->_wp_id;
- }
+ } /* SyndicatedPost::wp_id() */
/**
* SyndicatedPost::secure_author_id(). Look up, or create, a numeric ID
@@ -2125,46 +2194,58 @@ function fix_post_modified_ts ($new_status, $old_status, $post) {
* @param string $new_status Unused action parameter.
* @param string $old_status Unused action parameter.
* @param object $post The database record for the post just inserted.
+ *
+ * @uses FeedWordPress::diagnostic()
*/
- function add_rss_meta($new_status, $old_status, $post) {
+ function add_rss_meta( $new_status, $old_status, $post ) {
global $wpdb;
- if ($new_status!='inherit') : // Bail if we are creating a revision.
- FeedWordPress::diagnostic('syndicated_posts:meta_data', 'Adding post meta-data: {'.implode(", ", array_keys($this->post['meta'])).'}');
+ if ( 'inherit' != $new_status ) : // Bail if we are creating a revision.
+ FeedWordPress::diagnostic(
+ 'syndicated_posts:meta_data',
+ 'Adding post meta-data: {'
+ . implode( ", ", array_keys( $this->post['meta']) )
+ . '}'
+ );
- if ( is_array($this->post) and isset($this->post['meta']) and is_array($this->post['meta']) ) :
+ if ( is_array( $this->post ) and isset( $this->post['meta'] ) and is_array( $this->post['meta'] ) ) :
$postId = $post->ID;
// Aggregated posts should NOT send out pingbacks.
// WordPress 2.1-2.2 claim you can tell them not to
// using $post_pingback, but they don't listen, so we
// make sure here.
- $result = $wpdb->query("
- DELETE FROM $wpdb->postmeta
- WHERE post_id='$postId' AND meta_key='_pingme'
- ");
+ $result = $wpdb->query( "
+ DELETE FROM $wpdb->postmeta
+ WHERE post_id='$postId' AND meta_key='_pingme'
+ " );
foreach ( $this->post['meta'] as $key => $values ) :
- $eKey = esc_sql($key);
+ $eKey = esc_sql( $key );
// If this is an update, clear out the old
// values to avoid duplication.
- $result = $wpdb->query("
- DELETE FROM $wpdb->postmeta
- WHERE post_id='$postId' AND meta_key='$eKey'
- ");
+ $result = $wpdb->query( "
+ DELETE FROM $wpdb->postmeta
+ WHERE post_id='$postId' AND meta_key='$eKey'
+ " );
// Allow for either a single value or an array
- if ( !is_array($values)) $values = array($values);
+ if ( ! is_array( $values ) ) {
+ $values = array( $values );
+ }
foreach ( $values as $value ) :
- FeedWordPress::diagnostic('syndicated_posts:meta_data', "Adding post meta-datum to post [$postId]: [$key] = ".MyPHP::val($value, /*no newlines=*/ true));
- add_post_meta($postId, $key, $value, /*unique=*/ false);
+ FeedWordPress::diagnostic(
+ 'syndicated_posts:meta_data',
+ "Adding post meta-datum to post [$postId]: [$key] = "
+ . MyPHP::val( $value, /*no newlines=*/ true )
+ );
+ add_post_meta( $postId, $key, $value, /*unique=*/ false );
endforeach;
endforeach;
- if ( $result === false)
- {
+ if ( false === $result ) :
error_log( "delete query failed: " . $wpdb->last_error );
- }
+ endif;
endif;
endif;
} /* SyndicatedPost::add_rss_meta () */