PluginProbe ʕ •ᴥ•ʔ
Admin Help Docs / 2.0.0.1
Admin Help Docs v2.0.0.1
2.0.1.1 trunk 1.4.3.2 2.0.0 2.0.0.1 2.0.0.2 2.0.1
admin-help-docs / inc / tabs / import.php
admin-help-docs / inc / tabs Last commit date
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', '&#9889;' );
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">&mdash;</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();