WP RSS Aggregator - Version 4.9

Version Description

(2016-06-14) = * Fixed bug: Potential security vulnerability related to triggering feed update. * Fixed bug: Error output on Feed Sources list is trimmed and cannot break the page layout. * Fixed bug: Certain notices could not be dismissed. * Fixed bug: Word trimming didn't always trim correctly with HTML. * Enhanced: Visual improvements.

Download this release

Release Info

Developer markzahra
Plugin Icon 128x128 WP RSS Aggregator
Version 4.9
Comparing to
See all releases

Code changes from version 4.8.2 to 4.9

Files changed (33) hide show
  1. css/admin-styles.css +23 -1
  2. includes/Aventura/Wprss/Core/Block/AbstractBlock.php +29 -0
  3. includes/Aventura/Wprss/Core/Block/BlockInterface.php +20 -0
  4. includes/Aventura/Wprss/Core/Block/Html/AbstractHtml.php +160 -0
  5. includes/Aventura/Wprss/Core/Block/Html/AbstractTag.php +82 -0
  6. includes/Aventura/Wprss/Core/Block/Html/Anchor.php +29 -0
  7. includes/Aventura/Wprss/Core/Block/Html/TagInterface.php +54 -0
  8. includes/Aventura/Wprss/Core/DataObject.php +21 -1
  9. includes/Aventura/Wprss/Core/Http/Message/AbstractResponse.php +32 -0
  10. includes/Aventura/Wprss/Core/Http/Message/Ajax/AbstractResponse.php +90 -0
  11. includes/Aventura/Wprss/Core/Http/Message/Ajax/AjaxInterface.php +38 -0
  12. includes/Aventura/Wprss/Core/Http/Message/Ajax/Response.php +12 -0
  13. includes/Aventura/Wprss/Core/Http/Message/MessageInterface.php +16 -0
  14. includes/Aventura/Wprss/Core/Http/Message/ResponseInterface.php +12 -0
  15. includes/Aventura/Wprss/Core/Licensing/Manager.php +0 -1
  16. includes/Aventura/Wprss/Core/Model/AjaxResponse.php +68 -0
  17. includes/Aventura/Wprss/Core/Model/ModelAbstract.php +23 -1
  18. includes/Aventura/Wprss/Core/Plugin.php +127 -3
  19. includes/Aventura/Wprss/Core/Plugin/PluginAbstract.php +38 -8
  20. includes/admin-addons.php +1 -1
  21. includes/admin-ajax-notice.php +16 -5
  22. includes/admin-display.php +41 -13
  23. includes/admin-help.php +1 -1
  24. includes/custom-post-types.php +2 -1
  25. includes/feed-access.php +61 -55
  26. includes/feed-importing.php +6 -5
  27. includes/misc-functions.php +23 -2
  28. js/admin-custom.js +35 -14
  29. js/admin-help.js +3 -1
  30. nbproject/project.properties +0 -7
  31. nbproject/project.xml +0 -9
  32. readme.txt +77 -48
  33. wp-rss-aggregator.php +3 -3
css/admin-styles.css CHANGED
@@ -621,6 +621,20 @@ body.post-type-wprss_blacklist .alignleft.actions.bulkactions {
621
  margin: 0;
622
  }
623
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
624
 
625
 
626
  /* Admin Notifications ====================================================== */
@@ -710,4 +724,12 @@ body.post-type-wprss_blacklist .alignleft.actions.bulkactions {
710
  .wprss-tr-hr th {
711
  padding-top: 10px;
712
  }
713
- }
 
 
 
 
 
 
 
 
621
  margin: 0;
622
  }
623
 
624
+ .wprss-section-tooltip-handle,
625
+ .wprss-section-tooltip-handle:hover,
626
+ .wprss-section-tooltip-handle:active,
627
+ .wprss-section-tooltip-handle:visited,
628
+ .wprss-section-tooltip-handle:link,
629
+ .wprss-section-tooltip-handle:focus {
630
+ color: #006799;
631
+ margin-left: 5px;
632
+ text-decoration: none;
633
+ outline: none;
634
+ cursor: help;
635
+ font-size: 15px;
636
+ }
637
+
638
 
639
 
640
  /* Admin Notifications ====================================================== */
724
  .wprss-tr-hr th {
725
  padding-top: 10px;
726
  }
727
+ }
728
+
729
+ .ajax-error,
730
+ .ajax-error:hover,
731
+ .ajax-error:visited,
732
+ .ajax-error:link,
733
+ .ajax-error:active {
734
+ color: #a00;
735
+ }
includes/Aventura/Wprss/Core/Block/AbstractBlock.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aventura\Wprss\Core\Block;
4
+
5
+ use Aventura\Wprss\Core;
6
+
7
+ /**
8
+ * Basic functionality for all blocks.
9
+ *
10
+ * @since 4.9
11
+ */
12
+ abstract class AbstractBlock extends Core\Model\ModelAbstract implements BlockInterface
13
+ {
14
+ /**
15
+ * {@inheritdoc}
16
+ * @since 4.9
17
+ */
18
+ public function __toString() {
19
+ return $this->getOutput();
20
+ }
21
+
22
+ /**
23
+ * A more structured way of retrieving this block's output.
24
+ *
25
+ * @since 4.9
26
+ * @return string Output generated by this block.
27
+ */
28
+ abstract public function getOutput();
29
+ }
includes/Aventura/Wprss/Core/Block/BlockInterface.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aventura\Wprss\Core\Block;
3
+
4
+ /**
5
+ * Something that can be a block.
6
+ *
7
+ * A block is a class that is responsible for generating output.
8
+ *
9
+ * @since 4.9
10
+ */
11
+ interface BlockInterface {
12
+
13
+ /**
14
+ * Retrieve the string output of this block.
15
+ *
16
+ * @since 4.9
17
+ * @return string The output generated by this block.
18
+ */
19
+ public function __toString();
20
+ }
includes/Aventura/Wprss/Core/Block/Html/AbstractHtml.php ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aventura\Wprss\Core\Block\Html;
4
+ use Aventura\Wprss\Core\Block;
5
+
6
+ /**
7
+ * Something that generates HTML output.
8
+ *
9
+ * @since 4.9
10
+ */
11
+ abstract class AbstractHtml extends Block\AbstractBlock
12
+ {
13
+ const QUOTE_SINGLE = "'";
14
+ const QUOTE_DOUBLE = '"';
15
+
16
+ /**
17
+ * Get the quote that this class will use to surround attribute values.
18
+ *
19
+ * @since 4.9
20
+ * @return string The string that will surround an HTML attribute value.
21
+ */
22
+ public static function getAttributeQuote()
23
+ {
24
+ return static::QUOTE_DOUBLE;
25
+ }
26
+
27
+ /**
28
+ * Generate an attribute-value pair string.
29
+ *
30
+ * This string is intended to be part of an HTML tag.
31
+ *
32
+ * @since 4.9
33
+ * @param string $name The attribute name.
34
+ * @param string $value The attribute value.
35
+ * @param null|string $surround The quote symbol to use to surround the
36
+ * attribute value.
37
+ * @return string A string that represents an HTML tag's attribute-value
38
+ * pair.
39
+ */
40
+ public static function getAttributePairString($name, $value = '', $surround = null)
41
+ {
42
+ $surround = static::defaultParam($surround, static::getAttributeQuote());
43
+ return sprintf('%2$s=%1$s%3$s%1$s', $surround, $name, $value);
44
+ }
45
+
46
+ /**
47
+ * Generate a string of attribute-value pairs.
48
+ *
49
+ * Intended to be used in an HTML tag.
50
+ *
51
+ * @since 4.9
52
+ * @see getAttributeQuote()
53
+ * @see getAttributeStringsArray()
54
+ * @param array $attributes An array of attributes, where key is attribute
55
+ * name, and value is attribute value.
56
+ * @param string|bool $escape If array, the values of only those attributes,
57
+ * the names of which are listed here, will be escaped.
58
+ * Othwewise, whether or not all attribute values will be escaped.
59
+ * @param string $surround The quote to use for surrounding the attribute
60
+ * value.
61
+ * One of the QUOTE_* constants.
62
+ * Default: What this class's getAttributeQuote() returns.
63
+ * @return string A string that consists of attribute-value pairs with
64
+ * spaces in between.
65
+ */
66
+ public static function getAttributesStringFromArray($attributes = array(), $escape = true, $surround = null)
67
+ {
68
+ $surround = static::defaultParam($surround, static::getAttributeQuote());
69
+ return implode(' ', static::getAttributeStringsArray($attributes, $escape, $surround));
70
+ }
71
+
72
+ /**
73
+ * Generates an array of attribute-value pair strings, by attribute name.
74
+ *
75
+ * @since 4.9
76
+ * @see getAttributeQuote()
77
+ * @see getAttributePairString()
78
+ * @see sanitizeAttributeName()
79
+ * @param array $attributes An array of attributes, where key is attribute
80
+ * name, and value is attribute value.
81
+ * @param string|bool $escape If array, the values of only those attributes,
82
+ * the names of which are listed here, will be escaped.
83
+ * Othwewise, whether or not all attribute values will be escaped.
84
+ * @param string $surround The quote to use for surrounding the attribute
85
+ * value.
86
+ * One of the QUOTE_* constants.
87
+ * Default: What this class's getAttributeQuote() returns.
88
+ * @return array An array, where keys are attribute names, and values are
89
+ * attribute-value pair strings, e.g. 'name="value"';
90
+ */
91
+ public static function getAttributeStringsArray($attributes = array(), $escape = true, $surround = null)
92
+ {
93
+ $surround = static::defaultParam($surround, static::getAttributeQuote());
94
+
95
+ // Allow a single attribute
96
+ if (is_string($escape)) {
97
+ $escape = (array) $escape;
98
+ }
99
+
100
+ $attrStrings = array();
101
+ foreach( $attributes as $_key => $_value ) {
102
+ $key = $_key;
103
+
104
+ // Should we standardize?
105
+ $isEscape = is_array($escape)
106
+ ? in_array($_key, $escape) // Array with keys to standardize
107
+ : (bool) $escape; // Same for all
108
+
109
+ // Cleaning attribute name and value
110
+ if ($isEscape) {
111
+ $key = static::sanitizeAttributeName($_key);
112
+ $_value = static::escapeAttributeValue($_value, $surround);
113
+ }
114
+
115
+ $attrStrings[$key] = static::getAttributePairString($key, $_value, $surround);
116
+ }
117
+
118
+ return $attrStrings;
119
+ }
120
+
121
+ /**
122
+ * Sanitizes an HTML tag's attribute name.
123
+ *
124
+ * Result will not contain white space before or after the name.
125
+ *
126
+ * @since 4.9
127
+ * @param string $attribute The attribute name to sanitize.
128
+ * @return string A valid attribute name.
129
+ */
130
+ public static function sanitizeAttributeName($attribute)
131
+ {
132
+ return trim($attribute);
133
+ }
134
+
135
+ /**
136
+ * Escapes an HTML tag's attribute value.
137
+ *
138
+ * If single quote is used to surround the value, only single quotes in the
139
+ * value will be escaped.
140
+ * Othwewise, all quotes in the value will be escaped.
141
+ *
142
+ * @since 4.9
143
+ * @see htmlspecialchars()
144
+ * @param type $attribute
145
+ * @param string $quote The quote that is used for surrounding the attribute
146
+ * value.
147
+ * Default: What this class's getAttributeQuote() returns.
148
+ * @return string An attribute value that is valid for insertion into an
149
+ * HTML document.
150
+ */
151
+ public static function escapeAttributeValue($attribute, $quote = null)
152
+ {
153
+ $quote = static::defaultParam($quote, static::getAttributeQuote());
154
+
155
+ $quoteStyle = $quote === static::QUOTE_SINGLE
156
+ ? ENT_QUOTES
157
+ : ENT_COMPAT;
158
+ return htmlspecialchars($attribute, $quoteStyle);
159
+ }
160
+ }
includes/Aventura/Wprss/Core/Block/Html/AbstractTag.php ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aventura\Wprss\Core\Block\Html;
4
+
5
+ /**
6
+ * Base functionlity for HTML tags.
7
+ *
8
+ * @since 4.9
9
+ */
10
+ abstract class AbstractTag extends AbstractHtml implements TagInterface
11
+ {
12
+ const TAG_NAME = 'div';
13
+
14
+ const K_CONTENT = 'content';
15
+ const K_BASE_NAMESPACE_DEPTH = 'base_namespace_depth';
16
+
17
+ /**
18
+ * {@inheritdoc}
19
+ *
20
+ * @since 4.9
21
+ */
22
+ public function getAttributes($attributes = false)
23
+ {
24
+ return !empty($attributes) && is_array($attributes)
25
+ ? $this->getDataForKeys($attributes)
26
+ : $this->getDataForKeys($this->_getNonAttributeKeys(), true);
27
+ }
28
+
29
+ /**
30
+ * {@inheritdoc}
31
+ *
32
+ * @since 4.9
33
+ */
34
+ public function setAttributes($attributes)
35
+ {
36
+ $this->addData($attributes);
37
+ return $this;
38
+ }
39
+
40
+ /**
41
+ * Gets names of data keys which do not count as attribute names.
42
+ *
43
+ * @since 4.9
44
+ * @return array A numeric array of key names.
45
+ */
46
+ protected function _getNonAttributeKeys()
47
+ {
48
+ return array(static::K_CONTENT, static::K_BASE_NAMESPACE_DEPTH);
49
+ }
50
+
51
+ /**
52
+ * {@inheritdoc}
53
+ *
54
+ * @since 4.9
55
+ * @return string This tag's content.
56
+ */
57
+ public function getContent()
58
+ {
59
+ return $this->getData(static::K_CONTENT);
60
+ }
61
+
62
+ /**
63
+ * {@inheritdoc}
64
+ *
65
+ * @since 4.9
66
+ */
67
+ public function setContent($content)
68
+ {
69
+ $this->setData(static::K_CONTENT, $content);
70
+ return $this;
71
+ }
72
+
73
+ /**
74
+ * {@inheritdoc}
75
+ *
76
+ * @since 4.9
77
+ */
78
+ public function getTagName()
79
+ {
80
+ return static::TAG_NAME;
81
+ }
82
+ }
includes/Aventura/Wprss/Core/Block/Html/Anchor.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Aventura\Wprss\Core\Block\Html;
3
+
4
+ /**
5
+ * Represents an HTML anchor (link).
6
+ *
7
+ * @since 4.9
8
+ */
9
+ class Anchor extends AbstractTag
10
+ {
11
+ const TAG_NAME = 'a';
12
+
13
+ /**
14
+ * Renders the anchor HTML.
15
+ *
16
+ * @since 4.9
17
+ */
18
+ public function getOutput()
19
+ {
20
+ $attributes = $this->getAttributes();
21
+ $attributes = count($attributes)
22
+ ? ' '.static::getAttributesStringFromArray($attributes)
23
+ : '';
24
+ $content = $this->getContent();
25
+ $tagName = $this->getTagName();
26
+
27
+ return sprintf('<%1$s%2$s>%3$s</%1$s>', $tagName, $attributes, $content);
28
+ }
29
+ }
includes/Aventura/Wprss/Core/Block/Html/TagInterface.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aventura\Wprss\Core\Block\Html;
4
+
5
+ /**
6
+ * Something that can be an HTML tag.
7
+ *
8
+ * @since 4.9
9
+ */
10
+ interface TagInterface
11
+ {
12
+
13
+ /**
14
+ * Retrieve an array of attributes.
15
+ *
16
+ * Keys are attribute names, values are attribute values.
17
+ *
18
+ * @param array|bool If is a non-empty array, will return attribute values
19
+ * for attributes in that array; Otherwise, return all attributes.
20
+ * @since 4.9
21
+ */
22
+ public function getAttributes($attributes);
23
+
24
+ /**
25
+ * Set attributes.
26
+ *
27
+ * Will set attributes to those specified in this array. Existing values
28
+ * remain.
29
+ *
30
+ * @since 4.9
31
+ */
32
+ public function setAttributes($attributes);
33
+
34
+ /**
35
+ * Get content of the tag.
36
+ *
37
+ * @since 4.9
38
+ */
39
+ public function getContent();
40
+
41
+ /**
42
+ * Set content of the tag.
43
+ *
44
+ * @since 4.9
45
+ */
46
+ public function setContent($content);
47
+
48
+ /**
49
+ * Get name of the tag.
50
+ *
51
+ * @since 4.9
52
+ */
53
+ public function getTagName();
54
+ }
includes/Aventura/Wprss/Core/DataObject.php CHANGED
@@ -832,7 +832,7 @@ class DataObject implements \ArrayAccess, DataObjectInterface {
832
  /**
833
  * Enter description here...
834
  *
835
- * @since 4.8.1
836
  * @param string $field
837
  * @param boolean $flag
838
  * @return Wp_Rss_SpinnerChief_Data_Object
@@ -852,6 +852,26 @@ class DataObject implements \ArrayAccess, DataObjectInterface {
852
  }
853
  return $this;
854
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
855
  }
856
 
857
 
832
  /**
833
  * Enter description here...
834
  *
835
+ * @since 4.8.1
836
  * @param string $field
837
  * @param boolean $flag
838
  * @return Wp_Rss_SpinnerChief_Data_Object
852
  }
853
  return $this;
854
  }
855
+
856
+ /**
857
+ * Gets data for keys that are specified.
858
+ *
859
+ * The second parameter inverts the check.
860
+ *
861
+ * @since 4.9
862
+ * @param string|array $keys A key or an array of keys to match.
863
+ * @param bool $exclude If true, returns elements which are not
864
+ * specified.
865
+ * @return array An array with elements that match the
866
+ * specified keys.
867
+ */
868
+ public function getDataForKeys($keys, $exclude = false)
869
+ {
870
+ $keys = array_flip((array) $keys);
871
+ return $exclude
872
+ ? array_diff_key($this->_data, $keys)
873
+ : array_intersect_key($this->_data, $keys);
874
+ }
875
  }
