css
3 months ago
js
3 months ago
admin-menu.php
3 months ago
documentation.php
3 months ago
faq.php
3 months ago
import.php
3 months ago
settings.php
3 months ago
support.php
3 months ago
import.php
810 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Import Editor Loader |
| 4 | */ |
| 5 | |
| 6 | namespace PluginRx\AdminHelpDocs; |
| 7 | |
| 8 | if ( ! defined( 'ABSPATH' ) ) exit; |
| 9 | |
| 10 | class ImportEditor { |
| 11 | |
| 12 | |
| 13 | /** |
| 14 | * The single instance of the class |
| 15 | * |
| 16 | * @var self|null |
| 17 | */ |
| 18 | private static ?ImportEditor $instance = null; |
| 19 | |
| 20 | |
| 21 | /** |
| 22 | * Get the singleton instance |
| 23 | * |
| 24 | * @return self |
| 25 | */ |
| 26 | public static function instance() : self { |
| 27 | return self::$instance ??= new self(); |
| 28 | } // End instance() |
| 29 | |
| 30 | |
| 31 | /** |
| 32 | * Constructor |
| 33 | */ |
| 34 | private function __construct() { |
| 35 | add_action( 'helpdocs_subheader_left', [ $this, 'header_buttons' ] ); |
| 36 | add_action( 'helpdocs_subheader_right', [ $this, 'debug_quick_link' ] ); |
| 37 | add_action( 'admin_head', [ $this, 'pre_cache_locations' ] ); |
| 38 | add_action( 'admin_init', [ $this, 'save_import_settings' ] ); |
| 39 | add_action( 'wp_ajax_helpdocs_fetch_remote_docs', [ $this, 'ajax_fetch_remote_docs' ] ); |
| 40 | add_action( 'wp_ajax_helpdocs_import_individual_doc', [ $this, 'ajax_import_individual_doc' ] ); |
| 41 | } // End __construct() |
| 42 | |
| 43 | |
| 44 | /** |
| 45 | * Add header buttons |
| 46 | * |
| 47 | * @param string $current_tab The current tab slug |
| 48 | */ |
| 49 | public function header_buttons( $current_tab ) { |
| 50 | if ( $current_tab === 'import' ) { |
| 51 | $import_id = isset( $_GET[ 'id' ] ) ? absint( $_GET[ 'id' ] ) : 0; // phpcs:ignore |
| 52 | $status = get_post_status( $import_id ); |
| 53 | |
| 54 | $is_active = ( 'publish' === $status ); |
| 55 | $legacy_enabled = get_post_meta( $import_id, 'helpdocs_enabled', true ); |
| 56 | if ( '0' === $legacy_enabled ) { |
| 57 | $is_active = false; |
| 58 | } |
| 59 | |
| 60 | $show_success = isset( $_GET[ 'settings-updated' ] ) && 'true' === sanitize_text_field( wp_unslash( $_GET[ 'settings-updated' ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 61 | ?> |
| 62 | <div class="helpdocs-header-controls"> |
| 63 | <div class="helpdocs-active-toggle"> |
| 64 | <span class="helpdocs-toggle-label"><?php esc_html_e( 'Active', 'admin-help-docs' ); ?></span> |
| 65 | <label class="helpdocs-switch"> |
| 66 | <input type="checkbox" name="helpdocs_import_active" id="helpdocs_import_active" value="1" <?php checked( $is_active ); ?> form="helpdocs_import_form"> |
| 67 | <span class="helpdocs-slider"></span> |
| 68 | </label> |
| 69 | </div> |
| 70 | <span id="helpdocs-inactive-notice" style="display: <?php echo ( $import_id && ! $is_active ) ? 'inline' : 'none'; ?>;">← <?php echo esc_html__( 'Don\'t forget to activate the import.', 'admin-help-docs' ); ?></span> |
| 71 | <span id="helpdocs-save-reminder"><?php echo esc_html__( 'Remember to click "Save Import Settings" after making changes to your import.', 'admin-help-docs' ); ?></span> |
| 72 | <span id="helpdocs-saved-success" style="display: <?php echo $show_success ? 'inline' : 'none'; ?>;"><?php echo esc_html__( 'Settings saved successfully.', 'admin-help-docs' ); ?></span> |
| 73 | </div> |
| 74 | <?php |
| 75 | } |
| 76 | } // End header_buttons() |
| 77 | |
| 78 | |
| 79 | /** |
| 80 | * Add debug quick link for Dev Debug Tools plugin if active |
| 81 | * |
| 82 | * @param string $current_tab The current tab slug |
| 83 | */ |
| 84 | public function debug_quick_link( $current_tab ) { |
| 85 | if ( $current_tab === 'import' ) { |
| 86 | if ( is_plugin_active( 'dev-debug-tools/dev-debug-tools.php' ) ) { |
| 87 | $import_id = isset( $_GET[ 'id' ] ) ? absint( $_GET[ 'id' ] ) : 0; // phpcs:ignore |
| 88 | if ( ! $import_id ) { |
| 89 | return; |
| 90 | } |
| 91 | |
| 92 | $icon = apply_filters( 'ddtt_quick_link_icon', '⚡' ); |
| 93 | $nonce = wp_create_nonce( 'ddtt_metadata_lookup' ); |
| 94 | ?> |
| 95 | <div class="ddtt-debug-quick-link"> |
| 96 | <span class="ddtt-icon"><?php echo esc_html( $icon ); ?></span><a href="/wp-admin/admin.php?page=dev-debug-tools&tool=metadata&s=post&lookup=<?php echo esc_attr( $import_id ); ?>&_wpnonce=<?php echo esc_attr( $nonce ); ?>" target="_blank"> |
| 97 | <?php esc_html_e( 'Debug Import', 'admin-help-docs' ); ?> |
| 98 | </a> |
| 99 | </div> |
| 100 | <?php |
| 101 | } |
| 102 | } |
| 103 | } // End debug_quick_link() |
| 104 | |
| 105 | |
| 106 | /** |
| 107 | * Pre-cache site locations on the import page to speed up the UI |
| 108 | */ |
| 109 | public function pre_cache_locations() { |
| 110 | if ( Menu::get_current_page() !== Bootstrap::textdomain() || Menu::get_current_tab() !== 'import' ) { |
| 111 | return; |
| 112 | } |
| 113 | |
| 114 | HelpDocs::site_locations(); |
| 115 | } // End pre_cache_locations() |
| 116 | |
| 117 | |
| 118 | /** |
| 119 | * Render the tab |
| 120 | */ |
| 121 | public function render_tab() { |
| 122 | $import_id = isset( $_GET[ 'id' ] ) ? absint( $_GET[ 'id' ] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 123 | $post_title = $import_id ? get_the_title( $import_id ) : ''; |
| 124 | $website_url = sanitize_url( get_post_meta( $import_id, 'helpdocs_url', true ) ); |
| 125 | |
| 126 | $selected_docs = get_post_meta( $import_id, 'helpdocs_docs', true ) ?: []; |
| 127 | $selected_tocs = get_post_meta( $import_id, 'helpdocs_tocs', true ) ?: []; |
| 128 | |
| 129 | $all_docs = get_post_meta( $import_id, 'helpdocs_all', true ); |
| 130 | $all_tocs = get_post_meta( $import_id, 'helpdocs_all_tocs', true ); |
| 131 | |
| 132 | $api_key = get_post_meta( $import_id, 'helpdocs_api_key', true ); |
| 133 | |
| 134 | $table_row_header = ' |
| 135 | <tr> |
| 136 | <th scope="col" class="manage-column column-feed">' . esc_html__( 'Auto Feed', 'admin-help-docs' ) . '</th> |
| 137 | <th scope="col" class="manage-column column-title">' . esc_html__( 'Title', 'admin-help-docs' ) . '</th> |
| 138 | <th scope="col" class="manage-column column-date">' . esc_html__( 'Publish Date', 'admin-help-docs' ) . '</th> |
| 139 | <th scope="col" class="manage-column column-author">' . esc_html__( 'Created By', 'admin-help-docs' ) . '</th> |
| 140 | <th scope="col" class="manage-column column-location">' . esc_html__( 'Site Location', 'admin-help-docs' ) . '</th> |
| 141 | <th scope="col" class="manage-column column-toc">' . esc_html__( 'TOC', 'admin-help-docs' ) . '</th> |
| 142 | <th scope="col" class="manage-column column-action">' . esc_html__( 'Action', 'admin-help-docs' ) . '</th> |
| 143 | </tr>'; |
| 144 | ?> |
| 145 | <div class="helpdocs-full-width-box"> |
| 146 | |
| 147 | <p class="helpdocs-instructions"><em><?php echo esc_html__( "You can choose to remotely feed documents from the other website, which will update automatically if they are changed on the other site. This is useful if you manage several sites and want to control them in one spot. You may also import them individually, which will clone them and add them to this website and no longer be linked to the remote site.", 'admin-help-docs' ); ?></em></p> |
| 148 | |
| 149 | <p class="helpdocs-instructions"><em><?php echo esc_html__( "The \"TOC\" option allows you to add the doc to the Dashboard Table of Contents, provided that you have enabled Dashboard TOC in your settings and the feed's site location is set to \"Main Documentation Page.\"", 'admin-help-docs' ); ?></em></p> |
| 150 | |
| 151 | <form id="helpdocs_import_form" method="post"> |
| 152 | <input type="hidden" name="action" value="helpdocs_import_docs"> |
| 153 | <input type="hidden" name="id" id="helpdocs_import_post_id" value="<?php echo esc_attr( $import_id ); ?>"> |
| 154 | <?php wp_nonce_field( 'helpdocs_save_import_nonce', 'helpdocs_save_import_nonce' ); ?> |
| 155 | |
| 156 | <div id="helpdocs_title_container"> |
| 157 | <label for="helpdocs_import_title"><?php esc_html_e( 'Import Name', 'admin-help-docs' ); ?>:</label> |
| 158 | <input name="helpdocs_import_title" id="helpdocs_import_title" type="text" value="<?php echo esc_attr( $post_title ); ?>" placeholder="<?php esc_attr_e( 'e.g. My Remote Docs', 'admin-help-docs' ); ?>"> |
| 159 | </div> |
| 160 | |
| 161 | <div id="helpdocs_website_url_container"> |
| 162 | <label for="helpdocs_website_url"><?php esc_html_e( 'Enter the URL of the website you would like to import help docs from', 'admin-help-docs' ); ?>:</label> |
| 163 | <div id="helpdocs_url_field"> |
| 164 | <input name="helpdocs_website_url" id="helpdocs_website_url" type="url" value="<?php echo esc_url( $website_url ); ?>" placeholder="https://example.com"> |
| 165 | |
| 166 | <div class="helpdocs-api-key-container"> |
| 167 | <input name="helpdocs_api_key" id="helpdocs_api_key" type="password" value="<?php echo esc_attr( $api_key ); ?>" placeholder="<?php esc_attr_e( 'API Key (Optional)', 'admin-help-docs' ); ?>" style="width: 200px; padding-right: 30px;"> |
| 168 | <span class="dashicons dashicons-visibility helpdocs-toggle-visibility"></span> |
| 169 | </div> |
| 170 | |
| 171 | <a id="helpdocs_fetch_remote_docs" class="helpdocs-button" href="#"><?php esc_html_e( 'Fetch Docs', 'admin-help-docs' ); ?></a> |
| 172 | <?php if ( Bootstrap::is_test_mode() ) : ?> |
| 173 | <a href="<?php echo esc_url( $website_url ); ?>/wp-json/admin-help-docs/v1/docs" class="helpdocs-button button-secondary" target="_blank"><?php esc_html_e( 'v1', 'admin-help-docs' ); ?></a> |
| 174 | <a href="<?php echo esc_url( $website_url ); ?>/wp-json/admin-help-docs/v2/docs" class="helpdocs-button button-secondary" target="_blank"><?php esc_html_e( 'v2', 'admin-help-docs' ); ?></a> |
| 175 | <?php endif; ?> |
| 176 | </div> |
| 177 | </div> |
| 178 | |
| 179 | <?php |
| 180 | $import_data = $website_url ? self::get_all_import_data( $website_url, $api_key ) : []; |
| 181 | $docs = $import_data[ 'docs' ] ?? []; |
| 182 | $error = $import_data[ 'error' ] ?? ''; |
| 183 | $api_version = $import_data[ 'version' ] ?? 'v2'; |
| 184 | $total_items = count( $docs ); |
| 185 | $version_class = $api_version === 'v1' && ! empty( $docs ) ? 'display:block;' : 'display:none;'; |
| 186 | ?> |
| 187 | |
| 188 | <div id="helpdocs_version_notice" class="helpdocs_warning_notice" style="<?php echo esc_attr( $version_class ); ?>"><p> |
| 189 | <?php echo esc_html__( 'Note: This remote site is using an older version of the plugin. Some newer features, like multiple locations per document, may be limited until the remote site is updated.', 'admin-help-docs' ); ?> |
| 190 | </p></div> |
| 191 | |
| 192 | <div id="helpdocs_api_error" class="helpdocs_error_notice" style="<?php echo ( $error === 'unauthorized' ) ? '' : 'display:none;'; ?>"> |
| 193 | <p><strong><?php esc_html_e( 'Connection Unauthorized:', 'admin-help-docs' ); ?></strong> <?php esc_html_e( 'The remote site requires a valid API Key. Please check your key and try again.', 'admin-help-docs' ); ?></p> |
| 194 | </div> |
| 195 | |
| 196 | <div id="helpdocs_connection_error" class="helpdocs_error_notice" style="<?php echo ( $error === 'connection_failed' ) ? '' : 'display:none;'; ?>"> |
| 197 | <p><?php esc_html_e( 'Could not connect to the remote site. Please check the URL.', 'admin-help-docs' ); ?></p> |
| 198 | </div> |
| 199 | |
| 200 | <div id="helpdocs_remote_docs_wrapper" style="<?php echo empty( $website_url ) ? 'display:none;' : ''; ?>"> |
| 201 | |
| 202 | <div id="helpdocs_tablenav_top" class="tablenav top"> |
| 203 | <div class="alignleft actions"> |
| 204 | <button type="button" class="button helpdocs-select-all-toggle" data-type="feed"><?php esc_html_e( 'Select All Auto Feeds', 'admin-help-docs' ); ?></button> |
| 205 | <button type="button" class="button helpdocs-select-all-toggle" data-type="toc"><?php esc_html_e( 'Select All TOCs', 'admin-help-docs' ); ?></button> |
| 206 | <label class="helpdocs-all-checkbox"> |
| 207 | <input type="checkbox" name="helpdocs_all" id="helpdocs_all" value="1" <?php checked( $all_docs, '1' ); ?>> |
| 208 | <strong><?php esc_html_e( 'Feed All Documents Automatically', 'admin-help-docs' ); ?></strong> |
| 209 | </label> |
| 210 | <label class="helpdocs-all-checkbox" id="helpdocs_all_tocs_container" style="<?php echo ( '1' === $all_docs ) ? 'display:inline-flex;' : 'display:none;'; ?>"> |
| 211 | <input type="checkbox" name="helpdocs_all_tocs" id="helpdocs_all_tocs" value="1" <?php checked( $all_tocs, '1' ); ?>> |
| 212 | <?php esc_html_e( 'Add All Main Docs to Dashboard Table of Contents (Must be Enabled in Settings)', 'admin-help-docs' ); ?> |
| 213 | </label> |
| 214 | </div> |
| 215 | <div class="tablenav-pages"> |
| 216 | <span class="displaying-num"> |
| 217 | <?php if ( $total_items ) echo esc_html( sprintf( _n( '%s item', '%s items', $total_items, 'admin-help-docs' ), number_format_i18n( $total_items ) ) ); ?> |
| 218 | </span> |
| 219 | </div> |
| 220 | <br class="clear"> |
| 221 | </div> |
| 222 | |
| 223 | <table id="helpdocs_imports_table" class="wp-list-table widefat fixed striped posts" data-import-id="<?php echo esc_attr( $import_id ); ?>"> |
| 224 | <thead><?php echo wp_kses_post( $table_row_header ); ?></thead> |
| 225 | <tbody id="the-list"> |
| 226 | <?php |
| 227 | if ( ! empty( $docs ) ) { |
| 228 | $this->render_table_rows( $docs, $selected_docs, $selected_tocs ); |
| 229 | } |
| 230 | ?> |
| 231 | </tbody> |
| 232 | <tfoot><?php echo wp_kses_post( $table_row_header ); ?></tfoot> |
| 233 | </table> |
| 234 | |
| 235 | <div id="helpdocs_tablenav_bottom" class="tablenav bottom"> |
| 236 | <div class="tablenav-pages"> |
| 237 | <span class="displaying-num"> |
| 238 | <?php if ( $total_items ) echo esc_html( sprintf( _n( '%s item', '%s items', $total_items, 'admin-help-docs' ), number_format_i18n( $total_items ) ) ); ?> |
| 239 | </span> |
| 240 | </div> |
| 241 | <br class="clear"> |
| 242 | </div> |
| 243 | </div> |
| 244 | |
| 245 | <div id="helpdocs_no_docs_found" style="<?php echo ( $website_url && empty( $docs ) ) ? '' : 'display:none;'; ?>"> |
| 246 | <p><em><?php echo esc_html__( 'No documents found at the provided URL.', 'admin-help-docs' ); ?></em></p> |
| 247 | </div> |
| 248 | </form> |
| 249 | </div> |
| 250 | <?php |
| 251 | } // End render_tab() |
| 252 | |
| 253 | |
| 254 | /** |
| 255 | * Render table rows for the import list |
| 256 | * |
| 257 | * @param array $docs Array of document objects. |
| 258 | * @param array $selected_docs IDs of already selected docs. |
| 259 | * @param array $selected_tocs IDs of already selected TOCs. |
| 260 | */ |
| 261 | private function render_table_rows( $docs, $selected_docs = [], $selected_tocs = [] ) { |
| 262 | if ( empty( $docs ) ) { |
| 263 | return; |
| 264 | } |
| 265 | |
| 266 | foreach ( $docs as $doc ) { |
| 267 | $doc_id = absint( $doc->ID ); |
| 268 | $is_imported = in_array( $doc_id, $selected_docs ); |
| 269 | $has_toc = in_array( $doc_id, $selected_tocs ); |
| 270 | |
| 271 | // Handle multi-location logic for remote docs |
| 272 | $locations = isset( $doc->locations ) ? (array) $doc->locations : []; |
| 273 | if ( empty( $locations ) && ! empty( $doc->site_location ) ) { |
| 274 | $locations = [ |
| 275 | [ |
| 276 | 'site_location' => $doc->site_location, |
| 277 | 'page_location' => $doc->page_location ?? '', |
| 278 | 'custom' => $doc->custom ?? '', |
| 279 | 'post_types' => $doc->post_types ?? [], |
| 280 | 'order' => $doc->order ?? '', |
| 281 | 'css_selector' => $doc->css_selector ?? '', |
| 282 | ] |
| 283 | ]; |
| 284 | } |
| 285 | |
| 286 | $can_have_toc = false; |
| 287 | foreach ( $locations as $loc ) { |
| 288 | $loc_array = (array) $loc; |
| 289 | if ( isset( $loc_array[ 'site_location' ] ) && 'main' === base64_decode( $loc_array[ 'site_location' ] ) ) { |
| 290 | $can_have_toc = true; |
| 291 | break; |
| 292 | } |
| 293 | } |
| 294 | ?> |
| 295 | <tr data-doc-id="<?php echo esc_attr( $doc_id ); ?>"> |
| 296 | <td class="column-feed check-column"> |
| 297 | <input type="checkbox" name="helpdocs_auto_feed[]" value="<?php echo esc_attr( $doc_id ); ?>" class="feed-checkbox" <?php checked( $is_imported ); ?>> |
| 298 | </td> |
| 299 | <td class="column-title title"> |
| 300 | <strong><?php echo esc_html( $doc->title ); ?></strong> |
| 301 | <button type="button" class="toggle-row"><span class="screen-reader-text"><?php esc_html_e( 'Show more details', 'admin-help-docs' ); ?></span></button> |
| 302 | </td> |
| 303 | <td class="column-date"><?php echo esc_html( date_i18n( get_option( 'date_format' ), strtotime( $doc->publish_date ) ) ); ?></td> |
| 304 | <td class="column-author"><?php echo esc_html( $doc->created_by ); ?></td> |
| 305 | |
| 306 | <td class="column-location"> |
| 307 | <?php if ( empty( $locations ) ) : ?> |
| 308 | <span class="description"><?php esc_html_e( 'No locations set.', 'admin-help-docs' ); ?></span> |
| 309 | <?php else : ?> |
| 310 | <ul style="margin:0; padding:0; list-style:none;"> |
| 311 | <?php foreach ( $locations as $loc ) { |
| 312 | $loc_array = (array) $loc; |
| 313 | $site_val = $loc_array[ 'site_location' ] ?? ''; |
| 314 | if ( empty( $site_val ) ) continue; |
| 315 | |
| 316 | $label = Helpers::get_admin_page_title_from_url( $site_val ); |
| 317 | $details = []; |
| 318 | |
| 319 | // 1. Page Location |
| 320 | if ( ! empty( $loc_array[ 'page_location' ] ) ) { |
| 321 | $page_loc = ucwords( str_replace( '_', ' ', $loc_array[ 'page_location' ] ) ); |
| 322 | if ( $page_loc === 'Contextual' ) $page_loc .= ' Help Tab'; |
| 323 | $details[] = '<em>' . esc_html( $page_loc ) . '</em>'; |
| 324 | } |
| 325 | |
| 326 | // 2. Custom URL |
| 327 | if ( ! empty( $loc_array[ 'custom' ] ) ) { |
| 328 | $details[] = '<code style="font-size:10px;">' . esc_html( $loc_array[ 'custom' ] ) . '</code>'; |
| 329 | } |
| 330 | |
| 331 | // 3. Post Types (Handled serialized remote data) |
| 332 | if ( ! empty( $loc_array[ 'post_types' ] ) ) { |
| 333 | $pt_raw = $loc_array[ 'post_types' ]; |
| 334 | $pts = is_serialized( $pt_raw ) ? unserialize( $pt_raw ) : $pt_raw; |
| 335 | $pts = is_array( $pts ) ? $pts : [ $pts ]; |
| 336 | |
| 337 | if ( ! empty( $pts ) ) { |
| 338 | $clean_pts = array_map( function( $pt ) { |
| 339 | return ucwords( str_replace( [ '-', '_' ], ' ', $pt ) ); |
| 340 | }, $pts ); |
| 341 | $details[] = 'Types: ' . esc_html( implode( ', ', $clean_pts ) ); |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | echo '<li style="margin-bottom: 8px; border-bottom: 1px solid #eee; padding-bottom: 4px;">'; |
| 346 | echo '<strong>' . wp_kses_post( $label ) . '</strong>'; |
| 347 | if ( ! empty( $details ) ) { |
| 348 | echo '<br><span class="description" style="font-size:11px;">' . wp_kses_post( implode( ' | ', $details ) ) . '</span>'; |
| 349 | } |
| 350 | echo '</li>'; |
| 351 | } ?> |
| 352 | </ul> |
| 353 | <?php endif; ?> |
| 354 | </td> |
| 355 | |
| 356 | <td class="column-toc check-column"> |
| 357 | <?php if ( $can_have_toc ) : ?> |
| 358 | <input type="checkbox" name="helpdocs_tocs[]" value="<?php echo esc_attr( $doc_id ); ?>" class="toc-checkbox" <?php checked( $has_toc ); ?>> |
| 359 | <?php else : ?> |
| 360 | <span class="dash">—</span> |
| 361 | <?php endif; ?> |
| 362 | </td> |
| 363 | <td class="column-action"> |
| 364 | <button type="button" class="button button-secondary helpdocs-clone-individual" data-id="<?php echo esc_attr( $doc_id ); ?>"> |
| 365 | <?php esc_html_e( 'Copy to Local', 'admin-help-docs' ); ?> |
| 366 | </button> |
| 367 | </td> |
| 368 | </tr> |
| 369 | <?php |
| 370 | } |
| 371 | } // End render_table_rows() |
| 372 | |
| 373 | |
| 374 | /** |
| 375 | * Check if the current request is a browser "Hard Refresh" |
| 376 | * * @return bool |
| 377 | */ |
| 378 | private static function is_hard_refresh() { |
| 379 | $cache_control = filter_input( INPUT_SERVER, 'HTTP_CACHE_CONTROL', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); |
| 380 | $pragma = filter_input( INPUT_SERVER, 'HTTP_PRAGMA', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); |
| 381 | |
| 382 | $is_no_cache = ( 'no-cache' === $cache_control || 'no-cache' === $pragma ); |
| 383 | |
| 384 | $is_max_age = ( 'max-age=0' === $cache_control ); |
| 385 | |
| 386 | return ( $is_no_cache || $is_max_age ); |
| 387 | } // End is_hard_refresh() |
| 388 | |
| 389 | |
| 390 | /** |
| 391 | * Get all import data for a given website |
| 392 | * |
| 393 | * @param string $website The website to get imports for |
| 394 | * @return array Array of import objects |
| 395 | */ |
| 396 | public static function get_all_import_data( $website_url, $api_key = '' ) { |
| 397 | $import_id = isset( $_GET[ 'id' ] ) ? absint( $_GET[ 'id' ] ) : 0; // phpcs:ignore |
| 398 | |
| 399 | if ( empty( $api_key ) ) { |
| 400 | $api_key = isset( $_POST[ 'helpdocs_api_key' ] ) ? sanitize_text_field( wp_unslash( $_POST[ 'helpdocs_api_key' ] ) ) : get_post_meta( $import_id, 'helpdocs_api_key', true ); // phpcs:ignore |
| 401 | } |
| 402 | |
| 403 | $cache_key = 'helpdocs_remote_' . md5( $website_url . $api_key ); |
| 404 | |
| 405 | if ( self::is_hard_refresh() ) { |
| 406 | delete_transient( $cache_key ); |
| 407 | } |
| 408 | |
| 409 | $cached = get_transient( $cache_key ); |
| 410 | |
| 411 | if ( false !== $cached ) { |
| 412 | return $cached; |
| 413 | } |
| 414 | |
| 415 | $v2_url = API::api_path(); |
| 416 | $v2_url = str_replace( home_url(), $website_url, $v2_url ); |
| 417 | |
| 418 | $version_found = 'v2'; |
| 419 | $response = self::fetch_remote_json( $v2_url, $api_key ); |
| 420 | $status = wp_remote_retrieve_response_code( $response ); |
| 421 | |
| 422 | // If V2 is 404, try V1 |
| 423 | if ( ! is_wp_error( $response ) && $status === 404 ) { |
| 424 | $version_found = 'v1'; |
| 425 | $v1_url = str_replace( '/v2/', '/v1/', $v2_url ); |
| 426 | $response = self::fetch_remote_json( $v1_url, $api_key ); |
| 427 | $status = wp_remote_retrieve_response_code( $response ); |
| 428 | } |
| 429 | |
| 430 | if ( is_wp_error( $response ) ) { |
| 431 | return [ 'error' => 'connection_failed' ]; |
| 432 | } |
| 433 | |
| 434 | if ( $status === 401 ) { |
| 435 | return [ 'error' => 'unauthorized' ]; |
| 436 | } |
| 437 | |
| 438 | $docs = json_decode( wp_remote_retrieve_body( $response ) ); |
| 439 | |
| 440 | if ( ! empty( $docs ) && is_array( $docs ) ) { |
| 441 | $data = [ |
| 442 | 'version' => $version_found, |
| 443 | 'docs' => $docs, |
| 444 | ]; |
| 445 | set_transient( $cache_key, $data, apply_filters( 'helpdocs_import_cache_duration', 12 * HOUR_IN_SECONDS, $website_url ) ); |
| 446 | return $data; |
| 447 | } |
| 448 | |
| 449 | return [ 'docs' => [] ]; |
| 450 | } // End get_all_import_data() |
| 451 | |
| 452 | |
| 453 | /** |
| 454 | * Helper to keep the remote request clean |
| 455 | */ |
| 456 | private static function fetch_remote_json( $url, $api_key = '' ) { |
| 457 | $args = [ |
| 458 | 'timeout' => 20, |
| 459 | 'httpversion' => '1.1', |
| 460 | 'user-agent' => 'AdminHelpDocs Importer; ' . home_url(), |
| 461 | 'headers' => [ |
| 462 | 'Accept' => 'application/json', |
| 463 | ], |
| 464 | ]; |
| 465 | |
| 466 | if ( ! empty( $api_key ) ) { |
| 467 | $args[ 'headers' ][ 'X-HelpDocs-API-Key' ] = $api_key; |
| 468 | } |
| 469 | |
| 470 | return wp_remote_get( $url, $args ); |
| 471 | } // End fetch_remote_json() |
| 472 | |
| 473 | |
| 474 | /** |
| 475 | * Save import settings |
| 476 | */ |
| 477 | public function save_import_settings() { |
| 478 | if ( Menu::get_current_page() !== Bootstrap::textdomain() || Menu::get_current_tab() !== 'import' ) { |
| 479 | return; |
| 480 | } |
| 481 | |
| 482 | if ( ! isset( $_POST[ 'action' ] ) || 'helpdocs_import_docs' !== sanitize_text_field( wp_unslash( $_POST[ 'action' ] ) ) ) { |
| 483 | return; |
| 484 | } |
| 485 | |
| 486 | if ( ! isset( $_POST[ 'helpdocs_save_import_nonce' ] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST[ 'helpdocs_save_import_nonce' ] ) ), 'helpdocs_save_import_nonce' ) ) { |
| 487 | return; |
| 488 | } |
| 489 | |
| 490 | if ( ! Helpers::user_can_edit() ) { |
| 491 | return; |
| 492 | } |
| 493 | |
| 494 | $website_url = isset( $_POST[ 'helpdocs_website_url' ] ) ? sanitize_url( wp_unslash( $_POST[ 'helpdocs_website_url' ] ) ) : ''; |
| 495 | if ( empty( $website_url ) ) { |
| 496 | return; |
| 497 | } |
| 498 | |
| 499 | $import_id = isset( $_POST[ 'id' ] ) ? absint( $_POST[ 'id' ] ) : 0; |
| 500 | $is_active = isset( $_POST[ 'helpdocs_import_active' ] ); |
| 501 | $post_status = $is_active ? 'publish' : 'draft'; |
| 502 | $import_title = isset( $_POST[ 'helpdocs_import_title' ] ) ? sanitize_text_field( wp_unslash( $_POST[ 'helpdocs_import_title' ] ) ) : ''; |
| 503 | |
| 504 | if ( empty( $import_title ) ) { |
| 505 | $import_title = ucfirst( str_ireplace( [ 'http://', 'https://', 'www.' ], '', $website_url ) ); |
| 506 | $import_title = untrailingslashit( $import_title ); |
| 507 | } |
| 508 | |
| 509 | $original_title = $import_title; |
| 510 | $count = 1; |
| 511 | while ( $this->title_exists( $import_title, $import_id ) ) { |
| 512 | $count++; |
| 513 | $import_title = ( $original_title ) . ' (' . $count . ')'; |
| 514 | } |
| 515 | |
| 516 | $post_data = [ |
| 517 | 'post_type' => Imports::$post_type, |
| 518 | 'post_title' => $import_title, |
| 519 | 'post_status' => $post_status, |
| 520 | ]; |
| 521 | |
| 522 | if ( ! $import_id ) { |
| 523 | $import_id = wp_insert_post( $post_data ); |
| 524 | } else { |
| 525 | $post_data[ 'ID' ] = $import_id; |
| 526 | wp_update_post( $post_data ); |
| 527 | |
| 528 | // MIGRATION: Prune the old legacy key now that we're using the post status |
| 529 | delete_post_meta( $import_id, 'helpdocs_enabled' ); |
| 530 | } |
| 531 | |
| 532 | if ( is_wp_error( $import_id ) || ! $import_id ) { |
| 533 | return; |
| 534 | } |
| 535 | |
| 536 | $api_key = isset( $_POST[ 'helpdocs_api_key' ] ) ? sanitize_text_field( wp_unslash( $_POST[ 'helpdocs_api_key' ] ) ) : ''; |
| 537 | |
| 538 | $all_docs = isset( $_POST[ 'helpdocs_all' ] ) ? '1' : '0'; |
| 539 | $all_tocs = isset( $_POST[ 'helpdocs_all_tocs' ] ) ? '1' : '0'; |
| 540 | |
| 541 | $selected_docs = isset( $_POST[ 'helpdocs_auto_feed' ] ) ? array_map( 'absint', (array) wp_unslash( $_POST[ 'helpdocs_auto_feed' ] ) ) : []; |
| 542 | $selected_tocs = isset( $_POST[ 'helpdocs_tocs' ] ) ? array_map( 'absint', (array) wp_unslash( $_POST[ 'helpdocs_tocs' ] ) ) : []; |
| 543 | |
| 544 | // Save Metadata |
| 545 | update_post_meta( $import_id, 'helpdocs_api_key', $api_key ); |
| 546 | update_post_meta( $import_id, 'helpdocs_url', $website_url ); |
| 547 | update_post_meta( $import_id, 'helpdocs_all', $all_docs ); |
| 548 | update_post_meta( $import_id, 'helpdocs_all_tocs', $all_tocs ); |
| 549 | update_post_meta( $import_id, 'helpdocs_docs', $selected_docs ); |
| 550 | update_post_meta( $import_id, 'helpdocs_tocs', $selected_tocs ); |
| 551 | |
| 552 | // Redirect with ID and Success Message |
| 553 | $redirect_url = add_query_arg( [ |
| 554 | 'id' => $import_id, |
| 555 | 'import-updated' => 'true', |
| 556 | ], wp_get_referer() ); |
| 557 | |
| 558 | wp_safe_redirect( $redirect_url ); |
| 559 | exit; |
| 560 | } // End save_import_settings() |
| 561 | |
| 562 | |
| 563 | /** |
| 564 | * Helper to check if title already exists for this CPT |
| 565 | */ |
| 566 | private function title_exists( $title, $post_id ) { |
| 567 | global $wpdb; |
| 568 | return $wpdb->get_var( $wpdb->prepare( // phpcs:ignore |
| 569 | "SELECT ID FROM $wpdb->posts WHERE post_title = %s AND post_type = %s AND ID != %d LIMIT 1", |
| 570 | $title, |
| 571 | Imports::$post_type, |
| 572 | $post_id |
| 573 | ) ); |
| 574 | } // End title_exists() |
| 575 | |
| 576 | |
| 577 | /** |
| 578 | * AJAX handler to fetch remote documents |
| 579 | */ |
| 580 | public function ajax_fetch_remote_docs() { |
| 581 | check_ajax_referer( 'helpdocs_import_fetch_nonce', 'nonce' ); |
| 582 | |
| 583 | if ( ! Helpers::user_can_edit() ) { |
| 584 | wp_send_json_error( [ 'message' => __( 'You do not have permission to fetch remote documents.', 'admin-help-docs' ) ] ); |
| 585 | } |
| 586 | |
| 587 | $url = isset( $_POST[ 'url' ] ) ? sanitize_url( wp_unslash( $_POST[ 'url' ] ) ) : ''; |
| 588 | $api_key = isset( $_POST[ 'api_key' ] ) ? sanitize_text_field( wp_unslash( $_POST[ 'api_key' ] ) ) : ''; |
| 589 | |
| 590 | if ( empty( $url ) ) { |
| 591 | wp_send_json_error( [ 'message' => __( 'Please enter a valid URL.', 'admin-help-docs' ) ] ); |
| 592 | } |
| 593 | |
| 594 | // Delete transient using the new cache key logic (url + key) |
| 595 | delete_transient( 'helpdocs_remote_' . md5( $url . $api_key ) ); |
| 596 | |
| 597 | $import_data = self::get_all_import_data( $url, $api_key ); |
| 598 | $docs = $import_data[ 'docs' ] ?? []; |
| 599 | $version = $import_data[ 'version' ] ?? 'v2'; |
| 600 | $error = $import_data[ 'error' ] ?? ''; |
| 601 | |
| 602 | // If we have a specific error (unauthorized or connection_failed) |
| 603 | if ( ! empty( $error ) ) { |
| 604 | wp_send_json_success( [ |
| 605 | 'error' => $error, |
| 606 | 'docs' => [], |
| 607 | 'count' => 0, |
| 608 | 'version' => $version |
| 609 | ] ); |
| 610 | } |
| 611 | |
| 612 | if ( empty( $docs ) ) { |
| 613 | wp_send_json_error( [ 'message' => __( 'No documents found at this URL.', 'admin-help-docs' ) ] ); |
| 614 | } |
| 615 | |
| 616 | ob_start(); |
| 617 | $this->render_table_rows( $docs ); |
| 618 | $html = ob_get_clean(); |
| 619 | |
| 620 | wp_send_json_success( [ |
| 621 | 'html' => $html, |
| 622 | 'count' => count( $docs ), |
| 623 | 'version' => $version, |
| 624 | 'error' => '' |
| 625 | ] ); |
| 626 | } // End ajax_fetch_remote_docs() |
| 627 | |
| 628 | |
| 629 | /** |
| 630 | * AJAX handler to import an individual document |
| 631 | */ |
| 632 | public function ajax_import_individual_doc() { |
| 633 | check_ajax_referer( 'helpdocs_import_clone_nonce', 'nonce' ); |
| 634 | if ( ! Helpers::user_can_edit() ) { |
| 635 | wp_send_json_error( __( 'You do not have permission to import documents.', 'admin-help-docs' ) ); |
| 636 | } |
| 637 | |
| 638 | $doc_id = isset( $_POST[ 'doc_id' ] ) ? absint( $_POST[ 'doc_id' ] ) : 0; |
| 639 | $import_id = isset( $_POST[ 'import_id' ] ) ? absint( $_POST[ 'import_id' ] ) : 0; |
| 640 | $url = isset( $_POST[ 'website_url' ] ) ? sanitize_url( wp_unslash( $_POST[ 'website_url' ] ) ) : ''; |
| 641 | $api_key = isset( $_POST[ 'api_key' ] ) ? sanitize_text_field( wp_unslash( $_POST[ 'api_key' ] ) ) : ''; |
| 642 | |
| 643 | if ( ! $doc_id || ! $url ) { |
| 644 | wp_send_json_error( __( 'Invalid request data.', 'admin-help-docs' ) ); |
| 645 | } |
| 646 | |
| 647 | $cache_key = 'helpdocs_remote_' . md5( $url . $api_key ); |
| 648 | delete_transient( $cache_key ); |
| 649 | |
| 650 | $import_data = self::get_all_import_data( $url, $api_key ); |
| 651 | if ( ! empty( $import_data[ 'error' ] ) ) { |
| 652 | wp_send_json_error( sprintf( __( 'Remote error: %s', 'admin-help-docs' ), $import_data[ 'error' ] ) ); |
| 653 | } |
| 654 | |
| 655 | // Fetch all remote data and find our specific doc |
| 656 | $remote_docs = $import_data[ 'docs' ] ?? []; |
| 657 | $raw_target = null; |
| 658 | |
| 659 | foreach ( (array) $remote_docs as $doc ) { |
| 660 | if ( isset( $doc->ID ) && absint( $doc->ID ) === $doc_id ) { |
| 661 | $raw_target = $doc; |
| 662 | break; |
| 663 | } |
| 664 | } |
| 665 | |
| 666 | if ( ! $raw_target ) { |
| 667 | wp_send_json_error( __( 'Could not find the document in the remote feed.', 'admin-help-docs' ) ); |
| 668 | } |
| 669 | |
| 670 | // Convert the entire stdClass object and all nested objects into associative arrays. |
| 671 | $target_doc = json_decode( json_encode( $raw_target ), true ); |
| 672 | |
| 673 | // 1. Prepare and Insert the Post |
| 674 | $new_post_id = wp_insert_post( [ |
| 675 | 'post_title' => sanitize_text_field( $target_doc[ 'title' ] ), |
| 676 | 'post_content' => wp_kses_post( $target_doc[ 'content' ] ), |
| 677 | 'post_excerpt' => sanitize_text_field( $target_doc[ 'desc' ] ?? '' ), |
| 678 | 'post_status' => 'publish', |
| 679 | 'post_type' => HelpDocs::$post_type, |
| 680 | ] ); |
| 681 | |
| 682 | if ( is_wp_error( $new_post_id ) ) { |
| 683 | wp_send_json_error( $new_post_id->get_error_message() ); |
| 684 | } |
| 685 | |
| 686 | // 2. Handle Taxonomies |
| 687 | if ( ! empty( $target_doc[ 'taxonomies' ] ) && is_array( $target_doc[ 'taxonomies' ] ) ) { |
| 688 | foreach ( $target_doc[ 'taxonomies' ] as $taxonomy => $terms ) { |
| 689 | if ( ! taxonomy_exists( $taxonomy ) || empty( $terms ) ) { |
| 690 | continue; |
| 691 | } |
| 692 | |
| 693 | $term_ids = []; |
| 694 | foreach ( $terms as $term_data ) { |
| 695 | $term = get_term_by( 'slug', $term_data[ 'slug' ], $taxonomy ); |
| 696 | if ( ! $term ) { |
| 697 | $new_term = wp_insert_term( $term_data[ 'name' ], $taxonomy, [ 'slug' => $term_data[ 'slug' ] ] ); |
| 698 | if ( ! is_wp_error( $new_term ) ) { |
| 699 | $term_ids[] = (int) $new_term[ 'term_id' ]; |
| 700 | } |
| 701 | } else { |
| 702 | $term_ids[] = (int) $term->term_id; |
| 703 | } |
| 704 | } |
| 705 | wp_set_object_terms( $new_post_id, $term_ids, $taxonomy ); |
| 706 | } |
| 707 | } |
| 708 | |
| 709 | // 3. Normalize Locations |
| 710 | $all_site_locations = HelpDocs::site_locations(); |
| 711 | $final_locations = []; |
| 712 | $source_locations = []; |
| 713 | |
| 714 | if ( ! empty( $target_doc[ 'locations' ] ) && is_array( $target_doc[ 'locations' ] ) ) { |
| 715 | $source_locations = $target_doc[ 'locations' ]; |
| 716 | } elseif ( ! empty( $target_doc[ 'site_location' ] ) ) { |
| 717 | $source_locations = [ |
| 718 | [ |
| 719 | 'site_location' => $target_doc[ 'site_location' ], |
| 720 | 'page_location' => $target_doc[ 'page_location' ] ?? '', |
| 721 | 'custom' => $target_doc[ 'custom' ] ?? '', |
| 722 | 'post_types' => $target_doc[ 'post_types' ] ?? [], |
| 723 | 'order' => $target_doc[ 'order' ] ?? 0, |
| 724 | 'toc' => $target_doc[ 'toc' ] ?? false, |
| 725 | 'css_selector' => $target_doc[ 'css_selector' ] ?? '', |
| 726 | 'addt_params' => $target_doc[ 'addt_params' ] ?? false, |
| 727 | ] |
| 728 | ]; |
| 729 | } |
| 730 | |
| 731 | foreach ( $source_locations as $loc ) { |
| 732 | // $loc is now guaranteed to be an array because of our json_decode(..., true) |
| 733 | $site_key_encoded = sanitize_text_field( $loc[ 'site_location' ] ?? '' ); |
| 734 | $site_key_decoded = base64_decode( $site_key_encoded ); |
| 735 | |
| 736 | if ( isset( $all_site_locations[ $site_key_decoded ] ) ) { |
| 737 | $clean = [ 'site_location' => $site_key_encoded ]; |
| 738 | $allowed_fields = $all_site_locations[ $site_key_decoded ][ 'fields' ] ?? []; |
| 739 | |
| 740 | foreach ( $allowed_fields as $field_rule ) { |
| 741 | $field_parts = explode( ':', $field_rule ); |
| 742 | $field_name = ( count( $field_parts ) > 1 ) ? end( $field_parts ) : $field_rule; |
| 743 | $val = $loc[ $field_name ] ?? ''; |
| 744 | |
| 745 | switch ( $field_name ) { |
| 746 | case 'order': |
| 747 | $clean[ $field_name ] = intval( $val ); |
| 748 | break; |
| 749 | case 'toc': |
| 750 | case 'addt_params': |
| 751 | $clean[ $field_name ] = ! empty( $val ); |
| 752 | break; |
| 753 | case 'post_types': |
| 754 | // Ensure this is saved as a clean array of keys |
| 755 | $pts = is_serialized( $val ) ? unserialize( $val ) : (array) $val; |
| 756 | $clean[ $field_name ] = array_values( array_map( 'sanitize_key', (array) $pts ) ); |
| 757 | break; |
| 758 | case 'custom': |
| 759 | $clean[ $field_name ] = esc_url_raw( $val ); |
| 760 | break; |
| 761 | case 'page_location': |
| 762 | $clean[ $field_name ] = sanitize_key( $val ); |
| 763 | break; |
| 764 | default: |
| 765 | $clean[ $field_name ] = sanitize_text_field( $val ); |
| 766 | break; |
| 767 | } |
| 768 | } |
| 769 | $final_locations[] = $clean; |
| 770 | } |
| 771 | } |
| 772 | |
| 773 | // 4. Update Meta |
| 774 | update_post_meta( $new_post_id, 'helpdocs_locations', $final_locations ); |
| 775 | update_post_meta( $new_post_id, 'helpdocs_view_roles', array_map( 'sanitize_key', (array) ( $target_doc->view_roles ?? [] ) ) ); |
| 776 | update_post_meta( $new_post_id, 'helpdocs_imported_doc_id', $doc_id ); |
| 777 | update_post_meta( $new_post_id, 'helpdocs_import_id', $import_id ); |
| 778 | update_post_meta( $new_post_id, 'helpdocs_imported_from', $url ); |
| 779 | update_post_meta( $new_post_id, 'helpdocs_imported_by', get_current_user_id() ); |
| 780 | |
| 781 | // Compatibility meta for Classic Editor |
| 782 | if ( ! empty( $target_doc->editor_type ) ) { |
| 783 | update_post_meta( $new_post_id, 'classic-editor-remember', sanitize_text_field( $target_doc->editor_type ) ); |
| 784 | } |
| 785 | |
| 786 | // Support for extra fields added via 'helpdocs_api_doc_object_out' filter |
| 787 | $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' ]; |
| 788 | |
| 789 | foreach ( $target_doc as $key => $value ) { |
| 790 | if ( ! in_array( $key, $standard_keys ) ) { |
| 791 | update_post_meta( $new_post_id, 'helpdocs_' . sanitize_key( $key ), is_scalar( $value ) ? sanitize_text_field( $value ) : $value ); |
| 792 | } |
| 793 | } |
| 794 | |
| 795 | Helpers::flush_location_cache(); |
| 796 | |
| 797 | wp_send_json_success( __( 'Document imported successfully.', 'admin-help-docs' ) ); |
| 798 | } // End ajax_import_individual_doc() |
| 799 | |
| 800 | |
| 801 | /** |
| 802 | * Prevent cloning and unserializing |
| 803 | */ |
| 804 | public function __clone() {} |
| 805 | public function __wakeup() {} |
| 806 | |
| 807 | } |
| 808 | |
| 809 | |
| 810 | ImportEditor::instance(); |