WP RSS Aggregator - Version 4.21.1

Version Description

(2022-07-20) = Fixed - An out-of-memory PHP error when importing items.

Download this release

Release Info

Developer Mekku
Plugin Icon 128x128 WP RSS Aggregator
Version 4.21.1
Comparing to
See all releases

Code changes from version 4.21 to 4.21.1

CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
 
 
 
 
 
7
  ## [4.21] - 2022-07-13
8
  ### Change
9
  * Updated Twig to v1.42.2, to support PHP 8 or later.
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
 
7
+ ## [4.21.1] - 2022-07-20
8
+ ### Fixed
9
+ * An out-of-memory PHP error when importing items.
10
+
11
  ## [4.21] - 2022-07-13
12
  ### Change
13
  * Updated Twig to v1.42.2, to support PHP 8 or later.
includes/feed-importing-new.php DELETED
@@ -1,407 +0,0 @@
1
- <?php
2
-
3
- namespace RebelCode\Wpra\Core\Importer;
4
-
5
- use SimplePie_Item;
6
- use WP_Post;
7
-
8
- /**
9
- * Fetches new items from a feed.
10
- *
11
- * @since [*next-version*]
12
- *
13
- * @param int|string $feedId The ID of the feed source.
14
- *
15
- * @return SimplePie_Item[]
16
- */
17
- function fetchNewItems($feedId)
18
- {
19
- $feed = get_post($feedId);
20
- $logger = wpra_get_logger($feedId);
21
-
22
- if ($feed instanceof WP_Post) {
23
- $logger->info('Fetching items');
24
- } else {
25
- $logger->error('Feed source #{0} does not exist', [$feedId]);
26
- }
27
-
28
- // Filter the URL
29
- $feedUrl = apply_filters('wprss_feed_source_url', $feed->wprss_url, $feedId);
30
- if ($feedUrl !== $feed->wprss_url) {
31
- $logger->debug('Filtered RSS URL: {0}', [$feedUrl]);
32
- }
33
-
34
- // Validate the URL
35
- if (!wprss_validate_url($feedUrl)) {
36
- $logger->error('Feed URL is not valid!');
37
-
38
- return [];
39
- }
40
-
41
- // Get the feed items from the source
42
- $items = wprss_get_feed_items($feedUrl, $feedId);
43
- $items = (empty($items) || !is_array($items)) ? [] : $items;
44
-
45
- // See `wprss_item_comparators` filter
46
- wprss_sort_items($items);
47
-
48
- // Apply fixed limit
49
- {
50
- $feedLimit = $feed->wprss_limit;
51
- $globalLimit = wprss_get_general_setting('limit_feed_items_imported');
52
- $filteredLimits = array_filter([$feedLimit, $globalLimit], function ($limit) {
53
- return !empty($limit) && intval($limit) > 0;
54
- });
55
- $limit = (int) reset($filteredLimits);
56
-
57
- if ($limit > 0) {
58
- $preLimitCount = count($items);
59
- $items = array_slice($items, 0, $limit);
60
-
61
- $logger->debug('{0} items in the feed, {1} items after applying limit', [
62
- $preLimitCount,
63
- $limit,
64
- ]);
65
- }
66
- }
67
-
68
- // Process "unique titles only" option
69
- {
70
- $feedUto = $feed->wprss_unique_titles;
71
- $globalUto = wprss_get_general_setting('unique_titles');
72
- $uto = empty($feedUto) ? $globalUto : $feedUto;
73
- $uto = filter_var($uto, FILTER_VALIDATE_BOOLEAN);
74
- }
75
-
76
- $items = apply_filters('wpra/importer/items/before_filters', $items, $feed, $logger);
77
-
78
- // Gather the titles and permalinks of the items that are being fetched
79
- $existingTitles = [];
80
- $existingPermalinks = wprss_get_existing_permalinks();
81
-
82
- $newItems = [];
83
- foreach ($items as $item) {
84
- // Filter the item
85
- $item = apply_filters('wpra/importer/item', $item, $feed, $logger);
86
-
87
- $title = $item->get_title();
88
- $permalink = wprss_normalize_permalink($item->get_permalink(), $item, $feedId);
89
-
90
- // Check if already imported
91
- if (array_key_exists($permalink, $existingPermalinks)) {
92
- $logger->debug('Skipping item "{0}": already imported', [$title]);
93
-
94
- continue;
95
- }
96
-
97
- // Check if blacklisted
98
- if (wprss_is_blacklisted($permalink)) {
99
- $logger->debug('Skipping item "{0}": blacklisted', [$title]);
100
-
101
- continue;
102
- }
103
-
104
- // Check if title exists
105
- if ($uto) {
106
- $existingTitles[$title] = 1;
107
-
108
- if (wprss_item_title_exists($title) || array_key_exists($title, $existingTitles)) {
109
- $logger->debug('Skipping item "{0}": title is not unique', [$title]);
110
-
111
- continue;
112
- }
113
- }
114
-
115
- if (apply_filters('wpra/importer/filter_item', true, $item, $feed, $logger)) {
116
- $newItems[] = $item;
117
- }
118
- }
119
-
120
- $items = apply_filters('wpra/importer/items/after_filters', $items, $feed, $logger);
121
-
122
- $numOgItems = count($items);
123
- $numNewItems = count($newItems);
124
- if ($numOgItems !== $numNewItems) {
125
- $logger->debug('{0} items were skipped', [$numOgItems - $numNewItems]);
126
- }
127
-
128
- // Apply the "per-import" limit
129
- $importLimit = (int) wprss_get_general_setting('limit_feed_items_per_import');
130
- if ($importLimit > 0) {
131
- $items = array_slice($items, 0, $importLimit);
132
- }
133
-
134
- return $items;
135
- }
136
-
137
- /**
138
- * Imports a feed item.
139
- *
140
- * @since [*next-version*]
141
- *
142
- * @param SimplePie_Item $item The item to import.
143
- * @param int|string $feedId The ID of the feed source.
144
- *
145
- * @return bool True if the item was imported successfully, false if importing failed.
146
- */
147
- function importItem(SimplePie_Item $item, $feedId)
148
- {
149
- $logger = wpra_get_logger($feedId);
150
- $logger->debug('Importing item "{0}"', [$item->get_title()]);
151
-
152
- set_time_limit(wprss_get_item_import_time_limit());
153
-
154
- $lcc = legacyConditionalCheck($item, $feedId);
155
- $item = $lcc[0];
156
- $import = $lcc[1];
157
-
158
- // If item should not be imported,
159
- // -> return true if the item was imported by other means (not null)
160
- // -> return false if the item was blocked (is null)
161
- if (!$import) {
162
- return $item !== null;
163
- }
164
-
165
- $title = trim(html_entity_decode($item->get_title()));
166
- $title = empty($title) ? $item->get_id() : $title;
167
-
168
- $enclosure = $item->get_enclosure();
169
- $enclosureUrl = $enclosure ? $enclosure->get_link() : null;
170
-
171
- $permalink = htmlspecialchars_decode($item->get_permalink());
172
- $permalink = wprss_normalize_permalink($permalink, $item, $feedId);
173
-
174
- $dates = getImportDates($item, $feedId);
175
- $date = $dates[0];
176
- $dateGmt = $dates[1];
177
- $isFuture = $dates[2];
178
- $status = $isFuture ? 'future' : 'publish';
179
-
180
- $excerpt = wprss_sanitize_excerpt($item->get_description());
181
- $content = $item->get_content();
182
-
183
- $source = getSourceInfo($item);
184
- $author = getAuthorInfo($item);
185
-
186
- // Do not let WordPress sanitize the excerpt
187
- // WordPress sanitizes the excerpt because it's expected to be typed by a user and sent in a POST
188
- // request. However, our excerpt is being inserted as a raw string with custom sanitization.
189
- remove_all_filters('excerpt_save_pre');
190
-
191
- $postData = apply_filters(
192
- 'wprss_populate_post_data',
193
- [
194
- 'post_title' => $title,
195
- 'post_content' => $content,
196
- 'post_excerpt' => $excerpt,
197
- 'post_status' => $status,
198
- 'post_type' => 'wprss_feed_item',
199
- 'post_date' => $date,
200
- 'post_date_gmt' => $dateGmt,
201
- 'meta_input' => [
202
- 'wprss_feed_id' => $feedId,
203
- 'wprss_item_date' => $item->get_date(DATE_ISO8601),
204
- 'wprss_item_permalink' => $permalink,
205
- 'wprss_item_enclosure' => $enclosureUrl,
206
- 'wprss_item_source_name' => $source[0],
207
- 'wprss_item_source_url' => $source[1],
208
- 'wprss_item_author' => $author[0],
209
- 'wprss_item_author_email' => $author[1],
210
- 'wprss_item_author_link' => $author[2],
211
- ],
212
- ],
213
- $item
214
- );
215
-
216
- $postData = apply_filters('wpra/importer/item/post_data', $postData, $item, $feedId);
217
-
218
- if (defined('ICL_SITEPRESS_VERSION')) {
219
- @include_once(WP_PLUGIN_DIR . '/sitepress-multilingual-cms/inc/wpml-api.php');
220
- }
221
- if (defined('ICL_LANGUAGE_CODE')) {
222
- $_POST['icl_post_language'] = $language_code = ICL_LANGUAGE_CODE;
223
- }
224
-
225
- // Create and insert post object into the DB
226
- $postId = wp_insert_post($postData);
227
-
228
- if (is_wp_error($postId)) {
229
- update_post_meta(
230
- $feedId,
231
- 'wprss_error_last_import',
232
- 'An error occurred while inserting a feed item into the database.'
233
- );
234
-
235
- $logger->error('Failed to insert item into the database');
236
-
237
- return false;
238
- }
239
-
240
- do_action('wprss_items_create_post_meta', $postId, $item, $feedId);
241
- do_action('wpra/importer/item/inserted', $item, $feedId);
242
-
243
- $logger->notice('Imported item {0}. ID: {1}', [$title, $postId]);
244
-
245
- return true;
246
- }
247
-
248
- /**
249
- * Runs the legacy "post item conditionals" filter for a feed item.
250
- *
251
- * @since [*next-version*]
252
- *
253
- * @param SimplePie_Item $item The item.
254
- * @param int|string $feedId The ID of the feed source.
255
- *
256
- * @return array An array with 2 elements:
257
- * 1. the filtered item, or null if the item was rejected.
258
- * 2. a boolean that signifies whether the item should be imported or not.
259
- * A return value containing a non-null item and a false boolean signifies a non-rejected item, but that
260
- * should still not be imported because it was imported using a non-Core mechanism (ex. Feed to Post).
261
- */
262
- function legacyConditionalCheck(SimplePie_Item $item, $feedId)
263
- {
264
- $logger = wpra_get_logger($feedId);
265
-
266
- // Log the callbacks that are hooked into the filter
267
- $condCallbacks = wpra_get_hook_callbacks('wprss_insert_post_item_conditionals');
268
- if (count($condCallbacks) > 0) {
269
- $logger->debug('Hooks for `wprss_insert_post_item_conditionals`:');
270
-
271
- foreach ($condCallbacks as $callback) {
272
- $logger->debug('-> {0}', [wprss_format_hook_callback($callback)]);
273
- }
274
- }
275
-
276
- $title = $item->get_title();
277
- $permalink = htmlspecialchars_decode($item->get_permalink());
278
- $permalink = wprss_normalize_permalink($permalink, $item, $feedId);
279
-
280
- $preItem = $item;
281
- $postItem = apply_filters('wprss_insert_post_item_conditionals', $item, $feedId, $permalink);
282
- $updateCount = apply_filters('wprss_still_update_import_count', false);
283
-
284
- if (is_bool($postItem) || $postItem === null) {
285
- // Item is TRUE, or it's FALSE/NULL but it still counts
286
- if ($postItem || $updateCount) {
287
- $logger->debug('Item "{0}" was imported by an add-on or filter', [$title]);
288
-
289
- return [$preItem, false];
290
- }
291
-
292
- // Item was filtered
293
- if (has_filter('wprss_insert_post_item_conditionals', 'wprss_kf_check_post_item_keywords')) {
294
- $logger->info('Item "{0}" was rejected by your keyword or tag filtering.', [$title]);
295
- } else {
296
- $logger->notice('Item "{0}" was rejected by an add-on or filter.', [$title]);
297
- }
298
-
299
- return [null, false];
300
- } else {
301
- return [$postItem, true];
302
- }
303
- }
304
-
305
- function getImportDates(SimplePie_Item $item, $feedId)
306
- {
307
- $logger = wpra_get_logger($feedId);
308
-
309
- $dateFormat = 'Y-m-d H:i:s';
310
- $timestamp = $item->get_gmdate('U');
311
- $isFuture = false;
312
-
313
- if ($timestamp) {
314
- if ($timestamp > time()) {
315
- $scheduleItemsFilter = apply_filters('wpra/importer/allow_scheduled_items', false);
316
- $scheduleItemsOption = wprss_get_general_setting('schedule_future_items');
317
-
318
- if ($scheduleItemsFilter || $scheduleItemsOption) {
319
- // If can schedule future items, set the post status to "future" (aka scheduled)
320
- $isFuture = true;
321
- $logger->debug('Setting future status due to future date');
322
- } else {
323
- // If cannot schedule future items, clamp the timestamp to the current time
324
- $timestamp = min(time(), $timestamp);
325
- $logger->debug('Date clamped to present time');
326
- }
327
- }
328
- } else {
329
- // Item has no date ...
330
- $logger->debug('Item has no date. Using current time');
331
- $timestamp = time();
332
- }
333
-
334
- $date = $item->get_date($dateFormat);
335
- $dateGmt = gmdate($dateFormat, $timestamp);
336
-
337
- return [$date, $dateGmt, $isFuture];
338
- }
339
-
340
- function getSourceInfo(SimplePie_Item $item)
341
- {
342
- /* @var $item SimplePie_Item */
343
- $feed = $item->get_feed();
344
-
345
- // Get the source from the RSS item
346
- $source = $item->get_source();
347
-
348
- // Get the source name if available. If empty, default to the feed source CPT title
349
- $name = ($source === null) ? '' : $source->get_title();
350
- $name = empty($name) ? $feed->get_title() : $name;
351
-
352
- // Get the source URL if available. If empty, default to the RSS feed's URL
353
- $url = ($source === null) ? '' : $source->get_permalink();
354
- $url = empty($url) ? $feed->get_permalink() : $url;
355
-
356
- return [$name, $url];
357
- }
358
-
359
- function getAuthorInfo(SimplePie_Item $item)
360
- {
361
- $author = $item->get_author();
362
-
363
- if ($author) {
364
- $name = $author->get_name();
365
- $email = $author->get_email();
366
- $link = $author->get_link();
367
- } else {
368
- $name = '';
369
- $email = '';
370
- $link = '';
371
- }
372
-
373
- return [$name, $email, $link];
374
- }
375
-
376
- function getExcessItems($feedId, $add = 0)
377
- {
378
- $limit = getLimit($feedId);
379
-
380
- if ($limit <= 0) {
381
- return 0;
382
- }
383
-
384
- // Get existing items
385
- $dbItems = wprss_get_feed_items_for_source($feedId);
386
- $numDbItems = $dbItems->post_count + $add;
387
-
388
- if ($numDbItems <= $limit) {
389
- return 0;
390
- }
391
-
392
- $numExcess = $numDbItems - $limit;
393
-
394
- return array_slice(array_reverse($dbItems->posts), 0, $numExcess);
395
- }
396
-
397
- function getLimit($feedId)
398
- {
399
- $feedLimit = get_post_meta($feedId, 'wprss_limit', true);
400
- $globalLimit = wprss_get_general_setting('limit_feed_items_imported');
401
-
402
- $filteredLimits = array_filter([$feedLimit, $globalLimit], function ($limit) {
403
- return !empty($limit) && intval($limit) > 0;
404
- });
405
-
406
- return (int) reset($filteredLimits);
407
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/feed-importing.php CHANGED
@@ -23,6 +23,8 @@ add_action( 'wprss_fetch_single_feed_hook', 'wprss_fetch_insert_single_feed_item
23
  * Called on hook 'wprss_fetch_single_feed_hook'.
24
  *
25
  * @since 3.2
 
 
26
  */
27
  function wprss_fetch_insert_single_feed_items( $feed_ID ) {
28
  set_transient('wpra/feeds/importing/' . $feed_ID, true, 0);
@@ -104,56 +106,72 @@ function wprss_fetch_insert_single_feed_items( $feed_ID ) {
104
  ? wprss_get_general_setting('unique_titles')
105
  : $unique_titles_only;
106
  $unique_titles_only = filter_var($unique_titles_only, FILTER_VALIDATE_BOOLEAN);
107
- // Gather the titles of the items that are imported
108
- // The import process will check not only the titles in the DB but the titles currently in the feed
109
- $existing_titles = [];
110
 
111
  // Gather the existing feed item IDs for this feed source
112
  $useGuids = get_post_meta($feed_ID, 'wprss_use_guids', true);
113
  $useGuids = filter_var($useGuids, FILTER_VALIDATE_BOOLEAN);
114
- $existingIds = $useGuids
115
- ? wprss_get_existing_guids()
116
- : wprss_get_existing_permalinks();
 
 
 
117
 
118
  // Generate a list of items fetched, that are not already in the DB
119
  $new_items = array();
120
  foreach ( $items_to_insert as $item ) {
121
- $item_title = $item->get_title();
122
  $guid = $item->get_id();
123
  $permalink = $item->get_permalink();
124
  $permalink = wprss_normalize_permalink( $permalink, $item, $feed_ID );
125
 
126
  // Check if blacklisted
127
  if (wprss_is_blacklisted($permalink)) {
128
- $logger->debug('Item "{0}" is blacklisted', [$item_title]);
129
-
130
  continue;
131
  }
132
 
133
- // Check if already imported
134
- $idToCheck = $useGuids ? $guid : $permalink;
135
- if (array_key_exists($idToCheck, $existingIds)) {
136
- $logger->debug('Item "{0}" already exists in the database', [$item_title]);
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
  continue;
139
  }
140
 
141
- // Check if title exists (if the option is enabled)
142
- if ($unique_titles_only) {
143
- $title_exists_db = wprss_item_title_exists($item->get_title());
144
- $title_exists_feed = array_key_exists($item_title, $existing_titles);
145
- $title_exists = $title_exists_db || $title_exists_feed;
146
- // Add this item's title to the list to check against
147
- $existing_titles[$item_title] = 1;
148
 
149
- if ($title_exists) {
150
- $logger->debug('An item with the title "{0}" already exists', [$item_title]);
 
 
151
 
152
- continue;
153
- }
 
 
 
 
 
 
 
154
  }
155
 
156
- $existingIds[$idToCheck] = 1;
157
  $new_items[] = $item;
158
  }
159
 
@@ -184,14 +202,14 @@ function wprss_fetch_insert_single_feed_items( $feed_ID ) {
184
  ? 0
185
  : $num_new_items - $num_can_insert;
186
 
187
- // Get an array with the DB feed items in reverse order (oldest first)
188
  $db_feed_items_reversed = array_reverse( $db_feed_items->posts );
189
  // Cut the array to get only the first few that are to be deleted ( equal to $num_feed_items_to_delete )
190
  $feed_items_to_delete = array_slice( $db_feed_items_reversed, 0, $num_feed_items_to_delete );
191
 
192
  // Iterate the feed items and delete them
193
  $num_items_deleted = 0;
194
- foreach ( $feed_items_to_delete as $key => $post ) {
195
  wp_delete_post( $post->ID, TRUE );
196
  $num_items_deleted++;
197
  }
@@ -201,7 +219,7 @@ function wprss_fetch_insert_single_feed_items( $feed_ID ) {
201
  }
202
  }
203
 
204
- update_post_meta( $feed_ID, 'wprss_last_update', $last_update_time = time() );
205
  update_post_meta( $feed_ID, 'wprss_last_update_items', 0 );
206
 
207
  // Insert the items into the db
@@ -270,34 +288,31 @@ function wprss_get_feed_items( $feed_url, $source, $force_feed = FALSE ) {
270
  }
271
 
272
  if (defined('WP_DEBUG') && WP_DEBUG) {
273
- add_action('cron_request', 'wprss_cron_add_xdebug_cookie', 10);
274
- }
275
-
276
- /**
277
- * Allow debugging of wp_cron jobs using xDebug.
278
- *
279
- * This is done by taking the XDEBUG cookie received from the browser (which enables an xDebug session) and passing it
280
- * to WP Cron. That way, code initiated from a cron job will be debuggable.
281
- *
282
- * @param array $cronRequest
283
- *
284
- * @return array $cron_request_array with the current XDEBUG_SESSION cookie added if set
285
- */
286
- function wprss_cron_add_xdebug_cookie($cronRequest)
287
- {
288
- if (empty($_COOKIE['XDEBUG_SESSION'])) {
289
- return ($cronRequest);
290
- }
291
 
292
- $cookie = filter_var($_COOKIE['XDEBUG_SESSION'], FILTER_SANITIZE_STRING);
293
 
294
- if (empty($cronRequest['args']['cookies'])) {
295
- $cronRequest['args']['cookies'] = [];
296
- }
297
 
298
- $cronRequest['args']['cookies']['XDEBUG_SESSION'] = $cookie;
299
 
300
- return $cronRequest;
 
301
  }
302
 
303
  /**
@@ -364,7 +379,7 @@ function wprss_fetch_feed($url, $source = null, $param_force_feed = false)
364
 
365
  // If a feed source was passed
366
  if ($source !== null || $param_force_feed) {
367
- // Get the force feed option for the feed source
368
  $force_feed = get_post_meta($source, 'wprss_force_feed', true);
369
  // If turned on, force the feed
370
  if ($force_feed == 'true' || $param_force_feed) {
23
  * Called on hook 'wprss_fetch_single_feed_hook'.
24
  *
25
  * @since 3.2
26
+ *
27
+ * @throws Exception
28
  */
29
  function wprss_fetch_insert_single_feed_items( $feed_ID ) {
30
  set_transient('wpra/feeds/importing/' . $feed_ID, true, 0);
106
  ? wprss_get_general_setting('unique_titles')
107
  : $unique_titles_only;
108
  $unique_titles_only = filter_var($unique_titles_only, FILTER_VALIDATE_BOOLEAN);
 
 
 
109
 
110
  // Gather the existing feed item IDs for this feed source
111
  $useGuids = get_post_meta($feed_ID, 'wprss_use_guids', true);
112
  $useGuids = filter_var($useGuids, FILTER_VALIDATE_BOOLEAN);
113
+
114
+ // Gather the IDs and titles of the items that are imported
115
+ // The import process will not only check the IDs and titles against the DB, but also against the feed
116
+ // itself. This prevents duplicate items in the feed from importing duplicates.
117
+ $existingIds = [];
118
+ $existingTitles = [];
119
 
120
  // Generate a list of items fetched, that are not already in the DB
121
  $new_items = array();
122
  foreach ( $items_to_insert as $item ) {
123
+ $itemTitle = $item->get_title();
124
  $guid = $item->get_id();
125
  $permalink = $item->get_permalink();
126
  $permalink = wprss_normalize_permalink( $permalink, $item, $feed_ID );
127
 
128
  // Check if blacklisted
129
  if (wprss_is_blacklisted($permalink)) {
130
+ $logger->debug('Item "{0}" is blacklisted', [$itemTitle]);
 
131
  continue;
132
  }
133
 
134
+ $itemId = $useGuids ? $guid : $permalink;
135
+
136
+ // Check if already imported in database
137
+ //-----------------------------------------
138
+ $itemIdExists = $useGuids ? wprss_guid_exists($guid) : wprss_permalink_exists($permalink);
139
+ $itemsTitleExists = $unique_titles_only && wprss_item_title_exists($item->get_title());
140
+
141
+ if ($itemIdExists || $itemsTitleExists) {
142
+ $reason = $itemIdExists
143
+ ? ($useGuids ? 'GUID' : 'permalink')
144
+ : 'Non-unique title';
145
+
146
+ $logger->debug('Item "{title}" already exists in the database. Reason: {reason}', [
147
+ 'title' => $itemTitle,
148
+ 'reason' => $reason
149
+ ]);
150
 
151
  continue;
152
  }
153
 
154
+ // Check if item is duplicated in the feed
155
+ //-----------------------------------------
156
+ $itemIdIsDuped = array_key_exists($itemId, $existingIds);
157
+ $itemTitleIsDuped = $unique_titles_only && array_key_exists($itemTitle, $existingTitles);
 
 
 
158
 
159
+ if ($itemIdIsDuped || $itemTitleIsDuped) {
160
+ $reason = $itemIdIsDuped
161
+ ? ($useGuids ? 'GUID' : 'permalink')
162
+ : 'Non-unique title';
163
 
164
+ $logger->debug('Item "{title}" is duplicated in the feed. Reason: {reason}', [
165
+ 'title' => $itemTitle,
166
+ 'reason' => $reason,
167
+ ]);
168
+
169
+ continue;
170
+ } else {
171
+ $existingIds[$itemId] = 1;
172
+ $existingTitles[$itemTitle] = 1;
173
  }
174
 
 
175
  $new_items[] = $item;
176
  }
177
 
202
  ? 0
203
  : $num_new_items - $num_can_insert;
204
 
205
+ // Get an array with the DB feed items in reverse order (the oldest first)
206
  $db_feed_items_reversed = array_reverse( $db_feed_items->posts );
207
  // Cut the array to get only the first few that are to be deleted ( equal to $num_feed_items_to_delete )
208
  $feed_items_to_delete = array_slice( $db_feed_items_reversed, 0, $num_feed_items_to_delete );
209
 
210
  // Iterate the feed items and delete them
211
  $num_items_deleted = 0;
212
+ foreach ( $feed_items_to_delete as $post ) {
213
  wp_delete_post( $post->ID, TRUE );
214
  $num_items_deleted++;
215
  }
219
  }
220
  }
221
 
222
+ update_post_meta( $feed_ID, 'wprss_last_update', time() );
223
  update_post_meta( $feed_ID, 'wprss_last_update_items', 0 );
224
 
225
  // Insert the items into the db
288
  }
289
 
290
  if (defined('WP_DEBUG') && WP_DEBUG) {
291
+ /**
292
+ * Allow debugging of wp_cron jobs using xDebug.
293
+ *
294
+ * This is done by taking the XDEBUG cookie received from the browser (which enables an xDebug session) and passing it
295
+ * to WP Cron. That way, code initiated from a cron job will be debuggable.
296
+ *
297
+ * @param array $cronRequest
298
+ *
299
+ * @return array $cron_request_array with the current XDEBUG_SESSION cookie added if set
300
+ */
301
+ add_action('cron_request', function($cronRequest) {
302
+ if (empty($_COOKIE['XDEBUG_SESSION'])) {
303
+ return ($cronRequest);
304
+ }
 
 
 
 
305
 
306
+ $cookie = filter_var($_COOKIE['XDEBUG_SESSION'], FILTER_SANITIZE_STRING);
307
 
308
+ if (empty($cronRequest['args']['cookies'])) {
309
+ $cronRequest['args']['cookies'] = [];
310
+ }
311
 
312
+ $cronRequest['args']['cookies']['XDEBUG_SESSION'] = $cookie;
313
 
314
+ return $cronRequest;
315
+ });
316
  }
317
 
318
  /**
379
 
380
  // If a feed source was passed
381
  if ($source !== null || $param_force_feed) {
382
+ // Get the force-feed option for the feed source
383
  $force_feed = get_post_meta($source, 'wprss_force_feed', true);
384
  // If turned on, force the feed
385
  if ($force_feed == 'true' || $param_force_feed) {
includes/feed-processing.php CHANGED
@@ -120,39 +120,36 @@ function wprss_get_feed_items_for_source($source_id)
120
  return wprss_get_imported_items($source_id);
121
  }
122
 
123
- /**
124
- * Queries the DB to get the GUIDs for existing feed items.
125
- */
126
- function wprss_get_existing_guids() {
127
  global $wpdb;
128
 
129
- $cols = $wpdb->get_col(
130
- "SELECT q.`meta_value`
131
- FROM {$wpdb->postmeta} AS p
132
- JOIN {$wpdb->postmeta} AS q
133
- ON (q.`meta_key` = 'wprss_item_guid' AND p.`post_id` = q.`post_id`)"
 
 
134
  );
135
 
136
- return @array_flip($cols);
137
  }
138
 
139
- /**
140
- * Database query to get existing permalinks
141
- *
142
- * @since 3.0
143
- */
144
- function wprss_get_existing_permalinks()
145
- {
146
  global $wpdb;
147
 
148
- $cols = $wpdb->get_col(
149
- "SELECT q.`meta_value`
150
- FROM {$wpdb->postmeta} AS p
151
- JOIN {$wpdb->postmeta} AS q
152
- ON (q.`meta_key` = 'wprss_item_permalink' AND p.`post_id` = q.`post_id`)"
 
 
153
  );
154
 
155
- return @array_flip($cols);
156
  }
157
 
158
  /**
@@ -168,37 +165,17 @@ function wprss_item_title_exists($title)
168
  {
169
  global $wpdb;
170
 
171
- $query = $wpdb->prepare(
172
- "SELECT *
173
- FROM `{$wpdb->posts}` AS p
174
- JOIN `{$wpdb->postmeta}` AS q
175
- ON p.`ID` = q.`post_id`
176
- WHERE q.`meta_key` = 'wprss_feed_id' AND p.`post_title` = %s",
177
- [html_entity_decode($title)]
178
- );
179
-
180
- $cols = $wpdb->get_col($query);
181
-
182
- return count($cols) > 0;
183
- }
184
-
185
- /**
186
- * Database query to get existing titles
187
- *
188
- * @since 4.7
189
- */
190
- function wprss_get_existing_titles()
191
- {
192
- global $wpdb;
193
-
194
- $cols = $wpdb->get_col(
195
- "SELECT p.`post_title`
196
- FROM `{$wpdb->posts}` AS p
197
- JOIN `{$wpdb->postmeta}` AS q
198
- ON p.`ID` = q.`post_id`"
199
  );
200
 
201
- return @array_flip($cols);
202
  }
203
 
204
  add_action('publish_wprss_feed', 'wprss_fetch_insert_feed_items', 10);
120
  return wprss_get_imported_items($source_id);
121
  }
122
 
123
+ /** Checks if a permalink exists in the database. */
124
+ function wprss_permalink_exists($permalink) {
 
 
125
  global $wpdb;
126
 
127
+ $numRows = $wpdb->query(
128
+ $wpdb->prepare(
129
+ "SELECT `meta_value`
130
+ FROM {$wpdb->postmeta}
131
+ WHERE (`meta_key` = 'wprss_item_permalink' AND `meta_value` = %s)",
132
+ $permalink
133
+ )
134
  );
135
 
136
+ return !!$numRows;
137
  }
138
 
139
+ /** Checks if a GUID exists in the database. */
140
+ function wprss_guid_exists($guid) {
 
 
 
 
 
141
  global $wpdb;
142
 
143
+ $numRows = $wpdb->query(
144
+ $wpdb->prepare(
145
+ "SELECT `meta_value`
146
+ FROM {$wpdb->postmeta}
147
+ WHERE (`meta_key` = 'wprss_item_guid' AND `meta_value` = %s)",
148
+ $guid
149
+ )
150
  );
151
 
152
+ return !!$numRows;
153
  }
154
 
155
  /**
165
  {
166
  global $wpdb;
167
 
168
+ $numRows = $wpdb->query(
169
+ $wpdb->prepare(
170
+ "SELECT p.`post_title`
171
+ FROM {$wpdb->posts} AS p
172
+ JOIN {$wpdb->postmeta} AS q ON p.`ID` = q.`post_id`
173
+ WHERE q.`meta_key` = 'wprss_feed_id' AND p.`post_title` = %s",
174
+ [html_entity_decode($title)]
175
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  );
177
 
178
+ return !!$numRows;
179
  }
180
 
181
  add_action('publish_wprss_feed', 'wprss_fetch_insert_feed_items', 10);
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: RSS import, RSS aggregator, autoblog, feed to post, news aggregator, rss t
5
  Requires at least: 4.0 or higher
6
  Tested up to: 6.0
7
  Requires PHP: 5.4
8
- Stable tag: 4.21
9
  License: GPLv3
10
 
11
  The most powerful and reliable RSS aggregator for WordPress. Build a news aggregator, autoblog and more in minutes with unlimited RSS feeds.
@@ -255,6 +255,10 @@ Our complete Knowledge Base with FAQs can be found [here](https://kb.wprssaggreg
255
 
256
  == Changelog ==
257
 
 
 
 
 
258
  = 4.21 (2022-07-13) =
259
  **Changed**
260
  - Updated Twig to v1.42.2, to support PHP 8 or later.
5
  Requires at least: 4.0 or higher
6
  Tested up to: 6.0
7
  Requires PHP: 5.4
8
+ Stable tag: 4.21.1
9
  License: GPLv3
10
 
11
  The most powerful and reliable RSS aggregator for WordPress. Build a news aggregator, autoblog and more in minutes with unlimited RSS feeds.
255
 
256
  == Changelog ==
257
 
258
+ = 4.21.1 (2022-07-20) =
259
+ **Fixed**
260
+ - An out-of-memory PHP error when importing items.
261
+
262
  = 4.21 (2022-07-13) =
263
  **Changed**
264
  - Updated Twig to v1.42.2, to support PHP 8 or later.
vendor/composer/autoload_classmap.php CHANGED
@@ -387,24 +387,6 @@ return array(
387
  'RebelCode\\Wpra\\Core\\Util\\Sanitizers\\EquivalenceSanitizer' => $baseDir . '/src/Util/Sanitizers/EquivalenceSanitizer.php',
388
  'RebelCode\\Wpra\\Core\\Util\\Sanitizers\\IntSanitizer' => $baseDir . '/src/Util/Sanitizers/IntSanitizer.php',
389
  'RebelCode\\Wpra\\Core\\Util\\Sanitizers\\PhpFilterSanitizer' => $baseDir . '/src/Util/Sanitizers/PhpFilterSanitizer.php',
390
- 'RebelCode\\Wpra\\Core\\V5\\Feed' => $baseDir . '/src/V5/Feed.php',
391
- 'RebelCode\\Wpra\\Core\\V5\\FeedInterface' => $baseDir . '/src/V5/FeedInterface.php',
392
- 'RebelCode\\Wpra\\Core\\V5\\FeedItem' => $baseDir . '/src/V5/FeedItem.php',
393
- 'RebelCode\\Wpra\\Core\\V5\\FeedItem\\Author' => $baseDir . '/src/V5/FeedItem/Author.php',
394
- 'RebelCode\\Wpra\\Core\\V5\\FeedItem\\Category' => $baseDir . '/src/V5/FeedItem/Category.php',
395
- 'RebelCode\\Wpra\\Core\\V5\\Feed\\DbFeed' => $baseDir . '/src/V5/Feed/DbFeed.php',
396
- 'RebelCode\\Wpra\\Core\\V5\\Feed\\FeedMeta' => $baseDir . '/src/V5/Feed/FeedMeta.php',
397
- 'RebelCode\\Wpra\\Core\\V5\\ImportContext' => $baseDir . '/src/V5/ImportContext.php',
398
- 'RebelCode\\Wpra\\Core\\V5\\Importer' => $baseDir . '/src/V5/Importer.php',
399
- 'RebelCode\\Wpra\\Core\\V5\\Importer\\FetchData' => $baseDir . '/src/V5/Importer/FetchData.php',
400
- 'RebelCode\\Wpra\\Core\\V5\\Importer\\FetcherInterface' => $baseDir . '/src/V5/Importer/FetcherInterface.php',
401
- 'RebelCode\\Wpra\\Core\\V5\\Importer\\SimplePieFetcher' => $baseDir . '/src/V5/Importer/SimplePieFetcher.php',
402
- 'RebelCode\\Wpra\\Core\\V5\\IrItem' => $baseDir . '/src/V5/IrItem.php',
403
- 'RebelCode\\Wpra\\Core\\V5\\Settings' => $baseDir . '/src/V5/Settings.php',
404
- 'RebelCode\\Wpra\\Core\\V5\\Utils\\Context' => $baseDir . '/src/V5/Utils/Context.php',
405
- 'RebelCode\\Wpra\\Core\\V5\\Utils\\CriticalTask' => $baseDir . '/src/V5/Utils/CriticalTask.php',
406
- 'RebelCode\\Wpra\\Core\\V5\\Utils\\HtmlUtils' => $baseDir . '/src/V5/Utils/HtmlUtils.php',
407
- 'RebelCode\\Wpra\\Core\\V5\\Utils\\Result' => $baseDir . '/src/V5/Utils/Result.php',
408
  'RebelCode\\Wpra\\Core\\Wp\\Asset\\AbstractAsset' => $baseDir . '/src/Wp/Asset/AbstractAsset.php',
409
  'RebelCode\\Wpra\\Core\\Wp\\Asset\\AssetInterface' => $baseDir . '/src/Wp/Asset/AssetInterface.php',
410
  'RebelCode\\Wpra\\Core\\Wp\\Asset\\ScriptAsset' => $baseDir . '/src/Wp/Asset/ScriptAsset.php',
387
  'RebelCode\\Wpra\\Core\\Util\\Sanitizers\\EquivalenceSanitizer' => $baseDir . '/src/Util/Sanitizers/EquivalenceSanitizer.php',
388
  'RebelCode\\Wpra\\Core\\Util\\Sanitizers\\IntSanitizer' => $baseDir . '/src/Util/Sanitizers/IntSanitizer.php',
389
  'RebelCode\\Wpra\\Core\\Util\\Sanitizers\\PhpFilterSanitizer' => $baseDir . '/src/Util/Sanitizers/PhpFilterSanitizer.php',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  'RebelCode\\Wpra\\Core\\Wp\\Asset\\AbstractAsset' => $baseDir . '/src/Wp/Asset/AbstractAsset.php',
391
  'RebelCode\\Wpra\\Core\\Wp\\Asset\\AssetInterface' => $baseDir . '/src/Wp/Asset/AssetInterface.php',
392
  'RebelCode\\Wpra\\Core\\Wp\\Asset\\ScriptAsset' => $baseDir . '/src/Wp/Asset/ScriptAsset.php',
vendor/composer/autoload_static.php CHANGED
@@ -570,24 +570,6 @@ class ComposerStaticInit46c8b76c439f86ad826af1a4d36b4e60
570
  'RebelCode\\Wpra\\Core\\Util\\Sanitizers\\EquivalenceSanitizer' => __DIR__ . '/../..' . '/src/Util/Sanitizers/EquivalenceSanitizer.php',
571
  'RebelCode\\Wpra\\Core\\Util\\Sanitizers\\IntSanitizer' => __DIR__ . '/../..' . '/src/Util/Sanitizers/IntSanitizer.php',
572
  'RebelCode\\Wpra\\Core\\Util\\Sanitizers\\PhpFilterSanitizer' => __DIR__ . '/../..' . '/src/Util/Sanitizers/PhpFilterSanitizer.php',
573
- 'RebelCode\\Wpra\\Core\\V5\\Feed' => __DIR__ . '/../..' . '/src/V5/Feed.php',
574
- 'RebelCode\\Wpra\\Core\\V5\\FeedInterface' => __DIR__ . '/../..' . '/src/V5/FeedInterface.php',
575
- 'RebelCode\\Wpra\\Core\\V5\\FeedItem' => __DIR__ . '/../..' . '/src/V5/FeedItem.php',
576
- 'RebelCode\\Wpra\\Core\\V5\\FeedItem\\Author' => __DIR__ . '/../..' . '/src/V5/FeedItem/Author.php',
577
- 'RebelCode\\Wpra\\Core\\V5\\FeedItem\\Category' => __DIR__ . '/../..' . '/src/V5/FeedItem/Category.php',
578
- 'RebelCode\\Wpra\\Core\\V5\\Feed\\DbFeed' => __DIR__ . '/../..' . '/src/V5/Feed/DbFeed.php',
579
- 'RebelCode\\Wpra\\Core\\V5\\Feed\\FeedMeta' => __DIR__ . '/../..' . '/src/V5/Feed/FeedMeta.php',
580
- 'RebelCode\\Wpra\\Core\\V5\\ImportContext' => __DIR__ . '/../..' . '/src/V5/ImportContext.php',
581
- 'RebelCode\\Wpra\\Core\\V5\\Importer' => __DIR__ . '/../..' . '/src/V5/Importer.php',
582
- 'RebelCode\\Wpra\\Core\\V5\\Importer\\FetchData' => __DIR__ . '/../..' . '/src/V5/Importer/FetchData.php',
583
- 'RebelCode\\Wpra\\Core\\V5\\Importer\\FetcherInterface' => __DIR__ . '/../..' . '/src/V5/Importer/FetcherInterface.php',
584
- 'RebelCode\\Wpra\\Core\\V5\\Importer\\SimplePieFetcher' => __DIR__ . '/../..' . '/src/V5/Importer/SimplePieFetcher.php',
585
- 'RebelCode\\Wpra\\Core\\V5\\IrItem' => __DIR__ . '/../..' . '/src/V5/IrItem.php',
586
- 'RebelCode\\Wpra\\Core\\V5\\Settings' => __DIR__ . '/../..' . '/src/V5/Settings.php',
587
- 'RebelCode\\Wpra\\Core\\V5\\Utils\\Context' => __DIR__ . '/../..' . '/src/V5/Utils/Context.php',
588
- 'RebelCode\\Wpra\\Core\\V5\\Utils\\CriticalTask' => __DIR__ . '/../..' . '/src/V5/Utils/CriticalTask.php',
589
- 'RebelCode\\Wpra\\Core\\V5\\Utils\\HtmlUtils' => __DIR__ . '/../..' . '/src/V5/Utils/HtmlUtils.php',
590
- 'RebelCode\\Wpra\\Core\\V5\\Utils\\Result' => __DIR__ . '/../..' . '/src/V5/Utils/Result.php',
591
  'RebelCode\\Wpra\\Core\\Wp\\Asset\\AbstractAsset' => __DIR__ . '/../..' . '/src/Wp/Asset/AbstractAsset.php',
592
  'RebelCode\\Wpra\\Core\\Wp\\Asset\\AssetInterface' => __DIR__ . '/../..' . '/src/Wp/Asset/AssetInterface.php',
593
  'RebelCode\\Wpra\\Core\\Wp\\Asset\\ScriptAsset' => __DIR__ . '/../..' . '/src/Wp/Asset/ScriptAsset.php',
570
  'RebelCode\\Wpra\\Core\\Util\\Sanitizers\\EquivalenceSanitizer' => __DIR__ . '/../..' . '/src/Util/Sanitizers/EquivalenceSanitizer.php',
571
  'RebelCode\\Wpra\\Core\\Util\\Sanitizers\\IntSanitizer' => __DIR__ . '/../..' . '/src/Util/Sanitizers/IntSanitizer.php',
572
  'RebelCode\\Wpra\\Core\\Util\\Sanitizers\\PhpFilterSanitizer' => __DIR__ . '/../..' . '/src/Util/Sanitizers/PhpFilterSanitizer.php',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
573
  'RebelCode\\Wpra\\Core\\Wp\\Asset\\AbstractAsset' => __DIR__ . '/../..' . '/src/Wp/Asset/AbstractAsset.php',
574
  'RebelCode\\Wpra\\Core\\Wp\\Asset\\AssetInterface' => __DIR__ . '/../..' . '/src/Wp/Asset/AssetInterface.php',
575
  'RebelCode\\Wpra\\Core\\Wp\\Asset\\ScriptAsset' => __DIR__ . '/../..' . '/src/Wp/Asset/ScriptAsset.php',
vendor/composer/installed.php CHANGED
@@ -5,7 +5,7 @@
5
  'type' => 'wordpress-plugin',
6
  'install_path' => __DIR__ . '/../../',
7
  'aliases' => array(),
8
- 'reference' => 'a00bf969d77068eabd825565643094274770d0f5',
9
  'name' => 'wprss/core',
10
  'dev' => false,
11
  ),
@@ -343,7 +343,7 @@
343
  'type' => 'wordpress-plugin',
344
  'install_path' => __DIR__ . '/../../',
345
  'aliases' => array(),
346
- 'reference' => 'a00bf969d77068eabd825565643094274770d0f5',
347
  'dev_requirement' => false,
348
  ),
349
  ),
5
  'type' => 'wordpress-plugin',
6
  'install_path' => __DIR__ . '/../../',
7
  'aliases' => array(),
8
+ 'reference' => '34f842c00433aa9e45ab987b1f4ce449d487c550',
9
  'name' => 'wprss/core',
10
  'dev' => false,
11
  ),
343
  'type' => 'wordpress-plugin',
344
  'install_path' => __DIR__ . '/../../',
345
  'aliases' => array(),
346
+ 'reference' => '34f842c00433aa9e45ab987b1f4ce449d487c550',
347
  'dev_requirement' => false,
348
  ),
349
  ),
wp-rss-aggregator.php CHANGED
@@ -4,7 +4,7 @@
4
  * Plugin Name: WP RSS Aggregator
5
  * Plugin URI: https://www.wprssaggregator.com/#utm_source=wpadmin&utm_medium=plugin&utm_campaign=wpraplugin
6
  * Description: Imports and aggregates multiple RSS Feeds.
7
- * Version: 4.21
8
  * Author: RebelCode
9
  * Author URI: https://www.wprssaggregator.com
10
  * Text Domain: wprss
@@ -76,7 +76,7 @@ use RebelCode\Wpra\Core\Plugin;
76
 
77
  // Set the version number of the plugin.
78
  if( !defined( 'WPRSS_VERSION' ) )
79
- define( 'WPRSS_VERSION', '4.21' );
80
 
81
  if( !defined( 'WPRSS_WP_MIN_VERSION' ) )
82
  define( 'WPRSS_WP_MIN_VERSION', '4.8' );
4
  * Plugin Name: WP RSS Aggregator
5
  * Plugin URI: https://www.wprssaggregator.com/#utm_source=wpadmin&utm_medium=plugin&utm_campaign=wpraplugin
6
  * Description: Imports and aggregates multiple RSS Feeds.
7
+ * Version: 4.21.1
8
  * Author: RebelCode
9
  * Author URI: https://www.wprssaggregator.com
10
  * Text Domain: wprss
76
 
77
  // Set the version number of the plugin.
78
  if( !defined( 'WPRSS_VERSION' ) )
79
+ define( 'WPRSS_VERSION', '4.21.1' );
80
 
81
  if( !defined( 'WPRSS_WP_MIN_VERSION' ) )
82
  define( 'WPRSS_WP_MIN_VERSION', '4.8' );