876
 
877
 
includes/Aventura/Wprss/Core/Http/Message/AbstractResponse.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aventura\Wprss\Core\Http\Message;
4
+
5
+ use Aventura\Wprss\Core;
6
+
7
+ /**
8
+ * Base functionality for responses
9
+ *
10
+ * @since 4.9
11
+ */
12
+ abstract class AbstractResponse extends Core\DataObject implements ResponseInterface
13
+ {
14
+ /**
15
+ * @since 4.9
16
+ */
17
+ const K_BODY = 'body';
18
+
19
+ /**
20
+ * @since 4.9
21
+ */
22
+ protected $body;
23
+
24
+ /**
25
+ * {@inheritdoc}
26
+ * @since 4.9
27
+ */
28
+ public function getBody()
29
+ {
30
+ return $this->getData(static::K_BODY);
31
+ }
32
+ }
includes/Aventura/Wprss/Core/Http/Message/Ajax/AbstractResponse.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aventura\Wprss\Core\Http\Message\Ajax;
4
+
5
+ use Aventura\Wprss\Core;
6
+ use Aventura\Wprss\Core\Http\Message;
7
+
8
+ /**
9
+ * @since 4.9
10
+ */
11
+ abstract class AbstractResponse extends Message\AbstractResponse implements Core\Http\Message\Ajax\AjaxInterface
12
+ {
13
+ /** @since 4.9 */
14
+ protected $ajaxData;
15
+
16
+ /**
17
+ * {@inheritdoc}
18
+ *
19
+ * @since 4.9
20
+ * @see Core\DataObject::getData()
21
+ * @since 4.9
22
+ */
23
+ public function getAjaxData($key = null) {
24
+ $this->_getAjaxData()->getData($key);
25
+ }
26
+
27
+ /**
28
+ * {@inheritdoc}
29
+ *
30
+ * @since 4.9
31
+ * @see Core\DataObject::addData()
32
+ * @param type $key
33
+ * @param type $value
34
+ * @return \Aventura\Wprss\Core\Http\Message\Ajax\AbstractResponse
35
+ */
36
+ public function setAjaxData($key, $value = null) {
37
+ $data = $this->_getAjaxData();
38
+ is_array($key)
39
+ ? $data->addData($key)
40
+ : $data->setData($key, $value);
41
+ return $this;
42
+ }
43
+
44
+ /**
45
+ * @since 4.9
46
+ * @return Core\DataObject
47
+ */
48
+ protected function _getAjaxData()
49
+ {
50
+ if (is_null($this->ajaxData)) {
51
+ $this->ajaxData = new Core\DataObject();
52
+ }
53
+
54
+ return $this->ajaxData;
55
+ }
56
+
57
+ /**
58
+ * {@inheritdoc}
59
+ *
60
+ * @since 4.9
61
+ * @return string
62
+ */
63
+ public function getBody()
64
+ {
65
+ return ($body = $this->getData(static::K_BODY))
66
+ ? $body
67
+ : static::convertToJson($this->_getAjaxData());
68
+ }
69
+
70
+ /**
71
+ * Converts data in the passed object to a JSON representation.
72
+ *
73
+ * If a data object instance is used, will convert the object's data.
74
+ * If any other type, will convert the properties.
75
+ * If array, the array keys and values will represent the data.
76
+ *
77
+ * @since 4.9
78
+ * @param array|Core|Core\DataObjectInterface|object $object An object with data to convert.
79
+ * @return string The JSON-encoded data.
80
+ */
81
+ public static function convertToJson($object)
82
+ {
83
+ $object = $object instanceof Core\DataObjectInterface
84
+ ? $object->getData()
85
+ : (array) $object;
86
+ $json = json_encode($object);
87
+
88
+ return $json;
89
+ }
90
+ }
includes/Aventura/Wprss/Core/Http/Message/Ajax/AjaxInterface.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aventura\Wprss\Core\Http\Message\Ajax;
4
+
5
+ use Aventura\Wprss\Core;
6
+
7
+ /**
8
+ * @since 4.9
9
+ */
10
+ interface AjaxInterface extends Core\Http\Message\ResponseInterface
11
+ {
12
+ /**
13
+ * Set one or many AJAX data members.
14
+ *
15
+ * This is data particular to the AJAX response, and is separate from the
16
+ * message data.
17
+ *
18
+ * Existing keys will be preserved. Same keys will overwrite.
19
+ *
20
+ * @since 4.9
21
+ * @see Core\DataObjectInterface::setData()
22
+ * @param string|array $key The key to set the data for, or a data array,
23
+ * where keys are data keys, and values are data values. If array, the
24
+ * second parameter will be ignored.
25
+ * @param null|mixed $value The value to set for the key.
26
+ */
27
+ public function setAjaxData($key, $value = null);
28
+
29
+ /**
30
+ * Get one or all AJAX data members.
31
+ *
32
+ * @since 4.9
33
+ * @param string|null $key The key for which to get data. If null, an array
34
+ * containing all AJAX data is returned.
35
+ * @return array|mixed The data member value, or an array with all AJAX data.
36
+ */
37
+ public function getAjaxData($key = null);
38
+ }
includes/Aventura/Wprss/Core/Http/Message/Ajax/Response.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aventura\Wprss\Core\Http\Message\Ajax;
4
+
5
+ /**
6
+ * A concrete response, for easy use.
7
+ *
8
+ * @since 4.9
9
+ */
10
+ class Response extends AbstractResponse
11
+ {
12
+ }
includes/Aventura/Wprss/Core/Http/Message/MessageInterface.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aventura\Wprss\Core\Http\Message;
4
+
5
+ /**
6
+ * @todo Substitute with PSR-7 interface.
7
+ * @since 4.9
8
+ * @link https://github.com/php-fig/http-message/blob/master/src/MessageInterface.php
9
+ */
10
+ interface MessageInterface
11
+ {
12
+ /**
13
+ * @since 4.9
14
+ */
15
+ public function getBody();
16
+ }
includes/Aventura/Wprss/Core/Http/Message/ResponseInterface.php ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aventura\Wprss\Core\Http\Message;
4
+
5
+ /**
6
+ * @todo Substitute with PSR-7 interface.
7
+ * @since 4.9
8
+ * @link https://github.com/php-fig/http-message/blob/master/src/ResponseInterface.php
9
+ */
10
+ interface ResponseInterface
11
+ {
12
+ }
includes/Aventura/Wprss/Core/Licensing/Manager.php CHANGED
@@ -503,7 +503,6 @@ class Manager {
503
  if ( $params['license'] instanceof License ) $params['license'] = $params['license']->getKey();
504
  if ( $params['license'] ) $params['license'] = sanitize_text_field ( $params['license']);
505
  if ( $params['item_name'] ) $params['item_name'] = sanitize_text_field ( $params['item_name']);
506
- if ( $params['url'] ) $params['url'] = urlencode ( $params['url']);
507
 
508
  // Send the request to the API
509
  $response = wp_remote_post( add_query_arg( $params, $storeUrl ) );
503
  if ( $params['license'] instanceof License ) $params['license'] = $params['license']->getKey();
504
  if ( $params['license'] ) $params['license'] = sanitize_text_field ( $params['license']);
505
  if ( $params['item_name'] ) $params['item_name'] = sanitize_text_field ( $params['item_name']);
 
506
 
507
  // Send the request to the API
508
  $response = wp_remote_post( add_query_arg( $params, $storeUrl ) );
includes/Aventura/Wprss/Core/Model/AjaxResponse.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Aventura\Wprss\Core\Model;
4
+
5
+ use Aventura\Wprss\Core;
6
+
7
+ /**
8
+ * @since 4.9
9
+ */
10
+ class AjaxResponse extends Core\Http\Message\Ajax\AbstractResponse
11
+ {
12
+ /**
13
+ * A convenient way to create an instance of this class which indicates that an exception has occurred.
14
+ *
15
+ * @since 4.9
16
+ * @param \Exception $e An exception, from which to take the response data.
17
+ * @return AjaxResponse A new instance with relevant data from the exception.
18
+ */
19
+ public static function createFromException(\Exception $e)
20
+ {
21
+ $response = static::createFromError($e->getMessage())->setAjaxData(array(
22
+ 'error_code' => $e->getCode()
23
+ ));
24
+
25
+ if (static::isDebug()) {
26
+ $response->setAjaxData(array(
27
+ 'debug_trace' => $e->getTrace(),
28
+ 'debug_file' => $e->getFile(),
29
+ 'debug_line' => $e->getLine()
30
+ ));
31
+ }
32
+
33
+ return $response;
34
+ }
35
+
36
+ /**
37
+ * A convenient way to create an instance of this class whic indicates that an error has occurred.
38
+ *
39
+ * @since 4.9
40
+ * @param string $error Text of the error message.
41
+ * @return AjaxResponse A new instance with relevant error data.
42
+ */
43
+ public static function createFromError($error)
44
+ {
45
+ $response = new static();
46
+ $response->setAjaxData(array(
47
+ 'is_error' => true,
48
+ 'error_message' => $error
49
+ ));
50
+
51
+ return $response;
52
+ }
53
+
54
+ /**
55
+ * Determines if this class is in debug mode.
56
+ *
57
+ * In debug mode, extended information about exceptions will be added to
58
+ * instances generated from exceptions. This data may not be safe for
59
+ * displaying in production environments.
60
+ *
61
+ * @since 4.9
62
+ * @return bool True if debug mode is on for this class; false otherwise.
63
+ */
64
+ public static function isDebug()
65
+ {
66
+ return WP_DEBUG;
67
+ }
68
+ }
includes/Aventura/Wprss/Core/Model/ModelAbstract.php CHANGED
@@ -359,4 +359,26 @@ abstract class ModelAbstract extends Core\DataObject implements ModelInterface
359
  {
360
  return null;
361
  }
362
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  {
360
  return null;
361
  }
362
+
363
+ /**
364
+ * Convenient wrapper for providing a default value to a parameter.
365
+ *
366
+ * Will return the default value if the parameter matches the criteria;
367
+ * otherwise, returns the parameter unchanged.
368
+ * The comparison is made in strict mode.
369
+ *
370
+ * @since 4.9
371
+ * @param mixed $param The actual value.
372
+ * @param mixed $default The default value.
373
+ * @param mixed $criteria The condition.
374
+ * @return mixed The parameter, or the default value.
375
+ */
376
+ public static function defaultParam($param, $default, $criteria = null)
377
+ {
378
+ if ($param === $criteria) {
379
+ return $default;
380
+ }
381
+
382
+ return $param;
383
+ }
384
+ }
includes/Aventura/Wprss/Core/Plugin.php CHANGED
@@ -3,13 +3,137 @@
3
  namespace Aventura\Wprss\Core;
