diff --git a/includes/admin/feedzy-rss-feeds-admin.php b/includes/admin/feedzy-rss-feeds-admin.php index 337d7dd6..fd308c79 100644 --- a/includes/admin/feedzy-rss-feeds-admin.php +++ b/includes/admin/feedzy-rss-feeds-admin.php @@ -2513,4 +2513,133 @@ public function add_black_friday_data( $configs ) { return $configs; } + + /** + * Validate the feed URL and check if it's a valid RSS/Atom feed. + * + * @return void + */ + public function validate_feed() { + try { + if ( + ! isset( $_POST['nonce'] ) || + ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), FEEDZY_BASEFILE ) + ) { + wp_send_json_error( array( 'message' => __( 'Security check failed.', 'feedzy-rss-feeds' ) ) ); + } + + $feed_urls = isset( $_POST['feed_url'] ) ? sanitize_text_field( wp_unslash( $_POST['feed_url'] ) ) : ''; + + if ( empty( $feed_urls ) ) { + wp_send_json_error( array( 'message' => __( 'Feed URL cannot be empty.', 'feedzy-rss-feeds' ) ) ); + } + + $urls = array_map( 'trim', explode( ',', $feed_urls ) ); + $urls = array_filter( $urls ); + + if ( empty( $urls ) ) { + wp_send_json_error( array( 'message' => __( 'No valid URLs provided.', 'feedzy-rss-feeds' ) ) ); + } + + $results = array(); + + foreach ( $urls as $feed_url ) { + $feed_url = esc_url_raw( $feed_url ); + + if ( ! filter_var( $feed_url, FILTER_VALIDATE_URL ) ) { + $results[] = array( + 'url' => $feed_url, + 'status' => 'error', + 'message' => __( 'Invalid URL format', 'feedzy-rss-feeds' ), + ); + continue; + } + + $feed = $this->fetch_feed( array( $feed_url ), '1_mins', array() ); + + if ( is_wp_error( $feed ) ) { + $results[] = array( + 'url' => $feed_url, + 'status' => 'error', + 'message' => __( 'Error fetching feed: ', 'feedzy-rss-feeds' ) . $feed->get_error_message(), + ); + continue; + } + + if ( + ! is_object( $feed ) || + ! method_exists( $feed, 'get_item_quantity' ) + ) { + $results[] = array( + 'url' => $feed_url, + 'status' => 'error', + 'message' => __( 'Invalid feed object returned', 'feedzy-rss-feeds' ), + ); + continue; + } + + try { + $items = $feed->get_item_quantity(); + $title = $feed->get_title(); + $error = $feed->error(); + + if ( is_array( $error ) && ! empty( $error ) ) { + $results[] = array( + 'url' => $feed_url, + 'status' => 'error', + 'message' => __( 'Error fetching feed: ', 'feedzy-rss-feeds' ) . implode( ', ', $error ), + ); + continue; + } + + if ( 0 === $items ) { + $results[] = array( + 'url' => $feed_url, + 'status' => 'warning', + 'message' => __( 'Feed is empty', 'feedzy-rss-feeds' ), + ); + continue; + } + + $results[] = array( + 'url' => $feed_url, + 'status' => 'success', + 'message' => $title . sprintf( + /* translators: %d is the number of items found in the feed */ + _n( + '%d item found', + '%d items found', + $items, + 'feedzy-rss-feeds' + ), + $items + ), + 'items' => $items, + 'title' => $title, + ); + + } catch ( Throwable $e ) { + $results[] = array( + 'url' => $feed_url, + 'status' => 'error', + /* translators: %s is the error message */ + 'message' => sprintf( __( 'Error reading feed: %s', 'feedzy-rss-feeds' ), $e->getMessage() ), + ); + } + } + + wp_send_json_success( + array( + 'results' => $results, + ) + ); + } catch ( Throwable $e ) { + wp_send_json_error( + array( + /* translators: %s is the error message */ + 'message' => sprintf( __( 'An error occurred: %s', 'feedzy-rss-feeds' ), $e->getMessage() ), + ) + ); + } + } } diff --git a/includes/admin/feedzy-rss-feeds-import.php b/includes/admin/feedzy-rss-feeds-import.php index 902415a1..d536d42e 100644 --- a/includes/admin/feedzy-rss-feeds-import.php +++ b/includes/admin/feedzy-rss-feeds-import.php @@ -163,6 +163,7 @@ public function enqueue_styles() { array( 'ajax' => array( 'security' => wp_create_nonce( FEEDZY_BASEFILE ), + 'url' => admin_url( 'admin-ajax.php' ), ), 'i10n' => array( 'importing' => __( 'Importing', 'feedzy-rss-feeds' ) . '...', @@ -189,6 +190,10 @@ public function enqueue_styles() { esc_html__( 'Upload Import', 'feedzy-rss-feeds' ) ), 'is_pro' => feedzy_is_pro(), + 'validation_messages' => array( + 'invalid_feed_url' => __( 'Invalid feed URL.', 'feedzy-rss-feeds' ), + 'error_validating_feed_url' => __( 'Error validating feed URL.', 'feedzy-rss-feeds' ), + ), ), ) ); diff --git a/includes/feedzy-rss-feeds.php b/includes/feedzy-rss-feeds.php index 47fa150e..5c8df92e 100644 --- a/includes/feedzy-rss-feeds.php +++ b/includes/feedzy-rss-feeds.php @@ -202,6 +202,7 @@ private function define_admin_hooks() { self::$instance->loader->add_filter( 'admin_footer', self::$instance->admin, 'handle_upgrade_submenu' ); self::$instance->loader->add_action( 'current_screen', self::$instance->admin, 'handle_legacy' ); self::$instance->loader->add_action( 'init', self::$instance->admin, 'register_settings' ); + self::$instance->loader->add_action( 'wp_ajax_feedzy_validate_feed', self::$instance->admin, 'validate_feed' ); // 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/gutenberg/feedzy-rss-feeds-gutenberg-block.php b/includes/gutenberg/feedzy-rss-feeds-gutenberg-block.php index 8edb37c5..9f475aa2 100644 --- a/includes/gutenberg/feedzy-rss-feeds-gutenberg-block.php +++ b/includes/gutenberg/feedzy-rss-feeds-gutenberg-block.php @@ -75,6 +75,8 @@ public function feedzy_gutenberg_scripts() { 'imagepath' => esc_url( FEEDZY_ABSURL . 'img/' ), 'isPro' => feedzy_is_pro(), 'upsellLinkBlockEditor' => esc_url( tsdk_translate_link( tsdk_utmify( FEEDZY_UPSELL_LINK, 'keywordsfilter', 'blockeditor' ) ) ), + 'nonce' => wp_create_nonce( FEEDZY_BASEFILE ), + 'url' => admin_url( 'admin-ajax.php' ), ) ); diff --git a/includes/gutenberg/feedzy-rss-feeds-loop-block.php b/includes/gutenberg/feedzy-rss-feeds-loop-block.php index 4e63baf5..93a582c7 100644 --- a/includes/gutenberg/feedzy-rss-feeds-loop-block.php +++ b/includes/gutenberg/feedzy-rss-feeds-loop-block.php @@ -73,6 +73,8 @@ public function register_block() { array( 'imagepath' => esc_url( FEEDZY_ABSURL . 'img/' ), 'defaultImage' => esc_url( FEEDZY_ABSURL . 'img/feedzy.svg' ), + 'url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( FEEDZY_BASEFILE ), 'isPro' => feedzy_is_pro(), ) ); diff --git a/includes/views/css/import-metabox-edit.css b/includes/views/css/import-metabox-edit.css index 159d480a..81eb04f3 100644 --- a/includes/views/css/import-metabox-edit.css +++ b/includes/views/css/import-metabox-edit.css @@ -202,3 +202,33 @@ span.feedzy-spinner { .fz-import-field.hidden { display: none; } + +.feedzy-wrap .fz-validation-message { + margin: 10px 0; + padding: 12px 15px; + border-radius: 4px; + font-size: 14px; + position: relative; + z-index: 10; + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + border: 1px solid #5555559c; +} + +.feedzy-wrap .fz-validation-message button { + margin-left: auto; +} + +.fz-validation-message .fz-success { + color: #155724; +} + +.fz-validation-message .fz-error { + color: #721c24; +} + +.fz-validation-message .fz-warning { + color: #856404; +} \ No newline at end of file diff --git a/includes/views/import-metabox-edit.php b/includes/views/import-metabox-edit.php index 312a556d..8328a63b 100644 --- a/includes/views/import-metabox-edit.php +++ b/includes/views/import-metabox-edit.php @@ -42,25 +42,29 @@
{__( "Enter the full URL of the feed source you wish to display here, or the name of a group you've created. Also you can add multiple URLs just separate them with a comma. You can manage your groups feed from", @@ -521,7 +644,7 @@ class Editor extends Component { {__('here', 'feedzy-rss-feeds')}
- +{__('Fetching…', 'feedzy-rss-feeds')}
++ {isValidating + ? __( + 'Validating and fetching feed…', + 'feedzy-rss-feeds' + ) + : __('Loading…', 'feedzy-rss-feeds')} +
{__( 'Enter the full URL of the feed source you wish to display here, or select a Feed Group. Also you can add multiple URLs separated with a comma. You can manage your feed groups from', @@ -79,24 +195,9 @@ const BlockPlaceholder = ({ attributes, setAttributes, onSaveFeed }) => {