fix: paginated region/city pages, no routes before first save, safer structure save
- Add /{slug}/page/N/ rules and keep the page number through 301s
- Route only regions synced to a term, so unsaved presets never claim /europe/ etc.
- Flush rewrites on the request after activation so structure routes survive
- Suggest the existing term slug for new cities (keeps live /tour-destination/ URLs)
- Refuse saves without the editor sentinel; confirm before saving an empty structure
- Show term-sync failures to the admin instead of reporting success
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,8 @@ defined( 'ABSPATH' ) || exit;
|
||||
class VC_Structure_Admin {
|
||||
const PAGE = 'viator-collector-structure';
|
||||
|
||||
private static $sync_errors = array();
|
||||
|
||||
public static function init() {
|
||||
add_action( 'admin_menu', array( __CLASS__, 'admin_menu' ) );
|
||||
add_action( 'admin_post_vc_save_structure', array( __CLASS__, 'handle_save' ) );
|
||||
@@ -44,7 +46,8 @@ class VC_Structure_Admin {
|
||||
'failed' => __( 'Search failed.', 'viator-collector' ),
|
||||
'add' => __( 'Add', 'viator-collector' ),
|
||||
'used' => __( 'Already added', 'viator-collector' ),
|
||||
'confirm' => __( 'Remove this region and all its cities from the structure?', 'viator-collector' ),
|
||||
'confirm' => __( 'Remove this region and all its cities from the structure?', 'viator-collector' ),
|
||||
'confirmEmpty' => __( 'Save with no regions at all? Every region and city URL will stop working.', 'viator-collector' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
@@ -66,6 +69,11 @@ class VC_Structure_Admin {
|
||||
$errors = (array) $draft['errors'];
|
||||
$structure = (array) $draft['structure'];
|
||||
}
|
||||
$warnings = get_transient( self::warnings_key() );
|
||||
$warnings = is_array( $warnings ) ? $warnings : array();
|
||||
if ( $warnings ) {
|
||||
delete_transient( self::warnings_key() );
|
||||
}
|
||||
$notice = isset( $_GET['vc_notice'] ) ? sanitize_text_field( wp_unslash( $_GET['vc_notice'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
|
||||
?>
|
||||
<div class="wrap">
|
||||
@@ -73,7 +81,16 @@ class VC_Structure_Admin {
|
||||
<p><?php echo esc_html__( 'Regions appear in the header menu and at /{region}/. Each city is a Viator destination: its tours are imported automatically and listed at /{city}/, with tour pages at /{city}/{tour}/.', 'viator-collector' ); ?></p>
|
||||
|
||||
<?php if ( $notice ) : ?>
|
||||
<div class="notice notice-success is-dismissible"><p><?php echo esc_html( $notice ); ?></p></div>
|
||||
<div class="notice <?php echo $warnings ? 'notice-warning' : 'notice-success'; ?> is-dismissible"><p><?php echo esc_html( $notice ); ?></p></div>
|
||||
<?php endif; ?>
|
||||
<?php if ( $warnings ) : ?>
|
||||
<div class="notice notice-warning">
|
||||
<ul style="list-style:disc;margin-left:20px;">
|
||||
<?php foreach ( $warnings as $warning ) : ?>
|
||||
<li><?php echo esc_html( $warning ); ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ( $errors ) : ?>
|
||||
<div class="notice notice-error">
|
||||
@@ -190,7 +207,11 @@ class VC_Structure_Admin {
|
||||
unset( $region );
|
||||
|
||||
$map = ( new VC_API() )->get_destination_map();
|
||||
if ( is_wp_error( $map ) ) {
|
||||
if ( empty( $_POST['vc_structure_present'] ) ) {
|
||||
$errors = array( __( 'The structure editor did not load correctly, so nothing was saved. Reload the page and try again.', 'viator-collector' ) );
|
||||
} elseif ( ! $structure && empty( $_POST['vc_confirm_empty'] ) ) {
|
||||
$errors = array( __( 'Saving an empty structure needs confirmation; nothing was saved.', 'viator-collector' ) );
|
||||
} elseif ( is_wp_error( $map ) ) {
|
||||
$errors = array( __( 'Could not load Viator destinations; nothing was saved.', 'viator-collector' ) );
|
||||
} else {
|
||||
$errors = VC_Structure::validate( $structure, (array) $map, self::taken_slugs( $structure ) );
|
||||
@@ -211,21 +232,39 @@ class VC_Structure_Admin {
|
||||
self::draft_key(),
|
||||
array(
|
||||
'errors' => $errors,
|
||||
'structure' => $structure,
|
||||
// Without the editor's fields there is no draft worth keeping.
|
||||
'structure' => empty( $_POST['vc_structure_present'] ) ? VC_Structure::get() : $structure,
|
||||
),
|
||||
10 * MINUTE_IN_SECONDS
|
||||
);
|
||||
self::redirect();
|
||||
}
|
||||
|
||||
$structure = self::sync_terms( VC_Structure::get(), $structure );
|
||||
self::$sync_errors = array();
|
||||
$structure = self::sync_terms( VC_Structure::get(), $structure );
|
||||
update_option( VC_Structure::OPTION, $structure );
|
||||
VC_Structure::flush_cache();
|
||||
update_option( 'vc_flush_rewrite', 1 );
|
||||
VC_Logger::info( sprintf( 'Structure saved: %d regions, %d cities.', count( $structure ), count( VC_Structure::cities( $structure ) ) ) );
|
||||
if ( self::$sync_errors ) {
|
||||
set_transient( self::warnings_key(), self::$sync_errors, 10 * MINUTE_IN_SECONDS );
|
||||
self::redirect( __( 'Structure saved, but some pages could not be set up (see below).', 'viator-collector' ) );
|
||||
}
|
||||
self::redirect( __( 'Structure saved.', 'viator-collector' ) );
|
||||
}
|
||||
|
||||
private static function warnings_key() {
|
||||
return 'vc_structure_warnings_' . get_current_user_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a term-sync failure: logged, and shown to the admin after save.
|
||||
*/
|
||||
private static function sync_error( $message ) {
|
||||
VC_Logger::error( 'Structure: ' . $message );
|
||||
self::$sync_errors[] = $message;
|
||||
}
|
||||
|
||||
private static function redirect( $notice = '' ) {
|
||||
$args = array( 'page' => self::PAGE );
|
||||
if ( '' !== $notice ) {
|
||||
@@ -335,7 +374,7 @@ class VC_Structure_Admin {
|
||||
$result = wp_insert_term( $region['name'], $tax, array( 'slug' => $region['slug'] ) );
|
||||
}
|
||||
if ( is_wp_error( $result ) ) {
|
||||
VC_Logger::error( 'Structure: region "' . $region['name'] . '": ' . $result->get_error_message() );
|
||||
self::sync_error( 'region "' . $region['name'] . '": ' . $result->get_error_message() );
|
||||
continue;
|
||||
}
|
||||
$region['term_id'] = (int) $result['term_id'];
|
||||
@@ -344,7 +383,7 @@ class VC_Structure_Admin {
|
||||
foreach ( $region['cities'] as $city ) {
|
||||
$city_term = VC_Taxonomies::get_or_create_destination_by_id( $city['id'], $city['name'] );
|
||||
if ( ! $city_term ) {
|
||||
VC_Logger::error( 'Structure: could not create city term for ' . $city['id'] );
|
||||
self::sync_error( 'could not create the page for city ' . $city['id'] );
|
||||
continue;
|
||||
}
|
||||
$result = wp_update_term(
|
||||
@@ -356,7 +395,7 @@ class VC_Structure_Admin {
|
||||
)
|
||||
);
|
||||
if ( is_wp_error( $result ) ) {
|
||||
VC_Logger::error( 'Structure: city "' . $city['name'] . '": ' . $result->get_error_message() );
|
||||
self::sync_error( 'city "' . $city['name'] . '": ' . $result->get_error_message() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user