4
 
5
  /**
6
- * A dummy plugin for the Core plugin.
7
  *
8
  * @since 4.8.1
9
- * @todo Create real Core plugin in the Core plugin.
10
  */
11
  class Plugin extends Plugin\PluginAbstract
12
  {
13
  const CODE = 'wprss';
14
  const VERSION = WPRSS_VERSION;
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  namespace Aventura\Wprss\Core;
4
 
5
  /**
6
+ * The Core instance of WP RSS Aggregator.
7
  *
8
  * @since 4.8.1
 
9
  */
10
  class Plugin extends Plugin\PluginAbstract
11
  {
12
  const CODE = 'wprss';
13
  const VERSION = WPRSS_VERSION;
14
+
15
+ /**
16
+ * Hooks the rest of the functionality of this class.
17
+ *
18
+ * @since 4.9
19
+ */
20
+ public function hook()
21
+ {
22
+ $this->on('!plugins_loaded', array($this, 'delayedHook'));
23
+ }
24
+
25
+ /**
26
+ * Hooks in functionality after all plugins are loaded.
27
+ *
28
+ * @since 4.9
29
+ */
30
+ public function delayedHook()
31
+ {
32
+ $this->on('!plugin_row_meta', array($this, '_addPluginRowMeta'), null, 10, 2);
33
+ }
34
+
35
+ /**
36
+ * Returns all meta members that appear below a plugin row in the backend.
37
+ *
38
+ * Handles `plugin_row_meta` WP native filter.
39
+ *
40
+ * @since 4.9
41
+ * @param type $meta
42
+ * @param type $pluginBasename
43
+ * @return array Numeric array, where each element is a meta information
44
+ * piece (usually link).
45
+ */
46
+ public function _addPluginRowMeta($meta, $pluginBasename)
47
+ {
48
+ if ($pluginBasename !== $this->getBasename()) {
49
+ return $meta;
50
+ }
51
+
52
+ $meta = array_merge($meta, array_values($this->getPluginRowMeta()));
53
+ return $meta;
54
+ }
55
+
56
+ /**
57
+ * Returns a list of meta members for this plugin.
58
+ *
59
+ * Raises plugin-specific event `plugin_row_meta`.
60
+ *
61
+ * @since 4.9
62
+ * @return array An array of meta members for this plugin, by key.
63
+ */
64
+ public function getPluginRowMeta()
65
+ {
66
+ return $this->event('plugin_row_meta', array('links' => array(
67
+ 'getting_started' => $this->getAnchor(array(
68
+ 'target' => '_blank',
69
+ 'href' => 'https://docs.wprssaggregator.com/category/getting-started/'
70
+ ), $this->__('Getting Started')),
71
+ 'extensions' => $this->getAnchor(array(
72
+ 'target' => '_blank',
73
+ 'href' => 'https://www.wprssaggregator.com/extensions/'
74
+ ), $this->__('Extensions'))
75
+ )))->getData('links');
76
+ }
77
+
78
+ /**
79
+ * Get a new anchor block instance.
80
+ *
81
+ * @since 4.9
82
+ * @param array|string $attributes Keys are attribute names; values are attribute
83
+ * values. These will become the attributes of the anchor tag.
84
+ * If string, this will be treated as the value of the 'href' attribute.
85
+ * @param string $content Content for the anchor tag. Usually text.
86
+ * @return Block\Html\TagInterface An anchor block instance.
87
+ */
88
+ public function getAnchor($attributes = array(), $content = '')
89
+ {
90
+ if (is_string($attributes)) {
91
+ $attributes = array('href' => $attributes);
92
+ }
93
+ $block = $this->createAnchorBlock()
94
+ ->setAttributes($attributes)
95
+ ->setContent($content);
96
+
97
+ return $block;
98
+ }
99
+
100
+ /**
101
+ * Anchor block factory.
102
+ *
103
+ * @since 4.9
104
+ * @return Aventura\Wprss\Core\Block\Html\TagInterface
105
+ */
106
+ public function createAnchorBlock()
107
+ {
108
+ return new \Aventura\Wprss\Core\Block\Html\Anchor();
109
+ }
110
+
111
+ /**
112
+ * Create an AJAX response instance that contains error data.
113
+ *
114
+ * @since 4.9
115
+ * @param \Exception|string $error An exception, or error message.
116
+ * @return Http\Message\Ajax\Response
117
+ */
118
+ public function createAjaxErrorResponse($error)
119
+ {
120
+ return $error instanceof \Exception
121
+ ? Model\AjaxResponse::createFromException($error)
122
+ : Model\AjaxResponse::createFromError($error);
123
+ }
124
+
125
+ /**
126
+ * Creates an instance of an AJAX response.
127
+ *
128
+ * @since 4.9
129
+ * @param type $data
130
+ * @return Http\Message\Ajax\Response
131
+ */
132
+ public function createAjaxResponse($data = array())
133
+ {
134
+ $response = new Model\AjaxResponse();
135
+ $response->setAjaxData($data);
136
+
137
+ return $response;
138
+ }
139
+ }
includes/Aventura/Wprss/Core/Plugin/PluginAbstract.php CHANGED
@@ -45,13 +45,7 @@ class PluginAbstract extends Core\Model\ModelAbstract implements PluginInterface
45
  if (!isset($data['basename'])) {
46
  throw $this->exception('Could not create plugin instance: "basename" must be specified', array(__NAMESPACE__, 'Exception'));
47
  }
48
- $basename = trim($data['basename']);
49
-
50
- // Account for full path to main file.
51
- if (substr($basename, 0, 1) === '/' || substr_count($basename, '/') >= 2) {
52
- $basename = static::getPluginBasename($basename);
53
- }
54
- $data['basename'] = $basename;
55
 
56
  // Normalizing and setting component factory
57
  if (is_null($factory) && isset($data['component_factory'])) {
@@ -61,7 +55,6 @@ class PluginAbstract extends Core\Model\ModelAbstract implements PluginInterface
61
  if ($factory) {
62
  $this->setFactory($factory);
63
  }
64
- $this->setBasename($basename);
65
 
66
  parent::__construct($data);
67
  }
@@ -356,4 +349,41 @@ class PluginAbstract extends Core\Model\ModelAbstract implements PluginInterface
356
 
357
  return null;
358
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  }
45
  if (!isset($data['basename'])) {
46
  throw $this->exception('Could not create plugin instance: "basename" must be specified', array(__NAMESPACE__, 'Exception'));
47
  }
48
+ $data['basename'] = static::standardizeBasename($data['basename']);
 
 
 
 
 
 
49
 
50
  // Normalizing and setting component factory
51
  if (is_null($factory) && isset($data['component_factory'])) {
55
  if ($factory) {
56
  $this->setFactory($factory);
57
  }
 
58
 
59
  parent::__construct($data);
60
  }
349
 
350
  return null;
351
  }
352
+
353
+ /**
354
+ * Converts all directory separators into Unix-style ones.
355
+ *
356
+ * @since 4.9
357
+ * @param string $path A filesystem path.
358
+ * @return The path with standardized directory separators, and trimmed
359
+ * whitespace.
360
+ */
361
+ public static function standardizeDirectorySeparators($path)
362
+ {
363
+ return trim(str_replace(array('\\', '/'), '/', $path));
364
+ }
365
+
366
+ /**
367
+ * Will standardize a plugin basename.
368
+ *
369
+ * A standard plugin basename is a path to the main plugin file relative
370
+ * to the plugins directory, and with Unix directory separators if
371
+ * applicable.
372
+ *
373
+ * @since 4.9
374
+ * @see standardizeDirectorySeparators()
375
+ * @param string $path An absolute or relative path to a plugin main file.
376
+ * @return string A standardized plugin basename.
377
+ */
378
+ public static function standardizeBasename($path)
379
+ {
380
+ $path = static::standardizeDirectorySeparators($path);
381
+
382
+ // Account for full path to main file.
383
+ if (substr($path, 0, 1) === '/' || substr_count($path, '/') >= 2) {
384
+ $path = static::getPluginBasename($path);
385
+ }
386
+
387
+ return $path;
388
+ }
389
  }
includes/admin-addons.php CHANGED
@@ -16,7 +16,7 @@
16
 
17
  <h2><?php _e( 'Add-Ons', WPRSS_TEXT_DOMAIN ); ?></h2>
18
  <p><?php _e( "The following Add-ons are available to increase the functionality of the WP RSS Aggregator plugin.", WPRSS_TEXT_DOMAIN ); ?><br />
19
- <?php _e( "Each Add-on can be installed as a separate plugin. Note that activating the Feed to Post plugin will deactivate the Categories and Excerpts & Thumbnails add-ons.", WPRSS_TEXT_DOMAIN ); ?></p>
20
 
21
  <div id="add-ons" class="clearfix">
22
 
16
 
17
  <h2><?php _e( 'Add-Ons', WPRSS_TEXT_DOMAIN ); ?></h2>
