Skip to content

feat: improved feeds validation UX #1090

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions includes/admin/feedzy-rss-feeds-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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() ),
)
);
}
}
}
5 changes: 5 additions & 0 deletions includes/admin/feedzy-rss-feeds-import.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' ) . '...',
Expand All @@ -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' ),
),
),
)
);
Expand Down
1 change: 1 addition & 0 deletions includes/feedzy-rss-feeds.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
2 changes: 2 additions & 0 deletions includes/gutenberg/feedzy-rss-feeds-gutenberg-block.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' ),
)
);

Expand Down
2 changes: 2 additions & 0 deletions includes/gutenberg/feedzy-rss-feeds-loop-block.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)
);
Expand Down
30 changes: 30 additions & 0 deletions includes/views/css/import-metabox-edit.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
30 changes: 17 additions & 13 deletions includes/views/import-metabox-edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,29 @@
<div class="fz-input-group-left">
<div class="fz-group">
<div class="fz-input-icon">
<input type="text" id="feedzy-import-source" title="<?php esc_attr_e( 'Make sure you validate the feed by using the validate button on the right', 'feedzy-rss-feeds' ); ?>"
placeholder="<?php esc_attr_e( 'Paste your feed URL and click the plus icon to add it in the list', 'feedzy-rss-feeds' ); ?>"
class="form-control" />
<div class="fz-input-group-append">
<button class="fz-plus-btn add-outside-tags" disabled>
<span class="dashicons dashicons-plus-alt2"></span>
</button>
<div class="fz-feed-validator-wrapper">
<input type="text"
id="feedzy-import-source"
title="<?php esc_attr_e( 'Make sure you validate the feed by using the validate button on the right', 'feedzy-rss-feeds' ); ?>"
placeholder="<?php esc_attr_e( 'Paste your feed URL and click the plus icon to add it in the list', 'feedzy-rss-feeds' ); ?>"
class="form-control" />
<div class="fz-input-group-append">
<button class="btn btn-flate btn-icon add-outside-tags"
type="button"
id="feedzy-validate-feed"
role="button"
title="<?php esc_attr_e( 'Validate Feed', 'feedzy-rss-feeds' ); ?>">
<i class="dashicons dashicons-plus-alt2" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="cta">
<a class="btn btn-flate btn-icon" id="feedzy-validate-feed" target="_blank" data-href-base="https://validator.w3.org/feed/check.cgi?url="
href="#" title="<?php esc_attr_e( 'Validate Feed', 'feedzy-rss-feeds' ); ?>"><i
title="<?php esc_attr_e( 'Validate Feed', 'feedzy-rss-feeds' ); ?>"
class="dashicons dashicons-external"></i></a>
</div>
</div>
<div class="help-text">
<?php esc_html_e( 'You can add multiple sources at once, by separating them with commas. Make sure to use the validate button. Invalid feeds may not import anything.', 'feedzy-rss-feeds' ); ?>
</div>
<div class="fz-validation-summary">
</div>
</div>
<div class="fz-input-group-right">
<div class="dropdown">
Expand Down
Loading
Loading