diff --git a/.gitignore b/.gitignore index b693dbc4..379e53ad 100755 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,6 @@ cypress/screenshots/* cypress/integration/localhost* cypress/fixtures cypress.env.json +.phpunit.result.cache js/build artifacts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7eb74c74..2ef60b5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +##### [Version 4.4.9](https://github.com/Codeinwp/feedzy-rss-feeds/compare/v4.4.8...v4.4.9) (2024-05-30) + +Improvements +- Allow chained actions for `[#item_title]` +- Added floating widget to the dashboard +- Made item_date tag to respect and convert to site timezone on import +- Updated internal dependencies + +Fixes +- Fix `auto` option for default fallback image in Feedzy RSS Block. +- Fixed issue when images with non-Latin characters in the URL are not imported +- Fixed issue with fallback image per import job not working [PRO] +- Fixed usage of custom tag for featured image in the import settings [PRO] + ##### [Version 4.4.8](https://github.com/Codeinwp/feedzy-rss-feeds/compare/v4.4.7...v4.4.8) (2024-04-15) ### Fixes diff --git a/composer.lock b/composer.lock index 00a942a8..f4cb371d 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.18", + "version": "3.3.22", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "5463d7170ed7b9735223d6715442b8670a477688" + "reference": "1754febc3c25c4c884424e72801c4d3f2ec7097e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/5463d7170ed7b9735223d6715442b8670a477688", - "reference": "5463d7170ed7b9735223d6715442b8670a477688", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/1754febc3c25c4c884424e72801c4d3f2ec7097e", + "reference": "1754febc3c25c4c884424e72801c4d3f2ec7097e", "shasum": "" }, "require-dev": { @@ -42,9 +42,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.18" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.22" }, - "time": "2024-04-05T09:47:58+00:00" + "time": "2024-05-17T15:04:51+00:00" } ], "packages-dev": [ @@ -190,28 +190,28 @@ }, { "name": "phpcompatibility/phpcompatibility-paragonie", - "version": "1.3.2", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git", - "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26" + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", - "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/293975b465e0e709b571cbf0c957c6c0a7b9a2ac", + "reference": "293975b465e0e709b571cbf0c957c6c0a7b9a2ac", "shasum": "" }, "require": { "phpcompatibility/php-compatibility": "^9.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", "paragonie/random_compat": "dev-master", "paragonie/sodium_compat": "dev-master" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", @@ -241,22 +241,37 @@ ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/security/policy", "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie" }, - "time": "2022-10-25T01:46:02+00:00" + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-04-24T21:30:46+00:00" }, { "name": "phpcompatibility/phpcompatibility-wp", - "version": "2.1.4", + "version": "2.1.5", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", - "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5" + "reference": "01c1ff2704a58e46f0cb1ca9d06aee07b3589082" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", - "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/01c1ff2704a58e46f0cb1ca9d06aee07b3589082", + "reference": "01c1ff2704a58e46f0cb1ca9d06aee07b3589082", "shasum": "" }, "require": { @@ -264,10 +279,10 @@ "phpcompatibility/phpcompatibility-paragonie": "^1.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7" + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" }, "suggest": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.", "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." }, "type": "phpcodesniffer-standard", @@ -296,22 +311,37 @@ ], "support": { "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues", + "security": "https://github.com/PHPCompatibility/PHPCompatibilityWP/security/policy", "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP" }, - "time": "2022-10-24T09:00:36+00:00" + "funding": [ + { + "url": "https://github.com/PHPCompatibility", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-04-24T21:37:59+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.2", + "version": "3.9.2", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "aac1f6f347a5c5ac6bc98ad395007df00990f480" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/aac1f6f347a5c5ac6bc98ad395007df00990f480", + "reference": "aac1f6f347a5c5ac6bc98ad395007df00990f480", "shasum": "" }, "require": { @@ -321,11 +351,11 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "bin/phpcbf", + "bin/phpcs" ], "type": "library", "extra": { @@ -340,22 +370,45 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, - "time": "2023-02-22T23:07:41+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-04-23T20:25:34+00:00" }, { "name": "wp-coding-standards/wpcs", diff --git a/css/feedzy-rss-feeds.css b/css/feedzy-rss-feeds.css index 468f2f35..2eec96c5 100644 --- a/css/feedzy-rss-feeds.css +++ b/css/feedzy-rss-feeds.css @@ -2,7 +2,7 @@ * feedzy-rss-feeds.css * Feedzy RSS Feed * Copyright: (c) 2016 Themeisle, themeisle.com - * Version: 4.4.8 + * Version: 4.4.9 * Plugin Name: FEEDZY RSS Feeds * Plugin URI: http://themeisle.com/plugins/feedzy-rss-feeds/ * Author: Themeisle diff --git a/cypress.config.js b/cypress.config.js index fb34a570..0d519295 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -84,4 +84,7 @@ module.exports = defineConfig({ specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}', pageLoadTimeout : 300000, }, + blockHosts: [ + 'app.formbricks.com' + ], }) diff --git a/cypress/e2e/default/import_feed_free.js b/cypress/e2e/default/import_feed_free.js index 830c976e..256e3ed8 100644 --- a/cypress/e2e/default/import_feed_free.js +++ b/cypress/e2e/default/import_feed_free.js @@ -22,7 +22,72 @@ describe('Test Free - Import Feed', function() { cy.get('.fz-tabs-menu li a').should('have.length', settings.tabs); }) - it('Create/Verify/Run import', function() { + it('Check chained actions insertion for title', function() { + // Create a new import. + cy.visit('/wp-admin/post-new.php?post_type=feedzy_imports'); + + // Fill up the form + cy.get('#post_title').clear().type( 'Chained actions: ' + feed.url ); + cy.get('#feedzy-import-source').clear().type( feed.url ); + cy.get('#feedzy-import-source').next('.fz-input-group-append').find('.add-outside-tags').click(); + + // Open the Map Content tab + cy.get('#fz-import-map-content > .feedzy-accordion-item__button').click(); + + // Clear the title field. + cy.get(':nth-child(4) > .fz-right > .fz-form-group > .fz-input-group > .fz-input-group-left > .fz-group > .tagify > .tagify__input').type('{selectall}{backspace}'); + + // Add chained actions for title field. + cy.get(':nth-child(4) > .fz-right > .fz-form-group > .fz-input-group > .fz-input-group-right > .dropdown > .btn').click(); + cy.get(':nth-child(4) > .fz-right > .fz-form-group > .fz-input-group > .fz-input-group-right > .dropdown > .dropdown-menu > [data-field-tag="item_title"] > small').click(); // Select the [#item_title] tag. + + // Add a chained action. + cy.get('.fz-action-relative > .components-button').click(); + cy.get('.popover-action-list > ul > :nth-child(1)').click(); // Select the "Trim content" action. + + // Add another chained action. + cy.get('.fz-action-relative > .components-button').click(); + cy.get('.popover-action-list > ul > :nth-child(3)').click(); // Select the "Search and Replace" action. + + // Save actions. + cy.get('.fz-save-action').click(); + + // Save the serialized data directly to the input field. (The Tagify lib used for UI does not update the input field based on Cypress click actions). + cy.get('.tagify__input > .tagify__tag a').invoke('attr', 'data-actions').then((serializedActions) => { + cy.get('[name="feedzy_meta_data[import_post_title]"]').clear({force: true}).invoke('val', '[[{"value": "' + serializedActions + '"}]]' ).blur({force: true}); + }); + + // Save the import. + cy.get('button[type="submit"][name="save"]').scrollIntoView().click({force:true}); + + // Reopen the import. + cy.visit('/wp-admin/edit.php?post_type=feedzy_imports'); + cy.get('tr:nth-of-type(1) .row-title').click(); + + // Open the Map Content tab + cy.get('#fz-import-map-content > .feedzy-accordion-item__button').click(); + + // Check the saved actions. + cy.get('.tagify__input > .tagify__tag').should('be.visible'); + cy.get('.tagify__input > .tagify__tag a').invoke('attr', 'data-actions').then((serializedActions) => { + expect(serializedActions).to.include('item_title'); + expect(serializedActions).to.include('trim'); + expect(serializedActions).to.include('search_replace'); + + // Check if we have right actions. + const actions = JSON.parse(decodeURIComponent(serializedActions)); + + expect(actions).to.have.length(2); + expect(actions[0].id).to.equal('trim'); + expect(actions[0].tag).to.equal('item_title'); + + expect(actions[1].id).to.equal('search_replace'); + expect(actions[1].tag).to.equal('item_title'); + } ); + + }) + + it('Check the Create/Verify/Run workflow for feed import', function() { // 1. CREATE cy.visit('/wp-admin/post-new.php?post_type=feedzy_imports'); @@ -54,7 +119,7 @@ describe('Test Free - Import Feed', function() { cy.get('[name="feedzy_meta_data[import_post_title]"]').clear({force: true}).invoke('val', PREFIX + feed.title).blur({force: true}); cy.get('[name="feedzy_meta_data[import_post_content]"]').clear({force: true}).invoke('val', PREFIX + feed.fullcontent.content + feed.content).blur({force: true}); - cy.get('[name="feedzy_meta_data[import_post_featured_img]"]').clear({force: true}).invoke('val', feed.image.url).blur({force: true}); + cy.get('[name="feedzy_meta_data[import_post_featured_img]"]').clear({force: true}).invoke('val', feed.image.tag).blur({force: true}); // check disallowd magic tags const tags = feed.tags.disallowed; @@ -84,18 +149,15 @@ describe('Test Free - Import Feed', function() { // show a notice. //cy.get('div.notice.feedzy-error-critical').should('be.visible'); - }) - - it('Update the new import with VALID url', function() { + cy.visit('/wp-admin/edit.php?post_type=feedzy_imports'); cy.get('tr:nth-of-type(1) .row-title').click(); + // fill up the form cy.get('#post_title').clear().type( feed.url ); - cy.get('#feedzy-import-source').clear().type( feed.url ); - cy.get('#feedzy-import-source').next('.fz-input-group-append').find('.add-outside-tags').click(); - + cy.get('button[type="submit"][name="save"]').scrollIntoView().click({force:true}); // check if the import has been setup @@ -117,12 +179,12 @@ describe('Test Free - Import Feed', function() { cy.get('#feedzy_post_terms').invoke('show').then( () => { cy.get('#feedzy_post_terms option:selected').should('have.length', feed.taxonomy.length); }); - + cy.get('[name="feedzy_meta_data[import_post_title]"]').should('have.value', PREFIX + feed.title); cy.get('[name="feedzy_meta_data[import_post_content]"]').should('have.value', PREFIX + feed.fullcontent.content + feed.content + '\n'); // image from URL - cy.get('[name="feedzy_meta_data[import_post_featured_img]"]').should('have.value', feed.image.url); + cy.get('[name="feedzy_meta_data[import_post_featured_img]"]').should('have.value', feed.image.tag); // publish cy.get('button[type="submit"][name="publish"]').scrollIntoView().click({force:true}); @@ -254,6 +316,4 @@ describe('Test Free - Import Feed', function() { cy.get('.wp-block-post-author-name .wp-block-post-author-name__link:contains("wordpress")').should('exist'); }) - - }) diff --git a/feedzy-rss-feed.php b/feedzy-rss-feed.php index 1f05d0e2..fd2176dd 100644 --- a/feedzy-rss-feed.php +++ b/feedzy-rss-feed.php @@ -15,7 +15,7 @@ * Plugin Name: Feedzy RSS Feeds Lite * Plugin URI: https://themeisle.com/plugins/feedzy-rss-feeds/ * Description: A small and lightweight RSS aggregator plugin. Fast and very easy to use, it allows you to aggregate multiple RSS feeds into your WordPress site through fully customizable shortcodes & widgets. - * Version: 4.4.8 + * Version: 4.4.9 * Author: Themeisle * Author URI: http://themeisle.com * License: GPL-2.0+ @@ -208,3 +208,18 @@ function feedzy_themeisle_log_event( $name, $msg, $type, $file, $line ) { } add_filter( 'themeisle_sdk_enable_telemetry', '__return_true' ); + +add_filter( + 'feedzy_rss_feeds_float_widget_metadata', function () { + return array( + 'nice_name' => 'Feedzy', + 'logo' => FEEDZY_ABSURL . 'img/feedzy.svg', + 'primary_color' => '#4268CF', + 'pages' => array( 'feedzy_imports', 'edit-feedzy_imports', 'edit-feedzy_categories', 'feedzy_page_feedzy-settings', 'feedzy_page_feedzy-support' ), + 'has_upgrade_menu' => ! feedzy_is_pro(), + 'upgrade_link' => tsdk_utmify( FEEDZY_UPSELL_LINK, 'floatWidget' ), + 'documentation_link' => tsdk_utmify( 'https://docs.themeisle.com/collection/1569-feedzy-rss-feeds', 'floatWidget' ), + 'wizard_link' => ! feedzy_is_pro() && ! empty( get_option( 'feedzy_fresh_install', false ) ) ? admin_url( 'admin.php?page=feedzy-setup-wizard&tab#step-1' ) : '', + ); + } +); diff --git a/includes/admin/feedzy-rss-feeds-actions.php b/includes/admin/feedzy-rss-feeds-actions.php index 60243ebb..a6e8401e 100644 --- a/includes/admin/feedzy-rss-feeds-actions.php +++ b/includes/admin/feedzy-rss-feeds-actions.php @@ -23,11 +23,11 @@ class Feedzy_Rss_Feeds_Actions { private static $instance; /** - * Content actions. + * Serialized content actions. It can contain a mix of magic tags and simple text. * - * @var string $actions Content actions. + * @var string $raw_serialized_actions Content actions. */ - private $actions; + private $raw_serialized_actions; /** * Setting options. @@ -39,7 +39,7 @@ class Feedzy_Rss_Feeds_Actions { /** * Extract tags. * - * @var string $extract_tags Extract tags. + * @var array $extract_tags Extract tags. */ private $extract_tags; @@ -72,11 +72,11 @@ class Feedzy_Rss_Feeds_Actions { public $result = ''; /** - * Post content. + * The field content (title, description, post content, date, etc.) * - * @var string $post_content + * @var string $field_content */ - public $post_content = ''; + public $field_content = ''; /** * Default value. @@ -129,13 +129,13 @@ public static function instance() { /** * Run actions. * - * @param string $actions Item content actions. - * @return string + * @param string $raw_serialized_actions Item content actions. + * @return string|array */ - public function set_actions( $actions = '' ) { - $this->actions = $actions; - if ( empty( $this->actions ) ) { - return $this->actions; + public function set_raw_serialized_actions( $raw_serialized_actions = '' ) { + $this->raw_serialized_actions = $raw_serialized_actions; + if ( empty( $this->raw_serialized_actions ) ) { + return $this->raw_serialized_actions; } $this->extract_tags = $this->extract_magic_tags(); return $this->extract_tags; @@ -157,7 +157,20 @@ public function set_settings( $options ) { * @return array|array[] */ public function extract_magic_tags() { - $can_process = preg_match_all( '/\[\[\{(.*)\}\]\]/U', $this->actions, $item_magic_tags, PREG_PATTERN_ORDER ); + /** + * Transform the serialized string of magic tags to array. + * + * Input(string): [[{"value":"[{"id":"chat_gpt_rewrite","tag":"item_title","data":{"ChatGPT":"Create a long description: {content}"}},{"id":"fz_summarize","tag":"item_title","data":{"fz_summarize":true}}]"}]] with a nice weather. + * + * Output: + * [ + * [ + * [replace_to] => [[{"value":"[{"id":"chat_gpt_rewrite","tag":"item_title","data":{"ChatGPT":"Create a long description: {content}"}},{"id":"fz_summarize","tag":"item_title","data":{"fz_summarize":true}}]"}]] + * [replace_with] => [{"id":"chat_gpt_rewrite","tag":"item_title","data":{"ChatGPT":"Create a long description: {content}"}},{"id":"fz_summarize","tag":"item_title","data":{"fz_summarize":true}}] + * ] + * ] + */ + $can_process = preg_match_all( '/\[\[\{(.*)\}\]\]/U', $this->raw_serialized_actions, $item_magic_tags, PREG_PATTERN_ORDER ); if ( ! $can_process ) { return array(); } @@ -179,16 +192,18 @@ function( $tag ) { } /** - * Get magic tags. + * Get the extracted serialized actions from the Tagify tags. The actions can be a mix of Tagify tags and simple text. + * + * @return string The serialized actions. */ - public function get_tags() { + public function get_serialized_actions() { if ( ! is_array( $this->get_extract_tags() ) ) { - return $this->actions; + return ''; } $replace_to = array_column( $this->get_extract_tags(), 'replace_to' ); $replace_with = array_column( $this->get_extract_tags(), 'replace_with' ); - return str_replace( $replace_to, $replace_with, $this->actions ); + return str_replace( $replace_to, $replace_with, $this->raw_serialized_actions ); } /** @@ -199,21 +214,25 @@ public function get_extract_tags() { } /** - * Get actions. + * Get actions. Return pairs of serialized actions and their deserialized versions. + * + * Deserialized version is used to run the action job. While serialized version is used to replace the job result in the input content. + * + * @return array */ public function get_actions() { if ( ! is_array( $this->get_extract_tags() ) ) { - return $this->actions; + return array(); } $replace_with = array_column( $this->get_extract_tags(), 'replace_with' ); $actions = array_map( - function( $action ) { - $replace_with = json_decode( $action ); - if ( $replace_with ) { + function( $serialized_actions ) { + $job_actions = json_decode( $serialized_actions ); + if ( $job_actions ) { return array( - 'replace_to' => wp_json_encode( $replace_with ), - 'replace_with' => $replace_with, + 'serialized_actions' => $serialized_actions, + 'job_actions' => $job_actions, ); } return false; @@ -226,7 +245,7 @@ function( $action ) { /** * Run action job. * - * @param string $post_content Post content. + * @param string $field_content Field content. It can contain a mix of magic tags and simple text. * @param string $import_translation_lang Translation language code. * @param object $job Post object. * @param string $language_code Feed language code. @@ -234,38 +253,39 @@ function( $action ) { * @param string $default_value Default value. * @return string */ - public function run_action_job( $post_content, $import_translation_lang, $job, $language_code, $item, $default_value = '' ) { + public function run_action_job( $field_content, $import_translation_lang, $job, $language_code, $item, $default_value = '' ) { $this->item = $item; $this->job = $job; $this->language_code = $language_code; $this->translation_lang = $import_translation_lang; - $this->post_content = $post_content; + $this->field_content = $field_content; $this->default_value = $default_value; $actions = $this->get_actions(); if ( ! empty( $actions ) ) { foreach ( $actions as $key => $jobs ) { - if ( ! isset( $jobs['replace_with'] ) ) { + if ( ! isset( $jobs['job_actions'] ) ) { continue; } + $this->result = null; - $replace_with = isset( $jobs['replace_with'] ) ? $jobs['replace_with'] : array(); - $replace_to = isset( $jobs['replace_to'] ) ? $jobs['replace_to'] : ''; - foreach ( $replace_with as $job ) { + $jobs_actions = $jobs['job_actions']; + $replace_to = isset( $jobs['serialized_actions'] ) ? $jobs['serialized_actions'] : ''; + foreach ( $jobs_actions as $job ) { $this->current_job = $job; $this->result = $this->action_process(); } if ( 'item_image' === $this->type ) { - $this->post_content = str_replace( $replace_to, $this->result, wp_json_encode( $replace_with ) ); + $this->field_content = str_replace( $replace_to, $this->result, wp_json_encode( $jobs_actions ) ); } else { - $this->post_content = str_replace( $replace_to, $this->result, $this->post_content ); + $this->field_content = str_replace( $replace_to, $this->result, $this->field_content ); } } } if ( empty( $actions ) && 'item_image' === $this->type ) { return $default_value; } - return $this->post_content; + return $this->field_content; } /** diff --git a/includes/admin/feedzy-rss-feeds-import.php b/includes/admin/feedzy-rss-feeds-import.php index f9e036d5..15753f95 100644 --- a/includes/admin/feedzy-rss-feeds-import.php +++ b/includes/admin/feedzy-rss-feeds-import.php @@ -400,7 +400,10 @@ public function feedzy_import_feed_options() { $default_thumbnail_id = 0; if ( feedzy_is_pro() ) { $default_thumbnail_id = get_post_meta( $post->ID, 'default_thumbnail_id', true ); - if ( empty( $default_thumbnail_id ) ) { + if ( + empty( $default_thumbnail_id ) && + '0' !== $default_thumbnail_id // Can use the fallback image from Global Settings. + ) { $default_thumbnail_id = ! empty( $this->free_settings['general']['default-thumbnail-id'] ) ? (int) $this->free_settings['general']['default-thumbnail-id'] : 0; } } @@ -525,6 +528,9 @@ public function save_feedzy_import_feed_meta( $post_id, $post ) { add_post_meta( $post_id, $key, $value ); } if ( ! $value ) { + if ( 'default_thumbnail_id' === $key && '0' === $value ) { // Mark the feed as having no default fallback image (including the global fallback). + continue; + } delete_post_meta( $post_id, $key ); } } @@ -1413,18 +1419,19 @@ private function run_job( $job, $max ) { } // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date - $item_date = date( get_option( 'date_format' ) . ' at ' . get_option( 'time_format' ), $item['item_date'] ); + $item_date = wp_date( get_option( 'date_format' ) . ' at ' . get_option( 'time_format' ), $item['item_date'] ); $item_date = $item['item_date_formatted']; - // Transform any structure like [[{"value":"[#item_title]"}]] to [#item_title]. - $import_title = preg_replace( '/\[\[\{"value":"(\[#[^]]+\])"\}\]\]/', '$1', $import_title ); - // Get translated item title. $translated_title = ''; if ( $import_auto_translation && ( false !== strpos( $import_title, '[#translated_title]' ) || false !== strpos( $post_excerpt, '[#translated_title]' ) ) ) { $translated_title = apply_filters( 'feedzy_invoke_auto_translate_services', $item['item_title'], '[#translated_title]', $import_translation_lang, $job, $language_code, $item ); } + $import_title = rawurldecode( $import_title ); + $import_title = str_replace( PHP_EOL, "\r\n", $import_title ); + $import_title = trim( $import_title ); + $post_title = str_replace( array( '[#item_title]', @@ -1447,6 +1454,10 @@ private function run_job( $job, $max ) { $import_title ); + // Run all the actions stored for the embedded/serialized tags in the title field. + $title_action = $this->get_actions_runner( $post_title, 'item_title' ); + $post_title = $title_action->run_action_job( $title_action->get_serialized_actions(), $translated_title, $job, $language_code, $item ); + if ( $this->feedzy_is_business() ) { $post_title = apply_filters( 'feedzy_parse_custom_tags', $post_title, $item_obj ); } @@ -1546,8 +1557,8 @@ private function run_job( $job, $max ) { $post_content = apply_filters( 'feedzy_invoke_services', $post_content, 'full_content', $full_content, $job ); } // Item content action. - $content_action = $this->handle_content_actions( $post_content, 'item_content' ); - $post_content = $content_action->get_tags(); + $content_action = $this->get_actions_runner( $post_content, 'item_content' ); + $post_content = $content_action->get_serialized_actions(); // Item content action process. $post_content = $content_action->run_action_job( $post_content, $import_translation_lang, $job, $language_code, $item ); // Parse custom tags. @@ -1576,9 +1587,9 @@ private function run_job( $job, $max ) { } // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date - $item_date = date( 'Y-m-d H:i:s', $item['item_date'] ); + $item_date = wp_date( 'Y-m-d H:i:s', $item['item_date'] ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date - $now = date( 'Y-m-d H:i:s' ); + $now = wp_date( 'Y-m-d H:i:s' ); if ( trim( $import_date ) === '' ) { $post_date = $now; } @@ -1652,7 +1663,7 @@ private function run_job( $job, $max ) { $image_source_url = ''; $img_success = true; $new_post_id = 0; - $default_img_tag = ! empty( $import_featured_img ) ? '[#item_image]' : ''; + $default_img_tag = ! empty( $import_featured_img ) && is_string( $import_featured_img ) ? $import_featured_img : ''; // image tag if ( strpos( $default_img_tag, '[#item_image]' ) !== false ) { @@ -1759,7 +1770,7 @@ function( $term ) { do_action( 'feedzy_import_extra', $job, $item_obj, $new_post_id, $import_errors, $import_info ); - $default_img_tag = ! empty( $import_featured_img ) ? '[#item_image]' : ''; + $default_img_tag = ! empty( $import_featured_img ) && is_string( $import_featured_img ) ? $import_featured_img : ''; if ( ! empty( $default_img_tag ) && 'attachment' !== $import_post_type ) { $image_source_url = ''; $img_success = true; @@ -1832,7 +1843,7 @@ function( $term ) { // Item image action. $import_featured_img = rawurldecode( $import_featured_img ); $import_featured_img = trim( $import_featured_img ); - $img_action = $this->handle_content_actions( $import_featured_img, 'item_image' ); + $img_action = $this->get_actions_runner( $import_featured_img, 'item_image' ); // Item image action process. $image_source_url = $img_action->run_action_job( $import_featured_img, $import_translation_lang, $job, $language_code, $item, $image_source_url ); @@ -1996,6 +2007,55 @@ private function get_file_type_by_url( $url ) { return $content_type; } + /** + * Will escape the provided URL and convert it to ASCII. + * + * @param string $url The URL to convert. + * + * @return string + */ + private function convert_url_to_ascii( $url ) { + $parts = wp_parse_url( $url ); + if ( empty( $parts ) ) { + return esc_url( $url ); + } + + $scheme = ''; + if ( isset( $parts['scheme'] ) ) { + $scheme = $parts['scheme'] . '://'; + } + + $host = ''; + if ( isset( $parts['scheme'] ) ) { + $host = idn_to_ascii( $parts['host'], IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46 ); + } + + $url = $scheme . $host; + if ( isset( $parts['port'] ) ) { + $url .= ':' . $parts['port']; + } + if ( isset( $parts['path'] ) ) { + $ascii_path = ''; + $path = $parts['path']; + $len = strlen( $path ); + for ( $i = 0; $i < $len; $i++ ) { + if ( preg_match( '/^[A-Za-z0-9\/?=+%_.~-]$/', $path[ $i ] ) ) { + $ascii_path .= $path[ $i ]; + } else { + $ascii_path .= rawurlencode( $path[ $i ] ); + } + } + $url .= $ascii_path; + } + if ( isset( $parts['query'] ) ) { + $url .= '?' . $parts['query']; + } + if ( isset( $parts['fragment'] ) ) { + $url .= '#' . $parts['fragment']; + } + return esc_url( $url ); + } + /** * Downloads and sets a post featured image if possible. * @@ -2018,8 +2078,10 @@ private function try_save_featured_image( $img_source_url, $post_id, $post_title if ( ! $id ) { - // We escape the URL to ensure that valid URLs are passed by the filter. - if ( filter_var( esc_url( $img_source_url ), FILTER_VALIDATE_URL ) === false ) { + // We escape the URL to ensure that valid URLs are passed by the filter. We also convert the URL parts to ASCII. + // This is necessary because FILTER_VALIDATE_URL only validates against ASCII URLs. + $escaped_url = $this->convert_url_to_ascii( $img_source_url ); + if ( filter_var( $escaped_url, FILTER_VALIDATE_URL ) === false ) { $import_errors[] = 'Invalid Featured Image URL: ' . $img_source_url; return false; } @@ -2349,8 +2411,8 @@ public function render_magic_tags( $default, $tags, $type ) { $disabled[ str_replace( ':disabled', '', $tag ) ] = $label; continue; } - if ( in_array( $type, array( 'import_post_content', 'import_post_featured_img' ), true ) ) { - if ( in_array( $tag, array( 'item_content', 'item_description', 'item_full_content', 'item_categories', 'item_image' ), true ) ) { + if ( in_array( $type, array( 'import_post_content', 'import_post_featured_img', 'import_post_title' ), true ) ) { + if ( in_array( $tag, array( 'item_content', 'item_description', 'item_full_content', 'item_categories', 'item_image', 'item_title' ), true ) ) { $default .= '' . $label . ' [#' . $tag . ']'; continue; } @@ -2956,16 +3018,16 @@ private function wizard_import_feed() { } /** - * Handle item content actions. + * Get the content action runner used for processing the chained actions from the tags. * * @param string $actions Item content actions. * @param string $type Action type. * @return Feedzy_Rss_Feeds_Actions Instance of Feedzy_Rss_Feeds_Actions. */ - public function handle_content_actions( $actions = '', $type = '' ) { + public function get_actions_runner( $actions = '', $type = '' ) { $action_instance = Feedzy_Rss_Feeds_Actions::instance(); $action_instance->type = $type; - $action_instance->set_actions( $actions ); + $action_instance->set_raw_serialized_actions( $actions ); $action_instance->set_settings( $this->settings ); return $action_instance; } diff --git a/includes/feedzy-rss-feeds.php b/includes/feedzy-rss-feeds.php index 31604e54..80428c91 100644 --- a/includes/feedzy-rss-feeds.php +++ b/includes/feedzy-rss-feeds.php @@ -104,7 +104,7 @@ public static function instance() { */ public function init() { self::$plugin_name = 'feedzy-rss-feeds'; - self::$version = '4.4.8'; + self::$version = '4.4.9'; self::$instance->load_dependencies(); self::$instance->set_locale(); self::$instance->define_admin_hooks(); diff --git a/includes/views/import-metabox-edit.php b/includes/views/import-metabox-edit.php index ecf69cc4..60218942 100644 --- a/includes/views/import-metabox-edit.php +++ b/includes/views/import-metabox-edit.php @@ -389,7 +389,7 @@ class="dashicons dashicons-arrow-down-alt2"> ?> -