18
  <p><?php _e( "The following Add-ons are available to increase the functionality of the WP RSS Aggregator plugin.", WPRSS_TEXT_DOMAIN ); ?><br />
19
+ <?php _e( "Each Add-on can be installed as a separate plugin.", WPRSS_TEXT_DOMAIN ); ?></p>
20
 
21
  <div id="add-ons" class="clearfix">
22
 
includes/admin-ajax-notice.php CHANGED
@@ -144,6 +144,12 @@
144
  /**
145
  * Responsible for tracking and outputting admin notices
146
  *
 
 
 
 
 
 
147
  * @since 4.7.4
148
  */
149
  class WPRSS_Admin_Notices {
@@ -493,7 +499,7 @@ class WPRSS_Admin_Notices {
493
 
494
  // Auto-generate ID
495
  if ( is_null( $data['id'] ) )
496
- $data['id'] = $this->generate_unique_id( 'admin-notice-' );
497
 
498
  // Prefix ID
499
  $data['id'] = $this->prefix( $data['id'] );
@@ -1006,14 +1012,18 @@ class WPRSS_Admin_Notices {
1006
  * @param string $prefix The prefix to give to the generated ID.
1007
  * @return string A notice ID unique to this instance in the scope of this collection.
1008
  */
1009
- public function generate_unique_id( $prefix = '' ) {
1010
  do {
1011
- $id = uniqid( $prefix );
1012
  } while ( $this->has_notice( $id ) );
1013
 
1014
  return apply_filters( $this->prefix( 'admin_notice_generate_unique_id' ), $id, $prefix, $this );
1015
  }
1016
 
 
 
 
 
1017
 
1018
  /**
1019
  * Generate the HTML for all allowed notices, sequentially.
@@ -1094,12 +1104,13 @@ class WPRSS_Admin_Notices {
1094
  if ( is_array( $notice ) )
1095
  $notice = isset( $notice['id'] ) ? $notice['id'] : null;
1096
 
 
1097
  if ( is_null( $notice ) )
1098
  throw new Exception( sprintf( 'Could not hide notice: Notice ID must be specified' ) );
1099
  if ( is_null( $nonce ) )
1100
  throw new Exception( sprintf( 'Could not hide notice: nonce must be specified' ) );
1101
  if ( !($notice = $this->get_notices( $notice ) ) )
1102
- throw new Exception( sprintf( 'Could not hide notice: No notice found for ID "%1$s"', $notice ) );
1103
 
1104
  // Is it the right nonce?
1105
  if ( $notice['nonce'] !== $nonce )
@@ -1207,7 +1218,7 @@ function wprss_admin_notice_add( $notice ) {
1207
  try {
1208
  if ( !($collection = wprss_admin_notice_get_collection()) )
1209
  return false;
1210
-
1211
  $collection->add_notice( $notice );
1212
  } catch ( Exception $e ) {
1213
  return new WP_Error( 'could_not_add_admin_notice', $e->getMessage() );
144
  /**
145
  * Responsible for tracking and outputting admin notices
146
  *
147
+ * Usage:
148
+ * Initialize by calling {@see init()} early, before `admin_init`. On `plugins_loaded`
149
+ * is a good place.
150
+ * Do not add notices conditionally. Instead, add them always, but specify
151
+ * the `condition` index to {@see add_notice()}.
152
+ *
153
  * @since 4.7.4
154
  */
155
  class WPRSS_Admin_Notices {
499
 
500
  // Auto-generate ID
501
  if ( is_null( $data['id'] ) )
502
+ $data['id'] = $this->generate_unique_id( 'admin-notice-', $data );
503
 
504
  // Prefix ID
505
  $data['id'] = $this->prefix( $data['id'] );
1012
  * @param string $prefix The prefix to give to the generated ID.
1013
  * @return string A notice ID unique to this instance in the scope of this collection.
1014
  */
1015
+ public function generate_unique_id( $prefix = '', $data = null ) {
1016
  do {
1017
+ $id = is_null($data) ? uniqid( $prefix ) : $prefix . $this->hash($data);
1018
  } while ( $this->has_notice( $id ) );
1019
 
1020
  return apply_filters( $this->prefix( 'admin_notice_generate_unique_id' ), $id, $prefix, $this );
1021
  }
1022
 
1023
+ public function hash($data) {
1024
+ return md5(serialize($data));
1025
+ }
1026
+
1027
 
1028
  /**
1029
  * Generate the HTML for all allowed notices, sequentially.
1104
  if ( is_array( $notice ) )
1105
  $notice = isset( $notice['id'] ) ? $notice['id'] : null;
1106
 
1107
+ $notice_id = $notice;
1108
  if ( is_null( $notice ) )
1109
  throw new Exception( sprintf( 'Could not hide notice: Notice ID must be specified' ) );
1110
  if ( is_null( $nonce ) )
1111
  throw new Exception( sprintf( 'Could not hide notice: nonce must be specified' ) );
1112
  if ( !($notice = $this->get_notices( $notice ) ) )
1113
+ throw new Exception( sprintf( 'Could not hide notice: No notice found for ID "%1$s"', $notice_id ) );
1114
 
1115
  // Is it the right nonce?
1116
  if ( $notice['nonce'] !== $nonce )
1218
  try {
1219
  if ( !($collection = wprss_admin_notice_get_collection()) )
1220
  return false;
1221
+
1222
  $collection->add_notice( $notice );
1223
  } catch ( Exception $e ) {
1224
  return new WP_Error( 'could_not_add_admin_notice', $e->getMessage() );
includes/admin-display.php CHANGED
@@ -354,6 +354,7 @@
354
 
355
  $fetch_items_row_action_text = apply_filters( 'wprss_fetch_items_row_action_text', __( 'Fetch Items', WPRSS_TEXT_DOMAIN ) );
356
  $actions[ 'fetch' ] = '<a href="javascript:;" class="wprss_ajax_action" pid="'. $post->ID .'" purl="'.home_url().'/wp-admin/admin-ajax.php">' . $fetch_items_row_action_text . '</a>';
 
357
 
358
  $purge_feeds_row_action_text = apply_filters( 'wprss_purge_feeds_row_action_text', __( 'Delete Items', WPRSS_TEXT_DOMAIN ) );
359
  $purge_feeds_row_action_title = apply_filters( 'wprss_purge_feeds_row_action_title', __( 'Delete feed items imported by this feed source', WPRSS_TEXT_DOMAIN ) );
@@ -456,9 +457,26 @@
456
  * @since 3.3
457
  */
458
  function wprss_fetch_feeds_action_hook() {
459
- if ( isset( $_POST['id'] ) && !empty( $_POST['id'] ) ) {
460
- if ( ! current_user_can( 'edit_feed_sources' ) ) die();
 
 
 
 
 
 
461
  $id = $_POST['id'];
 
 
 
 
 
 
 
 
 
 
 
462
  update_post_meta( $id, 'wprss_force_next_fetch', '1' );
463
 
464
  // Prepare the schedule args
@@ -467,23 +485,33 @@
467
  // Get the current schedule - do nothing if not scheduled
468
  $next_scheduled = wp_next_scheduled( 'wprss_fetch_single_feed_hook', $schedule_args );
469
  if ( $next_scheduled !== FALSE ) {
470
- // If scheduled, unschedule it
471
- wp_unschedule_event( $next_scheduled, 'wprss_fetch_single_feed_hook', $schedule_args );
472
-
473
- // Get the interval option for the feed source
474
- $interval = get_post_meta( $id, 'wprss_update_interval', TRUE );
475
- // if the feed source uses its own interval
476
- if ( $interval !== '' && $interval !== wprss_get_default_feed_source_update_interval() ) {
477
- // Add meta in feed source. This is used to notify the source that it needs to reschedule it
478
- update_post_meta( $id, 'wprss_reschedule_event', $next_scheduled );
479
- }
480
  }
481
 
482
  // Schedule the event for 5 seconds from now
483
  wp_schedule_single_event( time() + 1, 'wprss_fetch_single_feed_hook', $schedule_args );
484
  wprss_flag_feed_as_updating( $id );
485
- die();
 
 
 
 
 
 
486
  }
 
 
 
 
487
  }
488
 
489
 
354
 
355
  $fetch_items_row_action_text = apply_filters( 'wprss_fetch_items_row_action_text', __( 'Fetch Items', WPRSS_TEXT_DOMAIN ) );
356
  $actions[ 'fetch' ] = '<a href="javascript:;" class="wprss_ajax_action" pid="'. $post->ID .'" purl="'.home_url().'/wp-admin/admin-ajax.php">' . $fetch_items_row_action_text . '</a>';
357
+ $actions[ 'fetch' ] .= wp_nonce_field( sprintf( 'wprss-fetch-items-for-%s', $post->ID ) );
358
 
359
  $purge_feeds_row_action_text = apply_filters( 'wprss_purge_feeds_row_action_text', __( 'Delete Items', WPRSS_TEXT_DOMAIN ) );
360
  $purge_feeds_row_action_title = apply_filters( 'wprss_purge_feeds_row_action_title', __( 'Delete feed items imported by this feed source', WPRSS_TEXT_DOMAIN ) );
457
  * @since 3.3
458
  */
459
  function wprss_fetch_feeds_action_hook() {
460
+ $response = wprss()->createAjaxResponse();
461
+ $wprss = wprss();
462
+ $kFeedSourceId = 'feed_source_id';
463
+ try {
464
+ $kId = 'id';
465
+ if (!isset( $_POST[$kId] ) || empty( $_POST[$kId] )) {
466
+ throw new Exception($wprss->__('Could not schedule fetch: source ID must be specified'));
467
+ }
468
  $id = $_POST['id'];
469
+ $response->setAjaxData($kFeedSourceId, $id);
470
+
471
+ if (!current_user_can('edit_feed_sources')) {
472
+ throw new Exception($wprss->__(array('Could not schedule fetch for source #%1$s: user must have sufficient priviledges', $id)));
473
+ }
474
+
475
+ // Verify admin referer
476
+ if (!wprss_verify_nonce( sprintf( 'wprss-fetch-items-for-%s', $id ), 'wprss_admin_ajax_nonce' )) {
477
+ throw new Exception($wprss->__(array('Could not schedule fetch for source #%1$s: nonce is expired', $id)));
478
+ }
479
+
480
  update_post_meta( $id, 'wprss_force_next_fetch', '1' );
481
 
482
  // Prepare the schedule args
485
  // Get the current schedule - do nothing if not scheduled
486
  $next_scheduled = wp_next_scheduled( 'wprss_fetch_single_feed_hook', $schedule_args );
487
  if ( $next_scheduled !== FALSE ) {
488
+ // If scheduled, unschedule it
489
+ wp_unschedule_event( $next_scheduled, 'wprss_fetch_single_feed_hook', $schedule_args );
490
+
491
+ // Get the interval option for the feed source
492
+ $interval = get_post_meta( $id, 'wprss_update_interval', TRUE );
493
+ // if the feed source uses its own interval
494
+ if ( $interval !== '' && $interval !== wprss_get_default_feed_source_update_interval() ) {
495
+ // Add meta in feed source. This is used to notify the source that it needs to reschedule it
496
+ update_post_meta( $id, 'wprss_reschedule_event', $next_scheduled );
497
+ }
498
  }
499
 
500
  // Schedule the event for 5 seconds from now
501
  wp_schedule_single_event( time() + 1, 'wprss_fetch_single_feed_hook', $schedule_args );
502
  wprss_flag_feed_as_updating( $id );
503
+ } catch (Exception $e) {
504
+ $response = wprss()->createAjaxErrorResponse($e);
505
+ if (isset($id)) {
506
+ $response->setAjaxData($kFeedSourceId, $id);
507
+ }
508
+ echo $response->getBody();
509
+ exit();
510
  }
511
+
512
+ $response->setAjaxData('message', $wprss->__(array('Fetch for feed source #%1$s successfully scheduled', $id)));
513
+ echo $response->getBody();
514
+ exit();
515
  }
516
 
517
 
includes/admin-help.php CHANGED
@@ -132,7 +132,7 @@
132
  </form>
133
  <div style='line-height:2.3em; margin-top:10px;'>
134
  <button id='send-message-btn' class='button button-primary'><?php _e('Send Message', WPRSS_TEXT_DOMAIN); ?></button>
135
- <span id='support-error'></span>
136
  </div>
137
 
138
  <?php
132
  </form>
133
  <div style='line-height:2.3em; margin-top:10px;'>
134
  <button id='send-message-btn' class='button button-primary'><?php _e('Send Message', WPRSS_TEXT_DOMAIN); ?></button>
135
+ <div id='support-error'></div>
136
  </div>
137
 
138
  <?php
includes/custom-post-types.php CHANGED
@@ -54,7 +54,8 @@
54
  'capability_type' => 'feed',
55
  'map_meta_cap' => true,
56
  'supports' => array( 'title' ),
57
- 'labels' => $labels
 
58
  )
59
  );
60
 
54
  'capability_type' => 'feed',
55
  'map_meta_cap' => true,
56
  'supports' => array( 'title' ),
57
+ 'labels' => $labels,
58
+ 'menu_icon' => 'dashicons-rss'
59
  )
60
  );
