css
22 hours ago
js
22 hours ago
admin-menu.php
22 hours ago
documentation.php
22 hours ago
faq.php
22 hours ago
import.php
22 hours ago
migrate.php
22 hours ago
settings.php
22 hours ago
support.php
22 hours ago
documentation.php
724 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Documentation Tab Loader |
| 4 | */ |
| 5 | |
| 6 | namespace PluginRx\AdminHelpDocs; |
| 7 | |
| 8 | if ( ! defined( 'ABSPATH' ) ) exit; |
| 9 | |
| 10 | class Documentation { |
| 11 | |
| 12 | /** |
| 13 | * The single instance of the class |
| 14 | * |
| 15 | * @var self|null |
| 16 | */ |
| 17 | private static ?Documentation $instance = null; |
| 18 | |
| 19 | |
| 20 | /** |
| 21 | * Get the singleton instance |
| 22 | * |
| 23 | * @return self |
| 24 | */ |
| 25 | public static function instance() : self { |
| 26 | return self::$instance ??= new self(); |
| 27 | } // End instance() |
| 28 | |
| 29 | |
| 30 | /** |
| 31 | * Constructor |
| 32 | */ |
| 33 | private function __construct() { |
| 34 | add_action( 'helpdocs_subheader_right', [ $this, 'render_search_box' ] ); |
| 35 | add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_styles' ] ); |
| 36 | add_action( 'wp_ajax_helpdocs_save_docs_order', [ $this, 'ajax_save_docs_order' ] ); |
| 37 | if ( get_option( 'helpdocs_curly_quotes' ) ) { |
| 38 | add_action( 'current_screen', [ $this, 'remove_curly_quotes' ] ); |
| 39 | } |
| 40 | } // End __construct() |
| 41 | |
| 42 | |
| 43 | /** |
| 44 | * Add a search box to the subheader on the folders page |
| 45 | * |
| 46 | * @param string $current_tab The current admin tab |
| 47 | * @return void |
| 48 | */ |
| 49 | public function render_search_box( string $current_tab ) { |
| 50 | if ( $current_tab !== 'documentation' ) { |
| 51 | return; |
| 52 | } |
| 53 | |
| 54 | $search_value = sanitize_text_field( wp_unslash( $_GET[ 'search' ] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 55 | ?> |
| 56 | <div id="helpdocs-header-action-links" style="display: none;"> |
| 57 | <a id="expand-all" class="action-links" href="#"><?php echo esc_html__( 'Expand Folders', 'admin-help-docs' ); ?></a> |
| 58 | <a id="collapse-all" class="action-links" href="#"><?php echo esc_html__( 'Collapse Folders', 'admin-help-docs' ); ?></a> |
| 59 | </div> |
| 60 | <form method="get" class="helpdocs-tax-search"> |
| 61 | <input type="hidden" name="page" value="<?php echo esc_attr( Bootstrap::textdomain() ); ?>"> |
| 62 | <input type="hidden" name="tab" value="documentation"> |
| 63 | <input type="search" |
| 64 | name="search" |
| 65 | value="<?php echo esc_attr( $search_value ); ?>" |
| 66 | placeholder="<?php echo esc_attr__( 'Search Docs', 'admin-help-docs' ); ?>" |
| 67 | class="helpdocs-search-input" /> |
| 68 | |
| 69 | <input type="submit" |
| 70 | class="helpdocs-button" |
| 71 | value="<?php echo esc_attr__( 'Search', 'admin-help-docs' ); ?>" /> |
| 72 | |
| 73 | <a href="<?php |
| 74 | $clear_url = remove_query_arg( 'search' ); |
| 75 | echo esc_url( $clear_url ); |
| 76 | ?>" class="helpdocs-button"> |
| 77 | <?php echo esc_html__( 'Clear', 'admin-help-docs' ); ?> |
| 78 | </a> |
| 79 | </form> |
| 80 | <?php |
| 81 | } // End render_search_box() |
| 82 | |
| 83 | |
| 84 | /** |
| 85 | * Render the tab |
| 86 | */ |
| 87 | public function render_tab() { |
| 88 | $user_can_edit = Helpers::user_can_edit(); |
| 89 | |
| 90 | $docs = Helpers::get_docs( [ |
| 91 | 'site_location' => 'main', |
| 92 | 'category' => isset( $_GET[ 'cat' ] ) ? absint( $_GET[ 'cat' ] ) : 0, // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 93 | 'tag' => isset( $_GET[ 'tag' ] ) ? sanitize_text_field( wp_unslash( $_GET[ 'tag' ] ) ) : '' // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 94 | ] ); |
| 95 | |
| 96 | $docs = array_filter( $docs, fn( $doc ) => Helpers::user_can_view( $doc->ID ) ); |
| 97 | if ( empty( $docs ) ) { |
| 98 | ?> |
| 99 | <br><br><br> |
| 100 | <?php |
| 101 | if ( $user_can_edit ) { |
| 102 | ?> |
| 103 | <em><?php esc_html_e( 'No documents found. Start by clicking "Add New" above!', 'admin-help-docs' ); ?></em> |
| 104 | <?php |
| 105 | } else { |
| 106 | ?> |
| 107 | <em><?php esc_html_e( 'No documents found.', 'admin-help-docs' ); ?></em> |
| 108 | <?php |
| 109 | } |
| 110 | return; |
| 111 | } |
| 112 | |
| 113 | $doc_ids = wp_list_pluck( $docs, 'ID' ); |
| 114 | |
| 115 | // $docs_for_testing = array_map( function( $doc ) { |
| 116 | // return [ |
| 117 | // 'ID' => $doc->ID, |
| 118 | // 'post_title' => $doc->post_title, |
| 119 | // 'helpdocs_order' => $doc->helpdocs_order, |
| 120 | // // 'feed_id' => $doc->feed_id ?? '', |
| 121 | // // 'helpdocs_view_roles' => $doc->helpdocs_view_roles ?? [], |
| 122 | // // 'helpdocs_locations' => $doc->helpdocs_locations ?? [], |
| 123 | // // 'local_folder_id' => $doc->local_folder_id ?? null, |
| 124 | // // 'taxonomies' => $doc->taxonomies ?? (object)[] |
| 125 | // ]; |
| 126 | // }, $docs ); |
| 127 | // dpr( $docs_for_testing ); |
| 128 | |
| 129 | $current_url = Bootstrap::tab_url( 'documentation' ); |
| 130 | |
| 131 | if ( isset( $_GET[ 'search' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 132 | $s = sanitize_text_field( wp_unslash( $_GET[ 'search' ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 133 | $current_url = add_query_arg( 'search', $s, $current_url ); |
| 134 | } else { |
| 135 | $s = ''; |
| 136 | } |
| 137 | |
| 138 | $folder_taxonomy = Folders::$taxonomy; |
| 139 | $folders = get_terms( [ |
| 140 | 'taxonomy' => $folder_taxonomy, |
| 141 | 'hide_empty' => false, |
| 142 | ] ); |
| 143 | |
| 144 | usort( $folders, function( $a, $b ) { |
| 145 | $order_a = get_term_meta( $a->term_id, 'helpdocs_order', true ); |
| 146 | $order_b = get_term_meta( $b->term_id, 'helpdocs_order', true ); |
| 147 | |
| 148 | $order_a_missing = ( $order_a === '' ); |
| 149 | $order_b_missing = ( $order_b === '' ); |
| 150 | |
| 151 | if ( $order_a_missing && $order_b_missing ) { |
| 152 | return strcasecmp( $a->name, $b->name ); |
| 153 | } |
| 154 | |
| 155 | if ( $order_a_missing ) { |
| 156 | return 1; |
| 157 | } |
| 158 | |
| 159 | if ( $order_b_missing ) { |
| 160 | return -1; |
| 161 | } |
| 162 | |
| 163 | return (int) $order_a - (int) $order_b; |
| 164 | } ); |
| 165 | |
| 166 | $mapped_folders = []; |
| 167 | foreach ( $folders as $folder ) { |
| 168 | $folder_id = $folder->term_id; |
| 169 | $folder_name = $folder->name; |
| 170 | $folder_slug = $folder->slug; |
| 171 | |
| 172 | // 1. Get local docs assigned to this folder |
| 173 | $folder_doc_args = [ |
| 174 | 'post_type' => HelpDocs::$post_type, |
| 175 | 'posts_per_page' => -1, |
| 176 | 'post_status' => 'publish', |
| 177 | 'tax_query' => [ |
| 178 | [ |
| 179 | 'taxonomy' => $folder_taxonomy, |
| 180 | 'field' => 'term_id', |
| 181 | 'terms' => $folder_id, |
| 182 | ] |
| 183 | ], |
| 184 | 'fields' => 'ids' |
| 185 | ]; |
| 186 | $folder_docs = get_posts( $folder_doc_args ); |
| 187 | |
| 188 | // 2. Identify and map imports |
| 189 | foreach ( $docs as $doc ) { |
| 190 | if ( ! str_starts_with( (string) $doc->ID, 'import_' ) ) { |
| 191 | continue; |
| 192 | } |
| 193 | |
| 194 | $is_match = false; |
| 195 | |
| 196 | // Check if explicitly mapped via local_folder_id |
| 197 | if ( isset( $doc->local_folder_id ) && absint( $doc->local_folder_id ) === $folder_id ) { |
| 198 | $is_match = true; |
| 199 | } |
| 200 | // Fallback to taxonomy matching if local_folder_id isn't set |
| 201 | elseif ( isset( $doc->taxonomies ) && ! empty( $doc->taxonomies->$folder_taxonomy ) ) { |
| 202 | foreach ( $doc->taxonomies->$folder_taxonomy as $term ) { |
| 203 | if ( sanitize_title( $term->slug ) === $folder_slug || sanitize_text_field( $term->name ) === $folder_name ) { |
| 204 | $is_match = true; |
| 205 | break; |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | if ( $is_match ) { |
| 211 | $folder_docs[] = $doc->ID; |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | // Clean up and restrict to current viewable docs |
| 216 | $folder_docs = array_intersect( $folder_docs, $doc_ids ); |
| 217 | |
| 218 | if ( $user_can_edit || ! empty( $folder_docs ) ) { |
| 219 | $mapped_folders[ $folder_id ] = [ |
| 220 | 'name' => $folder_name, |
| 221 | 'docs' => $folder_docs |
| 222 | ]; |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | $current_doc_id = false; |
| 227 | $is_import = false; |
| 228 | |
| 229 | if ( isset( $_GET[ 'id' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 230 | $current_doc_id = sanitize_key( $_GET[ 'id' ] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended |
| 231 | $is_import = str_starts_with( (string) $current_doc_id, 'import_' ); |
| 232 | if ( ! in_array( $current_doc_id, $doc_ids ) || ( ! $is_import && ! get_post_status( $current_doc_id ) ) ) { |
| 233 | $current_doc_id = false; |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | if ( ! $s && ! $current_doc_id ) { |
| 238 | $default_doc_id = get_option( 'helpdocs_default_doc' ); |
| 239 | $is_import = str_starts_with( (string) $default_doc_id, 'import_' ); |
| 240 | if ( $default_doc_id && ( $is_import || 'publish' == get_post_status( $default_doc_id ) ) ) { |
| 241 | $current_doc_id = $default_doc_id; |
| 242 | } else { |
| 243 | if ( ! empty( $docs ) ) { |
| 244 | $current_doc_id = $docs[0]->ID; |
| 245 | } |
| 246 | } |
| 247 | if ( $current_doc_id ) { |
| 248 | Helpers::add_qs_without_refresh( 'id', $current_doc_id ); |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | $current_doc = (Object)[]; |
| 253 | $feed = false; |
| 254 | ?> |
| 255 | <div id="helpdocs-sidebar" class="helpdocs-box"> |
| 256 | <ul id="helpdocs-docs-list"> |
| 257 | <?php |
| 258 | |
| 259 | $in_folders = []; |
| 260 | if ( $s == '' && ! empty( $folders ) ) { |
| 261 | foreach ( $mapped_folders as $folder_id => $folder ) { |
| 262 | |
| 263 | if ( ! $user_can_edit && empty( $folder[ 'docs' ] ) ) { |
| 264 | continue; |
| 265 | } |
| 266 | |
| 267 | $folder_count = count( $folder[ 'docs' ] ); |
| 268 | |
| 269 | if ( $current_doc_id && in_array( $current_doc_id, $folder[ 'docs' ] ) ) { |
| 270 | $active_folder = ' active-folder'; |
| 271 | } else { |
| 272 | $active_folder = ' hide-in-folder'; |
| 273 | } |
| 274 | |
| 275 | ?> |
| 276 | <li id="folder-<?php echo esc_attr( $folder_id ); ?>" class="helpdocs-folder<?php echo esc_attr( $active_folder ); ?>" data-type="folder" draggable="true" data-folder-id="<?php echo absint( $folder_id ); ?>"> |
| 277 | <a href="#"><span class="folder-icon"></span> <?php echo esc_html( $folder[ 'name' ] ); ?> (<span class="folder-count"><?php echo absint( $folder_count ); ?></span>)</a> |
| 278 | </li> |
| 279 | <?php |
| 280 | |
| 281 | foreach ( $docs as $doc ) { |
| 282 | |
| 283 | if ( in_array( $doc->ID, $folder[ 'docs' ] ) ) { |
| 284 | $in_folders[] = $doc->ID; |
| 285 | } else { |
| 286 | continue; |
| 287 | } |
| 288 | |
| 289 | $incl_doc = true; |
| 290 | if ( $s !== '' ) { |
| 291 | |
| 292 | if ( strpos( strtolower( $doc->post_title ), strtolower( $s ) ) !== false ) { |
| 293 | $incl_doc = true; |
| 294 | } else if ( strpos( strtolower( $doc->post_content ), strtolower( $s ) ) !== false ) { |
| 295 | $incl_doc = true; |
| 296 | } else { |
| 297 | $incl_doc = false; |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | if ( ! $incl_doc ) { |
| 302 | continue; |
| 303 | } |
| 304 | |
| 305 | if ( ! $current_doc_id ) { |
| 306 | $current_doc_id = $doc->ID; |
| 307 | Helpers::add_qs_without_refresh( 'id', $current_doc_id ); |
| 308 | } |
| 309 | |
| 310 | if ( $doc->ID == $current_doc_id ) { |
| 311 | $active = ' active'; |
| 312 | $current_doc = $doc; |
| 313 | } else { |
| 314 | $active = ''; |
| 315 | } |
| 316 | |
| 317 | $is_folder_active = ( $current_doc_id && in_array( $current_doc_id, $folder[ 'docs' ] ) ); |
| 318 | $visibility_class = $is_folder_active ? ' in-active-folder' : ''; |
| 319 | |
| 320 | if ( isset( $doc->auto_feed ) && $doc->auto_feed != '' ) { |
| 321 | $incl_feed = '&feed=true'; |
| 322 | $feed = $doc->ID; |
| 323 | $file_icon_class = 'file-import-icon'; |
| 324 | $data_import = 'true'; |
| 325 | $import_id = $doc->feed_id; |
| 326 | } else { |
| 327 | $incl_feed = ''; |
| 328 | $file_icon_class = 'file-icon'; |
| 329 | $data_import = 'false'; |
| 330 | $import_id = ''; |
| 331 | } |
| 332 | |
| 333 | $item_url = add_query_arg( [ |
| 334 | 'id' => $doc->ID, |
| 335 | ], $current_url ); |
| 336 | ?> |
| 337 | <li id="item-<?php echo esc_attr( $doc->ID ); ?>" class="helpdocs-sidebar-item in-folder<?php echo esc_attr( $visibility_class ); ?><?php echo esc_attr( $active ); ?>" draggable="true" data-type="item" data-item-id="<?php echo esc_attr( $doc->ID ); ?>" data-import="<?php echo esc_attr( $data_import ); ?>" data-folder-id="<?php echo esc_attr( $folder_id ?? 0 ); ?>" data-import-id="<?php echo esc_attr( $import_id ); ?>"> |
| 338 | <a href="<?php echo esc_url( $item_url ); ?><?php echo esc_attr( $incl_feed ); ?>"><span class="<?php echo esc_attr( $file_icon_class ); ?>"></span> <span class="item-title"><?php echo esc_html( $doc->post_title ); ?></span></a> |
| 339 | </li> |
| 340 | <?php |
| 341 | } |
| 342 | } |
| 343 | if ( $user_can_edit || ! empty( $in_folders ) ) { |
| 344 | ?> |
| 345 | <li id="folder-0" class="invisible-folder" data-folder="0"></li> |
| 346 | <?php |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | foreach ( $docs as $doc ) { |
| 351 | |
| 352 | $incl_doc = true; |
| 353 | if ( $s !== '' ) { |
| 354 | |
| 355 | if ( strpos( strtolower( $doc->post_title ), strtolower( $s ) ) !== false ) { |
| 356 | $incl_doc = true; |
| 357 | } elseif ( strpos( strtolower( $doc->post_content ), strtolower( $s ) ) !== false ) { |
| 358 | $incl_doc = true; |
| 359 | } else { |
| 360 | $incl_doc = false; |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | if ( ! $incl_doc ) { |
| 365 | continue; |
| 366 | } |
| 367 | |
| 368 | if ( $s == '' && in_array( $doc->ID, $in_folders ) ) { |
| 369 | continue; |
| 370 | } |
| 371 | |
| 372 | if ( ! $current_doc_id ) { |
| 373 | $current_doc_id = $doc->ID; |
| 374 | Helpers::add_qs_without_refresh( 'id', $current_doc_id ); |
| 375 | } |
| 376 | |
| 377 | if ( $doc->ID == $current_doc_id ) { |
| 378 | $active = ' active'; |
| 379 | $current_doc = $doc; |
| 380 | } else { |
| 381 | $active = ''; |
| 382 | } |
| 383 | |
| 384 | if ( isset( $doc->auto_feed ) && $doc->auto_feed != '' ) { |
| 385 | $incl_feed = '&feed=true'; |
| 386 | $feed = $doc->ID; |
| 387 | $file_icon_class = 'file-import-icon'; |
| 388 | $data_import = 'true'; |
| 389 | $import_id = $doc->feed_id; |
| 390 | } else { |
| 391 | $incl_feed = ''; |
| 392 | $file_icon_class = 'file-icon'; |
| 393 | $data_import = 'false'; |
| 394 | $import_id = ''; |
| 395 | } |
| 396 | |
| 397 | $item_url = add_query_arg( [ |
| 398 | 'id' => $doc->ID, |
| 399 | ], $current_url ); |
| 400 | ?> |
| 401 | <li id="item-<?php echo esc_attr( $doc->ID ); ?>" class="helpdocs-sidebar-item not-in-folder<?php echo esc_attr( $active ); ?>" draggable="true" data-type="item" data-item-id="<?php echo esc_attr( $doc->ID ); ?>" data-import="<?php echo esc_attr( $data_import ); ?>" data-folder-id="0" data-import-id="<?php echo esc_attr( $import_id ); ?>"> |
| 402 | <a href="<?php echo esc_url( $item_url ); ?><?php echo esc_attr( $incl_feed ); ?>"><span class="<?php echo esc_attr( $file_icon_class ); ?>"></span> <span class="item-title"><?php echo esc_html( $doc->post_title ); ?></span></a> |
| 403 | </li> |
| 404 | <?php |
| 405 | } |
| 406 | |
| 407 | ?> |
| 408 | </ul> |
| 409 | </div> |
| 410 | <?php |
| 411 | $current_doc_as_array = (array)$current_doc; |
| 412 | if ( ! empty( $current_doc_as_array ) ) { |
| 413 | |
| 414 | ?> |
| 415 | <div id="helpdocs-document-viewer" class="helpdocs-box"> |
| 416 | <?php |
| 417 | if ( ! get_option( 'helpdocs_hide_doc_meta' ) ) { |
| 418 | |
| 419 | if ( $is_import ) { |
| 420 | $created_by = isset( $current_doc->created_by ) ? sanitize_text_field( $current_doc->created_by ) : __( 'an unknown author', 'admin-help-docs' ); |
| 421 | $created_by .= ' from ' . esc_html( $current_doc->feed_id ? get_the_title( $current_doc->feed_id ) : __( 'an unknown source', 'admin-help-docs' ) ); |
| 422 | } elseif ( is_numeric( $current_doc->post_author ) ) { |
| 423 | $created_by = get_userdata( $current_doc->post_author ); |
| 424 | $created_by = $created_by->display_name; |
| 425 | } else { |
| 426 | $created_by = $current_doc->post_author; |
| 427 | } |
| 428 | $incl_created_by = __( 'Created: ', 'admin-help-docs' ) . date_i18n( |
| 429 | 'F j, Y g:i A T', |
| 430 | strtotime( $current_doc->post_date ) |
| 431 | ) . ' by ' . $created_by; |
| 432 | |
| 433 | if ( $current_doc->_edit_last ) { |
| 434 | |
| 435 | if ( is_numeric( $current_doc->_edit_last ) ) { |
| 436 | $modified_by = get_userdata( $current_doc->_edit_last ); |
| 437 | $modified_by = $modified_by->display_name; |
| 438 | } else { |
| 439 | $modified_by = $current_doc->_edit_last; |
| 440 | } |
| 441 | |
| 442 | $incl_modified = '<br>' . __( 'Last modified: ', 'admin-help-docs' ) . date_i18n( |
| 443 | 'F j, Y g:i A T', |
| 444 | strtotime( $current_doc->post_modified ) |
| 445 | ) . ' by ' . esc_attr( $modified_by ); |
| 446 | } else { |
| 447 | $incl_modified = ''; |
| 448 | } |
| 449 | } else { |
| 450 | $incl_created_by = ''; |
| 451 | $incl_modified = ''; |
| 452 | } |
| 453 | |
| 454 | if ( Helpers::user_can_edit() ) { |
| 455 | if ( $feed == $current_doc_id ) { |
| 456 | $post_id = $current_doc->feed_id; |
| 457 | } else { |
| 458 | $post_id = $current_doc_id; |
| 459 | } |
| 460 | $edit_url = get_edit_post_link( $post_id ); |
| 461 | $incl_edit = ' <span id="edit-link"><a href="' . esc_url( $edit_url ) . '">✎ ' . __( 'Edit', 'admin-help-docs' ) . '</a></span>'; |
| 462 | } else { |
| 463 | $incl_edit = ''; |
| 464 | } |
| 465 | |
| 466 | if ( $feed == $current_doc_id ) { |
| 467 | $incl_feed = '<br>' . __( 'Content feed: ', 'admin-help-docs' ) . $current_doc->auto_feed; |
| 468 | } else { |
| 469 | $incl_feed = ''; |
| 470 | } |
| 471 | |
| 472 | if ( $s != '' ) { |
| 473 | $post_title = preg_replace( '/'.$s.'/i', '<span class="highlight">$0</span>', sanitize_text_field( $current_doc->post_title ) ); |
| 474 | } else { |
| 475 | $post_title = sanitize_text_field( $current_doc->post_title ); |
| 476 | } |
| 477 | |
| 478 | ?> |
| 479 | <div id="helpdoc-header"> |
| 480 | <h2><?php echo wp_kses_post( $post_title ); ?></h2><?php echo wp_kses_post( $incl_edit ); ?> |
| 481 | <span id="helpdocs-meta"><?php echo wp_kses_post( $incl_created_by ); ?> |
| 482 | <?php echo wp_kses_post( $incl_modified ); ?> |
| 483 | <?php echo wp_kses_post( $incl_feed ); ?></span> |
| 484 | </div> |
| 485 | <?php |
| 486 | |
| 487 | if ( $s != '' ) { |
| 488 | $post_content = str_replace( $s, '<span class="highlight">'.$s.'</span>', $current_doc->post_content ); |
| 489 | } else { |
| 490 | $post_content = $current_doc->post_content; |
| 491 | } |
| 492 | |
| 493 | if ( get_option( 'helpdocs_auto_htoc' ) ) { |
| 494 | preg_match_all( '/<h([2-6])[^>]*>(.*?)<\/h\1>/i', $post_content, $matches, PREG_SET_ORDER ); |
| 495 | |
| 496 | if ( $matches ) { |
| 497 | $toc = '<div id="page-toc"><ul class="page-toc-list">'; |
| 498 | $prev_level = 0; |
| 499 | |
| 500 | foreach ( $matches as $match ) { |
| 501 | $level = intval( $match[1] ); |
| 502 | $heading_text = wp_strip_all_tags( $match[2] ); |
| 503 | $anchor = sanitize_title( $heading_text ); |
| 504 | |
| 505 | $post_content = preg_replace( |
| 506 | '/' . preg_quote( $match[0], '/' ) . '/', |
| 507 | '<h' . $level . ' id="' . $anchor . '">' . $heading_text . '</h' . $level . '>', |
| 508 | $post_content, |
| 509 | 1 // Only replace first occurrence |
| 510 | ); |
| 511 | |
| 512 | if ( $prev_level && $level > $prev_level ) { |
| 513 | $toc .= str_repeat( '<ul>', $level - $prev_level ); |
| 514 | } elseif ( $prev_level && $level < $prev_level ) { |
| 515 | $toc .= str_repeat( '</ul>', $prev_level - $level ); |
| 516 | } |
| 517 | |
| 518 | $toc .= '<li><a href="#' . $anchor . '">' . esc_html( rtrim( $heading_text, ':' ) ) . '</a></li>'; |
| 519 | $prev_level = $level; |
| 520 | } |
| 521 | |
| 522 | if ( $prev_level > 0 ) { |
| 523 | $toc .= str_repeat( '</ul>', $prev_level - 1 ); |
| 524 | } |
| 525 | |
| 526 | $toc .= '</ul></div>'; |
| 527 | |
| 528 | echo wp_kses_post( $toc ); |
| 529 | } |
| 530 | } else { |
| 531 | echo '<hr class="helpdocs-title-hr">'; |
| 532 | } |
| 533 | |
| 534 | $allowed_tags = wp_kses_allowed_html( 'post' ); |
| 535 | $allowed_tags = Helpers::allow_addt_tags( $allowed_tags ); |
| 536 | ?> |
| 537 | <div id="helpdoc-content"><?php echo wp_kses( apply_filters( 'the_content', $post_content ), $allowed_tags ); ?></div> |
| 538 | </div> |
| 539 | <?php |
| 540 | |
| 541 | // Search with no results |
| 542 | } else if ( $s !== '' ) { |
| 543 | |
| 544 | Helpers::remove_qs_without_refresh( 'id' ); |
| 545 | ?> |
| 546 | <div id="no-docs-found" class="helpdocs-box"><?php echo sprintf( esc_html__( 'No docs found with the keyword "%s"... Please try again.', 'admin-help-docs' ), esc_html( $s ) ); ?></div> |
| 547 | <?php |
| 548 | |
| 549 | // Otherwise redirect to page without doc id |
| 550 | } else { |
| 551 | // wp_safe_redirect( Bootstrap::tab_url( 'documentation' ) ); |
| 552 | } |
| 553 | } // End render_tab() |
| 554 | |
| 555 | |
| 556 | /** |
| 557 | * Enqueue admin styles. |
| 558 | */ |
| 559 | public function enqueue_admin_styles() { |
| 560 | $text_domain = Bootstrap::textdomain(); |
| 561 | $current_screen = get_current_screen(); |
| 562 | $is_helpdocs_screen = $current_screen->id === 'toplevel_page_' . $text_domain || $current_screen->id === 'toplevel_page_' . $text_domain . '-network'; |
| 563 | $current_tab = Menu::get_current_tab(); |
| 564 | |
| 565 | if ( $current_screen->id == HelpDocs::$post_type && $current_screen->base == 'post' && get_option( 'helpdocs_gutenberg_editor' ) ) { |
| 566 | wp_enqueue_style( |
| 567 | 'wp-block-library-frontend', |
| 568 | includes_url( 'css/dist/block-library/style.css' ), |
| 569 | [], |
| 570 | Bootstrap::script_version() |
| 571 | ); |
| 572 | } |
| 573 | |
| 574 | if ( $is_helpdocs_screen && $current_tab === 'documentation' && get_option( 'helpdocs_enqueue_frontend_styles' ) ) { |
| 575 | global $wp_styles; |
| 576 | |
| 577 | do_action( 'wp_enqueue_scripts' ); |
| 578 | |
| 579 | $skip = [ 'wp-block-library', 'wp-admin', 'colors', 'dashicons' ]; |
| 580 | |
| 581 | foreach ( $wp_styles->queue as $handle ) { |
| 582 | if ( in_array( $handle, $skip, true ) ) { |
| 583 | continue; |
| 584 | } |
| 585 | |
| 586 | $style = $wp_styles->registered[ $handle ] ?? null; |
| 587 | |
| 588 | if ( $style && ! wp_style_is( $handle, 'enqueued' ) ) { |
| 589 | wp_enqueue_style( |
| 590 | $handle, |
| 591 | $style->src, |
| 592 | $style->deps, |
| 593 | $style->ver |
| 594 | ); |
| 595 | } |
| 596 | } |
| 597 | |
| 598 | wp_add_inline_style( 'wp-admin', 'html.wp-toolbar { padding-top: 0 !important; }' ); |
| 599 | } |
| 600 | } // End enqueue_admin_styles() |
| 601 | |
| 602 | |
| 603 | /** |
| 604 | * AJAX handler to save the order of docs and folders |
| 605 | */ |
| 606 | public function ajax_save_docs_order() { |
| 607 | check_ajax_referer( 'helpdocs_documentation_nonce', 'nonce' ); |
| 608 | |
| 609 | if ( ! Helpers::user_can_edit() ) { |
| 610 | wp_send_json_error( __( 'Insufficient permissions.', 'admin-help-docs' ) ); |
| 611 | } |
| 612 | |
| 613 | // Get the arrays from wp_unslash before processing (sanitize below) |
| 614 | $folders = isset( $_POST[ 'folders' ] ) ? (array) wp_unslash( $_POST[ 'folders' ] ) : []; // phpcs:ignore |
| 615 | $items = isset( $_POST[ 'items' ] ) ? (array) wp_unslash( $_POST[ 'items' ] ) : []; // phpcs:ignore |
| 616 | |
| 617 | // 1. Update Folder Order |
| 618 | foreach ( $folders as $index => $folder_id ) { |
| 619 | update_term_meta( absint( $folder_id ), 'helpdocs_order', $index + 1 ); |
| 620 | } |
| 621 | |
| 622 | // 2. Update Items (Files) Order and Taxonomy |
| 623 | $import_customizations = []; |
| 624 | |
| 625 | foreach ( $items as $index => $item ) { |
| 626 | $raw_id = sanitize_text_field( $item[ 'id' ] ); |
| 627 | $folder_id = absint( $item[ 'folder' ] ); |
| 628 | $import_id = absint( $item[ 'import_id' ] ); |
| 629 | $new_order = $index + 1; |
| 630 | |
| 631 | // Imports |
| 632 | if ( ! empty( $import_id ) ) { |
| 633 | $remote_id = absint( str_replace( 'import_', '', $raw_id ) ); |
| 634 | $import_customizations[ $import_id ][ $remote_id ] = [ |
| 635 | 'order' => $new_order, |
| 636 | 'folder' => $folder_id |
| 637 | ]; |
| 638 | |
| 639 | // Local Docs |
| 640 | } else { |
| 641 | $item_id = absint( $raw_id ); |
| 642 | $locations = get_post_meta( $item_id, 'helpdocs_locations', true ); |
| 643 | |
| 644 | // Backward Compatibility: If locations is empty, migrate legacy meta |
| 645 | if ( ! is_array( $locations ) || empty( $locations ) ) { |
| 646 | $meta = get_post_meta( $item_id ); |
| 647 | |
| 648 | $locations = [ |
| 649 | [ |
| 650 | 'site_location' => $meta[ 'helpdocs_site_location' ][ 0 ] ?? '', |
| 651 | 'page_location' => $meta[ 'helpdocs_page_location' ][ 0 ] ?? '', |
| 652 | 'custom' => $meta[ 'helpdocs_custom' ][ 0 ] ?? '', |
| 653 | 'addt_params' => ! empty( $meta[ 'helpdocs_addt_params' ][ 0 ] ), |
| 654 | 'post_types' => isset( $meta[ 'helpdocs_post_types' ][ 0 ] ) ? Helpers::normalize_meta_array( $meta[ 'helpdocs_post_types' ][ 0 ] ) : [], |
| 655 | 'order' => $new_order, // Set the new order here |
| 656 | 'toc' => isset( $meta[ 'helpdocs_toc' ][ 0 ] ) ? filter_var( $meta[ 'helpdocs_toc' ][ 0 ], FILTER_VALIDATE_BOOLEAN ) : false, |
| 657 | 'css_selector' => $meta[ 'helpdocs_css_selector' ][ 0 ] ?? '', |
| 658 | ] |
| 659 | ]; |
| 660 | |
| 661 | // Cleanup legacy keys now that we've migrated |
| 662 | $legacy_keys = [ 'site_location', 'page_location', 'custom', 'addt_params', 'post_types', 'order', 'toc', 'priority' ]; |
| 663 | foreach ( $legacy_keys as $key ) { |
| 664 | delete_post_meta( $item_id, 'helpdocs_' . $key ); |
| 665 | } |
| 666 | } else { |
| 667 | // New method: Update the 'main' location order (bWFpbg==) |
| 668 | foreach ( $locations as $key => $loc ) { |
| 669 | if ( isset( $loc[ 'site_location' ] ) && $loc[ 'site_location' ] === 'bWFpbg==' ) { |
| 670 | $locations[ $key ][ 'order' ] = $new_order; |
| 671 | break; |
| 672 | } |
| 673 | } |
| 674 | } |
| 675 | |
| 676 | update_post_meta( $item_id, 'helpdocs_locations', $locations ); |
| 677 | |
| 678 | // Update Taxonomy Association (Local only) |
| 679 | wp_set_post_terms( $item_id, ( $folder_id > 0 ) ? [ $folder_id ] : [], Folders::$taxonomy ); |
| 680 | } |
| 681 | } |
| 682 | |
| 683 | // 3. Save bundled import customizations |
| 684 | foreach ( $import_customizations as $cpt_id => $data ) { |
| 685 | $existing = get_post_meta( $cpt_id, 'helpdocs_local_customizations', true ) ?: []; |
| 686 | $updated = array_replace( (array) $existing, $data ); |
| 687 | update_post_meta( $cpt_id, 'helpdocs_local_customizations', $updated ); |
| 688 | } |
| 689 | |
| 690 | Helpers::flush_location_cache(); |
| 691 | |
| 692 | wp_send_json_success(); |
| 693 | } // End ajax_save_docs_order() |
| 694 | |
| 695 | |
| 696 | /** |
| 697 | * Remove wptexturize filter to prevent curly quotes in documentation content |
| 698 | */ |
| 699 | public function remove_curly_quotes() { |
| 700 | $current_screen = get_current_screen(); |
| 701 | if ( ! is_admin() || ! $current_screen ) { |
| 702 | return; |
| 703 | } |
| 704 | |
| 705 | $text_domain = Bootstrap::textdomain(); |
| 706 | $is_helpdocs_screen = ( $current_screen->id === 'toplevel_page_' . $text_domain || $current_screen->id === 'toplevel_page_' . $text_domain . '-network' ); |
| 707 | $current_tab = Menu::get_current_tab(); |
| 708 | |
| 709 | if ( $is_helpdocs_screen && 'documentation' === $current_tab ) { |
| 710 | remove_filter( 'the_content', 'wptexturize' ); |
| 711 | } |
| 712 | } // End remove_curly_quotes() |
| 713 | |
| 714 | |
| 715 | /** |
| 716 | * Prevent cloning and unserializing |
| 717 | */ |
| 718 | public function __clone() {} |
| 719 | public function __wakeup() {} |
| 720 | |
| 721 | } |
| 722 | |
| 723 | |
| 724 | Documentation::instance(); |