settings = $settings; $this->api = $api; } // ----------------------------------------------------------------------- // Registration // ----------------------------------------------------------------------- public function register(): void { add_action( 'admin_menu', array( $this, 'add_menu_page' ) ); add_action( 'admin_menu', array( $this, 'add_products_menu_link' ), 99 ); add_action( 'admin_init', array( $this, 'handle_save' ) ); add_action( 'admin_init', array( $this, 'handle_sync_now' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); } // ----------------------------------------------------------------------- // Menu (top-level, left sidebar) // ----------------------------------------------------------------------- public function add_menu_page(): void { add_menu_page( __( 'Ranktim', 'ranktim-article-sync' ), // page __( 'Ranktim', 'ranktim-article-sync' ), // sidebar label 'manage_options', self::PAGE_SLUG, array( $this, 'render_page' ), $this->menu_icon(), // SVG data-URI icon 58 // position — below Appearance ); } /** * Add a direct Ranktim Products link below the plugin menu. * * This is intentionally an external link: product files remain under the * user's control and are uploaded manually on ranktim.com. */ public function add_products_menu_link(): void { global $submenu; if ( ! isset( $submenu[ self::PAGE_SLUG ] ) || ! is_array( $submenu[ self::PAGE_SLUG ] ) ) { return; } $submenu[ self::PAGE_SLUG ][] = array( // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- WordPress exposes $submenu for external admin links. __( 'Products', 'ranktim-article-sync' ), 'manage_options', 'https://ranktim.com/shopping/products', ); } /** * Return a data-URI for the sidebar menu icon. * Reads the local icon-logo.svg file and encodes it as base64. * Falls back to a simple inline SVG if the file is missing. */ private function menu_icon(): string { $file = RANKTIM_PLUGIN_DIR . 'includes/icon-logo.svg'; if ( file_exists( $file ) ) { // Re-colour the SVG paths to white so they show correctly on the dark WP sidebar. $svg = file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading a static SVG shipped inside this plugin; no HTTP request involved. $svg = preg_replace( '/fill="#[A-Fa-f0-9]{3,6}"/', 'fill="#ffffff"', $svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Required to build the data-URI that add_menu_page() expects for an SVG icon. return 'data:image/svg+xml;base64,' . base64_encode( $svg ); } // Fallback: simple flame shape. $fallback = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M10 2c0 0 4 5 4 9.5C14 14 12.2 16 10 16S6 14 6 11.5C6 9.8 6.8 8 6.8 8S5 10 5 13c0 4.3 2.2 7 5 7s5-3.2 5-6.5C15 9 10 2 10 2z" fill="#fff"/></svg>'; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Required to build the data-URI that add_menu_page() expects for an SVG icon. return 'data:image/svg+xml;base64,' . base64_encode( $fallback ); } // ----------------------------------------------------------------------- // Scoped assets (enqueued only on Ranktim's own screen) // ----------------------------------------------------------------------- public function enqueue_assets( string $hook ): void { // $hook matches "toplevel_page_{PAGE_SLUG}" for our top-level menu page. if ( 'toplevel_page_' . self::PAGE_SLUG !== $hook ) { return; } wp_enqueue_style( 'ranktim-admin', RANKTIM_PLUGIN_URL . 'assets/css/admin.css', array(), RANKTIM_VERSION ); wp_enqueue_script( 'ranktim-admin', RANKTIM_PLUGIN_URL . 'assets/js/admin.js', array(), RANKTIM_VERSION, true ); } // ----------------------------------------------------------------------- // Page renderer // ----------------------------------------------------------------------- public function render_page(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'ranktim-article-sync' ) ); } $post_mode = $this->settings->get_post_mode(); $existing_key = $this->settings->get_key(); $has_key = $existing_key !== ''; $uid = get_current_user_id(); // New WordPress publishing settings. $all_settings = $this->settings->get(); $default_category_id = $all_settings['default_category_id']; $default_author_id = $all_settings['default_author_id']; $default_tag_ids = $all_settings['default_tag_ids']; $remove_duplicate_content = $all_settings['remove_duplicate_content']; // Build masked key preview: or_xxxxxxxx ••••••••••••••••••••••••••••••••••••… xxxx $masked_key = ''; if ( $has_key ) { $prefix = substr( $existing_key, 0, 11 ); // "or_" + 8 chars $suffix = substr( $existing_key, -4 ); $masked_key = esc_html( $prefix ) . str_repeat( '•', 24 ) . esc_html( $suffix ); } $notice_type = get_transient( 'ranktim_notice_type_' . $uid ); $notice_msg = get_transient( 'ranktim_notice_msg_' . $uid ); if ( $notice_type ) { delete_transient( 'ranktim_notice_type_' . $uid ); delete_transient( 'ranktim_notice_msg_' . $uid ); } ?> <div id="ranktim-wrap"> <div id="ranktim-layout"> <!-- ===== Single centered card ===== --> <div id="ranktim-card"> <!-- Card header --> <div id="ranktim-card-header"> <div id="ranktim-card-header-top"> <img class="ranktim-card-logo" src="<?php echo esc_url( RANKTIM_PLUGIN_URL . 'assets/img/logo-ranktim.svg' ); ?>" alt="Ranktim" width="130" > <div id="ranktim-status-pill"> <span class="dot"></span> <?php echo $has_key ? esc_html__( 'Connected', 'ranktim-article-sync' ) : esc_html__( 'Not configured', 'ranktim-article-sync' ); ?> </div> </div> <h1><?php esc_html_e( 'Plugin Set Up', 'ranktim-article-sync' ); ?></h1> <p><?php esc_html_e( 'Configure the ranktim.com plugin to publish articles to your website.', 'ranktim-article-sync' ); ?></p> </div><!-- /#ranktim-card-header --> <!-- Card body --> <div id="ranktim-card-body"> <?php if ( $notice_type && $notice_msg ) : ?> <div class="ranktim-notice <?php echo esc_attr( $notice_type ); ?>"> <?php echo esc_html( $notice_msg ); ?> </div> <?php endif; ?> <form method="post" action="<?php echo esc_url( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) ); ?>"> <?php wp_nonce_field( self::NONCE_ACTION, self::NONCE_FIELD ); ?> <!-- ---- Integration Key ---- --> <p class="ranktim-section-label"><?php esc_html_e( 'Authentication', 'ranktim-article-sync' ); ?></p> <div class="ranktim-field"> <div class="ranktim-label-row"> <label class="ranktim-label" for="ranktim_integration_key"> <?php esc_html_e( 'Integration Key', 'ranktim-article-sync' ); ?> </label> <?php if ( $has_key ) : ?> <button type="button" id="ranktim-edit-key-btn" class="ranktim-link-btn" >✎ <?php esc_html_e( 'Edit', 'ranktim-article-sync' ); ?></button> <?php endif; ?> </div> <?php if ( $has_key ) : ?> <!-- Saved-key chip (shown by default when key exists) --> <div id="ranktim-key-chip"> <span class="ranktim-key-chip-icon">🔑</span> <code class="ranktim-key-chip-value"><?php echo esc_html( $masked_key ); ?></code> </div> <?php endif; ?> <!-- Input (always visible when no key; hidden initially when key exists) --> <div id="ranktim-key-input-wrap" class="ranktim-input-wrap<?php echo $has_key ? ' ranktim-is-hidden' : ''; ?>" > <span class="ranktim-key-icon">🔑</span> <input type="password" id="ranktim_integration_key" name="ranktim_integration_key" value="" autocomplete="new-password" placeholder="<?php esc_attr_e( 'Enter your integration key here…', 'ranktim-article-sync' ); ?>" > <?php if ( $has_key ) : ?> <button type="button" id="ranktim-cancel-key-btn" class="ranktim-cancel-edit-btn" title="<?php esc_attr_e( 'Cancel', 'ranktim-article-sync' ); ?>" >✕</button> <?php endif; ?> </div> <p class="ranktim-helper"> <?php if ( $has_key ) : ?> <?php esc_html_e( 'Leave blank to keep the current key. Click Edit to change it.', 'ranktim-article-sync' ); ?> <?php else : ?> <?php printf( /* translators: %s = linked "Ranktim App" */ esc_html__( 'Generate this key in the %s when creating your WordPress integration.', 'ranktim-article-sync' ), '<a href="https://ranktim.com/dashboard" target="_blank" rel="noopener noreferrer"><strong>' . esc_html__( 'Ranktim App', 'ranktim-article-sync' ) . '</strong></a>' ); ?> <?php endif; ?> </p> </div><!-- /.ranktim-field --> <hr class="ranktim-divider"> <!-- ---- Configure Your WordPress Settings ---- --> <p class="ranktim-section-label"><?php esc_html_e( 'Configure Your WordPress Settings', 'ranktim-article-sync' ); ?></p> <p class="ranktim-section-intro"><?php esc_html_e( 'These settings control how articles are published on your WordPress site. Make your selections below and click Save.', 'ranktim-article-sync' ); ?></p> <!-- How should articles be published? (Post Mode) --> <div class="ranktim-field"> <label class="ranktim-label"><?php esc_html_e( 'How should articles be published?', 'ranktim-article-sync' ); ?></label> <div class="ranktim-tiles" role="radiogroup"> <label class="ranktim-tile"> <input type="radio" name="ranktim_post_mode" value="publish" <?php checked( $post_mode, 'publish' ); ?>> <div class="ranktim-tile-inner"> <div class="ranktim-tile-icon">🚀</div> <div class="ranktim-tile-label"><?php esc_html_e( 'Publish immediately', 'ranktim-article-sync' ); ?></div> <div class="ranktim-tile-desc"><?php esc_html_e( 'Articles appear on your site right away', 'ranktim-article-sync' ); ?></div> </div> </label> <label class="ranktim-tile"> <input type="radio" name="ranktim_post_mode" value="draft" <?php checked( $post_mode, 'draft' ); ?>> <div class="ranktim-tile-inner"> <div class="ranktim-tile-icon">📝</div> <div class="ranktim-tile-label"><?php esc_html_e( 'Save as Draft', 'ranktim-article-sync' ); ?></div> <div class="ranktim-tile-desc"><?php esc_html_e( 'Saved for you to review first', 'ranktim-article-sync' ); ?></div> </div> </label> </div> <p class="ranktim-helper"><?php esc_html_e( 'Choose whether articles appear on your site right away or are saved for you to review first.', 'ranktim-article-sync' ); ?></p> </div><!-- /.ranktim-field --> <!-- Default Category --> <div class="ranktim-field"> <label class="ranktim-label" for="ranktim_default_category"> <?php esc_html_e( 'Default Category', 'ranktim-article-sync' ); ?> </label> <div class="ranktim-select-wrap"> <select id="ranktim_default_category" name="ranktim_default_category" class="ranktim-select"> <option value="0"><?php esc_html_e( '— Select Category —', 'ranktim-article-sync' ); ?></option> <?php $ranktim_cats = get_categories( array( 'hide_empty' => false ) ); foreach ( $ranktim_cats as $ranktim_cat ) : ?> <option value="<?php echo esc_attr( $ranktim_cat->term_id ); ?>" <?php selected( $default_category_id, $ranktim_cat->term_id ); ?>> <?php echo esc_html( $ranktim_cat->name ); ?> </option> <?php endforeach; ?> </select> </div> <p class="ranktim-helper"><?php esc_html_e( 'All articles from Ranktim will be assigned to this category.', 'ranktim-article-sync' ); ?></p> </div><!-- /.ranktim-field --> <!-- Default Author --> <div class="ranktim-field"> <label class="ranktim-label" for="ranktim_default_author"> <?php esc_html_e( 'Default Author', 'ranktim-article-sync' ); ?> </label> <div class="ranktim-select-wrap"> <select id="ranktim_default_author" name="ranktim_default_author" class="ranktim-select"> <option value="0"><?php esc_html_e( '— Select Author —', 'ranktim-article-sync' ); ?></option> <?php $ranktim_authors = get_users( array( 'capability' => 'publish_posts' ) ); foreach ( $ranktim_authors as $ranktim_author ) : ?> <option value="<?php echo esc_attr( $ranktim_author->ID ); ?>" <?php selected( $default_author_id, $ranktim_author->ID ); ?>> <?php echo esc_html( $ranktim_author->display_name ); ?> </option> <?php endforeach; ?> </select> </div> <p class="ranktim-helper"><?php esc_html_e( 'All articles from Ranktim will be assigned to this author.', 'ranktim-article-sync' ); ?></p> </div><!-- /.ranktim-field --> <!-- Default Tags --> <div class="ranktim-field"> <label class="ranktim-label" for="ranktim_default_tags"> <?php esc_html_e( 'Default Tags', 'ranktim-article-sync' ); ?> </label> <div class="ranktim-select-wrap"> <select id="ranktim_default_tags" name="ranktim_default_tags[]" multiple class="ranktim-select ranktim-select-multi"> <?php $ranktim_tags = get_tags( array( 'hide_empty' => false ) ); foreach ( $ranktim_tags as $ranktim_tag ) : ?> <option value="<?php echo esc_attr( $ranktim_tag->term_id ); ?>" <?php echo in_array( (int) $ranktim_tag->term_id, $default_tag_ids, true ) ? 'selected' : ''; ?>> <?php echo esc_html( $ranktim_tag->name ); ?> </option> <?php endforeach; ?> </select> </div> <p class="ranktim-helper"><?php esc_html_e( 'Hold Ctrl (or Cmd on Mac) to select multiple tags. All articles will be assigned these tags.', 'ranktim-article-sync' ); ?></p> </div><!-- /.ranktim-field --> <!-- Remove double title and image --> <div class="ranktim-field"> <label class="ranktim-label"> <?php esc_html_e( 'Remove double title and image', 'ranktim-article-sync' ); ?> </label> <div class="ranktim-checkbox-wrap"> <label class="ranktim-checkbox-label"> <input type="checkbox" name="ranktim_remove_duplicate_content" value="1" <?php checked( $remove_duplicate_content, true ); ?>> <span><?php esc_html_e( 'Automatically remove double content (first title and image)', 'ranktim-article-sync' ); ?></span> </label> <p class="ranktim-helper"><?php esc_html_e( 'This will automatically remove the first main heading and first image from the article text to prevent showing the same content twice.', 'ranktim-article-sync' ); ?></p> </div> </div><!-- /.ranktim-field --> <hr class="ranktim-divider"> <!-- ---- Actions ---- --> <div class="ranktim-actions"> <button type="submit" id="ranktim-save-btn"> ✓ <?php esc_html_e( 'Save Settings', 'ranktim-article-sync' ); ?> </button> <span class="ranktim-save-hint"> <?php esc_html_e( 'Changes take effect on the next sync.', 'ranktim-article-sync' ); ?> </span> </div> </form> <!-- Pricing CTA — always visible --> <div id="ranktim-products-nudge"> <div id="ranktim-products-nudge-text"> <strong>🛒 <?php esc_html_e( 'Upload products to Ranktim', 'ranktim-article-sync' ); ?></strong> <span><?php esc_html_e( 'Already exported products from WooCommerce? Open Ranktim Products and upload the file manually.', 'ranktim-article-sync' ); ?></span> </div> <a href="https://ranktim.com/shopping/products" target="_blank" rel="noopener noreferrer" id="ranktim-products-btn"> <?php esc_html_e( 'Open Ranktim Products', 'ranktim-article-sync' ); ?> ↗ </a> </div> <div id="ranktim-pricing-nudge"> <div id="ranktim-pricing-nudge-text"> <strong>✨ <?php esc_html_e( 'Unlock AI-powered SEO content on autopilot.', 'ranktim-article-sync' ); ?></strong> <span><?php esc_html_e( 'Ranktim writes, optimises, and publishes articles to your site — daily, automatically.', 'ranktim-article-sync' ); ?></span> </div> <a href="https://ranktim.com/pricing" target="_blank" rel="noopener noreferrer" id="ranktim-plans-btn"> <?php esc_html_e( 'View plans', 'ranktim-article-sync' ); ?> ↗ </a> </div> </div><!-- /#ranktim-card-body --> </div><!-- /#ranktim-card --> </div><!-- /#ranktim-layout --> <?php if ( $has_key ) : ?> <!-- ===== Recent Queue section ===== --> <div class="ranktim-queue-wrap"> <div id="ranktim-queue-card"> <div id="ranktim-queue-header"> <h2> 📅 <?php esc_html_e( 'Recent Content Queue', 'ranktim-article-sync' ); ?> <?php $queue_dates = Ranktim_Cron::get_sync_dates( true ); $queue_start = $queue_dates[0] ?? gmdate( 'Y-m-d' ); $queue_end = $queue_dates[ count( $queue_dates ) - 1 ] ?? $queue_start; ?> <span class="queue-date"><?php echo esc_html( $queue_start . ' → ' . $queue_end ); ?></span> </h2> <form method="post" action="<?php echo esc_url( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) ); ?>" class="ranktim-sync-form"> <?php wp_nonce_field( 'ranktim_sync_now', 'ranktim_sync_now_nonce' ); ?> <input type="hidden" name="ranktim_action" value="sync_now"> <button type="submit" class="ranktim-sync-btn"> ▶ <?php esc_html_e( 'Sync Now', 'ranktim-article-sync' ); ?> </button> </form> </div><!-- /#ranktim-queue-header --> <div id="ranktim-queue-body"> <?php $queue_items = array(); $queue_error = ''; $queue_note = ''; $queue_window = new Ranktim_Cron( $this->settings, $this->api ); $queue_data = $queue_window->get_queue_window( $existing_key, false ); $queue_items = $queue_data['items']; $queue_errors = $queue_data['errors']; $window_days = $queue_data['lookback_days'] + 1; if ( ! empty( $queue_errors ) && empty( $queue_items ) ) { $queue_error = reset( $queue_errors ); } elseif ( ! empty( $queue_errors ) ) { $queue_note = sprintf( /* translators: 1: number of days loaded successfully, 2: number of days with errors */ __( 'Showing available results from the last %1$d days. %2$d day(s) could not be loaded from the API.', 'ranktim-article-sync' ), $window_days, count( $queue_errors ) ); } ?> <?php if ( $queue_error ) : ?> <div class="ranktim-queue-error"> ⚠ <?php echo esc_html( $queue_error ); ?> </div> <?php else : ?> <?php if ( $queue_note ) : ?> <div class="ranktim-queue-note"> ℹ <?php echo esc_html( $queue_note ); ?> </div> <?php endif; ?> <?php if ( empty( $queue_items ) ) : ?> <div class="ranktim-queue-empty"> 📅 <?php esc_html_e( 'No articles were found in the recent sync window.', 'ranktim-article-sync' ); ?> </div> <?php else : ?> <?php foreach ( $queue_items as $q_item ) : $q_id = isset( $q_item['id'] ) ? $q_item['id'] : ''; $q_title = isset( $q_item['title'] ) ? $q_item['title'] : __( '(no title)', 'ranktim-article-sync' ); $q_status = isset( $q_item['status'] ) ? $q_item['status'] : ''; $q_date = isset( $q_item['scheduled_date'] ) ? $q_item['scheduled_date'] : ''; $q_imported = $q_id ? $this->item_is_imported( $q_id ) : false; $wp_post_id = $q_id ? $this->get_imported_post_id( $q_id ) : 0; if ( $q_imported ) { $badge_class = 'imported'; $badge_label = __( 'Imported', 'ranktim-article-sync' ); $q_icon = '✅'; } elseif ( 'ready' === $q_status ) { $badge_class = 'pending'; $badge_label = __( 'Ready — not imported yet', 'ranktim-article-sync' ); $q_icon = '📄'; } else { $badge_class = 'not-ready'; $badge_label = ucfirst( $q_status ); $q_icon = '⏳'; } ?> <div class="ranktim-queue-item"> <div class="ranktim-queue-item-icon"><?php echo esc_html( $q_icon ); ?></div> <div class="ranktim-queue-item-meta"> <div class="ranktim-queue-item-title" title="<?php echo esc_attr( $q_title ); ?>"><?php echo esc_html( $q_title ); ?></div> <div class="ranktim-queue-item-id"><?php echo esc_html( $q_date ? $q_date . ' • ' . $q_id : $q_id ); ?></div> </div> <span class="ranktim-queue-badge <?php echo esc_attr( $badge_class ); ?>"> <span class="badge-dot"></span> <?php echo esc_html( $badge_label ); ?> </span> <?php if ( $q_imported && $wp_post_id ) : ?> <a class="ranktim-queue-view-link" href="<?php echo esc_url( get_edit_post_link( $wp_post_id ) ); ?>" target="_blank" rel="noopener noreferrer"> <?php esc_html_e( 'View post ↗', 'ranktim-article-sync' ); ?> </a> <?php endif; ?> </div> <?php endforeach; ?> <?php endif; ?> <?php endif; ?> </div><!-- /#ranktim-queue-body --> </div><!-- /#ranktim-queue-card --> </div><!-- queue wrapper 580px --> <?php endif; ?> </div><!-- /#ranktim-wrap --> <?php } // ----------------------------------------------------------------------- // Sync Now handler // ----------------------------------------------------------------------- public function handle_sync_now(): void { if ( ! isset( $_POST['ranktim_sync_now_nonce'] ) ) { return; } if ( ! isset( $_POST['ranktim_action'] ) || 'sync_now' !== $_POST['ranktim_action'] ) { return; } if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Insufficient permissions.', 'ranktim-article-sync' ) ); } if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['ranktim_sync_now_nonce'] ) ), 'ranktim_sync_now' ) ) { wp_die( esc_html__( 'Security check failed. Please try again.', 'ranktim-article-sync' ) ); } $uid = get_current_user_id(); $key = $this->settings->get_key(); if ( empty( $key ) ) { $this->set_notice( $uid, 'error', __( 'Cannot sync: integration key not configured.', 'ranktim-article-sync' ) ); $this->redirect_back(); return; } // Run the same sync workflow the cron would run. $cron = new Ranktim_Cron( $this->settings, $this->api ); $cron->run_sync( true ); Ranktim_Cron::clear_queue_window_cache( $key ); $this->set_notice( $uid, 'success', __( 'Sync completed. The recent queue below was refreshed and any missing articles from the catch-up window were imported when available.', 'ranktim-article-sync' ) ); $this->redirect_back(); } // ----------------------------------------------------------------------- // Save handler // ----------------------------------------------------------------------- public function handle_save(): void { if ( ! isset( $_POST[ self::NONCE_FIELD ] ) ) { return; } if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Insufficient permissions.', 'ranktim-article-sync' ) ); } if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST[ self::NONCE_FIELD ] ) ), self::NONCE_ACTION ) ) { wp_die( esc_html__( 'Security check failed. Please try again.', 'ranktim-article-sync' ) ); } $uid = get_current_user_id(); // Validate integration key. $raw_key = isset( $_POST['ranktim_integration_key'] ) ? sanitize_text_field( wp_unslash( $_POST['ranktim_integration_key'] ) ) : ''; $final_key = $this->settings->get_key(); if ( '' !== $raw_key ) { if ( ! preg_match( '/^or_[a-f0-9]{64}$/i', $raw_key ) ) { $this->set_notice( $uid, 'error', __( 'Invalid Integration Key. It must start with "or_" followed by 64 hexadecimal characters.', 'ranktim-article-sync' ) ); $this->redirect_back(); return; } $final_key = $raw_key; } elseif ( '' === $final_key ) { $this->set_notice( $uid, 'error', __( 'Integration Key is required.', 'ranktim-article-sync' ) ); $this->redirect_back(); return; } // Validate post mode. $post_mode = isset( $_POST['ranktim_post_mode'] ) ? sanitize_key( wp_unslash( $_POST['ranktim_post_mode'] ) ) : 'publish'; if ( ! in_array( $post_mode, array( 'publish', 'draft' ), true ) ) { $post_mode = 'publish'; } // Default category. $default_category_id = isset( $_POST['ranktim_default_category'] ) ? absint( wp_unslash( $_POST['ranktim_default_category'] ) ) : 0; // Default author. $default_author_id = isset( $_POST['ranktim_default_author'] ) ? absint( wp_unslash( $_POST['ranktim_default_author'] ) ) : 0; // Default tags — multi-select, submitted as an array. $default_tag_ids = array(); if ( ! empty( $_POST['ranktim_default_tags'] ) && is_array( $_POST['ranktim_default_tags'] ) ) { $default_tag_ids = array_map( 'absint', wp_unslash( $_POST['ranktim_default_tags'] ) ); } // Remove duplicate content checkbox. $remove_duplicate_content = ! empty( $_POST['ranktim_remove_duplicate_content'] ); $this->settings->save( $final_key, $post_mode, $default_category_id, $default_author_id, $default_tag_ids, $remove_duplicate_content ); Ranktim_Cron::clear_queue_window_cache( $final_key ); $this->set_notice( $uid, 'success', __( 'Settings saved successfully.', 'ranktim-article-sync' ) ); $this->redirect_back(); } // ----------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------- private function set_notice( int $uid, string $type, string $msg ): void { set_transient( 'ranktim_notice_type_' . $uid, $type, 60 ); set_transient( 'ranktim_notice_msg_' . $uid, $msg, 60 ); } private function redirect_back(): void { wp_safe_redirect( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) ); exit; } /** * Check if a Ranktim item has already been imported as a WP post. * * @param string $item_id Ranktim item ID. * @return bool */ private function item_is_imported( string $item_id ): bool { $q = new WP_Query( array( 'post_type' => 'post', 'post_status' => array( 'publish', 'draft', 'pending', 'future', 'trash' ), 'meta_key' => '_ranktim_item_id', 'meta_value' => sanitize_text_field( $item_id ), 'posts_per_page' => 1, 'fields' => 'ids', 'no_found_rows' => true, ) ); return $q->have_posts(); } /** * Return the WP post ID for an imported Ranktim item (or 0 if not found). * * @param string $item_id Ranktim item ID. * @return int */ private function get_imported_post_id( string $item_id ): int { $q = new WP_Query( array( 'post_type' => 'post', 'post_status' => array( 'publish', 'draft', 'pending', 'future', 'trash' ), 'meta_key' => '_ranktim_item_id', 'meta_value' => sanitize_text_field( $item_id ), 'posts_per_page' => 1, 'fields' => 'ids', 'no_found_rows' => true, ) ); $posts = $q->posts; return ! empty( $posts ) ? (int) $posts[0] : 0; } }