61
 
includes/feed-access.php CHANGED
@@ -3,18 +3,18 @@
3
 
4
  /**
5
  * Centralizes control over resource fetching.
6
- *
7
  * @since 4.7
8
  */
9
  class WPRSS_Feed_Access {
10
-
11
  protected static $_instance;
12
-
13
  protected $_certificate_file_path;
14
-
15
  const SETTING_KEY_CERTIFICATE_PATH = 'certificate-path';
16
  const SETTING_KEY_FEED_REQUEST_USERAGENT = 'feed_request_useragent';
17
-
18
  /**
19
  * @since 4.7
20
  * @return WPRSS_Feed_Access The singleton instance of this class.
@@ -24,19 +24,19 @@ class WPRSS_Feed_Access {
24
  $class_name = __CLASS__;
25
  self::$_instance = new $class_name;
26
  }
27
-
28
  return self::$_instance;
29
  }
30
-
31
-
32
  public function __construct() {
33
  $this->_construct();
34
  }
35
-
36
-
37
  /**
38
  * The parameter-less constructor.
39
- *
40
  * @since 4.7
41
  */
42
  protected function _construct() {
@@ -44,11 +44,11 @@ class WPRSS_Feed_Access {
44
  add_action( 'wprss_settings_array', array( $this, 'add_settings' ) );
45
  add_action( 'wprss_default_settings_general', array( $this, 'add_default_settings' ) );
46
  }
47
-
48
-
49
  /**
50
  * Sets the path to the certificate, which will be used by WPRSS to fetch remote content.
51
- *
52
  * @since 4.7
53
  * @param string $path Absolute path to the certificate file.
54
  * @return \WPRSS_Feed_Access This instance.
@@ -57,11 +57,11 @@ class WPRSS_Feed_Access {
57
  $this->_certificate_file_path = $path;
58
  return $this;
59
  }
60
-
61
-
62
  /**
63
  * Gets the path to the certificate, which will be used by WPRSS to fetch remote content.
64
- *
65
  * @since 4.7
66
  * @see get_certificate_path_setting()
67
  * @return string Absolute path to the certificate file. By default will use the option.
@@ -72,24 +72,24 @@ class WPRSS_Feed_Access {
72
 
73
  return $this->_certificate_file_path;
74
  }
75
-
76
-
77
  /**
78
  * Gets the value of the option that stores the path to the certificate file.
79
  * Relative paths will be converted to absolute, as if relative to WP root.
80
- *
81
  * @since 4.7
82
  * @return string Absolute path to the certificate file.
83
  */
84
  public function get_certificate_path_setting() {
85
  $path = wprss_get_general_setting( self::SETTING_KEY_CERTIFICATE_PATH );
86
-
87
  if ( empty( $path ) )
88
  return $path;
89
-
90
  if ( !path_is_absolute( $path ) )
91
  $path = ABSPATH . $path;
92
-
93
  return $path;
94
  }
95
 
@@ -120,12 +120,12 @@ class WPRSS_Feed_Access {
120
  ? SIMPLEPIE_USERAGENT
121
  : $useragent;
122
  }
123
-
124
-
125
  /**
126
  * This happens immediately before feed initialization.
127
  * Handles the `wprss_fetch_feed_before` action.
128
- *
129
  * @since 4.7
130
  * @param SimplePie $feed The instance of the object that represents the feed to be fetched.
131
  * @param string $url The URL, from which the feed is going to be fetched.
@@ -135,11 +135,11 @@ class WPRSS_Feed_Access {
135
  $feed->set_useragent($this->get_useragent());
136
  WPRSS_SimplePie_File::set_default_certificate_file_path( $this->get_certificate_file_path() );
137
  }
138
-
139
-
140
  /**
141
  * Implements a `wprss_settings_array` filter.
142
- *
143
  * @since 4.7
144
  * @param array $settings The current settings array, where 1st dimension is secion code, 2nd is setting code, 3rd is setting option(s).
145
  * @return array The new settings array.
@@ -154,11 +154,11 @@ class WPRSS_Feed_Access {
154
  'label' => __( 'Feed Request Useragent', WPRSS_TEXT_DOMAIN ),
155
  'callback' => array( $this, 'render_feed_request_useragent_setting' )
156
  );
157
-
158
  return $settings;
159
  }
160
-
161
-
162
  /**
163
  * @since 4.7
164
  * @param array $settings The array of settings, where key is
@@ -171,11 +171,11 @@ class WPRSS_Feed_Access {
171
 
172
  return $settings;
173
  }
174
-
175
-
176
  /**
177
  * Renders the setting field for the certificate path.
178
- *
179
  * @since 4.7
180
  * @see wprss_admin_init
181
  * @param array $field Data of this field.
@@ -215,12 +215,12 @@ class WPRSS_SimplePie_File extends SimplePie_File {
215
 
216
  protected static $_default_certificate_file_path;
217
  protected $_certificate_file_path;
218
-
219
-
220
  /**
221
  * Copied from {@see SimplePie_File#__construct()}.
222
  * Adds call to {@see _before_curl_exec()}.
223
- *
224
  * @since 4.7
225
  */
226
  public function __construct( $url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false ) {
@@ -400,14 +400,14 @@ class WPRSS_SimplePie_File extends SimplePie_File {
400
  }
401
  }
402
  }
403
-
404
-
405
  /**
406
  * Additional preparation of the curl request.
407
  * Sets the {@link CURLOPT_CAINFO http://php.net/manual/en/function.curl-setopt.php}
408
  * cURL option to a value determined by {@see get_default_certificate_file_path}.
409
  * If the value is empty, leaves it as is.
410
- *
411
  * @since 4.7
412
  * @param resource $fp Pointer to a resource created by {@see curl_init()}.
413
  * @param string $url The URL, to which the cURL request is being made.
@@ -422,36 +422,36 @@ class WPRSS_SimplePie_File extends SimplePie_File {
422
 
423
  return $this;
424
  }
425
-
426
-
427
  /**
428
  * Gets the path to the certificate, which will be used by this instance
429
  * to fetch remote content.
430
- *
431
  * @since 4.7
432
  * @return string Path to the certificate file.
433
  */
434
  public function get_certificate_file_path() {
435
  return $this->_certificate_file_path;
436
  }
437
-
438
-
439
  /**
440
  * Gets the path to the certificate file, which will be used by future
441
  * instances of this class.
442
- *
443
  * @since 4.7
444
  * @return string Path to the certificate file.
445
  */
446
  public static function get_default_certificate_file_path() {
447
  return self::$_default_certificate_file_path;
448
  }
449
-
450
-
451
  /**
452
  * Sets the path to the certificate file.
453
  * This path will be used by future instances of this class.
454
- *
455
  * @since 4.7
456
  * @param string $path The path to the certificate file.
457
  */
@@ -469,20 +469,26 @@ class WPRSS_SimplePie_File extends SimplePie_File {
469
  */
470
  protected function _afterCurlHeadersParsed($curlInfo)
471
  {
 
 
 
 
 
 
 
 
472
  $error = implode("\n", array(
473
  'The resource could not be retrieved because of a %1$s error with code %2$d',
474
- 'Server returned %3$d characters:',
475
  '%4$s'
476
  ));
477
 
478
- $code = $this->status_code;
479
- $body = $this->body;
480
  if ($code >= 400 && $code < 500 ) { // Client error
481
- $this->error = sprintf($error, 'client', $code, strlen($body), $body);
482
  $this->success = false;
483
  }
484
  if ($code >= 500 && $code < 600 ) { // Server error
485
- $this->error = sprintf($error, 'server', $code, strlen($body), $body);
486
  $this->success = false;
487
  }
488
  }
3
 
4
  /**
5
  * Centralizes control over resource fetching.
6
+ *
7
  * @since 4.7
8
  */
9
  class WPRSS_Feed_Access {
10
+
11
  protected static $_instance;
12
+
13
  protected $_certificate_file_path;
14
+
15
  const SETTING_KEY_CERTIFICATE_PATH = 'certificate-path';
16
  const SETTING_KEY_FEED_REQUEST_USERAGENT = 'feed_request_useragent';
17
+
18
  /**
19
  * @since 4.7
20
  * @return WPRSS_Feed_Access The singleton instance of this class.
24
  $class_name = __CLASS__;
25
  self::$_instance = new $class_name;
26
  }
27
+
28
  return self::$_instance;
29
  }
30
+
31
+
32
  public function __construct() {
33
  $this->_construct();
34
  }
35
+
36
+
37
  /**
38
  * The parameter-less constructor.
39
+ *
40
  * @since 4.7
41
  */
42
  protected function _construct() {
44
  add_action( 'wprss_settings_array', array( $this, 'add_settings' ) );
45
  add_action( 'wprss_default_settings_general', array( $this, 'add_default_settings' ) );
46
  }
47
+
48
+
49
  /**
50
  * Sets the path to the certificate, which will be used by WPRSS to fetch remote content.
51
+ *
52
  * @since 4.7
53
  * @param string $path Absolute path to the certificate file.
54
  * @return \WPRSS_Feed_Access This instance.
57
  $this->_certificate_file_path = $path;
58
  return $this;
59
  }
60
+
61
+
62
  /**
63
  * Gets the path to the certificate, which will be used by WPRSS to fetch remote content.
64
+ *
65
  * @since 4.7
66
  * @see get_certificate_path_setting()
67
  * @return string Absolute path to the certificate file. By default will use the option.
72
 
73
  return $this->_certificate_file_path;
74
  }
75
+
76
+
77
  /**
78
  * Gets the value of the option that stores the path to the certificate file.
79
  * Relative paths will be converted to absolute, as if relative to WP root.
80
+ *
81
  * @since 4.7
82
  * @return string Absolute path to the certificate file.
83
  */
84
  public function get_certificate_path_setting() {
85
  $path = wprss_get_general_setting( self::SETTING_KEY_CERTIFICATE_PATH );
86
+
87
  if ( empty( $path ) )
88
  return $path;
89
+
90
  if ( !path_is_absolute( $path ) )
91
  $path = ABSPATH . $path;
92
+
93
  return $path;
94
  }
95
 
120
  ? SIMPLEPIE_USERAGENT
121
  : $useragent;
122
  }
123
+
124
+
125
  /**
126
  * This happens immediately before feed initialization.
127
  * Handles the `wprss_fetch_feed_before` action.
128
+ *
129
  * @since 4.7
130
  * @param SimplePie $feed The instance of the object that represents the feed to be fetched.
131
  * @param string $url The URL, from which the feed is going to be fetched.
135
  $feed->set_useragent($this->get_useragent());
136
  WPRSS_SimplePie_File::set_default_certificate_file_path( $this->get_certificate_file_path() );
137
  }
138
+
139
+
140
  /**
141
  * Implements a `wprss_settings_array` filter.
142
+ *
143
  * @since 4.7
144
  * @param array $settings The current settings array, where 1st dimension is secion code, 2nd is setting code, 3rd is setting option(s).
145
  * @return array The new settings array.
154
  'label' => __( 'Feed Request Useragent', WPRSS_TEXT_DOMAIN ),
155
  'callback' => array( $this, 'render_feed_request_useragent_setting' )
156
  );
157
+
158
  return $settings;
159
  }
160
+
161
+
162
  /**
163
  * @since 4.7
164
  * @param array $settings The array of settings, where key is
171
 
172
  return $settings;
173
  }
174
+
175
+
176
  /**
177
  * Renders the setting field for the certificate path.
178
+ *
179
  * @since 4.7
180
  * @see wprss_admin_init
181
  * @param array $field Data of this field.
215
 
216
  protected static $_default_certificate_file_path;
217
  protected $_certificate_file_path;
218
+
219
+
220
  /**
221
  * Copied from {@see SimplePie_File#__construct()}.
222
  * Adds call to {@see _before_curl_exec()}.
223
+ *
224
  * @since 4.7
225
  */
226
  public function __construct( $url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false ) {
400
  }
401
  }
402
  }
