←
-1, 'post_status' => 'publish', 'post_type' => Imports::$post_type, 'no_found_rows' => true, 'meta_query' => [ [ 'key' => 'helpdocs_url', 'value' => '', 'compare' => '!=', ], ], ] ); if ( empty( $import_posts ) ) { return; } $current_import_id = ( Menu::is_our_tab( 'import' ) && isset( $_GET[ 'id' ] ) ) ? absint( $_GET[ 'id' ] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $failed = []; foreach ( $import_posts as $import ) { $url = get_post_meta( $import->ID, 'helpdocs_url', true ); $api_key = get_post_meta( $import->ID, 'helpdocs_api_key', true ); $data = self::get_all_import_data( $url, $api_key ); if ( ! empty( $data[ 'error' ] ) ) { $failed[] = [ 'title' => $import->post_title, 'error' => $data[ 'error' ], 'is_current' => ( $import->ID === $current_import_id ), 'url' => add_query_arg( [ 'page' => Bootstrap::textdomain(), 'tab' => 'import', 'id' => $import->ID ], Bootstrap::admin_url( 'admin.php' ) ), ]; } } if ( empty( $failed ) ) { return; } echo '

' . esc_html__( 'Admin Help Docs:', 'admin-help-docs' ) . ' '; foreach ( $failed as $item ) { $reason = ( 'unauthorized' === $item[ 'error' ] ) ? __( 'invalid API key', 'admin-help-docs' ) : __( 'connection failed', 'admin-help-docs' ); if ( $item[ 'is_current' ] ) { printf( /* translators: 1: import title, 2: failure reason */ esc_html__( 'The import "%1$s" could not connect to its remote site (%2$s).', 'admin-help-docs' ), esc_html( $item[ 'title' ] ), esc_html( $reason ) ); } else { printf( /* translators: 1: import title, 2: failure reason, 3: edit URL */ esc_html__( 'The import "%1$s" could not connect to its remote site (%2$s). %3$s', 'admin-help-docs' ), esc_html( $item[ 'title' ] ), esc_html( $reason ), '' . esc_html__( 'Review import', 'admin-help-docs' ) . '' ); } echo ' '; } echo '

'; } // End render_failed_import_notice() /** * Render the tab */ public function render_tab() { $import_id = isset( $_GET[ 'id' ] ) ? absint( $_GET[ 'id' ] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_title = $import_id ? get_the_title( $import_id ) : ''; $website_url = sanitize_url( get_post_meta( $import_id, 'helpdocs_url', true ) ); $selected_docs = get_post_meta( $import_id, 'helpdocs_docs', true ) ?: []; $selected_tocs = get_post_meta( $import_id, 'helpdocs_tocs', true ) ?: []; $all_docs = get_post_meta( $import_id, 'helpdocs_all', true ); $all_tocs = get_post_meta( $import_id, 'helpdocs_all_tocs', true ); $api_key = get_post_meta( $import_id, 'helpdocs_api_key', true ); $table_row_header = ' ' . esc_html__( 'Auto Feed', 'admin-help-docs' ) . ' ' . esc_html__( 'Title', 'admin-help-docs' ) . ' ' . esc_html__( 'Publish Date', 'admin-help-docs' ) . ' ' . esc_html__( 'Created By', 'admin-help-docs' ) . ' ' . esc_html__( 'Site Location', 'admin-help-docs' ) . ' ' . esc_html__( 'TOC', 'admin-help-docs' ) . ' ' . esc_html__( 'Action', 'admin-help-docs' ) . ' '; ?>


render_table_rows( $docs, $selected_docs, $selected_tocs ); } ?>

