From 3b2a953ffba9f389ba2c0b39f6f6eec446821c5e Mon Sep 17 00:00:00 2001 From: RaduCristianPopescu <119046336+RaduCristianPopescu@users.noreply.github.com> Date: Mon, 4 Aug 2025 16:40:58 +0300 Subject: [PATCH 01/57] fix: remove all other notices on plugin pages (#1100) - Remove all notices on the page except `fz-notice` and `themeisle-sale` (Black Friday) - Previously, the CSS page was loaded on all the pages! Now it is restricted to our plugin pages. --- css/feedzy-rss-feed-import.css | 70 +++++++++++---- includes/admin/feedzy-rss-feeds-admin.php | 10 +-- includes/admin/feedzy-rss-feeds-import.php | 90 +++++++++++++------- includes/feedzy-rss-feeds-limited-offers.php | 4 +- includes/views/js/import-metabox-edit.js | 2 +- 5 files changed, 119 insertions(+), 57 deletions(-) diff --git a/css/feedzy-rss-feed-import.css b/css/feedzy-rss-feed-import.css index d2c83da18..75daee626 100644 --- a/css/feedzy-rss-feed-import.css +++ b/css/feedzy-rss-feed-import.css @@ -8,7 +8,7 @@ * Author: Themeisle */ -#feedzy_import_feeds{ +#feedzy_import_feeds { background: transparent; border: 0; box-shadow: none; @@ -18,49 +18,62 @@ padding-right: 12px; box-sizing: border-box; } -#feedzy_import_feeds > .postbox-header{ + +#feedzy_import_feeds>.postbox-header { display: none; } -.post-type-feedzy_imports:not(.edit-php) .page-title-action, .post-type-feedzy_imports:not(.edit-php) .wp-heading-inline { + +.post-type-feedzy_imports:not(.edit-php) .page-title-action, +.post-type-feedzy_imports:not(.edit-php) .wp-heading-inline { display: none !important; } -.post-type-feedzy_imports:not(.edit-php) #wpcontent{ + +.post-type-feedzy_imports:not(.edit-php) #wpcontent { padding-left: 0; } -.post-type-feedzy_imports:not(.edit-php) #post-body-content{ + +.post-type-feedzy_imports:not(.edit-php) #post-body-content { display: none; } -.post-type-feedzy_imports:not(.edit-php) .wrap{ + +.post-type-feedzy_imports:not(.edit-php) .wrap { margin-top: 0; margin-right: 0; } -.post-type-feedzy_imports:not(.edit-php) .wrap > .wp-heading-inline, -.post-type-feedzy_imports:not(.edit-php) .wrap > .wp-header-end{ + +.post-type-feedzy_imports:not(.edit-php) .wrap>.wp-heading-inline, +.post-type-feedzy_imports:not(.edit-php) .wrap>.wp-header-end { display: none; } -.post-type-feedzy_imports:not(.edit-php) #poststuff{ + +.post-type-feedzy_imports:not(.edit-php) #poststuff { padding-top: 0; } -.post-type-feedzy_imports:not(.edit-php) #poststuff .inside{ + +.post-type-feedzy_imports:not(.edit-php) #poststuff .inside { margin-top: 0; padding-left: 0; padding-right: 0; } -.post-type-feedzy_imports:not(.edit-php) #poststuff #post-body.columns-2{ + +.post-type-feedzy_imports:not(.edit-php) #poststuff #post-body.columns-2 { margin-right: 0; } -.post-type-feedzy_imports:not(.edit-php) #post-body.columns-2 #postbox-container-1{ + +.post-type-feedzy_imports:not(.edit-php) #post-body.columns-2 #postbox-container-1 { width: 100%; float: none; } + .feedzy_page_feedzy-settings #wpcontent, .feedzy_page_feedzy-support #wpcontent, -.feedzy_page_feedzy-integration #wpcontent{ +.feedzy_page_feedzy-integration #wpcontent { padding-left: 0; } + .feedzy_page_feedzy-settings .feedzy-header, .feedzy_page_feedzy-support .feedzy-header, -.feedzy_page_feedzy-integration .feedzy-header{ +.feedzy_page_feedzy-integration .feedzy-header { margin-bottom: 40px; } @@ -68,7 +81,7 @@ color: #ff0000; } -tr.feedzy-import-status-row > td { +tr.feedzy-import-status-row>td { margin: 0; padding: 0 } @@ -140,47 +153,60 @@ td.feedzy-has-popup { .feedzy-onboarding-modal { max-width: 350px; } + .feedzy-onboarding-modal .components-modal__header { text-align: center; } + .feedzy-onboarding-modal .components-modal__header h1 { width: 100%; } + .feedzy-onboarding-modal .feedzy-onboarding-modal-content { text-align: center; margin: 10px 0 15px; } + .feedzy-onboarding-modal .feedzy-onboarding-modal-action { text-align: center; margin: 10px 0; } + .feedzy-onboarding-modal .feedzy-onboarding-modal-action .components-button { padding: 2px 20px; font-size: 14px; margin-right: 10px; } + .components-modal__screen-overlay { background-color: rgba(0, 0, 0, 0.5); } + .react-joyride__tooltip { font-size: 13px !important; } -.react-joyride__tooltip > div { + +.react-joyride__tooltip>div { text-align: left !important; } -.react-joyride__tooltip div:nth-of-type( 2) { + +.react-joyride__tooltip div:nth-of-type(2) { margin-top: 5px !important; } + .react-joyride__tooltip button { text-decoration: underline; } + .react-joyride__beacon span:first-child { background-color: #4268CF !important; } + .react-joyride__beacon span:last-child { background-color: rgba(66, 104, 207, 0.4) !important; border: 2px solid #4268CF !important; } + .react-joyride__tooltip button[data-action="primary"] { background: #4268cf !important; border-color: #006a95 #00648c #00648c !important; @@ -193,20 +219,25 @@ td.feedzy-has-popup { line-height: 28px !important; font-size: 14px !important; } + .react-joyride__tooltip button[data-action="back"] { color: #23282d !important; font-size: 14px !important; } + .react-notification-root .notification-container-bottom-left { bottom: 40px; } + .react-notification-root .notification-container-mobile-bottom { bottom: 60px; } + .feedzy-onboarding-modal-action .components-button.is-primary, .feedzy-onboarding-modal-action .components-button.is-primary:hover:not(:disabled) { background: #4268cf; } + .feedzy-onboarding-modal-action .components-button.is-secondary, .feedzy-onboarding-modal-action .components-button.is-secondary:hover:not(:disabled), .feedzy-onboarding-modal-action .components-button.is-tertiary:hover:not(:disabled) { @@ -279,6 +310,11 @@ i.mce-i-feedzy-icon { text-align: center; } +/* NOTE: It will also exclude the Black Friday notice. */ +.notice:not(.fz-notice):not(.themeisle-sale) { + display: none; +} + @media screen and (max-width: 782px) { tr.feedzy-import-status-row table { width: 100%; diff --git a/includes/admin/feedzy-rss-feeds-admin.php b/includes/admin/feedzy-rss-feeds-admin.php index dcf455803..337d7dd6b 100644 --- a/includes/admin/feedzy-rss-feeds-admin.php +++ b/includes/admin/feedzy-rss-feeds-admin.php @@ -426,7 +426,7 @@ function closeModal(e) { From faffb58a750388a312b2f8b9e50736e702d35ab9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 04:31:21 +0000 Subject: [PATCH 31/57] chore(deps): bump codeinwp/themeisle-sdk from 3.3.47 to 3.3.48 Bumps [codeinwp/themeisle-sdk](https://github.com/Codeinwp/themeisle-sdk) from 3.3.47 to 3.3.48. - [Release notes](https://github.com/Codeinwp/themeisle-sdk/releases) - [Changelog](https://github.com/Codeinwp/themeisle-sdk/blob/v3.3.48/CHANGELOG.md) - [Commits](https://github.com/Codeinwp/themeisle-sdk/compare/v3.3.47...v3.3.48) --- updated-dependencies: - dependency-name: codeinwp/themeisle-sdk dependency-version: 3.3.48 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 33b3ec27e..a855ba171 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.47", + "version": "3.3.48", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "59499ba103c0369c98b9e0d878826939e3e8e408" + "reference": "0727d2cf2fc9bfb81b42968aeaf2bf4e340f021e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/59499ba103c0369c98b9e0d878826939e3e8e408", - "reference": "59499ba103c0369c98b9e0d878826939e3e8e408", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/0727d2cf2fc9bfb81b42968aeaf2bf4e340f021e", + "reference": "0727d2cf2fc9bfb81b42968aeaf2bf4e340f021e", "shasum": "" }, "require-dev": { @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.47" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.48" }, - "time": "2025-07-21T14:06:29+00:00" + "time": "2025-08-11T16:47:24+00:00" } ], "packages-dev": [ From 17214f900dd10f26e60f8fc9e600f0154eefca32 Mon Sep 17 00:00:00 2001 From: Soare Robert Daniel Date: Mon, 18 Aug 2025 12:26:13 +0300 Subject: [PATCH 32/57] feat: add errors report email frequency (#1142) --- includes/admin/feedzy-rss-feeds-admin.php | 2 +- includes/admin/feedzy-rss-feeds-import.php | 7 +- includes/admin/feedzy-rss-feeds-log.php | 41 +++++++++- .../admin/feedzy-rss-feeds-task-manager.php | 79 +++++++++++++++---- includes/layouts/feedzy-email-report.php | 27 ++++++- includes/layouts/settings.php | 5 +- includes/views/misc-view.php | 41 ++++++++++ tests/e2e/specs/logger.spec.js | 4 +- 8 files changed, 175 insertions(+), 31 deletions(-) diff --git a/includes/admin/feedzy-rss-feeds-admin.php b/includes/admin/feedzy-rss-feeds-admin.php index d237d05ce..f943e3c57 100644 --- a/includes/admin/feedzy-rss-feeds-admin.php +++ b/includes/admin/feedzy-rss-feeds-admin.php @@ -1411,8 +1411,8 @@ private function save_settings() { return; } $post_tab = isset( $_POST['tab'] ) ? sanitize_text_field( wp_unslash( $_POST['tab'] ) ) : ''; - $settings = apply_filters( 'feedzy_get_settings', array() ); + switch ( $post_tab ) { case 'general': $auto_categories_raw = array(); diff --git a/includes/admin/feedzy-rss-feeds-import.php b/includes/admin/feedzy-rss-feeds-import.php index b1609ff8f..9f3111a72 100644 --- a/includes/admin/feedzy-rss-feeds-import.php +++ b/includes/admin/feedzy-rss-feeds-import.php @@ -3248,14 +3248,15 @@ public function integration_tabs( $tabs ) { public function save_tab_settings( $settings, $tab ) { if ( ! isset( $_POST['nonce'] ) || - ! wp_verify_nonce( sanitize_text_field( wp_unslash( 'nonce' ) ), $tab ) + ! wp_verify_nonce( sanitize_text_field( $_POST['nonce'] ), $tab ) ) { return array(); } if ( 'misc' === $tab ) { - $settings['canonical'] = isset( $_POST['canonical'] ) ? absint( $_POST['canonical'] ) : 0; - $settings['general']['rss-feeds'] = isset( $_POST['rss-feeds'] ) ? absint( $_POST['rss-feeds'] ) : ''; + $settings['canonical'] = isset( $_POST['canonical'] ) ? absint( $_POST['canonical'] ) : 0; + $settings['general']['rss-feeds'] = isset( $_POST['rss-feeds'] ) ? absint( $_POST['rss-feeds'] ) : ''; + $settings['logs']['email_frequency'] = isset( $_POST['logs-email-frequency'] ) ? sanitize_text_field( wp_unslash( $_POST['logs-email-frequency'] ) ) : ''; } return $settings; diff --git a/includes/admin/feedzy-rss-feeds-log.php b/includes/admin/feedzy-rss-feeds-log.php index 1d4990281..6c0a35c37 100644 --- a/includes/admin/feedzy-rss-feeds-log.php +++ b/includes/admin/feedzy-rss-feeds-log.php @@ -16,6 +16,7 @@ class Feedzy_Rss_Feeds_Log { /** * Option key for storing log statistics. * + * @since 5.1.0 * @var string Option key for storing log statistics. */ const STATS_OPTION_KEY = 'feedzy_log_stats'; @@ -23,6 +24,7 @@ class Feedzy_Rss_Feeds_Log { /** * Debug level. * + * @since 5.1.0 * @var int Debug level. */ const DEBUG = 100; @@ -30,6 +32,7 @@ class Feedzy_Rss_Feeds_Log { /** * Info level. * + * @since 5.1.0 * @var int Info level. */ const INFO = 200; @@ -37,6 +40,7 @@ class Feedzy_Rss_Feeds_Log { /** * Warning level. * + * @since 5.1.0 * @var int Warning level. */ const WARNING = 300; @@ -44,6 +48,7 @@ class Feedzy_Rss_Feeds_Log { /** * Error level. * + * @since 5.1.0 * @var int Error level. */ const ERROR = 400; @@ -51,6 +56,7 @@ class Feedzy_Rss_Feeds_Log { /** * Ignore level. * + * @since 5.1.0 * @var int Ignore level. */ const NONE = 500; @@ -58,6 +64,7 @@ class Feedzy_Rss_Feeds_Log { /** * Log file name. * + * @since 5.1.0 * @var string Default log name. */ const FILE_NAME = 'feedzy'; @@ -65,6 +72,7 @@ class Feedzy_Rss_Feeds_Log { /** * Log file extension. * + * @since 5.1.0 * @var string Log file extension. */ const FILE_EXT = '.jsonl'; @@ -72,6 +80,7 @@ class Feedzy_Rss_Feeds_Log { /** * Default max file size. * + * @since 5.1.0 * @var int Default max file size in bytes (50MB). */ const DEFAULT_MAX_FILE_SIZE = 50 * 1024 * 1024; @@ -79,6 +88,7 @@ class Feedzy_Rss_Feeds_Log { /** * Default max files. * + * @since 5.1.0 * @var int Default max number of log files. */ const DEFAULT_MAX_FILES = 3; @@ -86,6 +96,7 @@ class Feedzy_Rss_Feeds_Log { /** * Log levels. * + * @since 5.1.0 * @var array Log levels. */ private static $levels = array( @@ -95,6 +106,12 @@ class Feedzy_Rss_Feeds_Log { self::ERROR => 'error', ); + /** + * Priorities mapping. + * + * @since 5.1.0 + * @var array Priorities mapping. + */ const PRIORITIES_MAPPING = array( 'debug' => self::DEBUG, 'info' => self::INFO, @@ -106,6 +123,7 @@ class Feedzy_Rss_Feeds_Log { /** * The single instance of the class. * + * @since 5.1.0 * @var ?self The single instance of the class. */ private static $instance = null; @@ -113,6 +131,7 @@ class Feedzy_Rss_Feeds_Log { /** * The path to the log file. * + * @since 5.1.0 * @var string The path to the log file. */ private $filepath; @@ -120,6 +139,7 @@ class Feedzy_Rss_Feeds_Log { /** * The WordPress filesystem instance. * + * @since 5.1.0 * @var \WP_Filesystem_Base|null The WordPress filesystem instance. */ private $filesystem; @@ -127,6 +147,7 @@ class Feedzy_Rss_Feeds_Log { /** * The context for the logger. * + * @since 5.1.0 * @var array The context for the logger. */ private $context = array(); @@ -134,6 +155,7 @@ class Feedzy_Rss_Feeds_Log { /** * The minimum log level threshold for logging messages. * + * @since 5.1.0 * @var int The minimum log level threshold. */ public $level_threshold = self::ERROR; @@ -141,6 +163,7 @@ class Feedzy_Rss_Feeds_Log { /** * Whether to retain error messages for import run errors meta. * + * @since 5.1.0 * @var string[] */ private $error_messages_accumulator = array(); @@ -148,6 +171,7 @@ class Feedzy_Rss_Feeds_Log { /** * Whether to retain error messages for import run errors meta. * + * @since 5.1.0 * @var bool Whether to retain error messages. */ private $retain_error_messages = false; @@ -155,13 +179,23 @@ class Feedzy_Rss_Feeds_Log { /** * Whether email reports can be sent. * + * @since 5.1.0 * @var bool Whether email reports can be sent. */ public $can_send_email = false; + /** + * The email frequency for sending reports. + * + * @since 5.1.0 + * @var string + */ + public $email_frequency = 'weekly'; + /** * The email address to send reports to. * + * @since 5.1.0 * @var string The email address to send reports to. */ public $to_email = ''; @@ -233,7 +267,7 @@ private function setup_log_directory() { * @return void */ private function init_saved_settings() { - $feedzy_settings = get_option( 'feedzy-settings', array() ); + $feedzy_settings = apply_filters( 'feedzy_get_settings', array() ); if ( ! isset( $feedzy_settings['logs'] ) ) { return; } @@ -242,8 +276,9 @@ private function init_saved_settings() { $this->level_threshold = self::PRIORITIES_MAPPING[ $feedzy_settings['logs']['level'] ]; } - $this->can_send_email = isset( $feedzy_settings['logs']['send_email_report'] ) && $feedzy_settings['logs']['send_email_report']; - $this->to_email = isset( $feedzy_settings['logs']['email'] ) ? sanitize_email( $feedzy_settings['logs']['email'] ) : ''; + $this->can_send_email = isset( $feedzy_settings['logs']['send_email_report'] ) && $feedzy_settings['logs']['send_email_report']; + $this->to_email = isset( $feedzy_settings['logs']['email'] ) ? sanitize_email( $feedzy_settings['logs']['email'] ) : ''; + $this->email_frequency = isset( $feedzy_settings['logs']['email_frequency'] ) ? sanitize_text_field( $feedzy_settings['logs']['email_frequency'] ) : 'weekly'; } /** diff --git a/includes/admin/feedzy-rss-feeds-task-manager.php b/includes/admin/feedzy-rss-feeds-task-manager.php index 482b91dbb..a6843684c 100644 --- a/includes/admin/feedzy-rss-feeds-task-manager.php +++ b/includes/admin/feedzy-rss-feeds-task-manager.php @@ -3,11 +3,12 @@ * Register and manage scheduled tasks for Feedzy RSS Feeds. * * @package Feedzy_Rss_Feeds_Task_Manager - * @version 5.1.0 */ /** * Class Feedzy_Rss_Feeds_Task_Manager. + * + * @since 5.1.0 */ class Feedzy_Rss_Feeds_Task_Manager { @@ -22,19 +23,23 @@ public function register_actions() { 'task_feedzy_send_error_report', array( $this, 'send_error_report' ) ); + + add_action( + 'update_option_feedzy-settings', + array( $this, 'maybe_reschedule_email_report' ), + 10, + 2 + ); add_action( 'task_feedzy_cleanup_logs', - function () { - Feedzy_Rss_Feeds_Log::get_instance()->should_clean_logs(); - } + array( $this, 'check_and_clean_logs' ) ); add_action( 'init', function () { - $this->schedule_weekly_tasks(); - $this->schedule_hourly_tasks(); + $this->schedule_tasks(); } ); } @@ -45,7 +50,22 @@ function () { * @since 5.1.0 * @return void */ - public function schedule_weekly_tasks() { + public function schedule_tasks() { + if ( + false === Feedzy_Rss_Feeds_Util_Scheduler::is_scheduled( 'task_feedzy_cleanup_logs' ) + ) { + Feedzy_Rss_Feeds_Util_Scheduler::schedule_event( time(), 'hourly', 'task_feedzy_cleanup_logs' ); + } + $this->schedule_email_report(); + } + + /** + * Schedule daily tasks. + * + * @since 5.1.0 + * @return void + */ + public function schedule_email_report() { $log_instance = Feedzy_Rss_Feeds_Log::get_instance(); if ( @@ -54,24 +74,53 @@ public function schedule_weekly_tasks() { ) { return; } - - Feedzy_Rss_Feeds_Util_Scheduler::schedule_event( time(), 'weekly', 'task_feedzy_send_error_report' ); + + $frequency = $log_instance->email_frequency; + if ( ! in_array( $frequency, array( 'daily', 'weekly' ), true ) ) { + $frequency = 'weekly'; + } + + Feedzy_Rss_Feeds_Util_Scheduler::schedule_event( time(), $frequency, 'task_feedzy_send_error_report' ); } /** - * Schedule daily tasks. + * When feedzy settings are updated, ensure the error report schedule matches the new frequency. * * @since 5.1.0 + * @param array $old_value Previous option value. + * @param array $value New option value. * @return void */ - public function schedule_hourly_tasks() { - if ( - false !== Feedzy_Rss_Feeds_Util_Scheduler::is_scheduled( 'task_feedzy_cleanup_logs' ) - ) { + public function maybe_reschedule_email_report( $old_value, $value ) { + $old_freq = isset( $old_value['logs']['email_frequency'] ) ? sanitize_text_field( $old_value['logs']['email_frequency'] ) : ''; + $new_freq = isset( $value['logs']['email_frequency'] ) ? sanitize_text_field( $value['logs']['email_frequency'] ) : ''; + + if ( $old_freq === $new_freq ) { return; } + + Feedzy_Rss_Feeds_Util_Scheduler::clear_scheduled_hook( 'task_feedzy_send_error_report' ); + + if ( ! in_array( $new_freq, array( 'daily', 'weekly' ), true ) ) { + $new_freq = 'weekly'; + } + + $send_reports = ! empty( $value['logs']['send_email_report'] ); + $to_email = isset( $value['logs']['email'] ) ? sanitize_email( $value['logs']['email'] ) : ''; + + if ( $send_reports && ! empty( $to_email ) ) { + Feedzy_Rss_Feeds_Util_Scheduler::schedule_event( time(), $new_freq, 'task_feedzy_send_error_report' ); + } + } - Feedzy_Rss_Feeds_Util_Scheduler::schedule_event( time(), 'hourly', 'task_feedzy_cleanup_logs' ); + /** + * Check and clean logs if necessary. + * + * @since 5.1.0 + * @return void + */ + public function check_and_clean_logs() { + Feedzy_Rss_Feeds_Log::get_instance()->should_clean_logs(); } /** diff --git a/includes/layouts/feedzy-email-report.php b/includes/layouts/feedzy-email-report.php index e32a6e34c..b77d2b249 100644 --- a/includes/layouts/feedzy-email-report.php +++ b/includes/layouts/feedzy-email-report.php @@ -11,10 +11,17 @@ } // Extract variables that should be available -$site_name = isset( $site_name ) ? $site_name : get_bloginfo( 'name' ); -$generated_date = isset( $generated_date ) ? $generated_date : date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ); -$stats = isset( $stats ) ? $stats : array(); -$logs_entries = isset( $logs_entries ) ? $logs_entries : array(); +$site_name = isset( $site_name ) ? $site_name : get_bloginfo( 'name' ); +$generated_date = isset( $generated_date ) ? $generated_date : date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ); +$stats = isset( $stats ) ? $stats : array(); +$logs_entries = isset( $logs_entries ) ? $logs_entries : array(); +$see_all_logs_link = add_query_arg( + array( + 'page' => 'feedzy-settings', + 'tab' => 'logs', + ), + admin_url( 'admin.php' ) +); ?> @@ -29,6 +36,9 @@ .timestamp { color: #6c757d; font-size: 0.9em; } .level { font-weight: bold; color: #dc3545; } .context { color: #6c757d; font-style: italic; font-size: 0.9em; margin-top: 5px; } + .logs-link { text-align: center; margin-top: 20px; } + .logs-link a { color: #0073aa; text-decoration: none; font-weight: bold; } + .logs-link a:hover { text-decoration: underline; } @@ -72,6 +82,7 @@

+
@@ -89,5 +100,13 @@ + + diff --git a/includes/layouts/settings.php b/includes/layouts/settings.php index 5eca1c189..e59bf576c 100644 --- a/includes/layouts/settings.php +++ b/includes/layouts/settings.php @@ -374,10 +374,11 @@ class="fz-switch-toggle" class="form-label" > - ()
- +
+ +
+
+
+
+
+ + free_settings['logs'], $this->free_settings['logs']['email_frequency'] ) ? $this->free_settings['logs']['email_frequency'] : ''; + + $registered_schedules = wp_get_schedules(); + $schedules = array(); + + if ( isset( $registered_schedules['weekly'] ) ) { + $schedules['weekly'] = $registered_schedules['weekly']; + } + + if ( isset( $registered_schedules['daily'] ) ) { + $schedules['daily'] = $registered_schedules['daily']; + } + + ?> + +
+
+
+
diff --git a/tests/e2e/specs/logger.spec.js b/tests/e2e/specs/logger.spec.js index 6d6f14eb7..1eb7e97b0 100644 --- a/tests/e2e/specs/logger.spec.js +++ b/tests/e2e/specs/logger.spec.js @@ -18,9 +18,7 @@ test.describe('Logger', () => { page.locator('select[name="logs-logging-level"]') ).toBeVisible(); - await expect( - page.getByText('Report errors via email (Once') - ).toBeVisible(); + await expect(page.getByText('Report errors via email')).toBeVisible(); }); test('check logs tabs', async ({ page, admin }) => { From ef3990024d9cbba1da68d9fb46b2ccf8701c344d Mon Sep 17 00:00:00 2001 From: Girish Panchal <79647963+girishpanchal30@users.noreply.github.com> Date: Mon, 18 Aug 2025 15:23:13 +0530 Subject: [PATCH 33/57] refactor: UI changes for Import Settings (#1118) --- css/metabox-settings.css | 1 + css/settings.css | 70 ++++++++- includes/admin/feedzy-rss-feeds-import.php | 6 + includes/views/import-metabox-edit.php | 160 +++++++++------------ includes/views/js/import-metabox-edit.js | 4 +- js/ActionPopup/index.js | 4 +- tests/e2e/utils.js | 2 +- 7 files changed, 146 insertions(+), 101 deletions(-) diff --git a/css/metabox-settings.css b/css/metabox-settings.css index fae7c4c5b..8701a53cb 100644 --- a/css/metabox-settings.css +++ b/css/metabox-settings.css @@ -42,6 +42,7 @@ .fz-input-group{flex-wrap: wrap;} .fz-input-group .fz-input-group-right{width: 100%; padding-left: 0; padding-top: 8px;} .fz-input-group .fz-input-group-right .btn.dropdown-toggle{width: 100%;} + .fz-input-group .fz-insert-tags{width: auto; padding: 0;} .fz-form-wrap .form-block.form-block-two-column .fz-left{width: 100%; padding-right: 0; padding-bottom: 24px;} .fz-form-wrap .form-block.form-block-two-column .fz-right{width: 100%;} .fz-form-action .fz-left{width: 100%; padding-bottom: 16px;} diff --git a/css/settings.css b/css/settings.css index c056ccf18..701904e5d 100644 --- a/css/settings.css +++ b/css/settings.css @@ -93,6 +93,9 @@ .mx-320{ max-width: 320px; } +.position-relative{position: relative;} +.position-absolute{position: absolute;} + .feedzy-wrap a:not(#fz-feedback-btn):focus{ box-shadow: none; outline: 0; @@ -140,12 +143,43 @@ } /* feedzy accordion style start */ +.feedzy-accordion-item :is( + .feedzy-accordion__step-title, + .btn, + .cta-text a, + .dashicons, + .h1, .h2, .h3, .h4, .h5, .h6, + .fz-panel-tab__header__label, + .fz-tabs-menu ul li a, + .fz-form-wrap :is( + .form-label, + .form-control, + .form-control .tagify__tag-text, + .chosen-container :is( + .chosen-single, + .chosen-choices + ), + .chosen-container-multi .chosen-choices :is( + li.search-choice, + li.search-field input[type="text"] + ) + ), + .fz-condition-control :is( + .components-input-control__label, + .components-select-control__input, + input, + button + ) +) { + font-size: 15px; +} .feedzy-accordion-item{ background: #ffffff; border: 1px solid #D9D9D9; } .feedzy-accordion-item__title{ position: relative; + padding: 8px 15px; } .fdz-upgrade-link{ width: 100%; @@ -169,34 +203,38 @@ border: 0; background: transparent; outline: 0; - padding: 30px 90px 30px 30px; + padding: 10px; } .feedzy-accordion-item .feedzy-accordion__step-number{ padding-bottom: 10px; } .feedzy-accordion-item .feedzy-accordion__step-title{ color: #050505; + line-height: 1.3; } .feedzy-accordion-item .feedzy-accordion__icon{ position: absolute; right: 30px; top: 50%; - margin-top: -24px; - width: 48px; - height: 48px; + margin-top: -15px; + width: 30px; + height: 30px; background: #F0F2F5; border-radius: 24px; display: flex; align-items: center; justify-content: center; font-size: 20px; - line-height: 1; + line-height: 1.5; color: #757575; -webkit-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out; } +.feedzy-accordion-item .feedzy-accordion__icon .dashicons{ + line-height: 1.5; +} .feedzy-accordion-item .feedzy-accordion__icon.feedzy-accordion__icon--success{ width: 24px; height: 24px; @@ -235,6 +273,8 @@ -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out; + font-size: 10px; + margin-right: 5px; } .feedzy-accordion .feedzy-accordion-item .is-active .feedzy-accordion__step-number{ opacity: 1; @@ -299,6 +339,9 @@ height: auto; min-height: 48px; padding-top: 2px; + line-height: 0; + display: flex; + align-items: center; } .fz-form-wrap .form-control:focus { @@ -379,6 +422,16 @@ fieldset[disabled] .form-control { .fz-input-group .fz-input-group-right .dropdown-toggle{ min-width: 150px; } +.fz-input-group .fz-insert-tags{ + top: 7px; + right: 10px; +} +.fz-input-group .fz-insert-tags button.dropdown-toggle { + padding: 5px 20px; + border: 2px solid; + border-radius: 5px; + min-width: auto; +} .fz-input-group .help-text{ padding-top: 8px; } @@ -2643,6 +2696,9 @@ li.draggable-item .components-panel__body-toggle.components-button{ font-weight: 500; color: #757575; } +span.dashicons { + line-height: 1.3; +} @-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } } @@ -2888,3 +2944,7 @@ button.feedzy-action-button { gap: 1rem; margin-bottom: 20px; } + +#feedzy-validate-feed .dashicons { + font-size: 20px; +} \ No newline at end of file diff --git a/includes/admin/feedzy-rss-feeds-import.php b/includes/admin/feedzy-rss-feeds-import.php index 9f3111a72..28c305c31 100644 --- a/includes/admin/feedzy-rss-feeds-import.php +++ b/includes/admin/feedzy-rss-feeds-import.php @@ -543,6 +543,12 @@ public function feedzy_import_feed_options() { $import_link_author[1] = 'checked'; } + // default values when creating a import. + if ( 'post-new.php' === $pagenow ) { + $import_date = '[#item_date]'; + $import_featured_img = '[[{"value":"%5B%7B%22id%22%3A%22%22%2C%22tag%22%3A%22item_image%22%2C%22data%22%3A%7B%7D%7D%5D"}]]'; + } + // maybe more options are required from pro? $pro_options = apply_filters( 'feedzy_metabox_options', array(), $post->ID ); diff --git a/includes/views/import-metabox-edit.php b/includes/views/import-metabox-edit.php index d9e78c39e..4573ea279 100644 --- a/includes/views/import-metabox-edit.php +++ b/includes/views/import-metabox-edit.php @@ -120,7 +120,7 @@ class="dashicons dashicons-arrow-down-alt2">
- + @@ -135,13 +135,6 @@ class="dashicons dashicons-arrow-down-alt2">
-
-

- -

-
  • @@ -161,7 +154,6 @@ class="dashicons dashicons-arrow-down-alt2">
-
-
-
+
+
+ +
-
-
- +
+ +
@@ -301,15 +292,23 @@ class="dashicons dashicons-arrow-down-alt2">
-
-
+
+
+ +
?>
-
- -
@@ -341,15 +329,24 @@ class="dashicons dashicons-arrow-down-alt2">
-
-
- + class="form-control fz-input-tagify" + value="" /> +
+ +
-
- -
@@ -393,15 +379,23 @@ class="form-control fz-textarea-tagify">
-
-
+
+
+ +
-
- -
@@ -454,7 +437,6 @@ class="fz-switch-toggle" type="checkbox" value="yes"
-
+
+ +
-
- -
@@ -557,9 +536,6 @@ class="fz-switch-toggle" type="checkbox" value="yes"
-
{ return document.querySelector('.fz-action-popup .fz-action-panel ul'); }; + const actionButtonTitle = action.length ? __( 'Save Actions', 'feedzy-rss-feeds' ) : __( 'Skip Actions', 'feedzy-rss-feeds' ); + // Click to open action popup. document.querySelectorAll('[data-action_popup]').forEach((actionItem) => { actionItem.addEventListener('click', (event) => { @@ -792,7 +794,7 @@ const ActionModal = () => { saveAction(); }} > - {__('Save Actions', 'feedzy-rss-feeds')} + { actionButtonTitle } )}
diff --git a/tests/e2e/utils.js b/tests/e2e/utils.js index f40bc462d..1ed2ef8dd 100644 --- a/tests/e2e/utils.js +++ b/tests/e2e/utils.js @@ -61,7 +61,7 @@ export async function addFeaturedImage(page, feedTag) { export async function addContentMapping(page, mapping) { await page.evaluate((mapping) => { document.querySelector( - 'textarea[name="feedzy_meta_data[import_post_content]"]' + 'input[name="feedzy_meta_data[import_post_content]"]' ).value = mapping; }, mapping); } From 38d0231398f0bbcab221607548cdaaace30d2e57 Mon Sep 17 00:00:00 2001 From: RaduCristianPopescu <119046336+RaduCristianPopescu@users.noreply.github.com> Date: Mon, 18 Aug 2025 13:26:15 +0300 Subject: [PATCH 34/57] feat: add custom cron schedules workflow (#1144) --- css/settings.css | 34 +++++- includes/admin/feedzy-rss-feeds-admin.php | 55 +++++++++ includes/feedzy-rss-feeds.php | 1 + includes/layouts/feedzy-schedules.php | 141 ++++++++++++++++++++++ includes/layouts/settings.php | 16 ++- js/feedzy-setting.js | 125 +++++++++++++++++++ 6 files changed, 370 insertions(+), 2 deletions(-) create mode 100644 includes/layouts/feedzy-schedules.php diff --git a/css/settings.css b/css/settings.css index 701904e5d..84af6c2b3 100644 --- a/css/settings.css +++ b/css/settings.css @@ -2947,4 +2947,36 @@ button.feedzy-action-button { #feedzy-validate-feed .dashicons { font-size: 20px; -} \ No newline at end of file +} + +.fz-schedule-counter { + text-align: right; + margin-bottom: 10px; + color: #757575; + font-size: 13px; +} + +.fz-schedules-table { + border-collapse: collapse; +} + +.fz-schedules-table th { + font-weight: 600; + background: #f6f7f7; +} + +.fz-schedules-table td { + padding: 10px; +} + +.fz-schedule-attributes { + color: #050505; +} + +.fz-delete-schedule.fz-is-destructive { + background: #dc3545; + border-color: #dc3545; + color: #ffffff; + font-size: 12px; + padding: 4px 12px; +} diff --git a/includes/admin/feedzy-rss-feeds-admin.php b/includes/admin/feedzy-rss-feeds-admin.php index f943e3c57..9b87e51ac 100644 --- a/includes/admin/feedzy-rss-feeds-admin.php +++ b/includes/admin/feedzy-rss-feeds-admin.php @@ -231,6 +231,7 @@ public function enqueue_styles_admin() { 'media_iframe_button' => __( 'Set default image', 'feedzy-rss-feeds' ), 'action_btn_text_1' => __( 'Choose image', 'feedzy-rss-feeds' ), 'action_btn_text_2' => __( 'Replace image', 'feedzy-rss-feeds' ), + 'delete_btn_label' => __( 'Delete', 'feedzy-rss-feeds' ), ), ) ); @@ -1464,6 +1465,31 @@ function ( $item ) { 'pass' => isset( $_POST['proxy-pass'] ) ? sanitize_text_field( wp_unslash( $_POST['proxy-pass'] ) ) : '', ); break; + case 'schedules': + if ( feedzy_is_pro() ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $custom_schedules = isset( $_POST['fz-custom-schedule-interval'] ) ? (array) wp_unslash( $_POST['fz-custom-schedule-interval'] ) : array(); + $settings['custom_schedules'] = array(); + if ( ! empty( $custom_schedules ) ) { + $cron_timout = defined( 'WP_CRON_LOCK_TIMEOUT' ) ? WP_CRON_LOCK_TIMEOUT : 60; + + foreach ( $custom_schedules as $key => $value ) { + $interval = isset( $value['interval'] ) ? absint( $value['interval'] ) : $cron_timout; + $display = isset( $value['display'] ) ? sanitize_text_field( $value['display'] ) : ''; + + if ( + is_numeric( $interval ) && $cron_timout <= $interval && + ! empty( $display ) + ) { + $settings['custom_schedules'][ $key ] = array( + 'interval' => $interval, + 'display' => $display, + ); + } + } + } + } + break; default: $settings = apply_filters( 'feedzy_save_tab_settings', $settings, $post_tab ); } @@ -3133,6 +3159,35 @@ public function validate_feed() { } } + /** + * Append custom schedules to the existing schedules. + * + * @since 5.1.0 + * @param array $schedules Existing schedules. + * @return array Modified schedules with custom schedules appended. + */ + public function append_custom_cron_schedules( $schedules ) { + if ( ! feedzy_is_pro() ) { + return $schedules; + } + + $saved_settings = apply_filters( 'feedzy_get_settings', array() ); + if ( ! empty( $saved_settings['custom_schedules'] ) && is_array( $saved_settings['custom_schedules'] ) ) { + $custom_schedules = $saved_settings['custom_schedules']; + + foreach ( $custom_schedules as $key => $value ) { + if ( ! empty( $value['interval'] ) && ! empty( $value['display'] ) ) { + $schedules[ $key ] = array( + 'interval' => intval( $value['interval'] ), + 'display' => sanitize_text_field( $value['display'] ), + ); + } + } + } + + return $schedules; + } + /** * Add slugs for internal cron schedules. * diff --git a/includes/feedzy-rss-feeds.php b/includes/feedzy-rss-feeds.php index 028bd85cb..f7232a981 100644 --- a/includes/feedzy-rss-feeds.php +++ b/includes/feedzy-rss-feeds.php @@ -206,6 +206,7 @@ private function define_admin_hooks() { self::$instance->loader->add_action( 'wp_ajax_feedzy_validate_feed', self::$instance->admin, 'validate_feed' ); self::$instance->loader->add_action( 'wp_ajax_feedzy_dashboard_subscribe', self::$instance->admin, 'feedzy_dashboard_subscribe' ); self::$instance->loader->add_filter( 'feedzy_internal_cron_schedule_slugs', self::$instance->admin, 'internal_cron_schedule_slugs', 10, 1 ); + self::$instance->loader->add_filter( 'cron_schedules', self::$instance->admin, 'append_custom_cron_schedules' ); // do not load this with the loader as this will need a corresponding remove_filter also. add_filter( 'update_post_metadata', array( self::$instance->admin, 'validate_category_feeds' ), 10, 5 ); diff --git a/includes/layouts/feedzy-schedules.php b/includes/layouts/feedzy-schedules.php new file mode 100644 index 000000000..3ef5a699e --- /dev/null +++ b/includes/layouts/feedzy-schedules.php @@ -0,0 +1,141 @@ + +
+
+
+

+ + + PRO + +

+
+
+
+ + + /> +
+ +
+ + + /> +
+ +
+ + + /> +
+
+ +
+
+
+
+
+ +
+
+
+ +
+ + + + + + + + + + + + $schedule ) : + $interval_seconds = $schedule['interval']; + $interval_display = $interval_seconds . ' (' . human_time_diff( 0, $interval_seconds ) . ')'; + ?> + + + + + + + + + + + + + + + +
+ + + + + + + +
+
+
+
\ No newline at end of file diff --git a/includes/layouts/settings.php b/includes/layouts/settings.php index e59bf576c..0db658483 100644 --- a/includes/layouts/settings.php +++ b/includes/layouts/settings.php @@ -99,6 +99,17 @@ class="" +
  • + + + + PRO + + +
  • - +
    diff --git a/js/feedzy-setting.js b/js/feedzy-setting.js index fc058b026..af059874f 100644 --- a/js/feedzy-setting.js +++ b/js/feedzy-setting.js @@ -171,6 +171,27 @@ jQuery(function ($) { initializeAutoCatActions(); + // Disable the Add Schedule button until all fields are filled. + const validateScheduleForm = () => { + const interval = $('#fz-schedule-interval').val().trim(); + const display = $('#fz-schedule-display').val().trim(); + const name = $('#fz-schedule-name').val().trim(); + const button = $('#fz-add-schedule'); + + const isValid = interval && display && name; + button.prop('disabled', !isValid); + button.toggleClass('disabled', !isValid); + }; + + // Initial validation check. + validateScheduleForm(); + + // Add event listeners to schedule form inputs. + $('#fz-schedule-interval, #fz-schedule-display, #fz-schedule-name').on( + 'input keyup', + validateScheduleForm + ); + $('#feedzy-delete-log-file').on('click', function (e) { e.preventDefault(); const _this = $(this); @@ -206,6 +227,110 @@ jQuery(function ($) { }); }); + $('#fz-add-schedule').on('click', function (e) { + e.preventDefault(); + + const formElem = document.querySelector('form:has(.fz-form-wrap)'); + if (formElem && formElem.checkValidity() === false) { + formElem.reportValidity(); + return; + } + + const interval = $('#fz-schedule-interval').val(); + const display = $('#fz-schedule-display').val(); + const name = $('#fz-schedule-name').val(); + + if (!interval || !display || !name) { + return; + } + + $('.fz-schedules-table').show(); + const scheduleTable = $('.fz-schedules-table tbody'); + + const newRow = $('').attr('data-schedule', name); + + const nameCell = $('') + .addClass('fz-schedule-attributes') + .append($('').text(name)); + + const intervalCell = $('') + .addClass('fz-schedule-attributes') + .text(interval); + const displayCell = $('') + .addClass('fz-schedule-attributes') + .text(display); + + const deleteButton = $('
    @@ -119,9 +119,6 @@ class="dashicons dashicons-arrow-down-alt2">
    - - -
    @@ -630,15 +627,14 @@ class="fz-switch-toggle" type="checkbox" value="yes" -
    - +
    -

    PRO' : ''; ?>

    +

    - @@ -674,11 +671,10 @@ class="fz-switch-toggle" type="checkbox" value="yes"
    -
    - +

    - PRO' : ''; ?> +

    diff --git a/js/Conditions/ConditionsControl.js b/js/Conditions/ConditionsControl.js index 14406d3a6..fb8b7a15a 100644 --- a/js/Conditions/ConditionsControl.js +++ b/js/Conditions/ConditionsControl.js @@ -6,11 +6,10 @@ import classNames from 'classnames'; /** * WordPress dependencies. */ -import { __ } from '@wordpress/i18n'; - +import { __, sprintf } from '@wordpress/i18n'; import { Button, SelectControl, TextControl } from '@wordpress/components'; - import { Icon, plus } from '@wordpress/icons'; +import { useState } from '@wordpress/element'; /** * Internal dependencies. @@ -18,6 +17,7 @@ import { Icon, plus } from '@wordpress/icons'; import PanelTab from './PanelTab'; import DateTimeControl from './DateTimeControl'; +const isPro = window.feedzyData.isPro; const SUPPORTED_FIELDS = [ { label: __('Title', 'feedzy-rss-feeds'), @@ -26,15 +26,18 @@ const SUPPORTED_FIELDS = [ { label: __('Description', 'feedzy-rss-feeds'), value: 'description', + disabled: !isPro, }, { label: __('Full Content', 'feedzy-rss-feeds'), value: 'fullcontent', + disabled: !isPro, }, { label: __('Author', 'feedzy-rss-feeds'), value: 'author', unsupportedOperators: ['greater_than', 'gte', 'less_than', 'lte'], + disabled: !isPro, }, { label: __('Date', 'feedzy-rss-feeds'), @@ -47,21 +50,24 @@ const SUPPORTED_FIELDS = [ 'contains', 'not_contains', ], + disabled: !isPro, }, { label: __('Featured Image', 'feedzy-rss-feeds'), value: 'featured_image', unsupportedOperators: ['greater_than', 'gte', 'less_than', 'lte'], + disabled: !isPro, }, { label: __('Link', 'feedzy-rss-feeds'), value: 'link', unsupportedOperators: ['greater_than', 'gte', 'less_than', 'lte'], + disabled: !isPro, }, ]; -const isPro = window.feedzyData.isPro; const ConditionsControl = ({ conditions, setConditions }) => { + const [modalOpen, setModelOpen] = useState(false); const onChangeMatch = (value) => { setConditions({ ...conditions, @@ -69,7 +75,18 @@ const ConditionsControl = ({ conditions, setConditions }) => { }); }; + const el = document.querySelector('.editor-sidebar__panel-tabs'); const addCondition = () => { + if (!isPro && 1 <= conditions.conditions.length) { + // the Inspector panel use sticky position with their own stacking context, + // which causes them to appear above our popup overlay. We set their z-index to 0 so the popup covers them. + if (el) { + el.style.zIndex = 0; + } + setModelOpen(true); + return; + } + const conditionsCopy = [...conditions.conditions]; conditionsCopy.push({ @@ -120,128 +137,220 @@ const ConditionsControl = ({ conditions, setConditions }) => { }); }; + const closeModal = () => { + if (el) { + el.style.zIndex = 0; + } + setModelOpen(false); + }; + return ( -
    - - - {conditions.conditions.map((condition, index) => { - const field = SUPPORTED_FIELDS.find( - (i) => i.value === condition.field - ); - const operators = Object.keys( - window?.feedzyConditionsData?.operators - ).filter((key) => !field?.unsupportedOperators?.includes(key)); - - return ( - removeCondition(index)} - initialOpen={index === 0} - > - - onChangeCondition(index, value, 'field') - } - disabled={!isPro} - /> - - ({ - label: window.feedzyConditionsData.operators[ - key - ], - value: key, - }))} - help={ - ['contains', 'not_contains'].includes( - condition?.operator - ) - ? __( - 'You can use comma(,) and plus(+) keyword.', - 'feedzy-rss-feeds' - ) - : '' - } - value={condition?.operator} - onChange={(value) => - onChangeCondition(index, value, 'operator') - } - disabled={!isPro} - /> - - {!['has_value', 'empty'].includes( - condition?.operator - ) && ( - <> - {condition?.field === 'date' ? ( - - onChangeCondition( - index, - value, - 'value' - ) - } - disabled={!isPro} - /> - ) : ( - - onChangeCondition( - index, - value, - 'value' - ) - } - disabled={!isPro} - /> + <> +
    + + + {conditions.conditions.map((condition, index) => { + const field = SUPPORTED_FIELDS.find( + (i) => i.value === condition.field + ); + const operators = Object.keys( + window?.feedzyConditionsData?.operators + ).filter( + (key) => !field?.unsupportedOperators?.includes(key) + ); + + return ( + removeCondition(index)} + initialOpen={index === 0} + > + + onChangeCondition(index, value, 'field') + } + /> + + - )} - - ); - })} - -
    - + +
    -
    + {modalOpen && ( +
    +
    + +
    +

    + {__( + 'Upgrade to Use Unlimited Conditions', + 'feedzy-rss-feeds' + )} +

    +

    + {__( + 'Filter Condition limit reached', + 'feedzy-rss-feeds' + )} + + {'(' + + sprintf( + // translators: %1$s is the number of imports used, %2$s is the total number of imports allowed. + __( + '%1$s/%2$s used', + 'feedzy-rss-feeds' + ), + '1', + '1' + ) + + ')'} + +

    +
    +
    +

    + {__( + "Your current plan supports only one filter condition. Upgrade to unlock unlimited import configurations and make the most of Feedzy's powerful features!", + 'feedzy-rss-feeds' + )} +

    +
    +
    + + + {__( + '30-day money-back guarantee. No questions asked.', + 'feedzy-rss-feeds' + )} + +
    +
    +
    + )} + ); }; diff --git a/js/Conditions/index.js b/js/Conditions/index.js index db5b5c97a..4fb7ef013 100644 --- a/js/Conditions/index.js +++ b/js/Conditions/index.js @@ -28,11 +28,6 @@ const App = () => { }); useEffect(() => { - if (!feedzyData.isPro) { - setConditions(dummyConditions); - return; - } - const field = document.getElementById('feed-post-filters-conditions'); if (field && field.value) { const parsedConditions = JSON.parse(field.value); @@ -45,10 +40,6 @@ const App = () => { }, []); useEffect(() => { - if (!feedzyData.isPro) { - return; - } - document.getElementById('feed-post-filters-conditions').value = JSON.stringify(conditions); }, [conditions]); diff --git a/js/FeedzyBlock/inspector.js b/js/FeedzyBlock/inspector.js index 7c25b0244..f3716399e 100644 --- a/js/FeedzyBlock/inspector.js +++ b/js/FeedzyBlock/inspector.js @@ -258,21 +258,14 @@ class Inspector extends Component { Pro - ), ]} initialOpen={false} - className={ - window.feedzyjs.isPro - ? 'feedzy-item-filter' - : 'feedzy-item-filter fz-locked' - } + className='feedzy-item-filter' > {!window.feedzyjs.isPro && (
    {__( - 'Unlock this feature and more advanced options with', + 'Unlock more advanced options with', 'feedzy-rss-feeds' )}{' '} @@ -307,6 +300,7 @@ class Inspector extends Component { { label: __('Author', 'feedzy-rss-feeds'), value: 'author', + disabled: !window.feedzyjs.isPro, }, { label: __( @@ -314,6 +308,7 @@ class Inspector extends Component { 'feedzy-rss-feeds' ), value: 'description', + disabled: !window.feedzyjs.isPro, }, ]} onChange={this.props.edit.onKeywordsIncludeOn} @@ -345,6 +340,7 @@ class Inspector extends Component { { label: __('Author', 'feedzy-rss-feeds'), value: 'author', + disabled: !window.feedzyjs.isPro, }, { label: __( @@ -352,28 +348,37 @@ class Inspector extends Component { 'feedzy-rss-feeds' ), value: 'description', + disabled: !window.feedzyjs.isPro, }, ]} onChange={this.props.edit.onKeywordsExcludeOn} /> -

    - {__( - 'Filter feed item by date range.', - 'feedzy-rss-feeds' - )} -

    - - + +
    +

    + {__( + 'Filter feed item by date range.', + 'feedzy-rss-feeds' + )} +

    + + +
    )} diff --git a/js/FeedzyLoop/controls.js b/js/FeedzyLoop/controls.js index 34abfc9e9..c28bb5fe6 100644 --- a/js/FeedzyLoop/controls.js +++ b/js/FeedzyLoop/controls.js @@ -195,9 +195,6 @@ const Controls = ({ Pro - ), ]} initialOpen={false} key="filters" @@ -206,7 +203,7 @@ const Controls = ({ {!window.feedzyData.isPro && (
    {__( - 'Unlock this feature and more advanced options with', + 'Unlock more advanced options with', 'feedzy-rss-feeds' )}{' '} @@ -222,7 +219,7 @@ const Controls = ({ conditions: [], match: 'all', } - : { + : attributes?.conditions || { match: 'all', conditions: [ { diff --git a/js/FeedzyLoop/editor.scss b/js/FeedzyLoop/editor.scss index c37cd05e9..d5bd46085 100644 --- a/js/FeedzyLoop/editor.scss +++ b/js/FeedzyLoop/editor.scss @@ -13,10 +13,6 @@ .fz-condition-control { padding: 0; - &.is-upsell { - opacity: 0.6; - } - .components-button { width: 100%; margin: 0; @@ -25,6 +21,10 @@ } } +.is-upsell { + opacity: 0.6; +} + .fz-panel-tab { z-index: 999999; margin: 24px 0; diff --git a/tests/e2e/specs/upsell.spec.js b/tests/e2e/specs/upsell.spec.js index f8204412f..386dea273 100644 --- a/tests/e2e/specs/upsell.spec.js +++ b/tests/e2e/specs/upsell.spec.js @@ -20,8 +20,9 @@ test.describe( 'Upsell', () => { // Hover over text named Filter by Keyword const filtersTab = page.locator('#feedzy-import-form > div.feedzy-accordion > div:nth-child(2)'); - // It should have 1 elements with .only-pro-content class. - await expect( filtersTab.locator('.pro-label').count() ).resolves.toBe(1); + await page.locator('.fz-action-btn').click(); + // 'Add condition' action button has .is-upsell class. + await expect(filtersTab.locator('.fz-action-btn.is-upsell')).toHaveCount(1); } ); @@ -29,22 +30,21 @@ test.describe( 'Upsell', () => { test( 'general settings', async({ editor, page }) => { await page.getByRole('button', { name: 'Step 4 General feed settings' }).click({ force: true }); + await page.locator('.fz-form-group:has( #fz-event-schedule )').scrollIntoViewIfNeeded() + // check all the option expect daily is disabled. + const otherOptions = page.locator('#fz-event-schedule option:not([value="daily"])'); + const count = await otherOptions.count(); - await page.locator('.fz-form-group:has( #feed-post-default-thumbnail )').hover({ force: true }); - let upgradeAlert = page.locator('#feedzy-import-form a[href*="utm_campaign=fallback-image"]'); - await expect( upgradeAlert ).toBeVisible(); - - await page.locator('.fz-form-group:has( #fz-event-schedule )').scrollIntoViewIfNeeded() - await page.locator('.fz-form-group:has( #fz-event-schedule )').hover({ force: true }); - upgradeAlert = page.locator('#feedzy-import-form a[href*="utm_campaign=schedule-import-job"]'); - await expect( upgradeAlert ).toBeVisible(); + for (let i = 0; i < count; i++) { + await expect(otherOptions.nth(i)).toBeDisabled(); + } // Click the advanced settings tab. await page.click('[data-id="fz-advanced-settings"]'); await page.locator('.fz-form-group:has( #feedzy_mark_duplicate )').hover({ force: true }); - upgradeAlert = page.locator('#feedzy-import-form a[href*="utm_campaign=remove-duplicates"]'); + const upgradeAlert = page.locator('#feedzy-import-form a[href*="utm_campaign=remove-duplicates"]'); await expect( upgradeAlert ).toBeVisible(); } ); }); From 2ce8ba0395a38dfae24c20fcb54b0927621bdc9e Mon Sep 17 00:00:00 2001 From: Girish Panchal <79647963+girishpanchal30@users.noreply.github.com> Date: Tue, 19 Aug 2025 14:27:22 +0530 Subject: [PATCH 36/57] feat: Improve onboarding experience (#1145) - Add preview feed in Step 2 - Add Fallback Image and Title Filter for feed settings after Step 2 --- includes/admin/feedzy-rss-feeds-admin.php | 16 ++++ includes/admin/feedzy-rss-feeds-import.php | 31 +++++- includes/layouts/setup-wizard.php | 65 ++++++++++--- includes/views/css/style-wizard.css | 33 +++++++ js/feedzy-setup-wizard.js | 104 ++++++++++++++++++++- 5 files changed, 230 insertions(+), 19 deletions(-) diff --git a/includes/admin/feedzy-rss-feeds-admin.php b/includes/admin/feedzy-rss-feeds-admin.php index a0b4ae184..50cf3a513 100644 --- a/includes/admin/feedzy-rss-feeds-admin.php +++ b/includes/admin/feedzy-rss-feeds-admin.php @@ -1978,6 +1978,10 @@ public function feedzy_load_setup_wizard_page() { * Enqueue setup wizard required scripts. */ public function feedzy_enqueue_setup_wizard_scripts() { + if ( ! did_action( 'wp_enqueue_media' ) ) { + wp_enqueue_media(); + } + wp_enqueue_style( $this->plugin_name . '_chosen' ); wp_enqueue_style( $this->plugin_name . '_smart_wizard', FEEDZY_ABSURL . 'css/smart_wizard_all.min.css', array(), $this->version ); wp_enqueue_style( $this->plugin_name . '_setup_wizard', FEEDZY_ABSURL . 'includes/views/css/style-wizard.css', array( $this->plugin_name . '-settings' ), $this->version, 'all' ); @@ -2003,6 +2007,18 @@ public function feedzy_enqueue_setup_wizard_scripts() { 'firstButtonText' => __( 'Create Page', 'feedzy-rss-feeds' ), 'secondButtonText' => __( 'Do not create', 'feedzy-rss-feeds' ), ), + 'mediaUploadText' => array( + 'iframeTitle' => __( 'Select image', 'feedzy-rss-feeds' ), + 'iframeButton' => __( 'Set default image', 'feedzy-rss-feeds' ), + 'actionButtonTextOne' => __( 'Choose image', 'feedzy-rss-feeds' ), + 'actionButtonTextTwo' => __( 'Replace image', 'feedzy-rss-feeds' ), + 'actionButtonTextThree' => __( 'Remove image', 'feedzy-rss-feeds' ), + ), + 'dryRun' => array( + 'loading' => '

    ' . __( 'Processing the source and loading the items that will be imported when it runs', 'feedzy-rss-feeds' ) . '...

    ' + . '

    ', + 'title' => __( 'Importable Items', 'feedzy-rss-feeds' ), + ), ) ); } diff --git a/includes/admin/feedzy-rss-feeds-import.php b/includes/admin/feedzy-rss-feeds-import.php index a343ce729..9f954c141 100644 --- a/includes/admin/feedzy-rss-feeds-import.php +++ b/includes/admin/feedzy-rss-feeds-import.php @@ -561,9 +561,14 @@ public function feedzy_import_feed_options() { $default_thumbnail_id = 0; $inherited_thumbnail_id = ! empty( $this->free_settings['general']['default-thumbnail-id'] ) ? (int) $this->free_settings['general']['default-thumbnail-id'] : 0; + $custom_thumbnail_id = get_post_meta( $post->ID, 'default_thumbnail_id', true ); + + if ( is_numeric( $custom_thumbnail_id ) ) { + $default_thumbnail_id = $custom_thumbnail_id; + } + if ( feedzy_is_pro() ) { - $default_thumbnail_id = get_post_meta( $post->ID, 'default_thumbnail_id', true ); - $import_schedule = array( + $import_schedule = array( 'fz_cron_schedule' => ! empty( $this->free_settings['general']['fz_cron_schedule'] ) ? $this->free_settings['general']['fz_cron_schedule'] : '', ); } @@ -3951,6 +3956,8 @@ private function wizard_import_feed() { $post_type = ! empty( $_POST['post_type'] ) ? sanitize_text_field( wp_unslash( $_POST['post_type'] ) ) : ''; $post_status = ! empty( $_POST['post_status'] ) ? sanitize_text_field( wp_unslash( $_POST['post_status'] ) ) : ''; + $fallback_image = ! empty( $_POST['fallback_image'] ) ? sanitize_text_field( wp_unslash( $_POST['fallback_image'] ) ) : ''; + $excluded_post_title = ! empty( $_POST['excluded_post_title'] ) ? sanitize_text_field( wp_unslash( $_POST['excluded_post_title'] ) ) : ''; $wizard_data = get_option( 'feedzy_wizard_data', array() ); $wizard_data = ! empty( $wizard_data ) ? $wizard_data : array(); $wizard_data['post_type'] = $post_type; @@ -3994,7 +4001,25 @@ private function wizard_import_feed() { // Update wizard data. $wizard_data['job_id'] = $job_id; update_option( 'feedzy_wizard_data', $wizard_data ); - + + $filter_conditions = array( + 'match' => 'all', + 'conditions' => array(), + ); + + if ( ! empty( $excluded_post_title ) ) { + $filter_conditions['conditions'] = array( + array( + 'field' => 'title', + 'operator' => 'not_contains', + 'value' => $excluded_post_title, + ), + ); + } + + update_post_meta( $job_id, 'filter_conditions', wp_json_encode( $filter_conditions ) ); + update_post_meta( $job_id, 'default_thumbnail_id', $fallback_image ); + $response = array( 'status' => true, ); diff --git a/includes/layouts/setup-wizard.php b/includes/layouts/setup-wizard.php index d729e9eab..8d61f8c53 100644 --- a/includes/layouts/setup-wizard.php +++ b/includes/layouts/setup-wizard.php @@ -24,6 +24,7 @@ } $published_status = array( 'publish', 'draft' ); +add_thickbox(); ?>
    @@ -138,6 +139,11 @@
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + + + + + +
    + +
    +
    +
    +
    +
    -
    - +
    + +
    + +
    diff --git a/includes/views/css/style-wizard.css b/includes/views/css/style-wizard.css index 8f963126f..1ebd809dc 100644 --- a/includes/views/css/style-wizard.css +++ b/includes/views/css/style-wizard.css @@ -471,3 +471,36 @@ .feedzy-accordion-item__title h2 { margin-bottom: 0; } +.btn-ghost.disabled { + cursor: none; + pointer-events: none; + color: #757575; +} +#TB_ajaxContent ul { + list-style: decimal; + margin-left: 20px; +} + +#TB_ajaxContent ul li { + padding: 5px !important; +} + +#TB_ajaxContent p.loading-img { + text-align: center; +} + +#TB_ajaxContent.loaded p.hide-when-loaded { + display: none; +} + +#TB_ajaxContent div.dry_run span { + display: block; +} + +#TB_ajaxContent div.dry_run span i.pass { + color: #149714; +} + +#TB_ajaxContent div.dry_run span i.fail { + color: #ca4a1f; +} \ No newline at end of file diff --git a/js/feedzy-setup-wizard.js b/js/feedzy-setup-wizard.js index a1a744cb1..4b304fe31 100644 --- a/js/feedzy-setup-wizard.js +++ b/js/feedzy-setup-wizard.js @@ -35,7 +35,7 @@ jQuery(function ($) { const feedUrl = $(this).attr('href'); $(this).parents('.fz-row').find('input:text').val(feedUrl); - $('[data-step_number="2"]').removeClass('disabled'); + $('[data-step_number="2"], #preflight').removeClass('disabled'); return false; }); @@ -211,6 +211,8 @@ jQuery(function ($) { post_status: $( 'select[name="feedzy_meta_data[import_post_status]"]' ).val(), + fallback_image: $('input[name="feedzy_meta_data[default_thumbnail_id]"]').val(), + excluded_post_title: $('input[name="feedzy_meta_data[exc_key]"]').val(), action: 'feedzy', _action: 'wizard_import_feed', }, @@ -329,9 +331,9 @@ jQuery(function ($) { // Remove disabled class from save button. $(document).on('input', '#wizard_feed_source', function () { if ('' === $(this).val()) { - $('[data-step_number="2"]').addClass('disabled'); + $('[data-step_number="2"], #preflight').addClass('disabled'); } else { - $('[data-step_number="2"]').removeClass('disabled'); + $('[data-step_number="2"], #preflight').removeClass('disabled'); } }); @@ -359,4 +361,100 @@ jQuery(function ($) { // Init chosen selectbox. $('.feedzy-chosen').chosen({ width: '100%' }); + + // on upload button click + $( 'body' ).on( 'click', '.feedzy-open-media', function( e ) { + e.preventDefault(); + const button = $( this ), + wp_media_uploader = wp.media( { + title: feedzySetupWizardData.mediaUploadText.iframeTitle, + library : { + type : 'image' + }, + button: { + text: feedzySetupWizardData.mediaUploadText.iframeButton + }, + multiple: false + } ).on( 'select', function() { // it also has "open" and "close" events + const selectedAttachments = wp_media_uploader.state().get( 'selection' ); + button.parents( '.fz-form-group' ).find( '.feedzy-media-preview' ).remove(); + // Display image preview when a single image is selected. + const attachment = selectedAttachments.first().toJSON(); + let attachmentUrl = attachment.url; + if ( attachment.sizes.thumbnail ) { + attachmentUrl = attachment.sizes.thumbnail.url; + } + if ( $( '.feedzy-media-preview' ).length ) { + $( '.feedzy-media-preview' ).find( 'img' ).attr( 'src', attachmentUrl ); + } else { + $( '
    ' ).insertBefore( button.parent() ); + } + // Get all selected attachment ids. + const ids = selectedAttachments.map( function( image ) { + return image.id; + } ).join( ',' ); + + button.parent().find( '.feedzy-remove-media' ).addClass( 'is-show' ); + button.parent().find( 'input:hidden' ).val( ids ).trigger( 'change' ); + $( '.feedzy-open-media' ).html( feedzySetupWizardData.mediaUploadText.actionButtonTextTwo ); + } ); + + wp_media_uploader.on(' open', function() { + const selectedVal = button.parent().find( 'input:hidden' ).val(); + if ( '' === selectedVal ) { + return; + } + const selection = wp_media_uploader.state().get('selection'); + + selectedVal.split(',').forEach(function( id ) { + const attachment = wp.media.attachment( id ); + attachment.fetch(); + selection.add(attachment ? [attachment] : []); + }); + } ); + + wp_media_uploader.open(); + }); + + $(document).on( 'click', '.feedzy-remove-media', function( e ) { + $(this) + e.preventDefault(); + $('.feedzy-media-preview').remove(); + $(this).removeClass('is-show'); + + // Reset the input. + $('input[name="feedzy_meta_data[default_thumbnail_id]"]').val(0); + $('.feedzy-open-media').html(feedzySetupWizardData.mediaUploadText.actionButtonTextOne); + } ); + + $('#preflight').on('click', function (e) { + e.preventDefault(); + const $fields = {}; + // collect all elements. + $('#smartwizard') + .find(':input') + .each(function (index, element) { + if ('undefined' === typeof $(element).attr('name')) { + return; + } + $fields[$(element).attr('name')] = $(element).val(); + }); + $fields['feedzy_meta_data[source]'] = $('#wizard_feed_source').val(); + tb_show(feedzySetupWizardData.dryRun.title, 'TB_inline?'); + $('#TB_ajaxContent').html(feedzySetupWizardData.dryRun.loading); + $.post( + ajaxurl, + { + security: window.feedzySetupWizardData.ajax.security, + fields: $.param($fields), + action: 'feedzy', + _action: 'dry_run', + environment: 'wizard', + }, + function(data) { + $('#TB_ajaxContent').addClass('loaded'); + $('#TB_ajaxContent div').html(data.data.output); + }, + ); + }); }); From 5949f49bf9e6cddebf91e314c486e5d6c332c894 Mon Sep 17 00:00:00 2001 From: Girish Panchal <79647963+girishpanchal30@users.noreply.github.com> Date: Tue, 19 Aug 2025 19:12:30 +0530 Subject: [PATCH 37/57] feat: add simpler layout selector and refferal URL pro feature (#1147) - Deprecate the Pattern Selector for Loop - Add Referral URL similar to Classic Loop - Move the layout selector inside the Inspector --- .../gutenberg/feedzy-rss-feeds-loop-block.php | 2 + js/FeedzyLoop/block.json | 6 +- js/FeedzyLoop/controls.js | 267 +++++++++++------- js/FeedzyLoop/edit.js | 36 ++- js/FeedzyLoop/editor.scss | 18 ++ tests/e2e/specs/loop.spec.js | 2 +- 6 files changed, 210 insertions(+), 121 deletions(-) diff --git a/includes/gutenberg/feedzy-rss-feeds-loop-block.php b/includes/gutenberg/feedzy-rss-feeds-loop-block.php index 93a582c74..2fde836d7 100644 --- a/includes/gutenberg/feedzy-rss-feeds-loop-block.php +++ b/includes/gutenberg/feedzy-rss-feeds-loop-block.php @@ -120,6 +120,7 @@ public function render_callback( $attributes, $content ) { } $column_count = isset( $attributes['layout'] ) && isset( $attributes['layout']['columnCount'] ) && ! empty( $attributes['layout']['columnCount'] ) ? $attributes['layout']['columnCount'] : 1; + $referral_url = isset( $attributes['referral_url'] ) ? $attributes['referral_url'] : ''; $default_query = array( 'max' => 5, @@ -146,6 +147,7 @@ public function render_callback( $attributes, $content ) { 'summary' => 'yes', 'summarylength' => '', 'filters' => wp_json_encode( $filters ), + 'referral_url' => $referral_url, ); $sizes = array( diff --git a/js/FeedzyLoop/block.json b/js/FeedzyLoop/block.json index 17ed06cfb..0b9410f60 100644 --- a/js/FeedzyLoop/block.json +++ b/js/FeedzyLoop/block.json @@ -88,7 +88,11 @@ "innerBlocksContent": { "type": "string", "default": "" - } + }, + "referral_url": { + "type": "string", + "default": "" + } }, "supports": { "align": [ "wide", "full" ], diff --git a/js/FeedzyLoop/controls.js b/js/FeedzyLoop/controls.js index c28bb5fe6..8ce84feff 100644 --- a/js/FeedzyLoop/controls.js +++ b/js/FeedzyLoop/controls.js @@ -13,15 +13,14 @@ import { SelectControl, ToolbarButton, ToolbarGroup, + TextControl, + BaseControl, } from '@wordpress/components'; -import { useState } from '@wordpress/element'; - /** * Internal dependencies. */ import ConditionsControl from '../Conditions/ConditionsControl'; -import PatternSelector from './components/PatternSelector'; const Controls = ({ attributes, @@ -32,15 +31,20 @@ const Controls = ({ onChangeQuery, setIsEditing, setIsPreviewing, + variations, + setVariations, }) => { - const [isPatternModalOpen, setIsPatternModalOpen] = useState(false); + const decodeHtmlEntities = (str) => { + if (typeof str !== 'string') { + return str; + } + const textarea = document.createElement('textarea'); + textarea.innerHTML = str; + return textarea.value; + }; return ( <> - {isPatternModalOpen && ( - - )} - - - setIsPatternModalOpen(true)}> - {__('Replace', 'feedzy-rss-feeds')} - - - setIsPreviewing(!isPreviewing)} @@ -68,29 +66,27 @@ const Controls = ({ - {!isEditing && ( - - - - )} - + +
    + {variations?.map((variation) => ( + + ))} +
    +
    + {__('Feedzy Loop Documentation', 'feedzy-rss-feeds')} - - - onChangeQuery({ type: 'sort', value }) - } - /> - - - onChangeQuery({ type: 'refresh', value }) - } - />
    + {!isEditing && ( + + + + )} + + Pro + ), + ]} + initialOpen={false} + className={ + window.feedzyjs.isPro + ? 'feedzy-pro-options' + : 'feedzy-pro-options fz-locked' + } + > + {!window.feedzyjs.isPro && ( +
    + {__( + 'Unlock this feature and more advanced options with', + 'feedzy-rss-feeds' + )}{' '} + + {__('Feedzy Pro', 'feedzy-rss-feeds')} + +
    + )} + { + window.tiTrk + ?.with('feedzy') + .add({ feature: 'block-referral-url' }); + setAttributes({ referral_url: value }); + }} + /> +
    +
    + + + onChangeQuery({ type: 'sort', value })} + /> + + + onChangeQuery({ type: 'refresh', value }) + } + /> ); diff --git a/js/FeedzyLoop/edit.js b/js/FeedzyLoop/edit.js index 3a74d77e5..7cc7220a7 100644 --- a/js/FeedzyLoop/edit.js +++ b/js/FeedzyLoop/edit.js @@ -149,6 +149,26 @@ const Edit = ({ attributes, setAttributes, clientId }) => { ); } + const setVariations = (nextVariation = defaultVariation) => { + if (nextVariation) { + setAttributes({ + layout: { + name: nextVariation.name, + } , + ...nextVariation.attributes + }); + + replaceInnerBlocks( + clientId, + createBlocksFromInnerBlocksTemplate( + nextVariation.innerBlocks + ), + true + ); + clearSelectedBlock(); + } + } + return ( <> { onChangeQuery={onChangeQuery} setIsEditing={setIsEditing} setIsPreviewing={setIsPreviewing} + variations={variations} + setVariations={setVariations} />
    @@ -168,19 +190,7 @@ const Edit = ({ attributes, setAttributes, clientId }) => { ) : ( { - if (nextVariation) { - setAttributes(nextVariation.attributes); - replaceInnerBlocks( - clientId, - createBlocksFromInnerBlocksTemplate( - nextVariation.innerBlocks - ), - true - ); - clearSelectedBlock(); - } - }} + onSelect={setVariations} /> )}
    diff --git a/js/FeedzyLoop/editor.scss b/js/FeedzyLoop/editor.scss index d5bd46085..5107f785c 100644 --- a/js/FeedzyLoop/editor.scss +++ b/js/FeedzyLoop/editor.scss @@ -229,3 +229,21 @@ input[type="text"].fz-input-field:disabled { outline: 1px solid #0000001a; } } + +.fz-block-variation { + .block-editor-block-variation-picker__variations { + flex-wrap: nowrap; + } + .block-editor-block-variation-picker__variations>li { + width: 35%; + } +} + +.fz-block-variation-picker { + display: flex; + justify-content: space-between; + + &> .components-button svg { + max-width: 70px; + } +} \ No newline at end of file diff --git a/tests/e2e/specs/loop.spec.js b/tests/e2e/specs/loop.spec.js index 37ebd186d..be4810ff7 100644 --- a/tests/e2e/specs/loop.spec.js +++ b/tests/e2e/specs/loop.spec.js @@ -56,7 +56,7 @@ test.describe('Feedzy Loop', () => { await loadFeedButton.click(); await page.waitForTimeout(1000); - await page.getByLabel('Display curated RSS content').click(); + await page.getByLabel('Display curated RSS content').first().click(); await page.waitForTimeout(1000); // Now that we have tested we can insert URL, we can test the Feed Group. From 0a9015e04b66b0cfc4c1cc3f7c6c653ea0264cae Mon Sep 17 00:00:00 2001 From: Soare Robert Daniel Date: Tue, 19 Aug 2025 20:13:27 +0300 Subject: [PATCH 38/57] feat: add fallback image per import on Loop (#1148) Also, hide the image block if it contains an empty `src` --- .../gutenberg/feedzy-rss-feeds-loop-block.php | 120 +++++++++++---- js/FeedzyBlock/inspector.js | 93 +++--------- js/FeedzyLoop/block.json | 7 + .../components/FallbackImageLoader.jsx | 100 +++++++++++++ js/FeedzyLoop/controls.js | 65 +++++++- js/FeedzyLoop/editor.scss | 5 + js/FeedzyLoop/style.scss | 5 + phpstan-baseline.neon | 10 -- tests/e2e/specs/loop.spec.js | 139 ++++++++++++++++-- 9 files changed, 419 insertions(+), 125 deletions(-) create mode 100644 js/FeedzyLoop/components/FallbackImageLoader.jsx diff --git a/includes/gutenberg/feedzy-rss-feeds-loop-block.php b/includes/gutenberg/feedzy-rss-feeds-loop-block.php index 2fde836d7..1c28f8403 100644 --- a/includes/gutenberg/feedzy-rss-feeds-loop-block.php +++ b/includes/gutenberg/feedzy-rss-feeds-loop-block.php @@ -49,7 +49,7 @@ private function __construct() { $this->version = Feedzy_Rss_Feeds::get_version(); $this->admin = Feedzy_Rss_Feeds::instance()->get_admin(); add_action( 'init', array( $this, 'register_block' ) ); - add_filter( 'feedzy_loop_item', array( $this, 'apply_magic_tags' ), 10, 2 ); + add_filter( 'feedzy_loop_item', array( $this, 'apply_magic_tags' ), 10, 3 ); } /** @@ -95,8 +95,8 @@ public function register_block() { /** * Render Callback * - * @param array $attributes The block attributes. - * @param string $content The block content. + * @param array $attributes The block attributes. + * @param string $content The block content. * @return string The block content. */ public function render_callback( $attributes, $content ) { @@ -128,8 +128,27 @@ public function render_callback( $attributes, $content ) { 'refresh' => '12_hours', ); - $query = isset( $attributes['query'] ) ? wp_parse_args( $attributes['query'], $default_query ) : $default_query; - $filters = isset( $attributes['conditions'] ) ? $attributes['conditions'] : array(); + $query = isset( $attributes['query'] ) ? wp_parse_args( $attributes['query'], $default_query ) : $default_query; + $filters = isset( $attributes['conditions'] ) ? $attributes['conditions'] : array(); + $thumb = 'auto'; + $default_thumbnail = ''; + + if ( isset( $attributes['thumb'] ) && ! empty( $attributes['thumb'] ) ) { + $thumb = $attributes['thumb']; + + if ( + 'yes' === $thumb && + isset( $attributes['fallbackImage'], $attributes['fallbackImage']['id'] ) && + ! empty( $attributes['fallbackImage']['id'] ) + ) { + $image_id = $attributes['fallbackImage']['id']; + $media_img = wp_get_attachment_image_src( $image_id ); + + if ( is_array( $media_img ) && ! empty( $media_img[0] ) ) { + $default_thumbnail = $media_img[0]; + } + } + } $options = array( 'feeds' => implode( ',', $feed_urls ), @@ -139,8 +158,8 @@ public function render_callback( $attributes, $content ) { 'target' => '_blank', 'keywords_ban' => '', 'columns' => '1', - 'thumb' => 'auto', - 'default' => '', + 'thumb' => $thumb, + 'default' => $default_thumbnail, 'title' => '', 'meta' => 'yes', 'multiple_meta' => 'no', @@ -170,7 +189,7 @@ public function render_callback( $attributes, $content ) { $loop = ''; foreach ( $feed_items as $key => $item ) { - $loop .= apply_filters( 'feedzy_loop_item', $content, $item ); + $loop .= apply_filters( 'feedzy_loop_item', $content, $item, $attributes ); } return sprintf( @@ -187,12 +206,13 @@ public function render_callback( $attributes, $content ) { /** * Magic Tags Replacement. * - * @param string $content The content. - * @param array $item The item. + * @param string $content The content. + * @param array $item The item. + * @param array $attributes The block attributes. * * @return string The content. */ - public function apply_magic_tags( $content, $item ) { + public function apply_magic_tags( $content, $item, $attributes ) { $pattern = '/\{\{feedzy_([^}]+)\}\}/'; $content = str_replace( array( @@ -208,8 +228,8 @@ public function apply_magic_tags( $content, $item ) { return preg_replace_callback( $pattern, - function ( $matches ) use ( $item ) { - return isset( $matches[1] ) ? $this->get_value( $matches[1], $item ) : ''; + function ( $matches ) use ( $item, $attributes ) { + return isset( $matches[1] ) ? $this->get_value( $matches[1], $item, $attributes ) : ''; }, $content ); @@ -218,12 +238,13 @@ function ( $matches ) use ( $item ) { /** * Get Dynamic Value. * - * @param string $key The key. - * @param array $item Feed item. + * @param string $key The key. + * @param array $item Feed item. + * @param array $attributes The block attributes. * * @return string The value. */ - public function get_value( $key, $item ) { + public function get_value( $key, $item, $attributes ) { switch ( $key ) { case 'title': return isset( $item['item_title'] ) ? $item['item_title'] : ''; @@ -257,15 +278,7 @@ public function get_value( $key, $item ) { case 'categories': return isset( $item['item_categories'] ) ? $item['item_categories'] : ''; case 'image': - $settings = apply_filters( 'feedzy_get_settings', array() ); - if ( $settings && ! empty( $settings['general']['default-thumbnail-id'] ) ) { - $default_img = wp_get_attachment_image_src( $settings['general']['default-thumbnail-id'], 'full' ); - $default_img = ! empty( $default_img ) ? reset( $default_img ) : ''; - } else { - $default_img = FEEDZY_ABSURL . 'img/feedzy.svg'; - } - - return isset( $item['item_img_path'] ) ? $item['item_img_path'] : $default_img; + return $this->get_thumbnail( $item, $attributes ); case 'media': return isset( $item['item_media']['src'] ) ? $item['item_media']['src'] : ''; case 'price': @@ -276,4 +289,61 @@ public function get_value( $key, $item ) { return ''; } } + + /** + * Get Thumbnail of feed item. + * + * Fallback to default thumbnail if not set. The Fallback image can be set in the block attributes or in the plugin settings. + * + * @param array $item The feed item. + * @param array $attributes The block attributes. + * + * @return string The thumbnail URL. + */ + private function get_thumbnail( $item, $attributes ) { + $settings = apply_filters( 'feedzy_get_settings', array() ); + $thumb = 'yes'; + + if ( isset( $attributes['thumb'] ) && ! empty( $attributes['thumb'] ) ) { + $thumb = $attributes['thumb']; + } + + if ( 'no' === $thumb ) { + return ''; + } + + if ( isset( $item['item_img_path'] ) && ! empty( $item['item_img_path'] ) ) { + return $item['item_img_path']; + } + + if ( 'auto' === $thumb ) { + return ''; + } + + // Try to find the fallback image. + if ( + isset( $attributes['fallbackImage'], $attributes['fallbackImage']['url'] ) && + ! empty( $attributes['fallbackImage']['url'] ) + ) { + $image_id = $attributes['fallbackImage']['id']; + $media_img = wp_get_attachment_image_src( $image_id ); + if ( is_array( $media_img ) && ! empty( $media_img[0] ) ) { + return $media_img[0]; + } + } + + if ( + isset( $settings, $settings['general'], $settings['general']['default-thumbnail-id'] ) && + ! empty( $settings['general']['default-thumbnail-id'] ) + ) { + $media_img = wp_get_attachment_image_src( $settings['general']['default-thumbnail-id'], 'full' ); + if ( + is_array( $media_img ) && ! empty( $media_img[0] ) + ) { + return $media_img[0]; + } + } + + return FEEDZY_ABSURL . 'img/feedzy.svg'; + } } diff --git a/js/FeedzyBlock/inspector.js b/js/FeedzyBlock/inspector.js index f3716399e..f4126f923 100644 --- a/js/FeedzyBlock/inspector.js +++ b/js/FeedzyBlock/inspector.js @@ -12,7 +12,6 @@ import RadioImageControl from './radio-image-control/'; import classnames from 'classnames'; import { __, sprintf } from '@wordpress/i18n'; import { applyFilters } from '@wordpress/hooks'; -import { MediaUpload } from '@wordpress/block-editor'; import { BaseControl, ExternalLink, @@ -23,11 +22,9 @@ import { Button, ToggleControl, SelectControl, - ResponsiveWrapper, Dashicon, - // eslint-disable-next-line @wordpress/no-unsafe-wp-apis - __experimentalHStack as HStack, } from '@wordpress/components'; +import { FallbackImageLoader } from '../FeedzyLoop/components/FallbackImageLoader'; /** * Create an Inspector Controls wrapper Component @@ -429,7 +426,22 @@ class Inspector extends Component { {this.props.attributes.thumb !== 'auto' && ( + this.props.edit.onDefault( + undefined + ) + } + label={__( + 'Fallback image if no image is found.', + 'feedzy-rss-feeds' + )} /> )} - - ( - - {props.attributes.default !== undefined && ( - - {__( - - )} - - - {props.attributes.default !== undefined && ( - - )} - - - - - )} - /> -
    - ); -} - export default Inspector; diff --git a/js/FeedzyLoop/block.json b/js/FeedzyLoop/block.json index 0b9410f60..d2ca8f426 100644 --- a/js/FeedzyLoop/block.json +++ b/js/FeedzyLoop/block.json @@ -55,6 +55,13 @@ } } }, + "thumb": { + "type": "string", + "default": "auto" + }, + "fallbackImage": { + "type": "object" + }, "conditions": { "type": "object", "properties": { diff --git a/js/FeedzyLoop/components/FallbackImageLoader.jsx b/js/FeedzyLoop/components/FallbackImageLoader.jsx new file mode 100644 index 000000000..11012140c --- /dev/null +++ b/js/FeedzyLoop/components/FallbackImageLoader.jsx @@ -0,0 +1,100 @@ +/** + * WordPress dependencies + */ +import { Fragment } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { MediaUpload } from '@wordpress/block-editor'; +import { + Button, + ResponsiveWrapper, + // eslint-disable-next-line @wordpress/no-unsafe-wp-apis + __experimentalHStack as HStack, +} from '@wordpress/components'; + +export function FallbackImageLoader({ + imageValue, + onChangeImage, + onRemoveImage, + label = __('Fallback image if no image is found.', 'feedzy-rss-feeds'), +}) { + const handleSelect = (media) => { + const imageData = { + url: media.url, + width: media.width, + height: media.height, + id: media.id, + }; + onChangeImage(imageData); + }; + + const handleRemove = () => { + onRemoveImage(); + }; + + return ( +
    + + ( + + {imageValue !== undefined && ( + + {__( + + )} + + + {imageValue !== undefined && ( + + )} + + + + + )} + /> +
    + ); +} + +export default FallbackImageLoader; diff --git a/js/FeedzyLoop/controls.js b/js/FeedzyLoop/controls.js index 8ce84feff..925f472c8 100644 --- a/js/FeedzyLoop/controls.js +++ b/js/FeedzyLoop/controls.js @@ -17,10 +17,13 @@ import { BaseControl, } from '@wordpress/components'; +import { Fragment } from '@wordpress/element'; + /** * Internal dependencies. */ import ConditionsControl from '../Conditions/ConditionsControl'; +import FallbackImageLoader from './components/FallbackImageLoader.jsx'; const Controls = ({ attributes, @@ -133,11 +136,67 @@ const Controls = ({ )} + + setAttributes({ thumb: value })} + className="feedzy-thumb" + /> + {attributes?.thumb !== 'no' && ( + + {attributes?.thumb !== 'auto' && ( + + setAttributes({ + fallbackImage: imageData, + }) + } + onRemoveImage={() => + setAttributes({ + fallbackImage: undefined, + }) + } + label={__( + 'Fallback image if no image is found.', + 'feedzy-rss-feeds' + )} + /> + )} + + )} + { expect(feedzyLoopChildren.length).toBe(5); }); - test('check validation for invalid URL', async ({ editor, page, admin }) => { + test('check validation for invalid URL', async ({ + editor, + page, + admin, + }) => { await admin.createNewPost(); await editor.insertBlock({ name: 'feedzy-rss-feeds/loop', }); - await page.getByPlaceholder('Enter URLs or select a Feed').fill( - 'http://invalid-url.com/feed' - ); + await page + .getByPlaceholder('Enter URLs or select a Feed') + .fill('http://invalid-url.com/feed'); await page.getByRole('button', { name: 'Load Feed' }).click(); await page.waitForSelector('.feedzy-validation-results'); - await expect( page.locator('.feedzy-validation-results .is-error').getByText('http://invalid-url.com/feed', { exact: true }) ).toBeVisible(); + await expect( + page + .locator('.feedzy-validation-results .is-error') + .getByText('http://invalid-url.com/feed', { exact: true }) + ).toBeVisible(); }); - test('check validation for invalid and valid url', async ({ editor, page, admin }) => { + test('check validation for invalid and valid url', async ({ + editor, + page, + admin, + }) => { await admin.createNewPost(); await editor.insertBlock({ name: 'feedzy-rss-feeds/loop', }); - await page.getByPlaceholder('Enter URLs or select a Feed').fill( - 'http://invalid-url.com/feed, https://www.nasa.gov/feeds/iotd-feed/' - ); + await page + .getByPlaceholder('Enter URLs or select a Feed') + .fill( + 'http://invalid-url.com/feed, https://www.nasa.gov/feeds/iotd-feed/' + ); await page.getByRole('button', { name: 'Load Feed' }).click(); await page.waitForSelector('.feedzy-validation-results'); - await expect( page.locator('.feedzy-validation-results .is-error').getByText('http://invalid-url.com/feed', { exact: true }) ).toBeVisible(); + await expect( + page + .locator('.feedzy-validation-results .is-error') + .getByText('http://invalid-url.com/feed', { exact: true }) + ).toBeVisible(); + + await expect( + page + .locator('.feedzy-validation-results .is-success') + .getByText('https://www.nasa.gov/feeds/iotd-feed/', { + exact: true, + }) + ).toBeVisible(); + }); + + test('check thumbnail display', async ({ editor, page, admin }) => { + await admin.createNewPost(); + + await editor.insertBlock({ + name: 'feedzy-rss-feeds/loop', + attributes: { + feed: { + type: 'url', + source: 'https://www.nasa.gov/feeds/iotd-feed/', + }, + query: { + max: 1, + }, + }, + }); + + await page + .getByLabel('Display curated RSS content') + .click({ force: true }); + + await page.waitForSelector('.feedzy-loop-columns-1'); + + expect( + await page + .locator(`.wp-block-feedzy-rss-feeds-loop img[src*="https"]`) + .count() + ).toBeGreaterThan(0); + }); + + test('check no thumbnail display', async ({ editor, page, admin }) => { + await admin.createNewPost(); + + await editor.insertBlock({ + name: 'feedzy-rss-feeds/loop', + attributes: { + feed: { + type: 'url', + source: 'https://www.nasa.gov/feeds/iotd-feed/', + }, + query: { + max: 1, + }, + thumb: 'no', + }, + }); + + await page + .getByLabel('Display curated RSS content') + .click({ force: true }); + + await page.waitForSelector('.feedzy-loop-columns-1'); + + expect( + await page + .locator(`.wp-block-feedzy-rss-feeds-loop img[src*="https"]`) + .count() + ).toBe(0); + }); + + test('check default SVG thumbnail display', async ({ + editor, + page, + admin, + }) => { + await admin.createNewPost(); + + await editor.insertBlock({ + name: 'feedzy-rss-feeds/loop', + attributes: { + feed: { + type: 'url', + source: 'https://fasterthanli.me/index.xml', + }, + query: { + max: 1, + }, + thumb: 'yes', + }, + }); + + await page + .getByLabel('Display curated RSS content') + .click({ force: true }); + + await page.waitForSelector('.feedzy-loop-columns-1'); - await expect( page.locator('.feedzy-validation-results .is-success').getByText('https://www.nasa.gov/feeds/iotd-feed/', { exact: true }) ).toBeVisible(); + expect( + await page + .locator(`.wp-block-feedzy-rss-feeds-loop img[src*=".svg"]`) + .count() + ).toBeGreaterThan(0); }); }); From d5a62e5cc2205d55c7c84ab6c29c547e19b9762d Mon Sep 17 00:00:00 2001 From: Soare Robert Daniel Date: Tue, 19 Aug 2025 20:24:17 +0300 Subject: [PATCH 39/57] refactor: set preview to `true` in Loop (#1150) --- js/FeedzyLoop/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/FeedzyLoop/edit.js b/js/FeedzyLoop/edit.js index 7cc7220a7..b072d37f8 100644 --- a/js/FeedzyLoop/edit.js +++ b/js/FeedzyLoop/edit.js @@ -45,7 +45,7 @@ const Edit = ({ attributes, setAttributes, clientId }) => { const blockProps = useBlockProps(); const [isEditing, setIsEditing] = useState(!attributes?.feed?.source); - const [isPreviewing, setIsPreviewing] = useState(false); + const [isPreviewing, setIsPreviewing] = useState(true); const { clearSelectedBlock, replaceInnerBlocks } = useDispatch(blockEditorStore); From fa3282322ceb169ed75f5725f91613ba8b530958 Mon Sep 17 00:00:00 2001 From: RaduCristianPopescu <119046336+RaduCristianPopescu@users.noreply.github.com> Date: Wed, 20 Aug 2025 10:14:01 +0300 Subject: [PATCH 40/57] fix: remove Need Help? for Log and small description on Schedule (#1149) --- includes/layouts/settings.php | 2 +- includes/views/import-metabox-edit.php | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/includes/layouts/settings.php b/includes/layouts/settings.php index 0db658483..2a68ba22d 100644 --- a/includes/layouts/settings.php +++ b/includes/layouts/settings.php @@ -557,7 +557,7 @@ class="form-control"
    - +
    diff --git a/includes/views/import-metabox-edit.php b/includes/views/import-metabox-edit.php index 7de854a37..9d393d7fd 100644 --- a/includes/views/import-metabox-edit.php +++ b/includes/views/import-metabox-edit.php @@ -664,9 +664,6 @@ class="fz-switch-toggle" type="checkbox" value="yes" -
    - -
    From df2146e28d3a23cbe53c39cc05d6d6307e1c9131 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 10:43:31 +0300 Subject: [PATCH 41/57] fix: tabs menu alignment with bottom bar Replace the `:after` with a simple border and set tje alignment to that the active border has no gap with the bottom bar. --- css/settings.css | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/css/settings.css b/css/settings.css index 4a0719360..41076e7a0 100644 --- a/css/settings.css +++ b/css/settings.css @@ -1132,9 +1132,10 @@ input.fz-switch-toggle[type=checkbox]:checked:before{ } /* FZ tab style start */ -.fz-tabs-menu ul{ +.fz-tabs-menu ul { display: flex; flex-wrap: wrap; + align-items: flex-end; border-bottom: 1px solid #D9D9D9; } @@ -1147,40 +1148,26 @@ input.fz-switch-toggle[type=checkbox]:checked:before{ padding: 15px 16px; text-decoration: none; position: relative; - margin-bottom: 8px; -webkit-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out; + + border-bottom: 4px solid transparent; } .fz-tabs-menu ul li { margin-bottom: 0; } -.fz-tabs-menu ul li a:after{ - content: ""; - width: 100%; - height: 4px; - position: absolute; - bottom: -8px; - left: 0; - background: #4268CF; - opacity: 0; - -webkit-transition: all 0.3s ease-in-out; - -moz-transition: all 0.3s ease-in-out; - -o-transition: all 0.3s ease-in-out; - transition: all 0.3s ease-in-out; -} .fz-tabs-menu ul li a:not(.active):hover{ background: rgba(66, 104, 207, .1); } .fz-tabs-menu ul li a.active{ color: #4268CF; + border-bottom-color: #4268CF; } -.fz-tabs-menu ul li a.active:after{ - opacity: 1; -} + .fz-tabs-menu ul li a .pro-label{ font-size: 10px; line-height: 1; @@ -1190,9 +1177,7 @@ input.fz-switch-toggle[type=checkbox]:checked:before{ } .fz-tabs-menu ul li a:focus { color: #4268CF; -} -.fz-tabs-menu ul li a:focus:after { - opacity: 1; + border-bottom-color: #4268CF; } .fz-tabs-content .fz-tab-content{ From d5daac4c59c9705edb9d622ebb50e334a9143046 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 11:57:44 +0300 Subject: [PATCH 42/57] refactor: logs usage --- .../feedzy-rss-feeds-admin-abstract.php | 8 +- includes/admin/feedzy-rss-feeds-import.php | 110 ++++++++++++++++-- 2 files changed, 105 insertions(+), 13 deletions(-) diff --git a/includes/abstract/feedzy-rss-feeds-admin-abstract.php b/includes/abstract/feedzy-rss-feeds-admin-abstract.php index f56ba8c93..7818c8245 100644 --- a/includes/abstract/feedzy-rss-feeds-admin-abstract.php +++ b/includes/abstract/feedzy-rss-feeds-admin-abstract.php @@ -863,7 +863,7 @@ function ( $time ) use ( $cache_time ) { if ( ! $wp_filesystem->exists( $dir ) ) { $done = $wp_filesystem->mkdir( $dir ); if ( false === $done ) { - Feedzy_Rss_Feeds_Log::error( + Feedzy_Rss_Feeds_Log::warning( sprintf( 'Unable to create SimplePie cache directory: %s', $dir ), array( 'feed_url' => $feed_url, @@ -912,7 +912,7 @@ function ( $time ) use ( $cache_time ) { if ( ! empty( $error ) ) { Feedzy_Rss_Feeds_Log::error( - sprintf( 'Error while parsing feed: %s', $error ), + sprintf( __( 'Error while parsing feed URL: %s', 'feedzy-rss-rds' ), $error ), array( 'feed_url' => $feed_url, 'cache' => $cache, @@ -922,7 +922,7 @@ function ( $time ) use ( $cache_time ) { // curl: (60) SSL certificate problem: unable to get local issuer certificate. if ( strpos( $error, 'SSL certificate' ) !== false ) { - Feedzy_Rss_Feeds_Log::error( + Feedzy_Rss_Feeds_Log::warning( sprintf( 'Got an SSL Error (%s), retrying by ignoring SSL', $error ), array( 'feed_url' => $feed_url, @@ -951,7 +951,7 @@ function ( $time ) use ( $cache_time ) { } } else { Feedzy_Rss_Feeds_Log::debug( - 'Cannot use raw data as this is a multifeed URL', + 'Cannot use raw data as this is a multi-feed URL', array( 'feed_url' => $feed_url, 'cache' => $cache, diff --git a/includes/admin/feedzy-rss-feeds-import.php b/includes/admin/feedzy-rss-feeds-import.php index 9f954c141..b7b8c5edb 100644 --- a/includes/admin/feedzy-rss-feeds-import.php +++ b/includes/admin/feedzy-rss-feeds-import.php @@ -1372,8 +1372,20 @@ private function get_taxonomies() { */ private function run_now() { check_ajax_referer( FEEDZY_BASEFILE, 'security' ); + + $job_id = filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT ); + $job = get_post( $job_id ); + + Feedzy_Rss_Feeds_Log::info( + sprintf( + 'Manual run for import: %s', + isset( $job->post_title ) ? $job->post_title : 'Unknown Job' + ), + array( + 'job_id' => $job_id, + ) + ); - $job = get_post( filter_input( INPUT_POST, 'id', FILTER_SANITIZE_NUMBER_INT ) ); $count = $this->run_job( $job, 100 ); $msg = 0 < $count ? __( 'Successfully run!', 'feedzy-rss-feeds' ) : __( 'Nothing imported!', 'feedzy-rss-feeds' ); @@ -1484,6 +1496,13 @@ function ( $errors, $feed, $url ) { implode( ',', $tags ) ); + Feedzy_Rss_Feeds_Log::debug( + 'Dry run shortcode generated', + array( + 'shortcode' => $shortcode, + ) + ); + wp_send_json_success( array( 'output' => do_shortcode( $shortcode ) ) ); } @@ -1527,14 +1546,39 @@ public function run_cron( $max = 100, $job_id = 0 ) { foreach ( $feedzy_imports as $job ) { try { $result = $this->run_job( $job, $max ); + + Feedzy_Rss_Feeds_Log::debug( + 'Cron job run for: ' . $job->post_title, + array( + 'job_id' => $job->ID, + 'result' => $result, + ) + ); + if ( empty( $result ) ) { $this->run_job( $job, $max ); + + Feedzy_Rss_Feeds_Log::debug( + 'Previous run did not return any results, running again for job: ' . $job->post_title, + array( + 'job_id' => $job->ID, + 'result' => $result, + ) + ); } do_action( 'feedzy_run_cron_extra', $job ); } catch ( Exception $e ) { if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( '[Feedzy Run Cron][Post title: ' . ( ! empty( $job->post_title ) ? $job->post_title : '' ) . '] Error: ' . $e->getMessage() ); } + + Feedzy_Rss_Feeds_Log::error( + sprintf( __( 'Error when running "%1$s": %2$s', 'feedzy-rss-feeds' ), $job->post_title, $e->getMessage() ), + array( + 'job_id' => $job->ID, + 'error' => $e->getMessage(), + ) + ); } } } @@ -1747,6 +1791,7 @@ private function run_job( $job, $max ) { 'job_id' => $job->ID, 'errors' => $results->get_error_messages(), 'options' => $options, + 'source' => $source, ) ); @@ -1775,8 +1820,8 @@ private function run_job( $job, $max ) { $import_errors[] = __( 'Title & Content are both empty.', 'feedzy-rss-feeds' ); $start_import = false; - Feedzy_Rss_Feeds_Log::error( - 'Import job cannot start because both title and content are empty.', + Feedzy_Rss_Feeds_Log::warning( + 'Import job cannot start because both Title and Content mapping are empty.', array( 'job_id' => $job->ID, ) @@ -1848,6 +1893,14 @@ function ( $tag ) use ( $item_obj, $item ) { $found_duplicates[ $item_hash ] = get_post_meta( $p, 'feedzy_' . $mark_duplicate_key, true ); $duplicates[ $item['item_url'] ] = $item['item_title']; wp_delete_post( $p, true ); + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Deleted a duplicate post: %1$s', $item['item_title'] ), + array( + 'item_url' => $item['item_url'], + 'post_id' => $p, + 'hash' => $item_hash, + ) + ); } } } @@ -2087,10 +2140,16 @@ function ( $attr, $key ) { ); Feedzy_Rss_Feeds_Log::error( - sprintf( 'Full content is empty for item %1$s. Error: %2$s', $item['item_url'], $full_content_error ), + sprintf( + // translators: %s: Error message for empty full content. + __( 'Full content is empty. Error: %s', 'feedzy-rss-feeds' ), + $full_content_error + ), array( 'job_id' => $job->ID, 'import_errors' => $import_errors, + 'item_url' => $item['item_url'], + 'source' => $source, ) ); } @@ -2123,18 +2182,47 @@ function ( $attr, $key ) { if ( $import_auto_translation && false !== strpos( $post_content, '[#translated_full_content]' ) ) { $translated_full_content = apply_filters( 'feedzy_invoke_auto_translate_services', $item['item_url'], '[#translated_full_content]', $import_translation_lang, $job, $language_code, $item ); $post_content = str_replace( '[#translated_full_content]', rtrim( $translated_full_content, '.' ), $post_content ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Using auto-translation service for item full content: %1$s', $item['item_url'] ), + array( + 'job_id' => $job->ID, + 'service' => 'feedzy_invoke_auto_translate_services', + 'service_output' => $translated_full_content, + 'language_code' => $language_code, + ) + ); } // Rewriter item content from feedzy API. if ( $rewrite_service_endabled && false !== strpos( $post_content, '[#content_feedzy_rewrite]' ) ) { $item_content = ! empty( $item['item_content'] ) ? $item['item_content'] : $item['item_description']; $content_feedzy_rewrite = apply_filters( 'feedzy_invoke_content_rewrite_services', $item_content, '[#content_feedzy_rewrite]', $job, $item ); $post_content = str_replace( '[#content_feedzy_rewrite]', $content_feedzy_rewrite, $post_content ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Using Feedzy rewrite service for item content: %1$s', $item['item_url'] ), + array( + 'job_id' => $job->ID, + 'service' => 'feedzy_invoke_content_rewrite_services', + 'service_input' => $item_content, + 'service_output' => $content_feedzy_rewrite, + ) + ); } // Rewriter item full content from feedzy API. if ( $rewrite_service_endabled && false !== strpos( $post_content, '[#full_content_feedzy_rewrite]' ) ) { $full_content_feedzy_rewrite = apply_filters( 'feedzy_invoke_content_rewrite_services', $item['item_url'], '[#full_content_feedzy_rewrite]', $job, $item ); $post_content = str_replace( '[#full_content_feedzy_rewrite]', $full_content_feedzy_rewrite, $post_content ); + + Feedzy_Rss_Feeds_Log::debug( + sprintf( 'Using Feedzy rewrite service for item full content: %1$s', $item['item_url'] ), + array( + 'job_id' => $job->ID, + 'service' => 'feedzy_invoke_content_rewrite_services', + 'service_output' => $full_content_feedzy_rewrite, + ) + ); } // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date @@ -2313,7 +2401,7 @@ function ( $attr, $key ) { Feedzy_Rss_Feeds_Log::error( sprintf( // translators: %1$s is the item URL, %2$s is the error message. - 'Error while inserting post for %1$s: %2$s', + __( 'Could not save the "%1$s" as post: %2$s', 'feedzy-rss-feeds' ), esc_url( $item['item_url'] ), $new_post_id->get_error_message() ), @@ -2331,8 +2419,9 @@ function ( $attr, $key ) { Feedzy_Rss_Feeds_Log::info( 'Created a new post: ' . $new_post['post_title'], array( - 'job_id' => $job->ID, - 'post_id' => $new_post_id, + 'job_id' => $job->ID, + 'post_id' => $new_post_id, + 'post_link' => get_permalink( $new_post_id ), ) ); @@ -2518,10 +2607,12 @@ function ( $term ) { } } else { Feedzy_Rss_Feeds_Log::error( - sprintf( 'Error fetching image from Graby for item %1$s: %2$s', $item['item_url'], $response->get_error_message() ), + sprintf( __( 'Error fetching image from Graby for item "%1$s": %2$s', 'feedzy-rss-feeds' ), $item['item_url'], $response->get_error_message() ), array( 'job_id' => $job->ID, 'response' => $response, + 'item_url' => $item['item_url'], + 'source' => $source, ) ); } @@ -2637,6 +2728,7 @@ function ( $term ) { ), array( 'job_id' => $job->ID, + 'source' => $source, ) ); } @@ -2921,7 +3013,7 @@ private function try_save_featured_image( $img_source_url, $post_id, $post_title Feedzy_Rss_Feeds_Log::error( // translators: %s the name of the post. - sprintf( __( 'Could not rename temporary file for %s', 'feedzy-rss-feeds' ), get_the_title( $post_id ) ), + sprintf( __( 'Could not rename temporary file for: %s', 'feedzy-rss-feeds' ), get_the_title( $post_id ) ), array( 'post_id' => $post_id, 'local_file' => $local_file, From 82d5e82e492e6cec259e9272f15f7c4fbb6412dc Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 12:03:55 +0300 Subject: [PATCH 43/57] fix: post action btn icon placement --- includes/views/css/import-metabox-edit.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/views/css/import-metabox-edit.css b/includes/views/css/import-metabox-edit.css index 1e6053af3..1da338b6d 100644 --- a/includes/views/css/import-metabox-edit.css +++ b/includes/views/css/import-metabox-edit.css @@ -142,7 +142,7 @@ span.feedzy-spinner { .wp-core-ui .fz-export-import-btn, .wp-core-ui .feedzy-import-limit{ display: inline-flex !important; - align-items: center; + align-items: stretch; } .wp-core-ui .fz-header-action { From 0dfec6c16bc7ff441a654f9de85eb7307b0379e3 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 12:10:26 +0300 Subject: [PATCH 44/57] fix: restrict dashicon line-height only for page content --- css/settings.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/css/settings.css b/css/settings.css index 41076e7a0..a412151c0 100644 --- a/css/settings.css +++ b/css/settings.css @@ -2681,9 +2681,11 @@ li.draggable-item .components-panel__body-toggle.components-button{ font-weight: 500; color: #757575; } -span.dashicons { + +#wpbody-content span.dashicons { line-height: 1.3; } + @-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } } From a19d9f251696a33ec28c0c771f3d6c24f5760025 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 12:27:38 +0300 Subject: [PATCH 45/57] refactor: move `Email Reporting Frequency` to General tab --- includes/admin/feedzy-rss-feeds-admin.php | 1 + includes/admin/feedzy-rss-feeds-import.php | 5 +-- .../admin/feedzy-rss-feeds-task-manager.php | 5 +++ includes/layouts/settings.php | 37 +++++++++++++++++ includes/views/misc-view.php | 41 ------------------- 5 files changed, 45 insertions(+), 44 deletions(-) diff --git a/includes/admin/feedzy-rss-feeds-admin.php b/includes/admin/feedzy-rss-feeds-admin.php index 50cf3a513..d5830c4ce 100644 --- a/includes/admin/feedzy-rss-feeds-admin.php +++ b/includes/admin/feedzy-rss-feeds-admin.php @@ -1456,6 +1456,7 @@ function ( $item ) { $settings['logs']['level'] = isset( $_POST['logs-logging-level'] ) ? sanitize_text_field( wp_unslash( $_POST['logs-logging-level'] ) ) : ''; $settings['logs']['email'] = isset( $_POST['feedzy-email-error-address'] ) ? sanitize_email( wp_unslash( $_POST['feedzy-email-error-address'] ) ) : ''; $settings['logs']['send_email_report'] = isset( $_POST['feedzy-email-error-enabled'] ) ? absint( wp_unslash( $_POST['feedzy-email-error-enabled'] ) ) : ''; + $settings['logs']['email_frequency'] = isset( $_POST['logs-email-frequency'] ) ? sanitize_text_field( wp_unslash( $_POST['logs-email-frequency'] ) ) : ''; break; case 'headers': $settings['header']['user-agent'] = isset( $_POST['user-agent'] ) ? sanitize_text_field( wp_unslash( $_POST['user-agent'] ) ) : ''; diff --git a/includes/admin/feedzy-rss-feeds-import.php b/includes/admin/feedzy-rss-feeds-import.php index b7b8c5edb..d52218236 100644 --- a/includes/admin/feedzy-rss-feeds-import.php +++ b/includes/admin/feedzy-rss-feeds-import.php @@ -3356,9 +3356,8 @@ public function save_tab_settings( $settings, $tab ) { } if ( 'misc' === $tab ) { - $settings['canonical'] = isset( $_POST['canonical'] ) ? absint( $_POST['canonical'] ) : 0; - $settings['general']['rss-feeds'] = isset( $_POST['rss-feeds'] ) ? absint( $_POST['rss-feeds'] ) : ''; - $settings['logs']['email_frequency'] = isset( $_POST['logs-email-frequency'] ) ? sanitize_text_field( wp_unslash( $_POST['logs-email-frequency'] ) ) : ''; + $settings['canonical'] = isset( $_POST['canonical'] ) ? absint( $_POST['canonical'] ) : 0; + $settings['general']['rss-feeds'] = isset( $_POST['rss-feeds'] ) ? absint( $_POST['rss-feeds'] ) : ''; } return $settings; diff --git a/includes/admin/feedzy-rss-feeds-task-manager.php b/includes/admin/feedzy-rss-feeds-task-manager.php index a6843684c..c869140cc 100644 --- a/includes/admin/feedzy-rss-feeds-task-manager.php +++ b/includes/admin/feedzy-rss-feeds-task-manager.php @@ -92,6 +92,11 @@ public function schedule_email_report() { * @return void */ public function maybe_reschedule_email_report( $old_value, $value ) { + if ( ! isset( $value['logs'], $value['logs']['send_email_report'] ) || ! boolval( $value['logs']['send_email_report'] ) ) { + Feedzy_Rss_Feeds_Util_Scheduler::clear_scheduled_hook( 'task_feedzy_send_error_report' ); + return; + } + $old_freq = isset( $old_value['logs']['email_frequency'] ) ? sanitize_text_field( $old_value['logs']['email_frequency'] ) : ''; $new_freq = isset( $value['logs']['email_frequency'] ) ? sanitize_text_field( $value['logs']['email_frequency'] ) : ''; diff --git a/includes/layouts/settings.php b/includes/layouts/settings.php index 2a68ba22d..e572caa34 100644 --- a/includes/layouts/settings.php +++ b/includes/layouts/settings.php @@ -405,6 +405,43 @@ class="form-control" placeholder="" >
    +
    + + + +
    diff --git a/includes/views/misc-view.php b/includes/views/misc-view.php index 88589b4fe..543b10c31 100644 --- a/includes/views/misc-view.php +++ b/includes/views/misc-view.php @@ -46,45 +46,4 @@
    -
    -
    -
    -
    - - free_settings['logs'], $this->free_settings['logs']['email_frequency'] ) ? $this->free_settings['logs']['email_frequency'] : ''; - - $registered_schedules = wp_get_schedules(); - $schedules = array(); - - if ( isset( $registered_schedules['weekly'] ) ) { - $schedules['weekly'] = $registered_schedules['weekly']; - } - - if ( isset( $registered_schedules['daily'] ) ) { - $schedules['daily'] = $registered_schedules['daily']; - } - - ?> - -
    -
    -
    -
    From db0393a23ecacba9e932370a3378e89d430699d6 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 12:32:47 +0300 Subject: [PATCH 46/57] fix: translations issues --- includes/abstract/feedzy-rss-feeds-admin-abstract.php | 3 ++- includes/admin/feedzy-rss-feeds-import.php | 2 ++ includes/admin/feedzy-rss-feeds-ui.php | 2 +- includes/views/misc-view.php | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/includes/abstract/feedzy-rss-feeds-admin-abstract.php b/includes/abstract/feedzy-rss-feeds-admin-abstract.php index 7818c8245..bb6650787 100644 --- a/includes/abstract/feedzy-rss-feeds-admin-abstract.php +++ b/includes/abstract/feedzy-rss-feeds-admin-abstract.php @@ -912,7 +912,8 @@ function ( $time ) use ( $cache_time ) { if ( ! empty( $error ) ) { Feedzy_Rss_Feeds_Log::error( - sprintf( __( 'Error while parsing feed URL: %s', 'feedzy-rss-rds' ), $error ), + // translators: %1$s is the feed URL, %2$s is the error message. + sprintf( __( 'Error while parsing feed URL "%1$s": %2$s', 'feedzy-rss-feeds' ), $feed_url, $error ), array( 'feed_url' => $feed_url, 'cache' => $cache, diff --git a/includes/admin/feedzy-rss-feeds-import.php b/includes/admin/feedzy-rss-feeds-import.php index d52218236..a32915884 100644 --- a/includes/admin/feedzy-rss-feeds-import.php +++ b/includes/admin/feedzy-rss-feeds-import.php @@ -1573,6 +1573,7 @@ public function run_cron( $max = 100, $job_id = 0 ) { } Feedzy_Rss_Feeds_Log::error( + // translators: %1$s is the import job title, %2$s is the error message. sprintf( __( 'Error when running "%1$s": %2$s', 'feedzy-rss-feeds' ), $job->post_title, $e->getMessage() ), array( 'job_id' => $job->ID, @@ -2607,6 +2608,7 @@ function ( $term ) { } } else { Feedzy_Rss_Feeds_Log::error( + // translators: %1$s is the item URL, %2$s is the error message. sprintf( __( 'Error fetching image from Graby for item "%1$s": %2$s', 'feedzy-rss-feeds' ), $item['item_url'], $response->get_error_message() ), array( 'job_id' => $job->ID, diff --git a/includes/admin/feedzy-rss-feeds-ui.php b/includes/admin/feedzy-rss-feeds-ui.php index a7e9006d6..aa02b874e 100644 --- a/includes/admin/feedzy-rss-feeds-ui.php +++ b/includes/admin/feedzy-rss-feeds-ui.php @@ -237,7 +237,7 @@ public function feedzy_import_post_title_section() { $upgrade_url = tsdk_translate_link( tsdk_utmify( FEEDZY_UPSELL_LINK, 'post_title', 'import-screen' ) ); $content = __( 'You are using Feedzy Lite.', 'feedzy-rss-feeds' ) . ' '; - // translators: %1$s: opening anchor tag, %2$s: closing anchor tag + // translators: %1$s: opening anchor tag, %2$s: closing anchor tag. $content .= wp_sprintf( __( 'Unlock more powerful features, by %1$s upgrading to Feedzy Pro %2$s and get 50%% off.', 'feedzy-rss-feeds' ), '', '' ); echo wp_kses_post( $content ); diff --git a/includes/views/misc-view.php b/includes/views/misc-view.php index 543b10c31..833ab5746 100644 --- a/includes/views/misc-view.php +++ b/includes/views/misc-view.php @@ -20,7 +20,7 @@ ', '' From fda04bb33e132fe798666243752557ea813da017 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 13:22:50 +0300 Subject: [PATCH 47/57] fix: date parsing compatibility in Feedzy Classic for Safari Part of https://github.com/Codeinwp/feedzy-rss-feeds-pro/issues/864 The way `itemDateTime` was constructed was not valid for Safari implementation of Date. Switched to using only `moment` library. --- js/FeedzyBlock/Editor.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/js/FeedzyBlock/Editor.js b/js/FeedzyBlock/Editor.js index 902c9ee25..979e7843f 100644 --- a/js/FeedzyBlock/Editor.js +++ b/js/FeedzyBlock/Editor.js @@ -713,17 +713,14 @@ class Editor extends Component { const categories = unescapeHTML(item.categories) || ''; if (this.metaExists('tz=local')) { - let itemDateTimeObj = new Date( - itemDateTime + const itemDateTimeObj = window.moment( + itemDateTime, + 'MMMM D, YYYY h:mm a [UTC] Z' ); - itemDateTimeObj = - itemDateTimeObj.toUTCString(); - itemDate = window.moment - .utc(itemDateTimeObj) - .format('MMMM D, YYYY'); - itemTime = window.moment - .utc(itemDateTimeObj) - .format('h:mm A'); + + itemDate = + itemDateTimeObj.format('MMMM D, YYYY'); + itemTime = itemDateTimeObj.format('h:mm A'); } let author = From 0ea8d946520074ee872326990b73f46d8ac55bab Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 13:50:08 +0300 Subject: [PATCH 48/57] fix: labels tweaks - Remove `Powerful features available only...` - Add hierarchy icon for Integration and Settings - Add PRO label for disabled field type in conditions --- includes/admin/feedzy-rss-feeds-admin.php | 4 ++-- includes/layouts/feedzy-pro.php | 1 - js/Conditions/ConditionsControl.js | 7 ++++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/includes/admin/feedzy-rss-feeds-admin.php b/includes/admin/feedzy-rss-feeds-admin.php index d5830c4ce..0292f688f 100644 --- a/includes/admin/feedzy-rss-feeds-admin.php +++ b/includes/admin/feedzy-rss-feeds-admin.php @@ -1276,7 +1276,7 @@ public function feedzy_menu_pages() { add_submenu_page( 'feedzy-admin-menu', __( 'Settings', 'feedzy-rss-feeds' ), - __( 'Settings', 'feedzy-rss-feeds' ), + '↳ ' . __( 'Settings', 'feedzy-rss-feeds' ), 'manage_options', 'feedzy-settings', array( @@ -1287,7 +1287,7 @@ public function feedzy_menu_pages() { add_submenu_page( 'feedzy-admin-menu', __( 'Integration', 'feedzy-rss-feeds' ), - __( 'Integration', 'feedzy-rss-feeds' ), + '↳ ' . __( 'Integration', 'feedzy-rss-feeds' ), 'manage_options', 'feedzy-integration', array( diff --git a/includes/layouts/feedzy-pro.php b/includes/layouts/feedzy-pro.php index 4b17957f6..296b95d45 100644 --- a/includes/layouts/feedzy-pro.php +++ b/includes/layouts/feedzy-pro.php @@ -1,6 +1,5 @@
    -

      diff --git a/js/Conditions/ConditionsControl.js b/js/Conditions/ConditionsControl.js index fb8b7a15a..f1eaae0ac 100644 --- a/js/Conditions/ConditionsControl.js +++ b/js/Conditions/ConditionsControl.js @@ -178,6 +178,11 @@ const ConditionsControl = ({ conditions, setConditions }) => { ).filter( (key) => !field?.unsupportedOperators?.includes(key) ); + const fieldOptions = SUPPORTED_FIELDS.map((f) => ({ + label: `${f.label}${f.disabled ? ' (PRO)' : ''}`, + value: f.value, + disabled: f.disabled, + })); return ( { onChangeCondition(index, value, 'field') } From ac636a819cdf94b7e72780e34354cf4145f51518 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 14:16:29 +0300 Subject: [PATCH 49/57] fix: open the Service Action when added --- js/ActionPopup/RewriteActionItem.js | 2 +- js/ActionPopup/SortableItem.js | 17 +++++++++-------- js/ActionPopup/index.js | 10 ++++++++-- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/js/ActionPopup/RewriteActionItem.js b/js/ActionPopup/RewriteActionItem.js index db55c0745..d26c3b1d5 100644 --- a/js/ActionPopup/RewriteActionItem.js +++ b/js/ActionPopup/RewriteActionItem.js @@ -284,7 +284,7 @@ const RewriteActionItem = ({ counter, item, loopIndex, propRef }) => { { const SortableItem = ({ propRef, loopIndex, item }) => { const counter = loopIndex + 1; + const isInitialOpen = loopIndex === propRef?.lastAddedActionIdx; if ('trim' === item.id) { return ( @@ -132,7 +133,7 @@ const SortableItem = ({ propRef, loopIndex, item }) => { @@ -194,7 +195,7 @@ const SortableItem = ({ propRef, loopIndex, item }) => { @@ -322,7 +323,7 @@ const SortableItem = ({ propRef, loopIndex, item }) => { { @@ -472,7 +473,7 @@ const SortableItem = ({ propRef, loopIndex, item }) => { 'feedzy-rss-feeds' )} icon={DragHandle} - initialOpen={false} + initialOpen={isInitialOpen} className="fz-hide-icon" > { { 'feedzy-rss-feeds' )} icon={DragHandle} - initialOpen={false} + initialOpen={isInitialOpen} > { { const [isDisabledAddNew, setDisabledAddNew] = useState(false); const [isLoading, setLoading] = useState(false); const [currentCustomRow, setcurrentCustomRow] = useState(null); + const [lastAddedActionIdx, setLastAddedActionIdx] = useState(-1); // Close the popup when click on outside the modal. const exitModalOnOutsideClick = useCallback( @@ -114,6 +115,7 @@ const ActionModal = () => { setAction([]); setLoading(false); setcurrentCustomRow(null); + setLastAddedActionIdx(-1); }; const hideIntroMessage = (status) => setHideMeg(status); const removeAction = (index) => { @@ -160,6 +162,7 @@ const ActionModal = () => { setDisabledAddNew(() => 'item_image' === shortCode); setAction((prevState) => [...prevState, ...newAction]); toggleVisible(false); + setLastAddedActionIdx(action.length); }; const onSortEnd = ({ oldIndex, newIndex }) => { @@ -252,7 +255,9 @@ const ActionModal = () => { return document.querySelector('.fz-action-popup .fz-action-panel ul'); }; - const actionButtonTitle = action.length ? __( 'Save Actions', 'feedzy-rss-feeds' ) : __( 'Skip Actions', 'feedzy-rss-feeds' ); + const actionButtonTitle = action.length + ? __('Save Actions', 'feedzy-rss-feeds') + : __('Skip Actions', 'feedzy-rss-feeds'); // Click to open action popup. document.querySelectorAll('[data-action_popup]').forEach((actionItem) => { @@ -464,6 +469,7 @@ const ActionModal = () => { distance={1} lockToContainerEdges={true} lockOffset="0%" + lastAddedActionIdx={lastAddedActionIdx} /> )} @@ -794,7 +800,7 @@ const ActionModal = () => { saveAction(); }} > - { actionButtonTitle } + {actionButtonTitle} )}
    From 6ae9c69d62cc70a5e66d96230636c92570da5161 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 15:38:39 +0300 Subject: [PATCH 50/57] fix: labels display --- includes/layouts/feedzy-tutorial.php | 2 +- includes/layouts/settings.php | 16 +-- js/FeedzyLoop/edit.js | 145 +++++++++++++++++---------- js/feedzy-setting.js | 19 +++- phpstan-baseline.neon | 5 - 5 files changed, 110 insertions(+), 77 deletions(-) diff --git a/includes/layouts/feedzy-tutorial.php b/includes/layouts/feedzy-tutorial.php index 84dc7a820..244c3b582 100644 --- a/includes/layouts/feedzy-tutorial.php +++ b/includes/layouts/feedzy-tutorial.php @@ -47,7 +47,7 @@
    diff --git a/includes/layouts/settings.php b/includes/layouts/settings.php index e572caa34..81c0afea3 100644 --- a/includes/layouts/settings.php +++ b/includes/layouts/settings.php @@ -179,16 +179,6 @@ class=""
    -
    - How it works: When importing posts from RSS feeds, some items may not include images. This fallback image ensures all your imported posts have visual content by automatically applying this image as the featured image when needed.', 'feedzy-rss-feeds' ), - array( - 'strong' => true, - ) - ); - ?> -
    @@ -388,11 +378,11 @@ class="form-label"
    - +
    -
    -
    - - -
    -
    -
    -
    -
    - /> - -
    -
    -
    -
    -
    @@ -446,40 +416,6 @@ class="fz-form-group fz-log-email-freq -
    - -
    -
    - - -
    -
    -
    -
    diff --git a/includes/views/misc-view.php b/includes/views/misc-view.php index 833ab5746..4fc650ce9 100644 --- a/includes/views/misc-view.php +++ b/includes/views/misc-view.php @@ -46,4 +46,51 @@
    +
    +
    + + +
    +
    + +
    +
    + + free_settings['general']['feedzy-delete-media'] ) && + 1 === intval( $this->free_settings['general']['feedzy-delete-media'] ) + ) { + $delete_media = 1; + } + + $feedzy_delete_days = isset( $this->free_settings['general']['feedzy-delete-days'] ) ? $this->free_settings['general']['feedzy-delete-days'] : 0; + ?> +
    +
    + + +
    +
    +
    +
    +
    + /> + +
    +
    +
    +
    +
    +
    From 594e670be7d2d0dd2a738931d9b191637fdf0972 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 16:42:15 +0300 Subject: [PATCH 52/57] fix: Loop controls positions on editing mode --- js/FeedzyLoop/controls.js | 578 ++++++++++++++++++++------------------ js/FeedzyLoop/edit.js | 139 +++++---- 2 files changed, 374 insertions(+), 343 deletions(-) diff --git a/js/FeedzyLoop/controls.js b/js/FeedzyLoop/controls.js index 925f472c8..bf652bbbd 100644 --- a/js/FeedzyLoop/controls.js +++ b/js/FeedzyLoop/controls.js @@ -1,7 +1,7 @@ /** * WordPress dependencies. */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { BlockControls, InspectorControls } from '@wordpress/block-editor'; @@ -25,6 +25,16 @@ import { Fragment } from '@wordpress/element'; import ConditionsControl from '../Conditions/ConditionsControl'; import FallbackImageLoader from './components/FallbackImageLoader.jsx'; +// Make this available to all components in this module +function decodeHtmlEntities(str) { + if (typeof str !== 'string') { + return str; + } + const textarea = document.createElement('textarea'); + textarea.innerHTML = str; + return textarea.value; +} + const Controls = ({ attributes, isEditing, @@ -37,15 +47,6 @@ const Controls = ({ variations, setVariations, }) => { - const decodeHtmlEntities = (str) => { - if (typeof str !== 'string') { - return str; - } - const textarea = document.createElement('textarea'); - textarea.innerHTML = str; - return textarea.value; - }; - return ( <> @@ -68,290 +69,331 @@ const Controls = ({ - - - -
    - {variations?.map((variation) => ( - - ))} -
    -
    + +
    + {!isEditing && ( + + )} +
    +
    - - onChangeLayout({ type: 'columnCount', value }) - } - min={1} - max={5} - /> + +
    + {!isEditing && ( + + )} +
    +
    + + ); +}; - - onChangeQuery({ type: 'max', value }) - } - min={1} - max={20} - /> +function CustomInspectorControls({ + attributes, + isEditing, + setIsEditing, + onChangeLayout, + onChangeQuery, + setAttributes, + variations, + setVariations, +}) { + return ( + + + +
    + {variations?.map((variation) => ( + + ))} +
    +
    - - {__('Feedzy Loop Documentation', 'feedzy-rss-feeds')} - -
    + + onChangeLayout({ type: 'columnCount', value }) + } + min={1} + max={5} + /> - {!isEditing && ( - - - - )} - - setAttributes({ thumb: value })} - className="feedzy-thumb" - /> + onChangeQuery({ type: 'max', value })} + min={1} + max={20} + /> - {attributes?.thumb !== 'no' && ( - - {attributes?.thumb !== 'auto' && ( - - setAttributes({ - fallbackImage: imageData, - }) - } - onRemoveImage={() => - setAttributes({ - fallbackImage: undefined, - }) - } - label={__( - 'Fallback image if no image is found.', - 'feedzy-rss-feeds' - )} - /> - )} - - )} - - - {!window.feedzyData.isPro && ( -
    - {__( - 'Unlock more advanced options with', - 'feedzy-rss-feeds' - )}{' '} - - {__('Feedzy Pro', 'feedzy-rss-feeds')} - -
    - )} + {__('Feedzy Loop Documentation', 'feedzy-rss-feeds')} + +
    - { - setAttributes({ conditions }); - }} - /> -
    + {!isEditing && ( Pro - ), - ]} initialOpen={false} - className={ - window.feedzyjs.isPro - ? 'feedzy-pro-options' - : 'feedzy-pro-options fz-locked' - } + title={__('Feed Source', 'feedzy-rss-feeds')} + key="source" > - {!window.feedzyjs.isPro && ( -
    - {__( - 'Unlock this feature and more advanced options with', - 'feedzy-rss-feeds' - )}{' '} - - {__('Feedzy Pro', 'feedzy-rss-feeds')} - -
    - )} - { - window.tiTrk - ?.with('feedzy') - .add({ feature: 'block-referral-url' }); - setAttributes({ referral_url: value }); +
    -
    + )} - + onChangeQuery({ type: 'sort', value })} + onChange={(value) => setAttributes({ thumb: value })} + className="feedzy-thumb" /> - - onChangeQuery({ type: 'refresh', value }) + {attributes?.thumb !== 'no' && ( + + {attributes?.thumb !== 'auto' && ( + + setAttributes({ + fallbackImage: imageData, + }) + } + onRemoveImage={() => + setAttributes({ + fallbackImage: undefined, + }) + } + label={__( + 'Fallback image if no image is found.', + 'feedzy-rss-feeds' + )} + /> + )} + + )} + + + + {!window.feedzyData?.isPro && ( +
    + {__( + 'Unlock more advanced options with', + 'feedzy-rss-feeds' + )}{' '} + + {__('Feedzy Pro', 'feedzy-rss-feeds')} + +
    + )} + + { + setAttributes({ conditions }); + }} /> -
    - + + + Pro + ), + ]} + initialOpen={false} + className={ + window.feedzyData?.isPro + ? 'feedzy-pro-options' + : 'feedzy-pro-options fz-locked' + } + > + {!window.feedzyData?.isPro && ( +
    + {__( + 'Unlock this feature and more advanced options with', + 'feedzy-rss-feeds' + )}{' '} + + {__('Feedzy Pro', 'feedzy-rss-feeds')} + +
    + )} + { + window.tiTrk + ?.with?.('feedzy') + ?.add?.({ feature: 'block-referral-url' }); + setAttributes({ referral_url: value }); + }} + /> +
    + ); -}; +} + +function CustomAdvancedControls({ attributes, onChangeQuery }) { + return ( + + onChangeQuery({ type: 'sort', value })} + /> + + onChangeQuery({ type: 'refresh', value })} + /> + + ); +} export default Controls; diff --git a/js/FeedzyLoop/edit.js b/js/FeedzyLoop/edit.js index acad974d1..32545790b 100644 --- a/js/FeedzyLoop/edit.js +++ b/js/FeedzyLoop/edit.js @@ -141,20 +141,74 @@ const Edit = ({ attributes, setAttributes, clientId }) => { } }; + let blockContent; + if (isEditing) { - return ( -
    - + ); + } else if ((!isSelected || isPreviewing) && innerBlocksContent) { + blockContent = ( + + {showPreviewNotice && ( + { + setShowPreviewNotice(false); + localStorage.setItem( + 'feedzy-hide-preview-notice', + 'true' + ); + }} + > +

    + + {__( + "You're in Preview Mode – This shows how your feed will look to visitors.", + 'feedzy-rss-feeds' + )} + +

    +

    + {sprintf( + // translators: %1$s is button label "Hide Preview". + __( + 'To customize each element (title, meta, description) and adjust layouts, spacing, colors, and typography, click "%1$s" in the toolbar above to enter the advanced editor.', + 'feedzy-rss-feeds' + ), + __('Hide Preview', 'feedzy-rss-feeds') + )} +

    +
    + )} + -
    + ); + } else if (!hasInnerBlocks && !isEditing) { + blockContent = ( + + ); + } else { + blockContent = ; } return ( - <> + { variations={variations} setVariations={setVariations} /> -
    - {(() => { - if ((!isSelected || isPreviewing) && innerBlocksContent) { - return ( - - {showPreviewNotice && ( - { - setShowPreviewNotice(false); - localStorage.setItem( - 'feedzy-hide-preview-notice', - 'true' - ); - }} - > -

    - - {__( - "You're in Preview Mode – This shows how your feed will look to visitors.", - 'feedzy-rss-feeds' - )} - -

    -

    - {sprintf( - // translators: %1$s is button label "Hide Preview". - __( - 'To customize each element (title, meta, description) and adjust layouts, spacing, colors, and typography, click "%1$s" in the toolbar above to enter the advanced editor.', - 'feedzy-rss-feeds' - ), - __( - 'Hide Preview', - 'feedzy-rss-feeds' - ) - )} -

    -
    - )} - -
    - ); - } - - if (hasInnerBlocks) { - return ; - } - - return ( - - ); - })()} -
    - +
    {blockContent}
    +
    ); }; From 8d913fe77ff6a2b5e7c54a91ee7edaacbf89eb64 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 16:44:17 +0300 Subject: [PATCH 53/57] chore: bump `tested_up` to 3.1 for PRO --- feedzy-rss-feed.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feedzy-rss-feed.php b/feedzy-rss-feed.php index eb3fd085a..8e7287482 100644 --- a/feedzy-rss-feed.php +++ b/feedzy-rss-feed.php @@ -152,7 +152,7 @@ function ( $compatibilities ) { $compatibilities['FeedzyPRO'] = array( 'basefile' => defined( 'FEEDZY_PRO_BASEFILE' ) ? FEEDZY_PRO_BASEFILE : '', 'required' => '2.4', - 'tested_up' => '3.0', + 'tested_up' => '3.1', ); return $compatibilities; } From 1acc4a8815d9388a895e43a341540c5b554c0572 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 17:07:14 +0300 Subject: [PATCH 54/57] dev: e2e fix --- tests/e2e/specs/loop.spec.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/e2e/specs/loop.spec.js b/tests/e2e/specs/loop.spec.js index ce4abc5e0..358b37ac4 100644 --- a/tests/e2e/specs/loop.spec.js +++ b/tests/e2e/specs/loop.spec.js @@ -61,11 +61,7 @@ test.describe('Feedzy Loop', () => { // Now that we have tested we can insert URL, we can test the Feed Group. - await page - .getByLabel('Block: Feedzy Loop') - .locator('div') - .nth(1) - .click(); + await page.locator('.wp-block-feedzy-rss-feeds-loop').nth(1).click(); await page.getByRole('button', { name: 'Edit Feed' }).click(); await page.getByRole('button', { name: 'Select Feed Group' }).click(); From af6757c6fd5cb4b481d3b4062a29c8ec83691e99 Mon Sep 17 00:00:00 2001 From: Soare Robert-Daniel Date: Wed, 20 Aug 2025 17:12:59 +0300 Subject: [PATCH 55/57] feat: focus on Post Title on new imports --- includes/views/js/import-metabox-edit.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/includes/views/js/import-metabox-edit.js b/includes/views/js/import-metabox-edit.js index f66afd28d..7a4e58e8c 100644 --- a/includes/views/js/import-metabox-edit.js +++ b/includes/views/js/import-metabox-edit.js @@ -219,7 +219,7 @@ const $closeButton = $('