403
+
404
+
405
  /**
406
  * Additional preparation of the curl request.
407
  * Sets the {@link CURLOPT_CAINFO http://php.net/manual/en/function.curl-setopt.php}
408
  * cURL option to a value determined by {@see get_default_certificate_file_path}.
409
  * If the value is empty, leaves it as is.
410
+ *
411
  * @since 4.7
412
  * @param resource $fp Pointer to a resource created by {@see curl_init()}.
413
  * @param string $url The URL, to which the cURL request is being made.
422
 
423
  return $this;
424
  }
425
+
426
+
427
  /**
428
  * Gets the path to the certificate, which will be used by this instance
429
  * to fetch remote content.
430
+ *
431
  * @since 4.7
432
  * @return string Path to the certificate file.
433
  */
434
  public function get_certificate_file_path() {
435
  return $this->_certificate_file_path;
436
  }
437
+
438
+
439
  /**
440
  * Gets the path to the certificate file, which will be used by future
441
  * instances of this class.
442
+ *
443
  * @since 4.7
444
  * @return string Path to the certificate file.
445
  */
446
  public static function get_default_certificate_file_path() {
447
  return self::$_default_certificate_file_path;
448
  }
449
+
450
+
451
  /**
452
  * Sets the path to the certificate file.
453
  * This path will be used by future instances of this class.
454
+ *
455
  * @since 4.7
456
  * @param string $path The path to the certificate file.
457
  */
469
  */
470
  protected function _afterCurlHeadersParsed($curlInfo)
471
  {
472
+ $bodyMaxLength = 150;
473
+ $code = $this->status_code;
474
+ $body = $this->body;
475
+ $bodyLength = strlen($body);
476
+ $bodyOutput = strlen($body) < $bodyMaxLength
477
+ ? $body
478
+ : substr($body, 0, $bodyMaxLength);
479
+ $outputLength = strlen($bodyOutput);
480
  $error = implode("\n", array(
481
  'The resource could not be retrieved because of a %1$s error with code %2$d',
482
+ 'Server returned %3$d characters' . ($outputLength < $bodyMaxLength ? '' : ', of which ' . $outputLength . ' are below') . ':',
483
  '%4$s'
484
  ));
485
 
 
 
486
  if ($code >= 400 && $code < 500 ) { // Client error
487
+ $this->error = sprintf($error, 'client', $code, $bodyLength, $bodyOutput);
488
  $this->success = false;
489
  }
490
  if ($code >= 500 && $code < 600 ) { // Server error
491
+ $this->error = sprintf($error, 'server', $code, $bodyLength, $bodyOutput);
492
  $this->success = false;
493
  }
494
  }
includes/feed-importing.php CHANGED
@@ -106,8 +106,9 @@
106
  // Generate a list of items fetched, that are not already in the DB
107
  $new_items = array();