ID ); $is_imported = in_array( $doc_id, $selected_docs ); $has_toc = in_array( $doc_id, $selected_tocs ); // Handle multi-location logic for remote docs $locations = isset( $doc->locations ) ? (array) $doc->locations : []; if ( empty( $locations ) && ! empty( $doc->site_location ) ) { $locations = [ [ 'site_location' => $doc->site_location, 'page_location' => $doc->page_location ?? '', 'custom' => $doc->custom ?? '', 'post_types' => $doc->post_types ?? [], 'order' => $doc->order ?? '', 'css_selector' => $doc->css_selector ?? '', ] ]; } $can_have_toc = false; foreach ( $locations as $loc ) { $loc_array = (array) $loc; if ( isset( $loc_array[ 'site_location' ] ) && 'main' === base64_decode( $loc_array[ 'site_location' ] ) ) { $can_have_toc = true; break; } } ?> > title ); ?> publish_date ) ) ); ?> created_by ); ?> > — [] ]; } if ( empty( $api_key ) ) { $import_id = isset( $_GET[ 'id' ] ) ? absint( $_GET[ 'id' ] ) : 0; // phpcs:ignore $api_key = isset( $_POST[ 'helpdocs_api_key' ] ) ? sanitize_text_field( wp_unslash( $_POST[ 'helpdocs_api_key' ] ) ) : (string) get_post_meta( $import_id, 'helpdocs_api_key', true ); // phpcs:ignore } $cache_key = 'helpdocs_remote_' . md5( $website_url . $api_key ); if ( isset( self::$request_cache[ $cache_key ] ) ) { return self::$request_cache[ $cache_key ]; } $cached = get_transient( $cache_key ); if ( false !== $cached ) { self::$request_cache[ $cache_key ] = $cached; return $cached; } $timeout = apply_filters( 'helpdocs_import_timeout', is_admin() && wp_doing_ajax() ? 20 : 5, $website_url ); $v2_url = str_replace( home_url(), $website_url, API::api_path() ); $version_found = 'v2'; $response = self::fetch_remote_json( $v2_url, $api_key, $timeout ); $status = wp_remote_retrieve_response_code( $response ); if ( ! is_wp_error( $response ) && 404 === $status ) { $version_found = 'v1'; $response = self::fetch_remote_json( str_replace( '/v2/', '/v1/', $v2_url ), $api_key, $timeout ); $status = wp_remote_retrieve_response_code( $response ); } if ( is_wp_error( $response ) ) { return self::cache_result( $cache_key, [ 'error' => 'connection_failed' ], true, $website_url ); } if ( 401 === $status ) { return self::cache_result( $cache_key, [ 'error' => 'unauthorized' ], true, $website_url ); } $docs = json_decode( wp_remote_retrieve_body( $response ) ); if ( ! empty( $docs ) && is_array( $docs ) ) { $data = [ 'version' => $version_found, 'docs' => $docs, ]; return self::cache_result( $cache_key, $data, false, $website_url ); } return self::cache_result( $cache_key, [ 'docs' => [] ], true, $website_url ); } // End get_all_import_data() /** * Store a lookup result in both the request cache and a transient * * @param string $cache_key * @param array $data * @param bool $is_failure Failures get a much shorter TTL so a fixed remote recovers quickly * @param string $website_url * @return array */ private static function cache_result( $cache_key, $data, $is_failure, $website_url ) { $success_ttl = apply_filters( 'helpdocs_import_cache_duration', 12 * HOUR_IN_SECONDS, $website_url ); $failure_ttl = apply_filters( 'helpdocs_import_failure_cache_duration', 15 * MINUTE_IN_SECONDS, $website_url ); set_transient( $cache_key, $data, $is_failure ? $failure_ttl : $success_ttl ); self::$request_cache[ $cache_key ] = $data; return $data; } // End cache_result() /** * Force a fresh lookup, bypassing both caches * * @param string $website_url * @param string $api_key * @return array */ public static function refresh_import_data( $website_url, $api_key = '' ) { $cache_key = 'helpdocs_remote_' . md5( esc_url_raw( trim( (string) $website_url ) ) . $api_key ); unset( self::$request_cache[ $cache_key ] ); delete_transient( $cache_key ); return self::get_all_import_data( $website_url, $api_key ); } // End refresh_import_data() /** * Helper to keep the remote request clean * * @param string $url * @param string $api_key * @param int $timeout * @return array|\WP_Error */ private static function fetch_remote_json( $url, $api_key = '', $timeout = 5 ) { $args = [ 'timeout' => absint( $timeout ), 'httpversion' => '1.1', 'user-agent' => 'AdminHelpDocs Importer; ' . home_url(), 'headers' => [ 'Accept' => 'application/json', ], ]; if ( ! empty( $api_key ) ) { $args[ 'headers' ][ 'X-HelpDocs-API-Key' ] = $api_key; } return wp_remote_get( $url, $args ); } // End fetch_remote_json() /** * Save import settings */ public function save_import_settings() { if ( Menu::get_current_page() !== Bootstrap::textdomain() || Menu::get_current_tab() !== 'import' ) { return; } if ( ! isset( $_POST[ 'action' ] ) || 'helpdocs_import_docs' !== sanitize_text_field( wp_unslash( $_POST[ 'action' ] ) ) ) { return; } if ( ! isset( $_POST[ 'helpdocs_save_import_nonce' ] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST[ 'helpdocs_save_import_nonce' ] ) ), 'helpdocs_save_import_nonce' ) ) { return; } if ( ! Helpers::user_can_edit() ) { return; } $website_url = isset( $_POST[ 'helpdocs_website_url' ] ) ? sanitize_url( wp_unslash( $_POST[ 'helpdocs_website_url' ] ) ) : ''; if ( empty( $website_url ) ) { return; } $import_id = isset( $_POST[ 'id' ] ) ? absint( $_POST[ 'id' ] ) : 0; $is_active = isset( $_POST[ 'helpdocs_import_active' ] ); $post_status = $is_active ? 'publish' : 'draft'; $import_title = isset( $_POST[ 'helpdocs_import_title' ] ) ? sanitize_text_field( wp_unslash( $_POST[ 'helpdocs_import_title' ] ) ) : ''; if ( empty( $import_title ) ) { $import_title = ucfirst( str_ireplace( [ 'http://', 'https://', 'www.' ], '', $website_url ) ); $import_title = untrailingslashit( $import_title ); } $original_title = $import_title; $count = 1; while ( $this->title_exists( $import_title, $import_id ) ) { $count++; $import_title = ( $original_title ) . ' (' . $count . ')'; } $post_data = [ 'post_type' => Imports::$post_type, 'post_title' => $import_title, 'post_status' => $post_status, ]; if ( ! $import_id ) { $import_id = wp_insert_post( $post_data ); } else { $post_data[ 'ID' ] = $import_id; wp_update_post( $post_data ); // MIGRATION: Prune the old legacy key now that we're using the post status delete_post_meta( $import_id, 'helpdocs_enabled' ); } if ( is_wp_error( $import_id ) || ! $import_id ) { return; } $api_key = isset( $_POST[ 'helpdocs_api_key' ] ) ? sanitize_text_field( wp_unslash( $_POST[ 'helpdocs_api_key' ] ) ) : ''; $all_docs = isset( $_POST[ 'helpdocs_all' ] ) ? '1' : '0'; $all_tocs = isset( $_POST[ 'helpdocs_all_tocs' ] ) ? '1' : '0'; $selected_docs = isset( $_POST[ 'helpdocs_auto_feed' ] ) ? array_map( 'absint', (array) wp_unslash( $_POST[ 'helpdocs_auto_feed' ] ) ) : []; $selected_tocs = isset( $_POST[ 'helpdocs_tocs' ] ) ? array_map( 'absint', (array) wp_unslash( $_POST[ 'helpdocs_tocs' ] ) ) : []; // Save Metadata update_post_meta( $import_id, 'helpdocs_api_key', $api_key ); update_post_meta( $import_id, 'helpdocs_url', $website_url ); update_post_meta( $import_id, 'helpdocs_all', $all_docs ); update_post_meta( $import_id, 'helpdocs_all_tocs', $all_tocs ); update_post_meta( $import_id, 'helpdocs_docs', $selected_docs ); update_post_meta( $import_id, 'helpdocs_tocs', $selected_tocs ); // Redirect with ID and Success Message $redirect_url = add_query_arg( [ 'id' => $import_id, 'import-updated' => 'true', ], wp_get_referer() ); wp_safe_redirect( $redirect_url ); exit; } // End save_import_settings() /** * Helper to check if title already exists for this CPT * * @param string $title The title to check * @param int $post_id The current post ID to exclude from the check */ private function title_exists( $title, $post_id ) { global $wpdb; return $wpdb->get_var( $wpdb->prepare( // phpcs:ignore "SELECT ID FROM $wpdb->posts WHERE post_title = %s AND post_type = %s AND ID != %d LIMIT 1", $title, Imports::$post_type, $post_id ) ); } // End title_exists() /** * AJAX handler to fetch remote documents */ public function ajax_fetch_remote_docs() { check_ajax_referer( 'helpdocs_import_fetch_nonce', 'nonce' ); if ( ! Helpers::user_can_edit() ) { wp_send_json_error( [ 'message' => __( 'You do not have permission to fetch remote documents.', 'admin-help-docs' ) ] ); } $url = isset( $_POST[ 'url' ] ) ? sanitize_url( wp_unslash( $_POST[ 'url' ] ) ) : ''; $api_key = isset( $_POST[ 'api_key' ] ) ? sanitize_text_field( wp_unslash( $_POST[ 'api_key' ] ) ) : ''; if ( empty( $url ) ) { wp_send_json_error( [ 'message' => __( 'Please enter a valid URL.', 'admin-help-docs' ) ] ); } $import_data = self::refresh_import_data( $url, $api_key ); $docs = $import_data[ 'docs' ] ?? []; $version = $import_data[ 'version' ] ?? 'v2'; $error = $import_data[ 'error' ] ?? ''; // If we have a specific error (unauthorized or connection_failed) if ( ! empty( $error ) ) { wp_send_json_success( [ 'error' => $error, 'docs' => [], 'count' => 0, 'version' => $version ] ); } if ( empty( $docs ) ) { wp_send_json_error( [ 'message' => __( 'No documents found at this URL.', 'admin-help-docs' ) ] ); } ob_start(); $this->render_table_rows( $docs ); $html = ob_get_clean(); wp_send_json_success( [ 'html' => $html, 'count' => count( $docs ), 'version' => $version, 'error' => '' ] ); } // End ajax_fetch_remote_docs() /** * AJAX handler to import an individual document */ public function ajax_import_individual_doc() { check_ajax_referer( 'helpdocs_import_clone_nonce', 'nonce' ); if ( ! Helpers::user_can_edit() ) { wp_send_json_error( __( 'You do not have permission to import documents.', 'admin-help-docs' ) ); } $doc_id = isset( $_POST[ 'doc_id' ] ) ? absint( $_POST[ 'doc_id' ] ) : 0; $import_id = isset( $_POST[ 'import_id' ] ) ? absint( $_POST[ 'import_id' ] ) : 0; $url = isset( $_POST[ 'website_url' ] ) ? sanitize_url( wp_unslash( $_POST[ 'website_url' ] ) ) : ''; $api_key = isset( $_POST[ 'api_key' ] ) ? sanitize_text_field( wp_unslash( $_POST[ 'api_key' ] ) ) : ''; if ( ! $doc_id || ! $url ) { wp_send_json_error( __( 'Invalid request data.', 'admin-help-docs' ) ); } $import_data = self::refresh_import_data( $url, $api_key ); if ( ! empty( $import_data[ 'error' ] ) ) { wp_send_json_error( sprintf( __( 'Remote error: %s', 'admin-help-docs' ), $import_data[ 'error' ] ) ); } // Fetch all remote data and find our specific doc $remote_docs = $import_data[ 'docs' ] ?? []; $raw_target = null; foreach ( (array) $remote_docs as $doc ) { if ( isset( $doc->ID ) && absint( $doc->ID ) === $doc_id ) { $raw_target = $doc; break; } } if ( ! $raw_target ) { wp_send_json_error( __( 'Could not find the document in the remote feed.', 'admin-help-docs' ) ); } // Convert the entire stdClass object and all nested objects into associative arrays. $target_doc = json_decode( json_encode( $raw_target ), true ); // 1. Prepare and Insert the Post $new_post_id = wp_insert_post( [ 'post_title' => sanitize_text_field( $target_doc[ 'title' ] ), 'post_content' => wp_kses_post( $target_doc[ 'content' ] ), 'post_excerpt' => sanitize_text_field( $target_doc[ 'desc' ] ?? '' ), 'post_status' => 'publish', 'post_type' => HelpDocs::$post_type, ] ); if ( is_wp_error( $new_post_id ) ) { wp_send_json_error( $new_post_id->get_error_message() ); } // 2. Handle Taxonomies if ( ! empty( $target_doc[ 'taxonomies' ] ) && is_array( $target_doc[ 'taxonomies' ] ) ) { foreach ( $target_doc[ 'taxonomies' ] as $taxonomy => $terms ) { if ( ! taxonomy_exists( $taxonomy ) || empty( $terms ) ) { continue; } $term_ids = []; foreach ( $terms as $term_data ) { $term = get_term_by( 'slug', $term_data[ 'slug' ], $taxonomy ); if ( ! $term ) { $new_term = wp_insert_term( $term_data[ 'name' ], $taxonomy, [ 'slug' => $term_data[ 'slug' ] ] ); if ( ! is_wp_error( $new_term ) ) { $term_ids[] = (int) $new_term[ 'term_id' ]; } } else { $term_ids[] = (int) $term->term_id; } } wp_set_object_terms( $new_post_id, $term_ids, $taxonomy ); } } // 3. Normalize Locations $all_site_locations = HelpDocs::site_locations(); $final_locations = []; $source_locations = []; if ( ! empty( $target_doc[ 'locations' ] ) && is_array( $target_doc[ 'locations' ] ) ) { $source_locations = $target_doc[ 'locations' ]; } elseif ( ! empty( $target_doc[ 'site_location' ] ) ) { $source_locations = [ [ 'site_location' => $target_doc[ 'site_location' ], 'page_location' => $target_doc[ 'page_location' ] ?? '', 'custom' => $target_doc[ 'custom' ] ?? '', 'post_types' => $target_doc[ 'post_types' ] ?? [], 'order' => $target_doc[ 'order' ] ?? 0, 'toc' => $target_doc[ 'toc' ] ?? false, 'css_selector' => $target_doc[ 'css_selector' ] ?? '', 'addt_params' => $target_doc[ 'addt_params' ] ?? false, ] ]; } foreach ( $source_locations as $loc ) { // $loc is now guaranteed to be an array because of our json_decode(..., true) $site_key_encoded = sanitize_text_field( $loc[ 'site_location' ] ?? '' ); $site_key_decoded = base64_decode( $site_key_encoded ); if ( isset( $all_site_locations[ $site_key_decoded ] ) ) { $clean = [ 'site_location' => $site_key_encoded ]; $allowed_fields = $all_site_locations[ $site_key_decoded ][ 'fields' ] ?? []; foreach ( $allowed_fields as $field_rule ) { $field_parts = explode( ':', $field_rule ); $field_name = ( count( $field_parts ) > 1 ) ? end( $field_parts ) : $field_rule; $val = $loc[ $field_name ] ?? ''; switch ( $field_name ) { case 'order': $clean[ $field_name ] = intval( $val ); break; case 'toc': case 'addt_params': $clean[ $field_name ] = ! empty( $val ); break; case 'post_types': // Ensure this is saved as a clean array of keys $pts = is_serialized( $val ) ? unserialize( $val ) : (array) $val; $clean[ $field_name ] = array_values( array_map( 'sanitize_key', (array) $pts ) ); break; case 'custom': $clean[ $field_name ] = esc_url_raw( $val ); break; case 'page_location': $clean[ $field_name ] = sanitize_key( $val ); break; default: $clean[ $field_name ] = sanitize_text_field( $val ); break; } } $final_locations[] = $clean; } } // 4. Update Meta update_post_meta( $new_post_id, 'helpdocs_locations', $final_locations ); update_post_meta( $new_post_id, 'helpdocs_view_roles', array_map( 'sanitize_key', (array) ( $target_doc->view_roles ?? [] ) ) ); update_post_meta( $new_post_id, 'helpdocs_imported_doc_id', $doc_id ); update_post_meta( $new_post_id, 'helpdocs_import_id', $import_id ); update_post_meta( $new_post_id, 'helpdocs_imported_from', $url ); update_post_meta( $new_post_id, 'helpdocs_imported_by', get_current_user_id() ); // Compatibility meta for Classic Editor if ( ! empty( $target_doc->editor_type ) ) { update_post_meta( $new_post_id, 'classic-editor-remember', sanitize_text_field( $target_doc->editor_type ) ); } // Support for extra fields added via 'helpdocs_api_doc_object_out' filter $standard_keys = [ 'ID', 'title', 'created_by', 'publish_date', 'modified_date', 'modified_by', 'desc', 'content', 'taxonomies', 'site_location', 'page_location', 'custom', 'post_types', 'order', 'css_selector', 'addt_params', 'view_roles', 'editor_type' ]; foreach ( $target_doc as $key => $value ) { if ( ! in_array( $key, $standard_keys ) ) { update_post_meta( $new_post_id, 'helpdocs_' . sanitize_key( $key ), is_scalar( $value ) ? sanitize_text_field( $value ) : $value ); } } Helpers::flush_location_cache(); wp_send_json_success( __( 'Document imported successfully.', 'admin-help-docs' ) ); } // End ajax_import_individual_doc() /** * Prevent cloning and unserializing */ public function __clone() {} public function __wakeup() {} } ImportEditor::instance();