diff --git a/.distignore b/.distignore index 4df8b2c26..d8134f3ec 100755 --- a/.distignore +++ b/.distignore @@ -44,4 +44,5 @@ docker-compose.ci.yml artifacts phpstan.neon phpstan-baseline.neon -languages \ No newline at end of file +languages +AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..7381662be --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,107 @@ +# Agent workflow + +## Project Overview + +Feedzy RSS Feeds is a WordPress plugin (lite/free version) for RSS aggregation, content curation, and autoblogging. It supports importing RSS feeds as WordPress posts, displaying feeds via shortcodes/blocks/widgets, and integrates with Elementor. A separate Pro plugin extends its functionality. + +- **Text domain:** `feedzy-rss-feeds` +- **Min PHP:** 7.2 | **Min WP:** 6.0 +- **Main bootstrap:** `feedzy-rss-feed.php` (defines constants, registers autoloader, runs plugin) + +## Commands + +### Build & Dev (JS/Blocks) +```bash +npm install # Install JS dependencies +npm run build # Production build all blocks/JS bundles +npm run dev # Watch mode for all blocks (parallel) +npm run build:block # Build just the Gutenberg block +npm run build:loop # Build just the Loop block +``` + +### PHP Linting & Static Analysis +```bash +composer install # Install PHP dependencies +composer run lint # Run PHPCS (WordPress Coding Standards) +composer run format # Auto-fix PHPCS issues +composer run phpstan # Run PHPStan (level 6, includes/ only) +``` + +### PHPUnit Tests +```bash +# Requires WordPress test suite (MySQL service needed) +bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1:3306 +phpunit # Run all unit tests +phpunit tests/test-plugin.php # Run a single test file +``` + +### E2E Tests (Playwright + wp-env) +```bash +npm run wp-env start # Start Docker-based WordPress environment +npm run test:e2e # Run Playwright E2E tests +npm run test:e2e:debug # Run E2E with Playwright UI mode +``` + +### Other +```bash +npm run grunt # Run Grunt tasks (readme.txt → readme.md conversion, version bumping) +npm run dist # Build dist package (bin/dist.sh) +npm run lint:js # Lint JS files via wp-scripts +``` + +## Architecture + +### Autoloading Convention +The plugin uses a custom `spl_autoload_register` in the bootstrap file. Classes prefixed with `Feedzy_Rss_Feeds` are resolved by converting underscores to hyphens and lowering the case, then searching these directories in order: +1. `includes/` +2. `includes/abstract/` +3. `includes/admin/` +4. `includes/gutenberg/` +5. `includes/util/` +6. `includes/elementor/` + +Example: `Feedzy_Rss_Feeds_Admin` → `includes/admin/feedzy-rss-feeds-admin.php` + +### Core Class Hierarchy +- **`Feedzy_Rss_Feeds`** (`includes/feedzy-rss-feeds.php`) — Singleton core class. Loads dependencies and registers all hooks via the Loader pattern. +- **`Feedzy_Rss_Feeds_Loader`** (`includes/feedzy-rss-feeds-loader.php`) — Central hook registration (actions/filters stored in arrays, executed via `run()`). +- **`Feedzy_Rss_Feeds_Admin_Abstract`** (`includes/abstract/`) — Base class for admin functionality including feed fetching, shortcode rendering, and image handling. +- **`Feedzy_Rss_Feeds_Admin`** (`includes/admin/feedzy-rss-feeds-admin.php`) — Extends abstract. Handles admin UI, post types (`feedzy_categories`, `feedzy_imports`), REST routes, settings, shortcode `[feedzy-rss]`. +- **`Feedzy_Rss_Feeds_Import`** (`includes/admin/feedzy-rss-feeds-import.php`) — Feed-to-post import engine. Manages cron jobs, import post type metaboxes, magic tags. + +### Custom Post Types +- `feedzy_categories` — Groups of feed URLs for reuse. +- `feedzy_imports` — Import job configurations (feed source → WordPress posts). + +### JavaScript / Block Architecture +Blocks are built with `@wordpress/scripts`. Each has its own entry point under `js/`: +- `js/FeedzyBlock/` → `build/block/` — Main Gutenberg block for displaying feeds +- `js/FeedzyLoop/` → `build/loop/` — Loop block variant +- `js/Onboarding/` → `build/onboarding/` — Setup wizard +- `js/ActionPopup/` → `build/action-popup/` — Action chain popup +- `js/Conditions/` → `build/conditions/` — Import filter conditions UI +- `js/FeedBack/` → `build/feedback/` — Feedback prompt +- `js/Review/` → `build/review/` — Review prompt + +Legacy JS files (non-bundled) in `js/` root: TinyMCE integration, Elementor widget, lazy loading, settings, categories. + +### Pro Plugin Integration +The lite plugin checks for Pro via `feedzy_is_pro()` and `FEEDZY_PRO_BASEFILE` / `FEEDZY_PRO_ABSPATH` constants. Many hooks have `feedzy_` prefixed filters that Pro extends. The import feature conditionally loads based on `feedzy_is_pro(false)` or `has_filter('feedzy_free_has_import')`. + +### Key WordPress Hooks +- Shortcode: `[feedzy-rss]` registered in `define_admin_hooks()` +- REST API: routes registered via `rest_route` on `rest_api_init` +- Cron: `feedzy_cron` action drives scheduled imports +- Logging: `feedzy_log` action → `Feedzy_Rss_Feeds_Log` class + +### Tests +- **PHPUnit tests:** `tests/test-*.php` files (prefixed with `test-`), bootstrapped by `tests/bootstrap.php`. Require WP test suite installation. +- **E2E tests:** `tests/e2e/specs/*.spec.js` using Playwright with `@wordpress/e2e-test-utils-playwright`. Run against `wp-env` Docker environment. +- **PHPStan stubs:** `tests/php/static-analysis-stubs/` provides type stubs for static analysis. + +## Coding Standards + +- Follows WordPress Coding Standards enforced via PHPCS (`phpcs.xml`) +- WordPress-VIP-Go rules included +- PHPStan level 6 for `includes/` directory +- JS linting via `@wordpress/eslint-plugin` diff --git a/composer.lock b/composer.lock index ca4524bcb..0d36c042d 100644 --- a/composer.lock +++ b/composer.lock @@ -8,21 +8,21 @@ "packages": [ { "name": "codeinwp/themeisle-sdk", - "version": "3.3.49", + "version": "3.3.51", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "605f78bbbd8526f7597a89077791043d9ecc8c20" + "reference": "bb2a8414b0418b18c68c9ff1df3d7fb10467928d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/605f78bbbd8526f7597a89077791043d9ecc8c20", - "reference": "605f78bbbd8526f7597a89077791043d9ecc8c20", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/bb2a8414b0418b18c68c9ff1df3d7fb10467928d", + "reference": "bb2a8414b0418b18c68c9ff1df3d7fb10467928d", "shasum": "" }, "require-dev": { "codeinwp/phpcs-ruleset": "dev-main", - "yoast/phpunit-polyfills": "^2.0" + "yoast/phpunit-polyfills": "^4.0" }, "type": "library", "notification-url": "https://packagist.org/downloads/", @@ -43,9 +43,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.49" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.51" }, - "time": "2025-09-18T13:41:05+00:00" + "time": "2026-03-30T07:58:49+00:00" } ], "packages-dev": [ @@ -2533,5 +2533,5 @@ "platform-overrides": { "php": "7.2" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/css/settings.css b/css/settings.css index f4a89a5d6..9cdc81383 100644 --- a/css/settings.css +++ b/css/settings.css @@ -835,7 +835,6 @@ input.fz-switch-toggle[type=checkbox]:checked:before{ outline: 0; cursor: pointer; text-decoration: none; - text-transform: capitalize; -webkit-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; @@ -1564,7 +1563,6 @@ input.fz-switch-toggle[type=checkbox]:checked:before{ background: transparent; border-color: #4268CF; color: #4268CF; - text-transform: capitalize; -webkit-transition: all 0.3s ease-in-out; -moz-transition: all 0.3s ease-in-out; -o-transition: all 0.3s ease-in-out; @@ -2250,7 +2248,6 @@ li.draggable-item .components-panel__body-toggle.components-button{ } .fz-content-action div .tagify__tag-text { padding-right: 10px; - text-transform: capitalize; } .popover-action-list ul li.fz-action-disabled { cursor: not-allowed !important; @@ -2971,3 +2968,7 @@ button.feedzy-action-button { font-size: 12px; padding: 4px 12px; } +.tagify__tag .feedzy-custom-tag { + cursor: auto; + line-height: 1.5; +} diff --git a/feedzy-rss-feed.php b/feedzy-rss-feed.php index 63ca97ceb..7ebc1bac5 100644 --- a/feedzy-rss-feed.php +++ b/feedzy-rss-feed.php @@ -324,6 +324,28 @@ function ( $labels ) { ); } + if ( isset( $labels['about_us'] ) ) { + $labels['about_us'] = array_merge( + $labels['about_us'], + array( + 'title' => __( 'About Us', 'feedzy-rss-feeds' ), + 'heroHeader' => __( 'Our Story', 'feedzy-rss-feeds' ), + 'heroTextFirst' => __( 'Themeisle was founded in 2012 by a group of passionate developers who wanted to create beautiful and functional WordPress themes and plugins. Since then, we have grown into a team of over 20 dedicated professionals who are committed to delivering the best possible products to our customers.', 'feedzy-rss-feeds' ), + 'heroTextSecond' => __( 'At Themeisle, we offer a wide range of WordPress themes and plugins that are designed to meet the needs of both beginners and advanced users. Our products are feature-rich, easy to use, and are designed to help you create beautiful and functional websites.', 'feedzy-rss-feeds' ), + 'teamImageCaption' => __( 'Our team in WCEU2022 in Portugal', 'feedzy-rss-feeds' ), + 'newsHeading' => __( 'Stay connected for news & updates!', 'feedzy-rss-feeds' ), + 'emailPlaceholder' => __( 'Your email address', 'feedzy-rss-feeds' ), + 'signMeUp' => __( 'Sign me up', 'feedzy-rss-feeds' ), + 'installNow' => __( 'Install Now', 'feedzy-rss-feeds' ), + 'activate' => __( 'Activate', 'feedzy-rss-feeds' ), + 'learnMore' => __( 'Learn More', 'feedzy-rss-feeds' ), + 'installed' => __( 'Installed', 'feedzy-rss-feeds' ), + 'notInstalled' => __( 'Not Installed', 'feedzy-rss-feeds' ), + 'active' => __( 'Active', 'feedzy-rss-feeds' ), + ) + ); + } + return $labels; } ); diff --git a/img/import-full-rss-posts.jpg b/img/import-full-rss-posts.jpg new file mode 100644 index 000000000..d2a8ca029 Binary files /dev/null and b/img/import-full-rss-posts.jpg differ diff --git a/img/jobs-from-rss.jpg b/img/jobs-from-rss.jpg new file mode 100644 index 000000000..145c11ac4 Binary files /dev/null and b/img/jobs-from-rss.jpg differ diff --git a/img/rss-to-wordpress.jpg b/img/rss-to-wordpress.jpg new file mode 100644 index 000000000..b1e8546da Binary files /dev/null and b/img/rss-to-wordpress.jpg differ diff --git a/img/youtube-to-wordpress.jpg b/img/youtube-to-wordpress.jpg new file mode 100644 index 000000000..38c64c009 Binary files /dev/null and b/img/youtube-to-wordpress.jpg differ diff --git a/includes/abstract/feedzy-rss-feeds-admin-abstract.php b/includes/abstract/feedzy-rss-feeds-admin-abstract.php index 2cc5d0a47..0b032db6f 100644 --- a/includes/abstract/feedzy-rss-feeds-admin-abstract.php +++ b/includes/abstract/feedzy-rss-feeds-admin-abstract.php @@ -822,10 +822,10 @@ function ( $url ) { * @since 3.1.7 * @access private * - * @param string $feed_url The feed URL. - * @param string $cache The cache string (eg. 1_hour, 30_min etc.). - * @param array $sc The shortcode attributes. - * @param bool $allow_https Defaults to constant FEEDZY_ALLOW_HTTPS. + * @param string|array $feed_url The feed URL. + * @param string $cache The cache string (eg. 1_hour, 30_min etc.). + * @param array $sc The shortcode attributes. + * @param bool $allow_https Defaults to constant FEEDZY_ALLOW_HTTPS. * * @return SimplePie */ @@ -858,9 +858,9 @@ private function init_feed( $feed_url, $cache, $sc, $allow_https = FEEDZY_ALLOW_ require_once ABSPATH . WPINC . '/class-wp-feed-cache-transient.php'; require_once ABSPATH . WPINC . '/class-wp-simplepie-file.php'; - $feed->set_file_class( 'WP_SimplePie_File' ); + $feed->get_registry()->register( SimplePie\File::class, 'WP_SimplePie_File', true ); $default_agent = $this->get_default_user_agent( $feed_url ); - $feed->set_useragent( apply_filters( 'http_headers_useragent', $default_agent ) ); + $feed->set_useragent( apply_filters( 'http_headers_useragent', $default_agent, is_array( $feed_url ) ? reset( $feed_url ) : $feed_url ) ); if ( false === apply_filters( 'feedzy_disable_db_cache', false, $feed_url ) ) { SimplePie_Cache::register( 'wp_transient', 'WP_Feed_Cache_Transient' ); $feed->set_cache_location( 'wp_transient' ); @@ -917,7 +917,7 @@ function ( $time ) use ( $cache_time ) { if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) { // phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___SERVER__HTTP_USER_AGENT__ $set_server_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) . SIMPLEPIE_USERAGENT ); - $feed->set_useragent( apply_filters( 'http_headers_useragent', $set_server_agent ) ); + $feed->set_useragent( apply_filters( 'http_headers_useragent', $set_server_agent, is_array( $feed_url ) ? reset( $feed_url ) : $feed_url ) ); } $feed->init(); @@ -1767,6 +1767,15 @@ public function feedzy_retrieve_image( $item, $sc = null ) { $the_thumbnail = $data['0']['attribs']['']['href']; } } + // xmlns:media thumbnail. + if ( empty( $the_thumbnail ) ) { + $data = $item->get_item_tags( 'http://search.yahoo.com/mrss/', 'thumbnail' ); + if ( isset( $data['0']['attribs']['']['url'] ) && ! empty( $data['0']['attribs']['']['url'] ) ) { + $the_thumbnail = $data['0']['attribs']['']['url']; + } elseif ( isset( $data['0']['data'] ) && ! empty( $data['0']['data'] ) ) { + $the_thumbnail = $data['0']['data']; + } + } // Content image. if ( empty( $the_thumbnail ) ) { $feed_description = $item->get_content(); diff --git a/includes/admin/feedzy-rss-feeds-actions.php b/includes/admin/feedzy-rss-feeds-actions.php index e7bb78154..66b09aa3c 100644 --- a/includes/admin/feedzy-rss-feeds-actions.php +++ b/includes/admin/feedzy-rss-feeds-actions.php @@ -315,6 +315,8 @@ public function action_process() { return $this->generate_image(); case 'modify_links': return $this->modify_links(); + case 'custom_html': + return $this->custom_html(); default: return $this->default_content(); } @@ -619,5 +621,14 @@ private function custom_field() { } return $this->default_value; } + + /** + * Get Custom HTML tag. + * + * @return string + */ + private function custom_html() { + return $this->current_job->tag; + } } } diff --git a/includes/admin/feedzy-rss-feeds-admin.php b/includes/admin/feedzy-rss-feeds-admin.php index 6d6ff973f..c072006fc 100644 --- a/includes/admin/feedzy-rss-feeds-admin.php +++ b/includes/admin/feedzy-rss-feeds-admin.php @@ -88,10 +88,6 @@ function ( $product_name, $page_slug ) { return; } - if ( in_array( $page_slug, array( 'imports', 'categories' ), true ) ) { - $this->add_banner_anchor(); - } - if ( in_array( $page_slug, array( 'imports', 'new-category', 'settings' ), true ) && 'yes' === get_option( 'feedzy_rss_feeds_logger_flag', false ) @@ -128,7 +124,7 @@ public function enqueue_styles() { } wp_register_style( $this->plugin_name, FEEDZY_ABSURL . 'css/feedzy-rss-feeds.css', array(), $this->version, 'all' ); } - + /** * Helper function to enqueue the license script with localization * @@ -136,12 +132,12 @@ public function enqueue_styles() { * @return void */ private function enqueue_license_script() { - wp_enqueue_script( - $this->plugin_name . '_license', - FEEDZY_ABSURL . 'js/feedzy-license.js', - array( 'jquery' ), - $this->version, - true + wp_enqueue_script( + $this->plugin_name . '_license', + FEEDZY_ABSURL . 'js/feedzy-license.js', + array( 'jquery' ), + $this->version, + true ); wp_localize_script( @@ -339,7 +335,7 @@ public function enqueue_styles_admin() { ( 'improve' === $tab ) || ( 'edit' !== $screen->base && 'feedzy_imports' === $screen->post_type ) || ( 'license' === $tab ) - ) + ) ) { $asset_file = include FEEDZY_ABSPATH . '/build/feedback/index.asset.php'; wp_enqueue_script( $this->plugin_name . '_feedback', FEEDZY_ABSURL . 'build/feedback/index.js', array_merge( $asset_file['dependencies'], array( 'wp-editor', 'wp-api', 'lodash' ) ), $asset_file['version'], true ); @@ -464,7 +460,7 @@ public function add_modals() { openModal('#feedzy-add-new-import'); event.preventDefault(); }); - + // Function to open the modal function openModal(modal) { jQuery(modal).show(); @@ -533,7 +529,7 @@ class="button button-primary button-large" - + ' . sprintf( // translators: %1$s and %2$s are placeholders for HTML anchor tags. - __( 'Please be aware that multiple feeds, when mashed together, may sometimes not work as expected as explained %1$s here %2$s.', 'feedzy-rss-feeds' ), + __( 'Please be aware that multiple feeds, when mashed together, may sometimes not work as expected as explained %1$shere%2$s.', 'feedzy-rss-feeds' ), '', '' ) @@ -1233,12 +1229,13 @@ public function manage_feedzy_category_columns( $column, $post_id ) { */ public function feedzy_filter_plugin_row_meta( $links, $file ) { if ( strpos( $file, 'feedzy-rss-feed.php' ) !== false ) { + $is_black_friday = apply_filters( 'themeisle_sdk_is_black_friday_sale', false ); $new_links = array(); $new_links['doc'] = '' . __( 'Documentation and examples', 'feedzy-rss-feeds' ) . ''; - if ( ! feedzy_is_pro() ) { + if ( ! $is_black_friday && ! feedzy_is_pro() ) { $new_links['more_features'] = '' . __( 'Upgrade to Pro', 'feedzy-rss-feeds' ) . ''; - } elseif ( false === apply_filters( 'feedzy_is_license_of_type', false, 'agency' ) ) { + } elseif ( ! $is_black_friday && false === apply_filters( 'feedzy_is_license_of_type', false, 'agency' ) ) { $new_links['more_features'] = '' . __( 'Upgrade your license', 'feedzy-rss-feeds' ) . ''; } $links = array_merge( $links, $new_links ); @@ -1512,7 +1509,7 @@ public function get_settings() { /** * Set up the HTTP parameters/headers. - * + * * @param string $url The URl. * * @access public @@ -1527,9 +1524,9 @@ public function pre_http_setup( $url ) { /** * Add the proxy settings as specified in the settings. - * + * * @param string $url The url. - * + * * @return void * * @access private @@ -1645,9 +1642,9 @@ public function send_through_proxy( $return_value, $uri, $check, $home ) { /** * Teardown the HTTP parameters/headers. - * + * * @param string $url The URL. - * + * * @return void * * @access public @@ -2037,13 +2034,13 @@ public function feedzy_dismiss_wizard( $redirect_to_dashboard = true ) { // Default to dashboard if no page is set. if ( - isset( $parsed_url['path'] ) && - strpos( $parsed_url['path'], '/wp-admin/admin.php' ) !== false && + isset( $parsed_url['path'] ) && + strpos( $parsed_url['path'], '/wp-admin/admin.php' ) !== false && empty( $parsed_url['query'] ) ) { $cleaned_url = add_query_arg( 'page', 'feedzy-support', $cleaned_url ); } - + wp_safe_redirect( $cleaned_url ); exit; } @@ -2381,7 +2378,7 @@ private function setup_wizard_subscribe_process() { */ public function feedzy_dashboard_subscribe() { check_ajax_referer( 'feedzy_subscribe_nonce', '_wpnonce' ); - + $email = ! empty( $_POST['email'] ) ? sanitize_email( $_POST['email'] ) : ''; $skip = ! empty( $_POST['skip'] ) ? sanitize_text_field( wp_unslash( $_POST['skip'] ) ) : ''; @@ -2397,9 +2394,9 @@ public function feedzy_dashboard_subscribe() { ) ); } - + update_option( 'feedzy_rss_feeds_logger_flag', 'yes' ); - + $request_res = wp_remote_post( FEEDZY_SUBSCRIBE_API, array( @@ -2417,7 +2414,7 @@ public function feedzy_dashboard_subscribe() { ), ) ); - + if ( ! is_wp_error( $request_res ) ) { $this->dismiss_subscribe_notice(); wp_send_json_success(); @@ -2737,21 +2734,6 @@ public function get_survey_data() { return $survey_data; } - /** - * Add banner anchor for promotions. - * - * @return void - */ - public function add_banner_anchor() { - add_action( - 'admin_notices', - function () { - echo '
'; - }, - 999 - ); - } - /** * List of languages supported for translations. */ @@ -3014,28 +2996,49 @@ private function enable_telemetry() { public function add_black_friday_data( $configs ) { $config = $configs['default']; - // translators: %1$s - HTML tag, %2$s - discount, %3$s - HTML tag, %4$s - product name. - $message_template = __( 'Our biggest sale of the year: %1$sup to %2$s OFF%3$s on %4$s. Don\'t miss this limited-time offer.', 'feedzy-rss-feeds' ); - $product_label = 'Feedzy'; - $discount = '70%'; - - $is_pro = feedzy_is_pro(); + $message = __( 'Feed-to-post automation, keyword filters, full-text import. Stop copying content manually. Exclusively for existing Feedzy users.', 'feedzy-rss-feeds' ); + $cta_label = __( 'Upgrade Now', 'feedzy-rss-feeds' ); + + $license_status = apply_filters( 'product_feedzy_license_status', false ); + $is_valid = 'valid' === $license_status; + $is_expired = 'expired' === $license_status || 'active-expired' === $license_status; + $pro_product_slug = defined( 'FEEDZY_PRO_BASEFILE' ) ? basename( dirname( FEEDZY_PRO_BASEFILE ) ) : ''; + + if ( $is_valid ) { + // translators: %s is the discount percentage. + $config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - up to %s off', 'feedzy-rss-feeds' ), '30%' ); + // translators: %1$s is the discount percentage for the upgrade, %2$s is the discount percentage for early renewal. + $message = sprintf( __( 'Upgrade your Feedzy Pro plan: %1$s off this week. Already on the plan you need? Renew early and save up to %2$s.', 'feedzy-rss-feeds' ), '30%', '20%' ); + $cta_label = __( 'See your options', 'feedzy-rss-feeds' ); + } elseif ( $is_expired ) { + // translators: %s is the discount percentage. + $config['upgrade_menu_text'] = sprintf( __( 'BF Sale - %s off', 'feedzy-rss-feeds' ), '50%' ); + // translators: %s is the discount percentage. + $config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - %s off', 'feedzy-rss-feeds' ), '50%' ); + $message = __( 'Your Feedzy Pro features are still here, just locked. Renew at a reduced rate this week.', 'feedzy-rss-feeds' ); + $cta_label = __( 'Reactivate now', 'feedzy-rss-feeds' ); + } else { + // translators: %s is the discount percentage. + $config['plugin_meta_message'] = sprintf( __( 'Black Friday Sale - %s off', 'feedzy-rss-feeds' ), '60%' ); + // translators: %s is the discount percentage for the upgrade. + $config['title'] = sprintf( __( 'Feedzy Pro: %s off this week', 'feedzy-rss-feeds' ), '60%' ); + // translators: %s is the discount percentage. + $config['upgrade_menu_text'] = sprintf( __( 'BF Sale - %s off', 'feedzy-rss-feeds' ), '60%' ); + } + + $url_params = array( + 'utm_term' => $is_valid ? 'plan-' . apply_filters( 'product_feedzy_license_plan', 0 ) : 'free', + 'lkey' => $is_valid || $is_expired ? apply_filters( 'product_feedzy_license_key', false ) : false, + 'expired' => $is_expired ? '1' : false, + ); - if ( $is_pro ) { - // translators: %1$s - HTML tag, %2$s - discount, %3$s - HTML tag, %4$s - product name. - $message_template = __( 'Get %1$sup to %2$s off%3$s when you upgrade your %4$s plan or renew early.', 'feedzy-rss-feeds' ); - $product_label = 'Feedzy Pro'; - $discount = '30%'; + if ( ( $is_valid || $is_expired ) && ! empty( $pro_product_slug ) ) { + $config['plugin_meta_targets'] = array( $pro_product_slug ); } - $product_label = sprintf( '%s', $product_label ); - $url_params = array( - 'utm_term' => $is_pro ? 'plan-' . apply_filters( 'product_feedzy_license_plan', 0 ) : 'free', - 'lkey' => $is_pro ? apply_filters( 'product_feedzy_license_key', false ) : false, - ); - - $config['message'] = sprintf( $message_template, '', $discount, '', $product_label ); - $config['sale_url'] = add_query_arg( + $config['cta_label'] = $cta_label; + $config['message'] = $message; + $config['sale_url'] = add_query_arg( $url_params, tsdk_translate_link( tsdk_utmify( 'https://themeisle.link/feedzy-bf', 'bfcm', 'feedzy' ) ) ); @@ -3143,7 +3146,7 @@ public function validate_feed() { $items, 'feedzy-rss-feeds' ), - $items + $items ), 'items' => $items, 'title' => $title, @@ -3176,7 +3179,7 @@ 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. @@ -3199,20 +3202,20 @@ public function append_custom_cron_schedules( $schedules ) { } } } - + return $schedules; } /** * Add slugs for internal cron schedules. - * + * * @param string[] $cron_slugs The cron slugs to be modified. * @return string[] */ public function internal_cron_schedule_slugs( $cron_slugs ) { $wp_standard_schedules = array( 'hourly', - 'twicedaily', + 'twicedaily', 'daily', 'weekly', ); diff --git a/includes/admin/feedzy-rss-feeds-import.php b/includes/admin/feedzy-rss-feeds-import.php index 650315a67..701eaf3fa 100644 --- a/includes/admin/feedzy-rss-feeds-import.php +++ b/includes/admin/feedzy-rss-feeds-import.php @@ -105,8 +105,12 @@ public function upsell_content( $content, $area, $location ) {
- ' . __( 'This feature is available in the Pro version. Unlock more features, by', 'feedzy-rss-feeds' ) . ' - ' . __( 'upgrading to Feedzy Pro', 'feedzy-rss-feeds' ) . ' + ' . sprintf( + // translators: %1$s and %2$s are HTML tags for the link to the upsell URL. + __( 'This feature is available in the Pro version. Unlock more features, by %1$supgrading to Feedzy Pro%2$s', 'feedzy-rss-feeds' ), + '', + '' + ) . '
'; @@ -434,8 +438,14 @@ public function feedzy_import_feed_options() { 'posts_per_page' => 100, ); $feed_categories = get_posts( $args ); - $post_types = get_post_types( '', 'names' ); - $post_types = array_diff( $post_types, array( 'feedzy_imports', 'feedzy_categories' ) ); + $post_types = get_post_types( '', 'objects' ); + $post_types = array_diff_key( + $post_types, + array( + 'feedzy_imports' => array(), + 'feedzy_categories' => array(), + ) + ); $published_status = array( 'publish', 'draft' ); $authors = get_users( array( 'number' => 100 ) ); @@ -446,11 +456,11 @@ public function feedzy_import_feed_options() { $import_post_type = get_post_meta( $post->ID, 'import_post_type', true ); $import_post_term = get_post_meta( $post->ID, 'import_post_term', true ); - if ( metadata_exists( $import_post_type, $post->ID, 'import_post_status' ) ) { - $import_post_status = get_post_meta( $post->ID, 'import_post_status', true ); - } else { - add_post_meta( $post->ID, 'import_post_status', 'publish' ); - $import_post_status = get_post_meta( $post->ID, 'import_post_status', true ); + + $import_post_status = get_post_meta( $post->ID, 'import_post_status', true ); + if ( empty( $import_post_status ) ) { + $import_post_status = 'publish'; + update_post_meta( $post->ID, 'import_post_status', $import_post_status ); } $source = get_post_meta( $post->ID, 'source', true ); $inc_key = get_post_meta( $post->ID, 'inc_key', true ); @@ -643,6 +653,7 @@ public function save_feedzy_import_feed_meta( $post_id, $post ) { } } else { if ( 'import_post_content' === $key ) { + $val = escape_html_to_tag( $val ); $val = feedzy_custom_tag_escape( $val ); } elseif ( 'default_thumbnail_id' === $key && ! empty( $val ) ) { $val = explode( ',', $val ); @@ -929,11 +940,27 @@ public function manage_feedzy_import_columns( $column, $post_id ) { $then = new DateTime(); $then = $then->setTimestamp( $last ); $in = $now->diff( $then ); - $msg = sprintf( - // translators: %1$d: number of hours, %2$d: number of minutes. - __( 'Ran %1$d hours %2$d minutes ago', 'feedzy-rss-feeds' ), - $in->format( '%h' ), - $in->format( '%i' ) + + $hours = (int) $in->format( '%h' ); + $minutes = (int) $in->format( '%i' ); + + $hours_text = sprintf( + // translators: %d: number of hours. + _n( '%d hour', '%d hours', $hours, 'feedzy-rss-feeds' ), + $hours + ); + + $minutes_text = sprintf( + // translators: %d: number of minutes. + _n( '%d minute', '%d minutes', $minutes, 'feedzy-rss-feeds' ), + $minutes + ); + + $msg = sprintf( + // translators: %1$s: hours text, %2$s: minutes text. + __( 'Ran %1$s %2$s ago', 'feedzy-rss-feeds' ), + $hours_text, + $minutes_text ); } @@ -1614,7 +1641,7 @@ private function run_job( $job, $max ) { $import_featured_img = get_post_meta( $job->ID, 'import_post_featured_img', true ); $import_post_type = get_post_meta( $job->ID, 'import_post_type', true ); $import_post_term = get_post_meta( $job->ID, 'import_post_term', true ); - $import_feed_limit = get_post_meta( $job->ID, 'import_feed_limit', true ); + $import_feed_limit = feedzy_is_pro() ? get_post_meta( $job->ID, 'import_feed_limit', true ) : ''; $import_item_img_url = get_post_meta( $job->ID, 'import_use_external_image', true ); $import_remove_duplicates = get_post_meta( $job->ID, 'import_remove_duplicates', true ); $import_selected_language = get_post_meta( $job->ID, 'language', true ); @@ -1627,10 +1654,14 @@ private function run_job( $job, $max ) { $import_post_author = get_post_meta( $job->ID, 'import_post_author', true ); $mark_duplicate_tag = get_post_meta( $job->ID, 'mark_duplicate_tag', true ); $mark_duplicate_tag = feedzy_is_pro() && ! empty( $mark_duplicate_tag ) ? preg_replace( '/[\[\]#]/', '', $mark_duplicate_tag ) : ''; - $max = $import_feed_limit; + $import_max = $import_feed_limit; $import_remove_html = get_post_meta( $job->ID, 'import_remove_html', true ); $import_order = get_post_meta( $job->ID, 'import_order', true ); + if ( ! defined( 'TI_UNIT_TESTING' ) ) { + $max = $import_max; + } + Feedzy_Rss_Feeds_Log::info( 'Running import job: ' . $job->post_title . ' (ID: ' . $job->ID . ')', array( @@ -2724,7 +2755,12 @@ function ( $term ) { Feedzy_Rss_Feeds_Log::error( sprintf( // translators: %1$d is the number of items without images, %2$d is the total number of items imported. - __( 'Unable to find an image for %1$d out of %2$d items imported', 'feedzy-rss-feeds' ), + _n( + 'Unable to find an image for %1$d out of %2$d item imported', + 'Unable to find an image for %1$d out of %2$d items imported', + $count, + 'feedzy-rss-feeds' + ), $import_image_errors, $count ), @@ -3663,7 +3699,11 @@ public function add_import_actions( $actions, $post ) { } elseif ( 1 === intval( get_post_meta( $post->ID, 'feedzy', true ) ) ) { // show an unclickable action that mentions that it is imported by us so that users are aware. $feedzy_job_id = get_post_meta( $post->ID, 'feedzy_job', true ); - $actions['feedzy'] = sprintf( '(%s %s)', __( 'Imported by Feedzy from', 'feedzy-rss-feeds' ), get_the_title( $feedzy_job_id ) ); + $actions['feedzy'] = sprintf( + // translators: %s is the title of job. + __( 'Imported by Feedzy from %s', 'feedzy-rss-feeds' ), + get_the_title( $feedzy_job_id ) + ); } return $actions; diff --git a/includes/admin/feedzy-rss-feeds-ui-lang.php b/includes/admin/feedzy-rss-feeds-ui-lang.php index f3396e1b6..7860737d6 100644 --- a/includes/admin/feedzy-rss-feeds-ui-lang.php +++ b/includes/admin/feedzy-rss-feeds-ui-lang.php @@ -90,12 +90,8 @@ public static function get_form_defaults() { */ public static function get_form_elements() { $meta = sprintf( - // translators: 1: tag for author, 2: closing tag, 3: tag for date, 4: closing tag, 5: tag for time, 6: closing tag, 7: tag for documentation link, 8: closing tag. - __( 'Should we display additional meta fields out of %1$s author %2$s, %3$s date %4$s and %5$s time %6$s? (comma-separated list, in order of display). View documentation %7$s here %8$s.', 'feedzy-rss-feeds' ), - '', - '', - '', - '', + // translators: 1: tag for author, 2: closing tag, 3: tag for documentation link, 4: closing tag. + __( 'Should we display additional meta fields out of %1$sauthor%2$s, %3$sdate%4$s and %5$stime%6$s? (comma-separated list, in order of display). View documentation %3$shere%4$s.', 'feedzy-rss-feeds' ), '', '', '', @@ -103,14 +99,8 @@ public static function get_form_elements() { ); if ( has_filter( 'feedzy_retrieve_categories' ) ) { $meta = sprintf( - // translators: 1: tag for author, 2: closing tag, 3: tag for date, 4: closing tag, 5: tag for time, 6: closing tag, 7: tag for categories, 8: closing tag, 9: tag for documentation link, 10: closing tag. - __( 'Should we display additional meta fields out of %1$s author%2$s, %3$s date%4$s, %5$s time %6$s and %7$s categories %8$s? (comma-separated list). View documentation %9$s here %10$s.', 'feedzy-rss-feeds' ), - '', - '', - '', - '', - '', - '', + // translators: %1$s is tag, %2$s is closing tag, %3$s is tag for documentation link, %4$s: closing tag. + __( 'Should we display additional meta fields out of %1$sauthor%2$s, %1$sdate%2$s, %1$stime%2$s and %1$scategories%2$s? (comma-separated list). View documentation %3$shere%4$s.', 'feedzy-rss-feeds' ), '', '', '', @@ -120,7 +110,7 @@ public static function get_form_elements() { $multiple = sprintf( // translators: 1: tag for source, 2: closing tag. - __( 'When using multiple sources, should we display additional meta fields? %1$s source %2$s (feed title).', 'feedzy-rss-feeds' ), + __( 'When using multiple sources, should we display additional meta fields?%1$ssource%2$s(feed title).', 'feedzy-rss-feeds' ), '', '' ); @@ -134,7 +124,7 @@ public static function get_form_elements() { __( 'The feed(s) URL (comma-separated list).', 'feedzy-rss-feeds' ) . ' ' . sprintf( // translators: 1: tag opening, 2: tag closing. - __( 'Click %1$s here %2$s to check if feed is valid.', 'feedzy-rss-feeds' ), + __( 'Click %1$shere%2$s to check if feed is valid.', 'feedzy-rss-feeds' ), '', '' ) @@ -484,12 +474,12 @@ public static function get_form_elements() { ), 'section_pro' => array( 'title' => __( 'PRO Options', 'feedzy-rss-feeds' ), - 'description' => __( 'Get access to more options and customizations with full version of Feedzy RSS Feeds . Use existing templates or extend them and make them your own.', 'feedzy-rss-feeds' ) . '
' . __( 'See more features of Feedzy RSS Feeds PRO', 'feedzy-rss-feeds' ) . '', + 'description' => __( 'Get access to more options and customizations with full version of Feedzy RSS Feeds. Use existing templates or extend them and make them your own.', 'feedzy-rss-feeds' ) . '
' . __( 'See more features of Feedzy RSS Feeds PRO', 'feedzy-rss-feeds' ) . '', 'elements' => array( 'price' => array( 'label' => sprintf( // translators: 1:
tag, 2: tag opening, 3: tag closing. - __( 'Should we display the price from the feed if it is available? %1$s You can read about how to extract price from a custom tag %2$s here %3$s', 'feedzy-rss-feeds' ), + __( 'Should we display the price from the feed if it is available?%1$sYou can read about how to extract price from a custom tag %2$shere%3$s', 'feedzy-rss-feeds' ), '
', '', '' @@ -511,7 +501,7 @@ public static function get_form_elements() { 'referral_url' => array( 'label' => sprintf( // translators: 1: tag opening, 2: tag closing. - __( 'Referral URL parameters as per %1$s this document here %2$s', 'feedzy-rss-feeds' ), + __( 'Referral URL parameters as per %1$sthis document here%2$s', 'feedzy-rss-feeds' ), '', '' ), @@ -531,7 +521,7 @@ public static function get_form_elements() { 'mapping' => array( 'label' => sprintf( // translators: 1: tag opening, 2: tag closing. - __( 'Provide mapping for custom feed elements as per %1$s this document here %2$s. This will only work for single feeds, not comma-separated feeds.', 'feedzy-rss-feeds' ), + __( 'Provide mapping for custom feed elements as per %1$sthis document here%2$s. This will only work for single feeds, not comma-separated feeds.', 'feedzy-rss-feeds' ), '', '' ), diff --git a/includes/admin/feedzy-rss-feeds-ui.php b/includes/admin/feedzy-rss-feeds-ui.php index aa02b874e..e1ae6d612 100644 --- a/includes/admin/feedzy-rss-feeds-ui.php +++ b/includes/admin/feedzy-rss-feeds-ui.php @@ -238,7 +238,7 @@ public function feedzy_import_post_title_section() { $content = __( 'You are using Feedzy Lite.', 'feedzy-rss-feeds' ) . ' '; // 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' ), '', '' ); + $content .= wp_sprintf( __( 'Unlock more powerful features, by %1$supgrading to Feedzy Pro%2$s and get 50%% off.', 'feedzy-rss-feeds' ), '', '' ); echo wp_kses_post( $content ); ?> diff --git a/includes/elementor/widgets/register-widget.php b/includes/elementor/widgets/register-widget.php index 745e7cc45..3abfd7075 100644 --- a/includes/elementor/widgets/register-widget.php +++ b/includes/elementor/widgets/register-widget.php @@ -106,7 +106,7 @@ protected function register_controls() { // phpcs:ignore PSR2.Methods.MethodDecl 'type' => Controls_Manager::TEXTAREA, 'description' => wp_sprintf( // translators: %1$s: opening anchor tag, %2$s: closing anchor tag. - __( 'You can add multiple sources at once, by separating them with commas. %1$s Click here %2$s to check if the feed is valid. Invalid feeds may not import anything.', 'feedzy-rss-feeds' ), + __( 'You can add multiple sources at once, by separating them with commas. %1$sClick here%2$s to check if the feed is valid. Invalid feeds may not import anything.', 'feedzy-rss-feeds' ), '', '' ), @@ -505,7 +505,7 @@ protected function register_controls() { // phpcs:ignore PSR2.Methods.MethodDecl 'description' => wp_sprintf( // translators: %1$s: opening anchor tag, %2$s: closing anchor tag. __( - 'Learn more about this feature in %1$s Feedzy docs %2$s .', + 'Learn more about this feature in %1$sFeedzy docs%2$s.', 'feedzy-rss-feeds' ), '', @@ -572,7 +572,7 @@ protected function register_controls() { // phpcs:ignore PSR2.Methods.MethodDecl ! feedzy_is_pro() ? wp_sprintf( // translators: %1$s: opening anchor tag, %2$s: closing anchor tag. - __( 'Unlock this feature and more advanced options with %1$s Feedzy Pro %1$s.', 'feedzy-rss-feeds' ), + __( 'Unlock this feature and more advanced options with %1$sFeedzy Pro%2$s.', 'feedzy-rss-feeds' ), '', '' ) diff --git a/includes/feedzy-rss-feeds-feed-tweaks.php b/includes/feedzy-rss-feeds-feed-tweaks.php index 48d8338e0..953dd6bc0 100644 --- a/includes/feedzy-rss-feeds-feed-tweaks.php +++ b/includes/feedzy-rss-feeds-feed-tweaks.php @@ -672,3 +672,72 @@ function feedzy_show_review_notice() { return $imported_posts->have_posts(); } + +/** + * Escape the HTML tag and convert it to the tagify tag value. + * + * @param string $content Content. + * @return string + */ +function escape_html_to_tag( $content ) { + + $protected_blocks = array(); + $placeholder_tpl = '__TAG_%d__'; + + $content = preg_replace_callback( + '/\[\[\{[\s\S]*?\}\]\]/', + function ( $value ) use ( &$protected_blocks, $placeholder_tpl ) { + $index = count( $protected_blocks ); + $protected_blocks[] = $value[0]; + return sprintf( $placeholder_tpl, $index ); + }, + $content + ); + + $base_content = $content; + $html_tags = array(); + $converted_value = ''; + + if ( preg_match_all( '/<[^>]+>[\s\S]*?<\/[^>]+>/', $content, $matches ) ) { + foreach ( $matches[0] as $match ) { + + $base_content = str_replace( $match, '', $base_content ); + $html_tags[] = array( + 'value' => rawurlencode( + wp_json_encode( + array( + array( + 'id' => 'custom_html', + 'tag' => $match, + ), + ) + ) + ), + ); + } + } + + foreach ( $protected_blocks as $i => $block ) { + $base_content = str_replace( + sprintf( $placeholder_tpl, $i ), + $block, + $base_content + ); + } + + foreach ( $html_tags as $tag ) { + $converted_value .= wp_json_encode( + array( + array( + $tag, + ), + ) + ); + } + + if ( ! empty( $converted_value ) ) { + $base_content .= $converted_value; + } + + return $base_content; +} diff --git a/includes/layouts/feedzy-documentation.php b/includes/layouts/feedzy-documentation.php index 86ce654cc..82737838b 100644 --- a/includes/layouts/feedzy-documentation.php +++ b/includes/layouts/feedzy-documentation.php @@ -92,6 +92,58 @@ +
  • +
    +
    + +
    +
    +

    +
    + +
    +
    +
    +
  • +
  • +
    +
    + +
    +
    +

    +
    + +
    +
    +
    +
  • +
  • +
    +
    + +
    +
    +

    +
    + +
    +
    +
    +
  • +
  • +
    +
    + +
    +
    +

    +
    + +
    +
    +
    +
  • diff --git a/includes/layouts/feedzy-improve.php b/includes/layouts/feedzy-improve.php index 97dc10bf1..7bb3e6073 100644 --- a/includes/layouts/feedzy-improve.php +++ b/includes/layouts/feedzy-improve.php @@ -8,9 +8,10 @@

    diff --git a/includes/layouts/feedzy-pro.php b/includes/layouts/feedzy-pro.php index 296b95d45..072dfa9f1 100644 --- a/includes/layouts/feedzy-pro.php +++ b/includes/layouts/feedzy-pro.php @@ -14,10 +14,10 @@

    -
  • +
  • -
  • +
  • @@ -27,10 +27,10 @@

    -
  • +
  • -
  • +
  • @@ -41,10 +41,10 @@

    -
  • +
  • -
  • +
  • @@ -57,10 +57,10 @@

    -
  • +
  • -
  • +
  • @@ -74,10 +74,10 @@

    -
  • +
  • -
  • +
  • @@ -89,13 +89,13 @@

    - +

    -
  • +
  • -
  • +
  • @@ -109,10 +109,10 @@

    -
  • +
  • -
  • +
  • @@ -126,15 +126,14 @@

    -
  • +
  • -
  • +
  • @@ -156,10 +155,10 @@ ?>

    -
  • +
  • -
  • +
  • @@ -173,10 +172,10 @@

    -
  • +
  • -
  • +
  • diff --git a/includes/layouts/feedzy-schedules.php b/includes/layouts/feedzy-schedules.php index 3ef5a699e..a3662141f 100644 --- a/includes/layouts/feedzy-schedules.php +++ b/includes/layouts/feedzy-schedules.php @@ -40,7 +40,7 @@ class="form-control" type="text" class="form-control" id="fz-schedule-display" - placeholder="Once Hourly" + placeholder= />
    @@ -53,7 +53,7 @@ class="form-control" type="text" class="form-control" id="fz-schedule-name" - placeholder="hourly" + placeholder= /> @@ -83,7 +83,7 @@ class="fz-schedules-table" $schedule_count = count( $custom_schedules ); // translators: %s is the number of custom schedules. - echo esc_html( sprintf( __( '%s items', 'feedzy-rss-feeds' ), $schedule_count ) ); + echo esc_html( sprintf( _n( '%d item', '%d items', $schedule_count, 'feedzy-rss-feeds' ) ) ); ?> diff --git a/includes/layouts/setup-wizard.php b/includes/layouts/setup-wizard.php index 8d61f8c53..af9105378 100644 --- a/includes/layouts/setup-wizard.php +++ b/includes/layouts/setup-wizard.php @@ -86,7 +86,7 @@