108
  foreach ( $items_to_insert as $item ) {
109
- $permalink = wprss_normalize_permalink( $item->get_permalink() );
110
- wprss_log_obj( 'Normalizing permalink', sprintf('%1$s -> %2$s', $item->get_permalink(), $permalink), null, WPRSS_LOG_LEVEL_SYSTEM );
 
111
 
112
  // Check if not blacklisted and not already imported
113
  $is_blacklisted = wprss_is_blacklisted( $permalink );
@@ -305,10 +306,10 @@
305
  * @return string The normalized permalink
306
  * @since 4.2.3
307
  */
308
- function wprss_normalize_permalink( $permalink ) {
309
  // Apply normalization functions on the permalink
310
  $permalink = trim( $permalink );
311
- $permalink = apply_filters( 'wprss_normalize_permalink', $permalink );
312
  // Return the normalized permalink
313
  return $permalink;
314
  }
@@ -464,7 +465,7 @@
464
  foreach ( $items as $item ) {
465
 
466
  // Normalize the URL
467
- $permalink = wprss_normalize_permalink( $item->get_permalink() );
468
  wprss_log_obj( 'Importing item', $permalink, null, WPRSS_LOG_LEVEL_INFO );
469
  wprss_log_obj( 'Original permalink', $item->get_permalink(), null, WPRSS_LOG_LEVEL_SYSTEM );
470
 
106
  // Generate a list of items fetched, that are not already in the DB
107
  $new_items = array();
108
  foreach ( $items_to_insert as $item ) {
109
+
110
+ $permalink = wprss_normalize_permalink( $item->get_permalink(), $item, $feed_ID );
111
+ wprss_log_obj( 'Normalized permalink', sprintf('%1$s -> %2$s', $item->get_permalink(), $permalink), null, WPRSS_LOG_LEVEL_SYSTEM );
112
 
113
  // Check if not blacklisted and not already imported
114
  $is_blacklisted = wprss_is_blacklisted( $permalink );
306
  * @return string The normalized permalink
307
  * @since 4.2.3
308
  */
309
+ function wprss_normalize_permalink( $permalink, $item, $feed_ID) {
310
  // Apply normalization functions on the permalink
311
  $permalink = trim( $permalink );
312
+ $permalink = apply_filters( 'wprss_normalize_permalink', $permalink, $item, $feed_ID);
313
  // Return the normalized permalink
314
  return $permalink;
315
  }
465
  foreach ( $items as $item ) {
466
 
467
  // Normalize the URL
468
+ $permalink = wprss_normalize_permalink( $item->get_permalink(), $item, $feed_ID );
469
  wprss_log_obj( 'Importing item', $permalink, null, WPRSS_LOG_LEVEL_INFO );
470
  wprss_log_obj( 'Original permalink', $item->get_permalink(), null, WPRSS_LOG_LEVEL_SYSTEM );
471
 
includes/misc-functions.php CHANGED
@@ -231,7 +231,7 @@ EOS;
231
  }
232
 
233
  // Stock trimming of words
234
- $plain_text = wp_trim_words_wprss( $plain_text, $max_words );
235
 
236
  /*
237
  * Put the tags back, using the offsets recorded
@@ -373,4 +373,25 @@ function wprss_validate_url( $url ) {
373
  ')';
374
 
375
  return preg_match('!' . $expression . '!', $url) ? $url : null;
376
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  }
232
 
233
  // Stock trimming of words
234
+ $plain_text = wp_trim_words_wprss( $plain_text, $max_words, '' );
235
 
236
  /*
237
  * Put the tags back, using the offsets recorded
373
  ')';
374
 
375
  return preg_match('!' . $expression . '!', $url) ? $url : null;
376
+ }
377
+
378
+ if (!function_exists('wprss_verify_nonce'))
379
+ {
380
+ /**
381
+ * Check if a WP nonce sent in a reques is valid.
382
+ *
383
+ * @since 4.9
384
+ * @see wp_verify_nonce()
385
+ * @param string $action ID of the action, for which checking the nonce.
386
+ * @param string $queryArg Name of the key in the $_REQUEST global
387
+ * which contains the nonce value.
388
+ * @return bool|int False if nonce invalid, 1 if it's the first 12 hours of
389
+ * validity, 2 if the second 12 hours.
390
+ */
391
+ function wprss_verify_nonce($action, $queryArg)
392
+ {
393
+ return isset($_REQUEST[$queryArg])
394
+ ? wp_verify_nonce($_REQUEST[$queryArg], $action)
395
+ : false;
396
+ }
397
+ }
js/admin-custom.js CHANGED
@@ -1,31 +1,52 @@
1
  // jQuery for 'Fetch Feed Items' Row Action in 'All Feed Sources' page
2
- function fetch_items_row_action_callback(){
3
  var link = jQuery(this);
4
  var original_text = link.text();
5
  var id = link.attr('pid');
6
  var url = link.attr('purl');
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  jQuery.ajax({
9
  url: ajaxurl,
10
  type: 'POST',
 
11
  data: {
12
  'action': 'wprss_fetch_feeds_row_action',
13
- 'id': id
 
 
14
  },
15
  success: function( response, status, jqXHR ){
16
- console.log( jqXHR );
17
- link.text( wprss_admin_custom.items_are_importing + '!' );
 
 
 
 
18
  jQuery('table.wp-list-table tbody tr.post-' + id + ' td.column-feed-count i.fa-spin').addClass('wprss-show');
19
- setTimeout( function(){
20
- link.text( original_text ).click( fetch_items_row_action_callback );
21
- }, 3500 );
22
- },
23
- error: function( response, status, error ){
24
- link.text( wprss_admin_custom.failed_to_import + ': ' + error );
25
- setTimeout( function(){
26
- link.text( original_text ).click( fetch_items_row_action_callback );
27
- }, 3500 );
28
  },
 
29
  timeout: 60000 // set timeout to 1 minute
30
  });
31
  /*
@@ -43,7 +64,7 @@ function fetch_items_row_action_callback(){
43
  }
44
  );*/
45
  link.text( wprss_admin_custom.please_wait );
46
- link.unbind('click');
47
  };
48
 
49
 
1
  // jQuery for 'Fetch Feed Items' Row Action in 'All Feed Sources' page
2
+ function fetch_items_row_action_callback(e){
3
  var link = jQuery(this);
4
  var original_text = link.text();
5
  var id = link.attr('pid');
6
  var url = link.attr('purl');
7
 
8
+ var errorImportingHandler = function(jqXHR, status, exceptionText) {
9
+ displayResultMessage(status === 'parsererror' ? 'Error parsing response' : exceptionText, 'ajax-error');;
10
+ };
11
+
12
+ var displayResultMessage = function(message, className) {
13
+ link.text(message);
14
+ if (className)
15
+ link.addClass(className);
16
+
17
+ setTimeout(function(){
18
+ link.text(original_text);
19
+ link.removeAttr('disabled');
20
+ if (className)
21
+ link.removeClass(className);
22
+ }, 3500);
23
+ };
24
+
25
+ e.preventDefault();
26
+ if (link.attr('disabled')) {
27
+ return;
28
+ }
29
+
30
  jQuery.ajax({
31
  url: ajaxurl,
32
  type: 'POST',
33
+ dataType: 'json',
34
  data: {
35
  'action': 'wprss_fetch_feeds_row_action',
36
+ 'id': id,
37
+ 'wprss_admin_ajax_nonce': link.next().val(), // nonce
38
+ 'wprss_admin_ajax_referer': link.next().next().val() // referer
39
  },
40
  success: function( response, status, jqXHR ){
41
+ if (response.is_error) {
42
+ errorImportingHandler(jqXHR, status, response.error_message);
43
+ return;
44
+ }
45
+
46
+ displayResultMessage(wprss_admin_custom.items_are_importing + '!');
47
  jQuery('table.wp-list-table tbody tr.post-' + id + ' td.column-feed-count i.fa-spin').addClass('wprss-show');
 
 
 
 
 
 
 
 
 
48
  },
49
+ error: errorImportingHandler,
50
  timeout: 60000 // set timeout to 1 minute
51
  });
52
  /*
64
  }
65
  );*/
66
  link.text( wprss_admin_custom.please_wait );
67
+ link.attr('disabled', 'disabled');
68
  };
69
 
70
 
js/admin-help.js CHANGED
@@ -45,7 +45,9 @@ jQuery( document ).ready( function($) {
45
  showError(response.error);
46
  } else {
47
  $('#support-error').html(wprss_admin_help['sent-ok']);
48
- $('#support-error').css('color', 'black');
 
 
49
  }
50
 
51
  return response;
45
  showError(response.error);
46
  } else {
47
  $('#support-error').html(wprss_admin_help['sent-ok']);
48
+ $('#support-error').css('color', 'green');
49
+
50
+ formElements.parents('form').get(0).reset();
51
  }
52
 
53
  return response;
nbproject/project.properties DELETED
@@ -1,7 +0,0 @@
1
- include.path=${php.global.include.path}
2
- php.version=PHP_53
3
- source.encoding=UTF-8
4
- src.dir=.
5
- tags.asp=false
6
- tags.short=false
7
- web.root=.
 
 
 
 
 
 
 
nbproject/project.xml DELETED
@@ -1,9 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project xmlns="http://www.netbeans.org/ns/project/1">
3
- <type>org.netbeans.modules.php.project</type>
4
- <configuration>
5
- <data xmlns="http://www.netbeans.org/ns/php-project/1">
6
- <name>WPRACORE</name>
7
- </data>
8
- </configuration>
9
- </project>
 
 
 
 
 
 
 
 
 
readme.txt CHANGED
@@ -1,10 +1,10 @@
1
  === WP RSS Aggregator ===
2
  Contributors: jeangalea, Mekku, xedin.unknown, markzahra, doytch, chiragswadia
3
  Plugin URI: http://www.wprssaggregator.com
4
- Tags: RSS feeds, aggregation, autoblog, content curation, feed reader, feed to post, RSS aggregator, RSS feeder, RSS import, RSS to post, syndication, multiple feed import
5
  Requires at least: 4.0
6
- Tested up to: 4.4.1
7
- Stable tag: 4.8.2
8
  License: GPLv2 or later
9
  WP RSS Aggregator is the most comprehensive RSS feed importer and autoblogging plugin for WordPress with premium add-ons for additional functionality.
10
 
@@ -18,53 +18,55 @@ With this free core version of WP RSS Aggregator you’ll be able to aggregate a
18
  Have a look at the core version of WP RSS Aggregator below, or [go here to view our introductory videos for the premium add-ons](http://docs.wprssaggregator.com/introductory-videos/).
19
  [youtube http://www.youtube.com/watch?v=fcENPsmJbvc]
20
 
21
- = Highlighted Features =
22
-
23
- * Set a name for each feed source.
24
- * Import any number of feed items from multiple RSS Feeds.
25
- * Display feed items using the [shortcodes](http://docs.wprssaggregator.com/shortcodes/) or by [calling the display function from within your theme](http://docs.wprssaggregator.com/shortcodes/#using-shortcodes-directly-in-templates).
26
- * Limit the age of the feed items stored in the database.
27
- * Set the number of feed items per feed source that you want to show and store.
28
- * Set the pagination for the displayed feed items.
29
- * Set a general feed import time interval.
30
- * Schedule feed imports for each individual feed source.
31
- * Choose whether to show/hide feed sources and dates, and set the date format.
32
- * Set the links as no-follow or not, or add no follow to the meta tag.
33
- * Customise the output using various [shortcode parameters](http://docs.wprssaggregator.com/shortcodes/#core-parameters).
34
- * Set the open link behaviour (lightbox, new window or current window).
35
- * Opens YouTube, DailyMotion and Vimeo videos directly.
36
- * Export a custom RSS feed based on your feed sources.
37
- * Extendable via [action and filter hooks](http://docs.wprssaggregator.com/category/developer-documentation/filters/).
38
- * Incorporates feed auto-discovery, which lets you add feed sources without knowing the exact URL.
39
- * Integrated with the Simplepie library that comes with WordPress. This includes RSS 0.91 and RSS 1.0 formats, the popular RSS 2.0 format, Atom etc.
40
-
41
- = Premium Add-Ons =
42
- WP RSS Aggregator also has a number of premium add-ons that add more functionality to the core plugin. They provide the means to create autoblogging websites, display job listings, import YouTube videos and a lot more. Take a look at our [Use Cases](http://www.wprssaggregator.com/use-cases/) and our [Showcase](http://www.wprssaggregator.com/showcase/) for more ideas. Here are the add-ons currently available:
43
-
44
- * [Feed to Post](http://www.wprssaggregator.com/extension/feed-to-post/) is an advanced importer that lets you import RSS feeds directly into WordPress posts or any other custom post type. You can use it to populate a website in minutes (autoblog). This is the most popular and feature-filled extension.
45
- * [Keyword Filtering](http://www.wprssaggregator.com/extension/keyword-filtering/) filters the feed items to be imported based on keywords, key phrases or tags, so you only get the items you're interested in.
46
- * [Excerpts & Thumbnails](http://www.wprssaggregator.com/extension/excerpts-thumbnails/) displays an excerpt and thumbnail image (taken from within the RSS feed) together with the title, date and source of each feed item.
47
- * [Categories](http://www.wprssaggregator.com/extension/categories/) categorises your feed sources and allows you to display feed items from a particular category within your site using the [shortcode parameters](http://docs.wprssaggregator.com/shortcodes/#categories-parameters).
48
- * [Full Text RSS Feeds](http://www.wprssaggregator.com/extension/full-text-rss-feeds/) adds connectivity to our Full Text Premium service, which allows you to import the full post content for an unlimited number of feed items per feed source, even when the feed itself doesn’t provide it. (Requires [Feed to Post](http://www.wprssaggregator.com/extension/feed-to-post/))
49
- * [WordAi](http://www.wprssaggregator.com/extension/wordai/) allows you to take an RSS feed and turn it into new content that is both completely unique and completely readable. (Requires [Feed to Post](http://www.wprssaggregator.com/extension/feed-to-post/) and a [WordAi account](https://wordai.com/))
50
- * [Widget](http://www.wprssaggregator.com/extension/widget/) adds a widget to your website that displays the imported feed items. It can also display excerpts and thumbnail images when used in conjunction with the [Excerpts & Thumbnails](http://www.wprssaggregator.com/extension/excerpts-thumbnails/) add-on.
51
- * [SpinnerChief](http://www.wprssaggregator.com/extension/spinnerchief/) is an extension for [Feed to Post](http://www.wprssaggregator.com/extension/feed-to-post/) that allows you to integrate the SpinnerChief article spinner so that the imported content is both completely unique and completely readable.
52
-
53
- [View a comparison of our three most popular add-ons here](http://www.wprssaggregator.com/product-comparison/). If you're unsure as to which add-ons you need, [we put together this post to help you out](http://www.wprssaggregator.com/add-ons-purchase/).
54
 
55
- There are also two premium bundles available, the [Simple Feeds Bundle](http://www.wprssaggregator.com/extension/simple-feeds-bundle/) and the [Advanced Feeds Bundle](http://www.wprssaggregator.com/extension/advanced-feeds-bundle/). [View a quick comparison of these bundles here](http://www.wprssaggregator.com/bundle-comparison/).
56
 
57
  We also provide a [Feed Creator](http://createfeed.wprssaggregator.com/) service that allows you to generate RSS feeds from any webpage, even if it doesn't have its own RSS feed.
58
 
59
- = Demo =
60
- The core plugin can be seen in use [on our website's demo page](http://www.wprssaggregator.com/demo/). There are also a number of [other demos](http://www.wprssaggregator.com/) available for our premium add-ons.
61
 
62
- For a fully fledged example of the plugin and add-ons in action check out the WordPress news site [WP News Desk](http://www.wpnewsdesk.com). If you want to create a news aggregator site like this you can follow our [step-by-step tutorial](http://www.wpmayor.com/how-to-set-up-a-news-aggregation-site-with-wordpress-wp-rss-aggregator/).
 
63
 
64
- = Documentation =
65
- Our [comprehensive documentation](http://docs.wprssaggregator.com/) provides you with everything you need to install, set up and customize the plugin to your needs. You can also browse through [a large number of FAQs](http://docs.wprssaggregator.com/category/faqs/) that cover almost everything there is to ask.
 
 
66
 
67
- = As featured on =
68
  * [WP Mayor](http://www.wpmayor.com/rss-feeds-review-wp-rss-aggregator/)
69
  * [LatestWP](http://www.latestwp.com/2015/03/15/wp-rss-aggregator-plugin-review/)
70
  * [WPBeginner](http://www.wpbeginner.com/plugins/how-to-fetch-feeds-in-wordpress-using-wp-rss-aggregator/)
@@ -81,7 +83,7 @@ Our [comprehensive documentation](http://docs.wprssaggregator.com/) provides you
81
  * [Kevin Muldoon](http://www.kevinmuldoon.com/wp-rss-aggregator-wordpress-plugin/)
82
  * [Magazine3](http://magazine3.com/blog/news-aggregator-website/)
83
 
84
- = Translations =
85
  * Italian - Davide De Maestri
86
  * Spanish - Andrew Kurtis
87
  * Brazilian Portugese - Bruno Calheira
@@ -89,10 +91,12 @@ Our [comprehensive documentation](http://docs.wprssaggregator.com/) provides you
89
 
90
  == Installation ==
91
 
92
- 1. Upload the `wp-rss-aggregator` folder to the `/wp-content/plugins/` directory.
93
- 2. Activate the WP RSS Aggregator plugin from the 'Plugins' section in your dashboard.
94
- 3. Configure the plugin by going to the `RSS Aggregator` menu item that appears in your dashboard menu.
95
- 4. Use the shortcode in your posts or pages: `[wp-rss-aggregator]`
 
 
96
 
97
  You can easily select the source for your feeds and also insert a limit via [shortcode parameters](http://docs.wprssaggregator.com/shortcodes/#core-parameters) as shown below. To get your feed source ID please refer to the ID column in your feed source listing page.
98
 
@@ -134,6 +138,8 @@ You can either use the shortcode in your posts and pages:
134
  or you can call the function directly within your theme:
135
  `<?php wprss_display_feed_items(); ?>`
136
 
 
 
137
  = Is there a limit on the number of feed sources I can use? =
138
 
139
  There is no limit in place for the number of feed sources. Having many (50+) feed sources should not present any problems in itself.
@@ -142,34 +148,50 @@ However, pulling in posts from many sites is bound to put your server under some
142
 
143
  Check out our dedicated page on [WordPress hosting](http://www.wprssaggregator.com/recommended-web-hosts/) recommendations.
144
 
 
 
145
  = Does WP RSS Aggregator work using JSON as the source? =
146
 
147
  No, our plugin does not currently import from JSON, it only imports from RSS and Atom structured XML.
148
 
 
 
149
  = Why do I get “No feed items found” when I insert the shortcode on a page or post? =
150
 
151
  Try adding a few more feed sources and make sure they are valid by using the RSS Feed Validator.
152
 
153
  Secondly make sure your WordPress cron system is working well. If not, the feeds cannot be imported. If in doubt you can go to RSS Aggregator > Debugging and hit the red button to re-import all feed items. If the problem persists contact support.
154
 
 
 
155
  = Can I store imported feed items as posts? =
156
 
157
  Yes! You can do that with the [Feed to Post](http://www.wprssaggregator.com/extensions/feed-to-post) add-on. You will not only be able to store items as posts, but also as other custom post types, as well as set the author, auto set tags and categories, import images into the gallery or set featured images, and much more.
158
 
 
 
159
  = Some RSS feeds only give a short excerpt. Any way around that? =
160
 
161
  Yes, along with the [Feed to Post](http://www.wprssaggregator.com/extensions/feed-to-post) add-on we have another add-on called [Full Text RSS Feeds](http://www.wprssaggregator.com/extension/full-text-rss-feeds/) that can get the full content of those feeds that only supply a short excerpt.
162
 
 
 
163
  = I’m not sure which premium add-ons are right for me. Can you help me out? =
164
 
165
  Sure! We wrote a [post](http://www.wprssaggregator.com/add-ons-purchase/) just for you. Read about which add-ons you should buy, we explain the different types of usage so you’ll know what to expect when purchasing.
166
 
167
  If you need any further help you can [contact our support team](http://www.wprssaggregator.com/contact/).
168
 
 
 
169
  = Where can I find the documentation for the plugin? =
170
 
171
  Our complete documentation with FAQs included can be found on [our dedicated documentation website](http://docs.wprssaggregator.com/).
172
 
 
 
 
 
173
 
174
  == Screenshots ==
175
 
@@ -188,6 +210,13 @@ Our complete documentation with FAQs included can be found on [our dedicated doc
188
 
189
  == Changelog ==
190
 
 
 
 
 
 
 
 
191
  = 4.8.2 (2016-02-22) =
192
  * Fixed bug: Interface methods used to conflict, causing fatal error on activation.
193
  * Fixed bug: Empty feed response used to cause misleading error message in log.
1
  === WP RSS Aggregator ===
2
  Contributors: jeangalea, Mekku, xedin.unknown, markzahra, doytch, chiragswadia
3
  Plugin URI: http://www.wprssaggregator.com
4
+ Tags: RSS, RSS feeds, aggregation, autoblog, content curation, feed reader, feed to post, RSS aggregator, RSS feeder, RSS import, RSS to post, syndication, multiple feed import
5
  Requires at least: 4.0
6
+ Tested up to: 4.5.2
7
+ Stable tag: 4.9
8
  License: GPLv2 or later
9
  WP RSS Aggregator is the most comprehensive RSS feed importer and autoblogging plugin for WordPress with premium add-ons for additional functionality.
10
 
18
  Have a look at the core version of WP RSS Aggregator below, or [go here to view our introductory videos for the premium add-ons](http://docs.wprssaggregator.com/introductory-videos/).
19
  [youtube http://www.youtube.com/watch?v=fcENPsmJbvc]
20
 
21
+ > ### Highlighted Features ###
22
+ > * Set a name for each feed source.
23
+ > * Import any number of feed items from multiple RSS Feeds.
24
+ > * Display feed items using the [shortcodes](http://docs.wprssaggregator.com/shortcodes/) or by [calling the display function from within your theme](http://docs.wprssaggregator.com/shortcodes/#using-shortcodes-directly-in-templates).
25
+ > * Limit the age of the feed items stored in the database.
26
+ > * Set the number of feed items per feed source that you want to show and store.
27
+ > * Set the pagination for the displayed feed items.
28
+ > * Set a general feed import time interval.
29
+ > * Schedule feed imports for each individual feed source.
30
+ > * Choose whether to show/hide feed sources and dates, and set the date format.
31
+ > * Set the links as no-follow or not, or add no follow to the meta tag.
32
+ > * Customise the output using various [shortcode parameters](http://docs.wprssaggregator.com/shortcodes/#core-parameters).
33
+ > * Set the open link behaviour (lightbox, new window or current window).
34
+ > * Opens YouTube, DailyMotion and Vimeo videos directly.
35
+ > * Export a custom RSS feed based on your feed sources.
36
+ > * Extendable via [action and filter hooks](http://docs.wprssaggregator.com/category/developer-documentation/filters/).
37
+ > * Incorporates feed auto-discovery, which lets you add feed sources without knowing the exact URL.
38
+ > * Integrated with the Simplepie library that comes with WordPress. This includes RSS 0.91 and RSS 1.0 formats, the popular RSS 2.0 format, Atom etc.
39
+
40
+ ### Premium Add-Ons ###
41
+ WP RSS Aggregator also has a number of premium add-ons that add more functionality to the core plugin. They provide the means to create auto-blogging websites, display job listings, import YouTube videos and a lot more. Take a look at our [Use Cases](http://www.wprssaggregator.com/use-cases/) and our [Showcase](http://www.wprssaggregator.com/showcase/) for more ideas. Here are the add-ons currently available:
42
+
43
+ > * [Feed to Post](http://www.wprssaggregator.com/extension/feed-to-post/) is an advanced importer that lets you import RSS feeds directly into WordPress posts or any other custom post type. You can use it to populate a website in minutes (autoblog). This is the most popular and feature-filled extension.
44
+ > * [Keyword Filtering](http://www.wprssaggregator.com/extension/keyword-filtering/) filters the feed items to be imported based on keywords, key phrases or tags, so you only get the items you're interested in.
45
+ > * [Excerpts & Thumbnails](http://www.wprssaggregator.com/extension/excerpts-thumbnails/) displays an excerpt and thumbnail image (taken from within the RSS feed) together with the title, date and source of each feed item.
46
+ > * [Categories](http://www.wprssaggregator.com/extension/categories/) categorises your feed sources and allows you to display feed items from a particular category within your site using the [shortcode parameters](http://docs.wprssaggregator.com/shortcodes/#categories-parameters).
47
+ > * [Full Text RSS Feeds](http://www.wprssaggregator.com/extension/full-text-rss-feeds/) adds connectivity to our Full Text Premium service, which allows you to import the full post content for an unlimited number of feed items per feed source, even when the feed itself doesn’t provide it. (Requires [Feed to Post](http://www.wprssaggregator.com/extension/feed-to-post/))
48
+ > * [WordAi](http://www.wprssaggregator.com/extension/wordai/) allows you to take an RSS feed and turn it into new content that is both completely unique and completely readable. (Requires [Feed to Post](http://www.wprssaggregator.com/extension/feed-to-post/) and a [WordAi account](https://wordai.com/))
49
+ > * [SpinnerChief](http://www.wprssaggregator.com/extension/spinnerchief/) is an extension for [Feed to Post](http://www.wprssaggregator.com/extension/feed-to-post/) that allows you to integrate the SpinnerChief article spinner so that the imported content is both completely unique and completely readable.
50
+ > * [Widget](http://www.wprssaggregator.com/extension/widget/) adds a widget to your website that displays the imported feed items. It can also display excerpts and thumbnail images when used in conjunction with the [Excerpts & Thumbnails](http://www.wprssaggregator.com/extension/excerpts-thumbnails/) add-on.
51
+
52
+ View a comparison of our three most popular add-ons [here](http://www.wprssaggregator.com/product-comparison/). If you're unsure as to which add-ons you need, we put together [this comprehensive post](http://www.wprssaggregator.com/add-ons-purchase/) to help you out.
 
53
 
54
+ There are also two premium bundles available, the [Simple Feeds Bundle](http://www.wprssaggregator.com/extension/simple-feeds-bundle/) and the [Advanced Feeds Bundle](http://www.wprssaggregator.com/extension/advanced-feeds-bundle/). View a quick comparison of these bundles [here](http://www.wprssaggregator.com/bundle-comparison/).
55
 
56
  We also provide a [Feed Creator](http://createfeed.wprssaggregator.com/) service that allows you to generate RSS feeds from any webpage, even if it doesn't have its own RSS feed.
57
 
58
+ > ### Demo ###
59
+ > The core plugin can be seen in use [on our website's demo page](http://www.wprssaggregator.com/demo/). There are also a number of [other demos](http://www.wprssaggregator.com/) available for our premium add-ons.
60
 
61
+ > ### Documentation ###
62
+ > Our [comprehensive documentation](http://docs.wprssaggregator.com/) provides you with everything you need to install, set up and customize the plugin to your needs. You can also browse through [a large number of FAQs](http://docs.wprssaggregator.com/category/faqs/) that cover almost everything there is to ask.
63
 
64
+ > ### Support ###
65
+ > The support section for the free version of WP RSS Aggregator can be found [here on the plugin repository](https://wordpress.org/support/plugin/wp-rss-aggregator). It’s important to read and follow the [Support Guidelines](https://wordpress.org/support/topic/support-guidelines-1?replies=4) before opening a new ticket.
66
+ >
67
+ > For support related to any of the premium add-ons you should open a [premium support ticket](https://www.wprssaggregator.com/contact/).
68
 
69
+ ### As featured on ###
70
  * [WP Mayor](http://www.wpmayor.com/rss-feeds-review-wp-rss-aggregator/)
71
  * [LatestWP](http://www.latestwp.com/2015/03/15/wp-rss-aggregator-plugin-review/)
72
  * [WPBeginner](http://www.wpbeginner.com/plugins/how-to-fetch-feeds-in-wordpress-using-wp-rss-aggregator/)
83
  * [Kevin Muldoon](http://www.kevinmuldoon.com/wp-rss-aggregator-wordpress-plugin/)
84
  * [Magazine3](http://magazine3.com/blog/news-aggregator-website/)
85
 
86
+ ### Translations ###
87
  * Italian - Davide De Maestri
88
  * Spanish - Andrew Kurtis
89
  * Brazilian Portugese - Bruno Calheira
91
 
92
  == Installation ==
93
 
94
+ Follow these instructions to install the core WP RSS Aggregator plugin:
95
+
96
+ > 1. Upload the `wp-rss-aggregator` folder to the `/wp-content/plugins/` directory.
97
+ > 2. Activate the WP RSS Aggregator plugin from the 'Plugins' section in your dashboard.
98
+ > 3. Configure the plugin by going to the `RSS Aggregator` menu item that appears in your dashboard menu.
99
+ > 4. Use the WP RSS Aggregator shortcode in your posts or pages: `[wp-rss-aggregator]`
100
 
101
  You can easily select the source for your feeds and also insert a limit via [shortcode parameters](http://docs.wprssaggregator.com/shortcodes/#core-parameters) as shown below. To get your feed source ID please refer to the ID column in your feed source listing page.
102
 
138
  or you can call the function directly within your theme:
139
  `<?php wprss_display_feed_items(); ?>`
140
 
141
+ - - -
142
+
143
  = Is there a limit on the number of feed sources I can use? =
144
 
145
  There is no limit in place for the number of feed sources. Having many (50+) feed sources should not present any problems in itself.
148
 
149
  Check out our dedicated page on [WordPress hosting](http://www.wprssaggregator.com/recommended-web-hosts/) recommendations.
150
 
151
+ - - -
152
+
153
  = Does WP RSS Aggregator work using JSON as the source? =
154
 
155
  No, our plugin does not currently import from JSON, it only imports from RSS and Atom structured XML.
156
 
157
+ - - -
158
+
159
  = Why do I get “No feed items found” when I insert the shortcode on a page or post? =
160
 
161
  Try adding a few more feed sources and make sure they are valid by using the RSS Feed Validator.
162
 
163
  Secondly make sure your WordPress cron system is working well. If not, the feeds cannot be imported. If in doubt you can go to RSS Aggregator > Debugging and hit the red button to re-import all feed items. If the problem persists contact support.
164
 
165
+ - - -
166
+
167
  = Can I store imported feed items as posts? =
168
 
169
  Yes! You can do that with the [Feed to Post](http://www.wprssaggregator.com/extensions/feed-to-post) add-on. You will not only be able to store items as posts, but also as other custom post types, as well as set the author, auto set tags and categories, import images into the gallery or set featured images, and much more.
170
 
171
+ - - -
172
+
173
  = Some RSS feeds only give a short excerpt. Any way around that? =
174
 
175
  Yes, along with the [Feed to Post](http://www.wprssaggregator.com/extensions/feed-to-post) add-on we have another add-on called [Full Text RSS Feeds](http://www.wprssaggregator.com/extension/full-text-rss-feeds/) that can get the full content of those feeds that only supply a short excerpt.
176
 
177
+ - - -
178
+
179
  = I’m not sure which premium add-ons are right for me. Can you help me out? =
180
 
181
  Sure! We wrote a [post](http://www.wprssaggregator.com/add-ons-purchase/) just for you. Read about which add-ons you should buy, we explain the different types of usage so you’ll know what to expect when purchasing.
182
 
183
  If you need any further help you can [contact our support team](http://www.wprssaggregator.com/contact/).
184
 
185
+ - - -
186
+
187
  = Where can I find the documentation for the plugin? =
188
 
189
  Our complete documentation with FAQs included can be found on [our dedicated documentation website](http://docs.wprssaggregator.com/).
190
 
191
+ - - -
192
+
193
+ > ### Got more questions? ###
194
+ > You can find the full list of FAQs [in our documentation](https://docs.wprssaggregator.com/category/faqs/).
195
 
196
  == Screenshots ==
197
 
210
 
211
  == Changelog ==
212
 
213
+ = 4.9 (2016-06-14) =
214
+ * Fixed bug: Potential security vulnerability related to triggering feed update.
215
+ * Fixed bug: Error output on Feed Sources list is trimmed and cannot break the page layout.
216
+ * Fixed bug: Certain notices could not be dismissed.
217
+ * Fixed bug: Word trimming didn't always trim correctly with HTML.
218
+ * Enhanced: Visual improvements.
219
+
220
  = 4.8.2 (2016-02-22) =
221
  * Fixed bug: Interface methods used to conflict, causing fatal error on activation.
222
  * Fixed bug: Empty feed response used to cause misleading error message in log.
wp-rss-aggregator.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: WP RSS Aggregator
4
  Plugin URI: http://www.wprssaggregator.com
5
  Description: Imports and aggregates multiple RSS Feeds using SimplePie
6
- Version: 4.8.2
7
  Author: Jean Galea
8
  Author URI: http://www.wprssaggregator.com
9
  License: GPLv2
@@ -29,7 +29,7 @@
29
 
30
  /**
31
  * @package WPRSSAggregator
32
- * @version 4.8.2
33
  * @since 1.0
34
  * @author Jean Galea <info@wprssaggregator.com>
35
  * @copyright Copyright (c) 2012-2015, Jean Galea
@@ -43,7 +43,7 @@
43
 
44
  // Set the version number of the plugin.
45
  if( !defined( 'WPRSS_VERSION' ) )
46
- define( 'WPRSS_VERSION', '4.8.2', true );
47
 
48
  if( !defined( 'WPRSS_WP_MIN_VERSION' ) )
49
  define( 'WPRSS_WP_MIN_VERSION', '4.0', true );
3
  Plugin Name: WP RSS Aggregator
4
  Plugin URI: http://www.wprssaggregator.com
5
  Description: Imports and aggregates multiple RSS Feeds using SimplePie
6
+ Version: 4.9
7
  Author: Jean Galea
8
  Author URI: http://www.wprssaggregator.com
9
  License: GPLv2
29
 
30
  /**
31
  * @package WPRSSAggregator
32
+ * @version 4.9
33
  * @since 1.0
34
  * @author Jean Galea <info@wprssaggregator.com>
35
  * @copyright Copyright (c) 2012-2015, Jean Galea
43
 
44
  // Set the version number of the plugin.
45
  if( !defined( 'WPRSS_VERSION' ) )
46
+ define( 'WPRSS_VERSION', '4.9', true );
47
 
48
  if( !defined( 'WPRSS_WP_MIN_VERSION' ) )
49
  define( 'WPRSS_WP_MIN_VERSION', '4.0', true );