Changeset 3464901
- Timestamp:
- 02/19/2026 09:22:22 AM (6 weeks ago)
- Location:
- osom-multi-theme-switcher/trunk
- Files:
-
- 1 added
- 8 edited
-
README.md (modified) (5 diffs)
-
includes/class-omts-admin-bar.php (modified) (1 diff)
-
includes/class-omts-admin-page.php (modified) (8 diffs)
-
includes/class-omts-ajax-handler.php (modified) (5 diffs)
-
includes/class-omts-status-sync.php (added)
-
includes/class-omts-theme-switcher.php (modified) (16 diffs)
-
includes/class-osom-multi-theme-switcher.php (modified) (4 diffs)
-
osom-multi-theme-switcher.php (modified) (2 diffs)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
osom-multi-theme-switcher/trunk/README.md
r3457914 r3464901 8 8 - Individual Pages 9 9 - Individual Posts 10 - Post Types (e.g., all Products)10 - Custom Post Types (all items or individual items) 11 11 - Custom URLs/Slugs 12 12 - Categories 13 13 - Tags 14 - Custom Taxonomies 14 15 15 16 - **Admin Dashboard Theme Switcher**: Switch between themes in the WordPress admin area from the top admin bar to access theme-specific settings … … 18 19 - **Real-time Updates**: Add and remove rules instantly with AJAX 19 20 - **Per-User Admin Theme**: Each admin user can view the dashboard with their preferred theme 21 - **Status Sync**: Automatically updates rules when post status changes (draft → published, etc.) 22 - **CPT Registry**: Remembers custom post types across themes so URL matching works even when a different theme is active 20 23 - **Compatible**: Works with any WordPress theme 21 24 … … 57 60 - **Page**: Apply a theme to a specific page 58 61 - **Post**: Apply a theme to a specific blog post 59 - ** Post Type**: Apply a theme to all posts of a certain type(e.g., all WooCommerce products)62 - **Custom Post Type**: Apply a theme to all posts of a certain type or individual items (e.g., all WooCommerce products) 60 63 - **Custom URL/Slug**: Apply a theme to a custom URL or slug (e.g., `/special-landing` or `about-us`) 61 64 - **Category**: Apply a theme to all posts in a category or the category archive page 62 65 - **Tag**: Apply a theme to all posts with a tag or the tag archive page 66 - **Taxonomy**: Apply a theme to terms of custom taxonomies 63 67 64 68 ### Deleting Rules … … 88 92 - `template` filter - Changes the parent theme 89 93 - `stylesheet` filter - Changes the child theme/stylesheet 94 - `setup_theme` hook - Ensures correct theme's functions.php is loaded 90 95 91 96 Rules are stored in the WordPress options table and checked on every page load. … … 106 111 ## Changelog 107 112 113 ### 1.2.1 114 - Added custom post type support (all items or individual items) 115 - Added custom taxonomy support 116 - Added category and tag rule types 117 - Added CPT registry for cross-theme URL matching 118 - Added status sync - rules auto-update when post status changes 119 - Added early matching for all rule types (before WP_Query) 120 - Added cascading selectors in admin UI 121 108 122 ### 1.0.0 109 123 - Initial release -
osom-multi-theme-switcher/trunk/includes/class-omts-admin-bar.php
r3461829 r3464901 130 130 plugin_dir_url( dirname( __FILE__ ) ) . 'assets/admin-bar-script.js', 131 131 array( 'jquery' ), 132 '1. 0.3',132 '1.2.1', 133 133 true 134 134 ); -
osom-multi-theme-switcher/trunk/includes/class-omts-admin-page.php
r3457914 r3464901 73 73 plugin_dir_url( dirname( __FILE__ ) ) . 'assets/admin-style.css', 74 74 array(), 75 '1. 0.0'75 '1.2.1' 76 76 ); 77 77 … … 80 80 plugin_dir_url( dirname( __FILE__ ) ) . 'assets/admin-script.js', 81 81 array( 'jquery' ), 82 '1. 0.0',82 '1.2.1', 83 83 true 84 84 ); … … 100 100 */ 101 101 public function render_admin_page() { 102 $rules = $this->theme_switcher->get_rules(); 103 $rest_prefixes = $this->theme_switcher->get_theme_rest_prefixes(); 104 $themes = wp_get_themes(); 105 $current_theme = wp_get_theme()->get_stylesheet(); 102 $rules = $this->theme_switcher->get_rules(); 103 $rest_prefixes = $this->theme_switcher->get_theme_rest_prefixes(); 104 $themes = wp_get_themes(); 106 105 ?> 107 106 <div class="wrap omts-admin-wrap"> … … 128 127 <td> 129 128 <select id="omts-rule-type" name="rule_type"> 129 <option value=""><?php esc_html_e( '-- Select Rule Type --', 'osom-multi-theme-switcher' ); ?></option> 130 130 <option value="page"><?php esc_html_e( 'Page', 'osom-multi-theme-switcher' ); ?></option> 131 131 <option value="post"><?php esc_html_e( 'Post', 'osom-multi-theme-switcher' ); ?></option> 132 <option value="post_type"><?php esc_html_e( 'Post Type', 'osom-multi-theme-switcher' ); ?></option> 133 <option value="draft_page"><?php esc_html_e( 'Draft Page', 'osom-multi-theme-switcher' ); ?></option> 134 <option value="draft_post"><?php esc_html_e( 'Draft Post', 'osom-multi-theme-switcher' ); ?></option> 135 <option value="pending_page"><?php esc_html_e( 'Pending Page', 'osom-multi-theme-switcher' ); ?></option> 136 <option value="pending_post"><?php esc_html_e( 'Pending Post', 'osom-multi-theme-switcher' ); ?></option> 137 <option value="private_page"><?php esc_html_e( 'Private Page', 'osom-multi-theme-switcher' ); ?></option> 138 <option value="private_post"><?php esc_html_e( 'Private Post', 'osom-multi-theme-switcher' ); ?></option> 139 <option value="future_page"><?php esc_html_e( 'Scheduled Page', 'osom-multi-theme-switcher' ); ?></option> 140 <option value="future_post"><?php esc_html_e( 'Scheduled Post', 'osom-multi-theme-switcher' ); ?></option> 132 <option value="custom_post_type"><?php esc_html_e( 'Custom Post Type', 'osom-multi-theme-switcher' ); ?></option> 133 <option value="taxonomy"><?php esc_html_e( 'Taxonomy', 'osom-multi-theme-switcher' ); ?></option> 141 134 <option value="url"><?php esc_html_e( 'Custom URL/Slug', 'osom-multi-theme-switcher' ); ?></option> 142 <option value="category"><?php esc_html_e( 'Category', 'osom-multi-theme-switcher' ); ?></option>143 <option value="tag"><?php esc_html_e( 'Tag', 'osom-multi-theme-switcher' ); ?></option>144 135 </select> 145 136 </td> 146 137 </tr> 147 <?php $this->render_page_row(); ?> 148 <?php $this->render_post_row(); ?> 149 <?php $this->render_post_type_row(); ?> 150 <?php $this->render_draft_page_row(); ?> 151 <?php $this->render_draft_post_row(); ?> 152 <?php $this->render_pending_page_row(); ?> 153 <?php $this->render_pending_post_row(); ?> 154 <?php $this->render_private_page_row(); ?> 155 <?php $this->render_private_post_row(); ?> 156 <?php $this->render_future_page_row(); ?> 157 <?php $this->render_future_post_row(); ?> 158 <?php $this->render_url_row(); ?> 159 <?php $this->render_category_row(); ?> 160 <?php $this->render_tag_row(); ?> 138 <tr id="omts-rule-object-row" class="omts-rule-row" style="display:none;"> 139 <th scope="row"> 140 <label for="omts-rule-object"><?php esc_html_e( 'Rule Object', 'osom-multi-theme-switcher' ); ?></label> 141 </th> 142 <td> 143 <select id="omts-rule-object" name="object_type"> 144 <option value=""><?php esc_html_e( '-- Select --', 'osom-multi-theme-switcher' ); ?></option> 145 </select> 146 <span class="spinner" id="omts-object-spinner"></span> 147 </td> 148 </tr> 149 <tr id="omts-rule-item-row" class="omts-rule-row" style="display:none;"> 150 <th scope="row"> 151 <label for="omts-rule-item"><?php esc_html_e( 'Rule Item', 'osom-multi-theme-switcher' ); ?></label> 152 </th> 153 <td> 154 <select id="omts-rule-item" name="item_id"> 155 <option value=""><?php esc_html_e( '-- Select --', 'osom-multi-theme-switcher' ); ?></option> 156 </select> 157 <span class="spinner" id="omts-item-spinner"></span> 158 </td> 159 </tr> 160 <tr id="omts-url-row" class="omts-rule-row" style="display:none;"> 161 <th scope="row"> 162 <label for="omts-url-input"><?php esc_html_e( 'Custom URL/Slug', 'osom-multi-theme-switcher' ); ?></label> 163 </th> 164 <td> 165 <input type="text" id="omts-url-input" name="custom_url" class="regular-text" placeholder="<?php esc_attr_e( 'e.g., /about-us or about-us', 'osom-multi-theme-switcher' ); ?>"> 166 <p class="description"> 167 <?php esc_html_e( 'Enter a URL path or slug (with or without leading slash)', 'osom-multi-theme-switcher' ); ?> 168 </p> 169 </td> 170 </tr> 161 171 <tr> 162 172 <th scope="row"> 163 <label for="omts-theme-select"><?php esc_html_e( ' AlternativeTheme', 'osom-multi-theme-switcher' ); ?></label>173 <label for="omts-theme-select"><?php esc_html_e( 'Theme', 'osom-multi-theme-switcher' ); ?></label> 164 174 </th> 165 175 <td> … … 204 214 <?php foreach ( $rules as $index => $rule ) : ?> 205 215 <tr data-index="<?php echo esc_attr( $index ); ?>"> 206 <td><?php echo esc_html( ucfirst( str_replace( '_', ' ', $rule['type'] )) ); ?></td>216 <td><?php echo esc_html( $this->get_rule_type_display( $rule['type'] ) ); ?></td> 207 217 <td><?php echo esc_html( $this->get_rule_target_display( $rule ) ); ?></td> 208 218 <td><?php echo esc_html( $this->theme_switcher->get_theme_name( $rule['theme'] ) ); ?></td> … … 317 327 318 328 /** 319 * Render page selection row. 320 * 321 * @since 1.0.0 322 */ 323 private function render_page_row() { 324 ?> 325 <tr id="omts-page-row" class="omts-rule-row"> 326 <th scope="row"> 327 <label for="omts-page-select"><?php esc_html_e( 'Select Page', 'osom-multi-theme-switcher' ); ?></label> 328 </th> 329 <td> 330 <select id="omts-page-select" name="page_id"> 331 <option value=""><?php esc_html_e( '-- Select Page --', 'osom-multi-theme-switcher' ); ?></option> 332 <?php 333 $pages = get_pages(); 334 foreach ( $pages as $page ) { 335 printf( 336 '<option value="%d">%s</option>', 337 esc_attr( $page->ID ), 338 esc_html( $page->post_title ) 339 ); 340 } 341 ?> 342 </select> 343 </td> 344 </tr> 345 <?php 346 } 347 348 /** 349 * Render post selection row. 350 * 351 * @since 1.0.0 352 */ 353 private function render_post_row() { 354 ?> 355 <tr id="omts-post-row" class="omts-rule-row" style="display:none;"> 356 <th scope="row"> 357 <label for="omts-post-select"><?php esc_html_e( 'Select Post', 'osom-multi-theme-switcher' ); ?></label> 358 </th> 359 <td> 360 <select id="omts-post-select" name="post_id"> 361 <option value=""><?php esc_html_e( '-- Select Post --', 'osom-multi-theme-switcher' ); ?></option> 362 <?php 363 $posts = get_posts( array( 'numberposts' => -1 ) ); 364 foreach ( $posts as $post ) { 365 printf( 366 '<option value="%d">%s</option>', 367 esc_attr( $post->ID ), 368 esc_html( $post->post_title ) 369 ); 370 } 371 ?> 372 </select> 373 </td> 374 </tr> 375 <?php 376 } 377 378 /** 379 * Render post type selection row. 380 * 381 * @since 1.0.0 382 */ 383 private function render_post_type_row() { 384 ?> 385 <tr id="omts-post-type-row" class="omts-rule-row" style="display:none;"> 386 <th scope="row"> 387 <label for="omts-post-type-select"><?php esc_html_e( 'Select Post Type', 'osom-multi-theme-switcher' ); ?></label> 388 </th> 389 <td> 390 <select id="omts-post-type-select" name="post_type"> 391 <option value=""><?php esc_html_e( '-- Select Post Type --', 'osom-multi-theme-switcher' ); ?></option> 392 <?php 393 $post_types = get_post_types( array( 'public' => true ), 'objects' ); 394 foreach ( $post_types as $post_type ) { 395 printf( 396 '<option value="%s">%s</option>', 397 esc_attr( $post_type->name ), 398 esc_html( $post_type->label ) 399 ); 400 } 401 ?> 402 </select> 403 </td> 404 </tr> 405 <?php 406 } 407 408 /** 409 * Render URL input row. 410 * 411 * @since 1.0.0 412 */ 413 private function render_url_row() { 414 ?> 415 <tr id="omts-url-row" class="omts-rule-row" style="display:none;"> 416 <th scope="row"> 417 <label for="omts-url-input"><?php esc_html_e( 'Custom URL/Slug', 'osom-multi-theme-switcher' ); ?></label> 418 </th> 419 <td> 420 <input type="text" id="omts-url-input" name="custom_url" class="regular-text" placeholder="<?php esc_attr_e( 'e.g., /about-us or about-us', 'osom-multi-theme-switcher' ); ?>"> 421 <p class="description"> 422 <?php esc_html_e( 'Enter a URL path or slug (with or without leading slash)', 'osom-multi-theme-switcher' ); ?> 423 </p> 424 </td> 425 </tr> 426 <?php 427 } 428 429 /** 430 * Render category selection row. 431 * 432 * @since 1.0.0 433 */ 434 private function render_category_row() { 435 ?> 436 <tr id="omts-category-row" class="omts-rule-row" style="display:none;"> 437 <th scope="row"> 438 <label for="omts-category-select"><?php esc_html_e( 'Select Category', 'osom-multi-theme-switcher' ); ?></label> 439 </th> 440 <td> 441 <select id="omts-category-select" name="category_id"> 442 <option value=""><?php esc_html_e( '-- Select Category --', 'osom-multi-theme-switcher' ); ?></option> 443 <?php 444 $categories = get_categories( array( 'hide_empty' => false ) ); 445 foreach ( $categories as $category ) { 446 printf( 447 '<option value="%d">%s</option>', 448 esc_attr( $category->term_id ), 449 esc_html( $category->name ) 450 ); 451 } 452 ?> 453 </select> 454 </td> 455 </tr> 456 <?php 457 } 458 459 /** 460 * Render tag selection row. 461 * 462 * @since 1.0.0 463 */ 464 private function render_tag_row() { 465 ?> 466 <tr id="omts-tag-row" class="omts-rule-row" style="display:none;"> 467 <th scope="row"> 468 <label for="omts-tag-select"><?php esc_html_e( 'Select Tag', 'osom-multi-theme-switcher' ); ?></label> 469 </th> 470 <td> 471 <select id="omts-tag-select" name="tag_id"> 472 <option value=""><?php esc_html_e( '-- Select Tag --', 'osom-multi-theme-switcher' ); ?></option> 473 <?php 474 $tags = get_tags( array( 'hide_empty' => false ) ); 475 foreach ( $tags as $tag ) { 476 printf( 477 '<option value="%d">%s</option>', 478 esc_attr( $tag->term_id ), 479 esc_html( $tag->name ) 480 ); 481 } 482 ?> 483 </select> 484 </td> 485 </tr> 486 <?php 487 } 488 489 /** 490 * Render draft page selection row. 491 * 492 * @since 1.0.2 493 */ 494 private function render_draft_page_row() { 495 ?> 496 <tr id="omts-draft-page-row" class="omts-rule-row" style="display:none;"> 497 <th scope="row"> 498 <label for="omts-draft-page-select"><?php esc_html_e( 'Select Draft Page', 'osom-multi-theme-switcher' ); ?></label> 499 </th> 500 <td> 501 <select id="omts-draft-page-select" name="page_id"> 502 <option value=""><?php esc_html_e( '-- Select Draft Page --', 'osom-multi-theme-switcher' ); ?></option> 503 <?php 504 $pages = get_pages( array( 'post_status' => 'draft' ) ); 505 foreach ( $pages as $page ) { 506 printf( 507 '<option value="%d">%s (Draft)</option>', 508 esc_attr( $page->ID ), 509 esc_html( $page->post_title ) 510 ); 511 } 512 ?> 513 </select> 514 </td> 515 </tr> 516 <?php 517 } 518 519 /** 520 * Render draft post selection row. 521 * 522 * @since 1.0.2 523 */ 524 private function render_draft_post_row() { 525 ?> 526 <tr id="omts-draft-post-row" class="omts-rule-row" style="display:none;"> 527 <th scope="row"> 528 <label for="omts-draft-post-select"><?php esc_html_e( 'Select Draft Post', 'osom-multi-theme-switcher' ); ?></label> 529 </th> 530 <td> 531 <select id="omts-draft-post-select" name="post_id"> 532 <option value=""><?php esc_html_e( '-- Select Draft Post --', 'osom-multi-theme-switcher' ); ?></option> 533 <?php 534 $posts = get_posts( array( 'numberposts' => -1, 'post_status' => 'draft' ) ); 535 foreach ( $posts as $post ) { 536 printf( 537 '<option value="%d">%s (Draft)</option>', 538 esc_attr( $post->ID ), 539 esc_html( $post->post_title ) 540 ); 541 } 542 ?> 543 </select> 544 </td> 545 </tr> 546 <?php 547 } 548 549 /** 550 * Render pending page selection row. 551 * 552 * @since 1.0.2 553 */ 554 private function render_pending_page_row() { 555 ?> 556 <tr id="omts-pending-page-row" class="omts-rule-row" style="display:none;"> 557 <th scope="row"> 558 <label for="omts-pending-page-select"><?php esc_html_e( 'Select Pending Page', 'osom-multi-theme-switcher' ); ?></label> 559 </th> 560 <td> 561 <select id="omts-pending-page-select" name="page_id"> 562 <option value=""><?php esc_html_e( '-- Select Pending Page --', 'osom-multi-theme-switcher' ); ?></option> 563 <?php 564 $pages = get_pages( array( 'post_status' => 'pending' ) ); 565 foreach ( $pages as $page ) { 566 printf( 567 '<option value="%d">%s (Pending)</option>', 568 esc_attr( $page->ID ), 569 esc_html( $page->post_title ) 570 ); 571 } 572 ?> 573 </select> 574 </td> 575 </tr> 576 <?php 577 } 578 579 /** 580 * Render pending post selection row. 581 * 582 * @since 1.0.2 583 */ 584 private function render_pending_post_row() { 585 ?> 586 <tr id="omts-pending-post-row" class="omts-rule-row" style="display:none;"> 587 <th scope="row"> 588 <label for="omts-pending-post-select"><?php esc_html_e( 'Select Pending Post', 'osom-multi-theme-switcher' ); ?></label> 589 </th> 590 <td> 591 <select id="omts-pending-post-select" name="post_id"> 592 <option value=""><?php esc_html_e( '-- Select Pending Post --', 'osom-multi-theme-switcher' ); ?></option> 593 <?php 594 $posts = get_posts( array( 'numberposts' => -1, 'post_status' => 'pending' ) ); 595 foreach ( $posts as $post ) { 596 printf( 597 '<option value="%d">%s (Pending)</option>', 598 esc_attr( $post->ID ), 599 esc_html( $post->post_title ) 600 ); 601 } 602 ?> 603 </select> 604 </td> 605 </tr> 606 <?php 607 } 608 609 /** 610 * Render private page selection row. 611 * 612 * @since 1.0.2 613 */ 614 private function render_private_page_row() { 615 ?> 616 <tr id="omts-private-page-row" class="omts-rule-row" style="display:none;"> 617 <th scope="row"> 618 <label for="omts-private-page-select"><?php esc_html_e( 'Select Private Page', 'osom-multi-theme-switcher' ); ?></label> 619 </th> 620 <td> 621 <select id="omts-private-page-select" name="page_id"> 622 <option value=""><?php esc_html_e( '-- Select Private Page --', 'osom-multi-theme-switcher' ); ?></option> 623 <?php 624 $pages = get_pages( array( 'post_status' => 'private' ) ); 625 foreach ( $pages as $page ) { 626 printf( 627 '<option value="%d">%s (Private)</option>', 628 esc_attr( $page->ID ), 629 esc_html( $page->post_title ) 630 ); 631 } 632 ?> 633 </select> 634 </td> 635 </tr> 636 <?php 637 } 638 639 /** 640 * Render private post selection row. 641 * 642 * @since 1.0.2 643 */ 644 private function render_private_post_row() { 645 ?> 646 <tr id="omts-private-post-row" class="omts-rule-row" style="display:none;"> 647 <th scope="row"> 648 <label for="omts-private-post-select"><?php esc_html_e( 'Select Private Post', 'osom-multi-theme-switcher' ); ?></label> 649 </th> 650 <td> 651 <select id="omts-private-post-select" name="post_id"> 652 <option value=""><?php esc_html_e( '-- Select Private Post --', 'osom-multi-theme-switcher' ); ?></option> 653 <?php 654 $posts = get_posts( array( 'numberposts' => -1, 'post_status' => 'private' ) ); 655 foreach ( $posts as $post ) { 656 printf( 657 '<option value="%d">%s (Private)</option>', 658 esc_attr( $post->ID ), 659 esc_html( $post->post_title ) 660 ); 661 } 662 ?> 663 </select> 664 </td> 665 </tr> 666 <?php 667 } 668 669 /** 670 * Render scheduled page selection row. 671 * 672 * @since 1.0.2 673 */ 674 private function render_future_page_row() { 675 ?> 676 <tr id="omts-future-page-row" class="omts-rule-row" style="display:none;"> 677 <th scope="row"> 678 <label for="omts-future-page-select"><?php esc_html_e( 'Select Scheduled Page', 'osom-multi-theme-switcher' ); ?></label> 679 </th> 680 <td> 681 <select id="omts-future-page-select" name="page_id"> 682 <option value=""><?php esc_html_e( '-- Select Scheduled Page --', 'osom-multi-theme-switcher' ); ?></option> 683 <?php 684 $pages = get_pages( array( 'post_status' => 'future' ) ); 685 foreach ( $pages as $page ) { 686 printf( 687 '<option value="%d">%s (Scheduled)</option>', 688 esc_attr( $page->ID ), 689 esc_html( $page->post_title ) 690 ); 691 } 692 ?> 693 </select> 694 </td> 695 </tr> 696 <?php 697 } 698 699 /** 700 * Render scheduled post selection row. 701 * 702 * @since 1.0.2 703 */ 704 private function render_future_post_row() { 705 ?> 706 <tr id="omts-future-post-row" class="omts-rule-row" style="display:none;"> 707 <th scope="row"> 708 <label for="omts-future-post-select"><?php esc_html_e( 'Select Scheduled Post', 'osom-multi-theme-switcher' ); ?></label> 709 </th> 710 <td> 711 <select id="omts-future-post-select" name="post_id"> 712 <option value=""><?php esc_html_e( '-- Select Scheduled Post --', 'osom-multi-theme-switcher' ); ?></option> 713 <?php 714 $posts = get_posts( array( 'numberposts' => -1, 'post_status' => 'future' ) ); 715 foreach ( $posts as $post ) { 716 printf( 717 '<option value="%d">%s (Scheduled)</option>', 718 esc_attr( $post->ID ), 719 esc_html( $post->post_title ) 720 ); 721 } 722 ?> 723 </select> 724 </td> 725 </tr> 726 <?php 329 * Get human-readable display name for rule type. 330 * 331 * @since 1.2.0 332 * 333 * @param string $type Rule type. 334 * @return string Display name. 335 */ 336 private function get_rule_type_display( $type ) { 337 return OMTS_Theme_Switcher::get_rule_type_display( $type ); 727 338 } 728 339 … … 755 366 case 'post_type': 756 367 $post_type_obj = get_post_type_object( $rule['value'] ); 757 return $post_type_obj ? $post_type_obj->label : $rule['value']; 368 return $post_type_obj 369 ? sprintf( 370 /* translators: %s: Post type label */ 371 __( 'All %s', 'osom-multi-theme-switcher' ), 372 $post_type_obj->label 373 ) 374 : $rule['value']; 758 375 759 376 case 'url': … … 768 385 return $tag ? $tag->name : __( 'Unknown Tag', 'osom-multi-theme-switcher' ); 769 386 387 case 'taxonomy': 388 $taxonomy = isset( $rule['taxonomy'] ) ? $rule['taxonomy'] : ''; 389 $term = get_term( $rule['value'], $taxonomy ); 390 if ( $term && ! is_wp_error( $term ) ) { 391 $tax_obj = get_taxonomy( $taxonomy ); 392 $tax_label = $tax_obj ? $tax_obj->label : $taxonomy; 393 return $term->name . ' (' . $tax_label . ')'; 394 } 395 return __( 'Unknown Term', 'osom-multi-theme-switcher' ); 396 397 case 'cpt_item': 398 $post = get_post( $rule['value'] ); 399 return $post ? $post->post_title : sprintf( 400 /* translators: %d: Post ID */ 401 __( 'Unknown Item (ID: %d)', 'osom-multi-theme-switcher' ), 402 $rule['value'] 403 ); 404 770 405 case 'draft_page': 771 $page = get_post( $rule['value'] );772 return $page ? $page->post_title . ' (Draft)' : sprintf(773 /* translators: %d: Page ID */774 __( 'Unknown Draft Page (ID: %d)', 'osom-multi-theme-switcher' ),775 $rule['value']776 );777 778 406 case 'draft_post': 779 $post = get_post( $rule['value'] ); 780 return $post ? $post->post_title . ' (Draft)' : sprintf( 781 /* translators: %d: Post ID */ 782 __( 'Unknown Draft Post (ID: %d)', 'osom-multi-theme-switcher' ), 407 case 'draft_cpt_item': 408 $post = get_post( $rule['value'] ); 409 return $post ? '(Draft) ' . $post->post_title : sprintf( 410 /* translators: %d: Post ID */ 411 __( 'Unknown Draft (ID: %d)', 'osom-multi-theme-switcher' ), 783 412 $rule['value'] 784 413 ); 785 414 786 415 case 'pending_page': 787 $page = get_post( $rule['value'] );788 return $page ? $page->post_title . ' (Pending)' : sprintf(789 /* translators: %d: Page ID */790 __( 'Unknown Pending Page (ID: %d)', 'osom-multi-theme-switcher' ),791 $rule['value']792 );793 794 416 case 'pending_post': 795 $post = get_post( $rule['value'] ); 796 return $post ? $post->post_title . ' (Pending)' : sprintf( 797 /* translators: %d: Post ID */ 798 __( 'Unknown Pending Post (ID: %d)', 'osom-multi-theme-switcher' ), 417 case 'pending_cpt_item': 418 $post = get_post( $rule['value'] ); 419 return $post ? '(Pending) ' . $post->post_title : sprintf( 420 /* translators: %d: Post ID */ 421 __( 'Unknown Pending (ID: %d)', 'osom-multi-theme-switcher' ), 799 422 $rule['value'] 800 423 ); 801 424 802 425 case 'private_page': 803 $page = get_post( $rule['value'] );804 return $page ? $page->post_title . ' (Private)' : sprintf(805 /* translators: %d: Page ID */806 __( 'Unknown Private Page (ID: %d)', 'osom-multi-theme-switcher' ),807 $rule['value']808 );809 810 426 case 'private_post': 811 $post = get_post( $rule['value'] ); 812 return $post ? $post->post_title . ' (Private)' : sprintf( 813 /* translators: %d: Post ID */ 814 __( 'Unknown Private Post (ID: %d)', 'osom-multi-theme-switcher' ), 427 case 'private_cpt_item': 428 $post = get_post( $rule['value'] ); 429 return $post ? '(Private) ' . $post->post_title : sprintf( 430 /* translators: %d: Post ID */ 431 __( 'Unknown Private (ID: %d)', 'osom-multi-theme-switcher' ), 815 432 $rule['value'] 816 433 ); 817 434 818 435 case 'future_page': 819 $page = get_post( $rule['value'] );820 return $page ? $page->post_title . ' (Scheduled)' : sprintf(821 /* translators: %d: Page ID */822 __( 'Unknown Scheduled Page (ID: %d)', 'osom-multi-theme-switcher' ),823 $rule['value']824 );825 826 436 case 'future_post': 827 $post = get_post( $rule['value'] ); 828 return $post ? $post->post_title . ' (Scheduled)' : sprintf( 829 /* translators: %d: Post ID */ 830 __( 'Unknown Scheduled Post (ID: %d)', 'osom-multi-theme-switcher' ), 437 case 'future_cpt_item': 438 $post = get_post( $rule['value'] ); 439 return $post ? '(Scheduled) ' . $post->post_title : sprintf( 440 /* translators: %d: Post ID */ 441 __( 'Unknown Scheduled (ID: %d)', 'osom-multi-theme-switcher' ), 831 442 $rule['value'] 832 443 ); -
osom-multi-theme-switcher/trunk/includes/class-omts-ajax-handler.php
r3457914 r3464901 52 52 add_action( 'wp_ajax_omts_save_rest_prefix', array( $this, 'ajax_save_rest_prefix' ) ); 53 53 add_action( 'wp_ajax_omts_delete_rest_prefix', array( $this, 'ajax_delete_rest_prefix' ) ); 54 add_action( 'wp_ajax_omts_get_rule_objects', array( $this, 'ajax_get_rule_objects' ) ); 55 add_action( 'wp_ajax_omts_get_rule_items', array( $this, 'ajax_get_rule_items' ) ); 54 56 } 55 57 … … 73 75 } 74 76 75 $rule = array( 76 'type' => $rule_type, 77 'theme' => $theme, 78 ); 79 80 // Get the value based on rule type. 81 switch ( $rule_type ) { 82 case 'page': 83 $rule['value'] = isset( $_POST['page_id'] ) ? intval( $_POST['page_id'] ) : 0; 84 break; 85 case 'post': 86 $rule['value'] = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0; 87 break; 88 case 'post_type': 89 $rule['value'] = isset( $_POST['post_type'] ) ? sanitize_text_field( wp_unslash( $_POST['post_type'] ) ) : ''; 90 break; 91 case 'draft_page': 92 $rule['value'] = isset( $_POST['page_id'] ) ? intval( $_POST['page_id'] ) : 0; 93 break; 94 case 'draft_post': 95 $rule['value'] = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0; 96 break; 97 case 'pending_page': 98 $rule['value'] = isset( $_POST['page_id'] ) ? intval( $_POST['page_id'] ) : 0; 99 break; 100 case 'pending_post': 101 $rule['value'] = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0; 102 break; 103 case 'private_page': 104 $rule['value'] = isset( $_POST['page_id'] ) ? intval( $_POST['page_id'] ) : 0; 105 break; 106 case 'private_post': 107 $rule['value'] = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0; 108 break; 109 case 'future_page': 110 $rule['value'] = isset( $_POST['page_id'] ) ? intval( $_POST['page_id'] ) : 0; 111 break; 112 case 'future_post': 113 $rule['value'] = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0; 114 break; 115 case 'url': 116 $rule['value'] = isset( $_POST['custom_url'] ) ? sanitize_text_field( wp_unslash( $_POST['custom_url'] ) ) : ''; 117 break; 118 case 'category': 119 $rule['value'] = isset( $_POST['category_id'] ) ? intval( $_POST['category_id'] ) : 0; 120 break; 121 case 'tag': 122 $rule['value'] = isset( $_POST['tag_id'] ) ? intval( $_POST['tag_id'] ) : 0; 123 break; 124 } 77 // Build rule from the cascading selector data. 78 $rule = $this->build_rule_from_request( $rule_type, $theme ); 125 79 126 80 if ( empty( $rule['value'] ) ) { … … 139 93 'index' => count( $rules ) - 1, 140 94 'target_display' => $this->get_rule_target_display( $rule ), 95 'type_display' => $this->get_rule_type_display( $rule['type'] ), 141 96 'theme_name' => $this->theme_switcher->get_theme_name( $theme ), 142 97 ) 143 98 ); 99 } 100 101 /** 102 * Build rule array from the cascading selector request data. 103 * 104 * @since 1.2.0 105 * 106 * @param string $rule_type Rule type from the form. 107 * @param string $theme Theme slug. 108 * @return array Rule array. 109 */ 110 private function build_rule_from_request( $rule_type, $theme ) { 111 $rule = array( 112 'type' => $rule_type, 113 'theme' => $theme, 114 ); 115 116 switch ( $rule_type ) { 117 case 'page': 118 case 'post': 119 $item_id = isset( $_POST['item_id'] ) ? intval( $_POST['item_id'] ) : 0; 120 if ( $item_id ) { 121 $post = get_post( $item_id ); 122 if ( $post ) { 123 $status = $post->post_status; 124 if ( 'publish' !== $status ) { 125 $status_map = array( 126 'draft' => 'draft_', 127 'pending' => 'pending_', 128 'private' => 'private_', 129 'future' => 'future_', 130 ); 131 if ( isset( $status_map[ $status ] ) ) { 132 $rule['type'] = $status_map[ $status ] . $rule_type; 133 } 134 } 135 } 136 } 137 $rule['value'] = $item_id; 138 break; 139 140 case 'custom_post_type': 141 $object_type = isset( $_POST['object_type'] ) ? sanitize_text_field( wp_unslash( $_POST['object_type'] ) ) : ''; 142 $item_id = isset( $_POST['item_id'] ) ? sanitize_text_field( wp_unslash( $_POST['item_id'] ) ) : ''; 143 144 if ( empty( $object_type ) ) { 145 $rule['value'] = ''; 146 break; 147 } 148 149 if ( '__all__' === $item_id ) { 150 // "All" option — store as post_type rule. 151 $rule['type'] = 'post_type'; 152 $rule['value'] = $object_type; 153 154 // Store URL slugs for early matching (before CPT is registered). 155 $pt_obj = get_post_type_object( $object_type ); 156 if ( $pt_obj ) { 157 if ( $pt_obj->has_archive ) { 158 $rule['archive_slug'] = true === $pt_obj->has_archive ? $object_type : $pt_obj->has_archive; 159 } 160 if ( is_array( $pt_obj->rewrite ) && isset( $pt_obj->rewrite['slug'] ) ) { 161 $rule['rewrite_slug'] = $pt_obj->rewrite['slug']; 162 } 163 } 164 } else { 165 // Individual CPT item. 166 $item_id = intval( $item_id ); 167 $post = get_post( $item_id ); 168 169 if ( ! $post || $post->post_type !== $object_type ) { 170 $rule['value'] = ''; 171 break; 172 } 173 174 if ( 'publish' === $post->post_status ) { 175 $rule['type'] = 'cpt_item'; 176 } else { 177 $status_map = array( 178 'draft' => 'draft_cpt_item', 179 'pending' => 'pending_cpt_item', 180 'private' => 'private_cpt_item', 181 'future' => 'future_cpt_item', 182 ); 183 $rule['type'] = isset( $status_map[ $post->post_status ] ) ? $status_map[ $post->post_status ] : 'cpt_item'; 184 } 185 $rule['value'] = $item_id; 186 $rule['post_type'] = $object_type; 187 } 188 break; 189 190 case 'taxonomy': 191 $object_type = isset( $_POST['object_type'] ) ? sanitize_text_field( wp_unslash( $_POST['object_type'] ) ) : ''; 192 $item_id = isset( $_POST['item_id'] ) ? intval( $_POST['item_id'] ) : 0; 193 194 if ( empty( $object_type ) ) { 195 $rule['value'] = ''; 196 break; 197 } 198 199 // Validate the term exists in the specified taxonomy. 200 $term = get_term( $item_id, $object_type ); 201 if ( ! $term || is_wp_error( $term ) || $term->taxonomy !== $object_type ) { 202 $rule['value'] = ''; 203 break; 204 } 205 206 if ( 'category' === $object_type ) { 207 $rule['type'] = 'category'; 208 } elseif ( 'post_tag' === $object_type ) { 209 $rule['type'] = 'tag'; 210 } else { 211 $rule['type'] = 'taxonomy'; 212 $rule['taxonomy'] = $object_type; 213 214 // Store rewrite slug for early matching (before taxonomy is registered). 215 $tax_obj = get_taxonomy( $object_type ); 216 if ( $tax_obj && is_array( $tax_obj->rewrite ) && isset( $tax_obj->rewrite['slug'] ) ) { 217 $rule['rewrite_slug'] = $tax_obj->rewrite['slug']; 218 } 219 } 220 $rule['value'] = $item_id; 221 break; 222 223 case 'url': 224 $rule['value'] = isset( $_POST['custom_url'] ) ? sanitize_text_field( wp_unslash( $_POST['custom_url'] ) ) : ''; 225 break; 226 227 default: 228 $rule['value'] = ''; 229 break; 230 } 231 232 return $rule; 233 } 234 235 /** 236 * Get human-readable display name for rule type. 237 * 238 * @since 1.2.0 239 * 240 * @param string $type Rule type. 241 * @return string Display name. 242 */ 243 private function get_rule_type_display( $type ) { 244 return OMTS_Theme_Switcher::get_rule_type_display( $type ); 245 } 246 247 /** 248 * AJAX handler: Get rule objects for cascading selector. 249 * 250 * @since 1.2.0 251 */ 252 public function ajax_get_rule_objects() { 253 check_ajax_referer( 'omts_nonce', 'nonce' ); 254 255 if ( ! current_user_can( 'manage_options' ) ) { 256 wp_send_json_error( __( 'Insufficient permissions', 'osom-multi-theme-switcher' ) ); 257 } 258 259 $rule_type = isset( $_POST['rule_type'] ) ? sanitize_text_field( wp_unslash( $_POST['rule_type'] ) ) : ''; 260 $objects = array(); 261 262 switch ( $rule_type ) { 263 case 'custom_post_type': 264 $post_types = get_post_types( array( 'public' => true ), 'objects' ); 265 foreach ( $post_types as $pt ) { 266 if ( in_array( $pt->name, array( 'page', 'post', 'attachment' ), true ) ) { 267 continue; 268 } 269 $objects[] = array( 270 'value' => $pt->name, 271 'label' => $pt->label, 272 ); 273 } 274 break; 275 276 case 'taxonomy': 277 $taxonomies = get_taxonomies( array( 'public' => true ), 'objects' ); 278 foreach ( $taxonomies as $tax ) { 279 $objects[] = array( 280 'value' => $tax->name, 281 'label' => $tax->label, 282 ); 283 } 284 break; 285 } 286 287 wp_send_json_success( array( 'objects' => $objects ) ); 288 } 289 290 /** 291 * AJAX handler: Get rule items for cascading selector. 292 * 293 * @since 1.2.0 294 */ 295 public function ajax_get_rule_items() { 296 check_ajax_referer( 'omts_nonce', 'nonce' ); 297 298 if ( ! current_user_can( 'manage_options' ) ) { 299 wp_send_json_error( __( 'Insufficient permissions', 'osom-multi-theme-switcher' ) ); 300 } 301 302 $rule_type = isset( $_POST['rule_type'] ) ? sanitize_text_field( wp_unslash( $_POST['rule_type'] ) ) : ''; 303 $object_type = isset( $_POST['object_type'] ) ? sanitize_text_field( wp_unslash( $_POST['object_type'] ) ) : ''; 304 $items = array(); 305 306 $all_statuses = array( 'publish', 'draft', 'pending', 'private', 'future' ); 307 $status_labels = array( 308 'draft' => __( 'Draft', 'osom-multi-theme-switcher' ), 309 'pending' => __( 'Pending', 'osom-multi-theme-switcher' ), 310 'private' => __( 'Private', 'osom-multi-theme-switcher' ), 311 'future' => __( 'Scheduled', 'osom-multi-theme-switcher' ), 312 ); 313 314 switch ( $rule_type ) { 315 case 'page': 316 $pages = get_posts( 317 array( 318 'post_type' => 'page', 319 'post_status' => $all_statuses, 320 'numberposts' => 500, 321 'orderby' => 'title', 322 'order' => 'ASC', 323 ) 324 ); 325 foreach ( $pages as $page ) { 326 $label = $page->post_title; 327 if ( 'publish' !== $page->post_status && isset( $status_labels[ $page->post_status ] ) ) { 328 $label = '(' . $status_labels[ $page->post_status ] . ') ' . $label; 329 } 330 $items[] = array( 331 'value' => $page->ID, 332 'label' => $label, 333 ); 334 } 335 break; 336 337 case 'post': 338 $posts = get_posts( 339 array( 340 'post_type' => 'post', 341 'post_status' => $all_statuses, 342 'numberposts' => 500, 343 'orderby' => 'title', 344 'order' => 'ASC', 345 ) 346 ); 347 foreach ( $posts as $post ) { 348 $label = $post->post_title; 349 if ( 'publish' !== $post->post_status && isset( $status_labels[ $post->post_status ] ) ) { 350 $label = '(' . $status_labels[ $post->post_status ] . ') ' . $label; 351 } 352 $items[] = array( 353 'value' => $post->ID, 354 'label' => $label, 355 ); 356 } 357 break; 358 359 case 'custom_post_type': 360 if ( empty( $object_type ) ) { 361 break; 362 } 363 364 $post_type_obj = get_post_type_object( $object_type ); 365 $all_label = $post_type_obj 366 ? sprintf( 367 /* translators: %s: Post type label */ 368 __( 'All %s', 'osom-multi-theme-switcher' ), 369 $post_type_obj->label 370 ) 371 : __( 'All', 'osom-multi-theme-switcher' ); 372 373 $items[] = array( 374 'value' => '__all__', 375 'label' => $all_label, 376 ); 377 378 $posts = get_posts( 379 array( 380 'post_type' => $object_type, 381 'post_status' => $all_statuses, 382 'numberposts' => 500, 383 'orderby' => 'title', 384 'order' => 'ASC', 385 ) 386 ); 387 foreach ( $posts as $post ) { 388 $label = $post->post_title; 389 if ( 'publish' !== $post->post_status && isset( $status_labels[ $post->post_status ] ) ) { 390 $label = '(' . $status_labels[ $post->post_status ] . ') ' . $label; 391 } 392 $items[] = array( 393 'value' => $post->ID, 394 'label' => $label, 395 ); 396 } 397 break; 398 399 case 'taxonomy': 400 if ( empty( $object_type ) ) { 401 break; 402 } 403 404 $terms = get_terms( 405 array( 406 'taxonomy' => $object_type, 407 'hide_empty' => false, 408 'orderby' => 'name', 409 'order' => 'ASC', 410 'number' => 500, 411 ) 412 ); 413 414 if ( ! is_wp_error( $terms ) ) { 415 foreach ( $terms as $term ) { 416 $items[] = array( 417 'value' => $term->term_id, 418 'label' => $term->name, 419 ); 420 } 421 } 422 break; 423 } 424 425 wp_send_json_success( array( 'items' => $items ) ); 144 426 } 145 427 … … 228 510 case 'post_type': 229 511 $post_type_obj = get_post_type_object( $rule['value'] ); 230 return $post_type_obj ? $post_type_obj->label : $rule['value']; 512 return $post_type_obj 513 ? sprintf( 514 /* translators: %s: Post type label */ 515 __( 'All %s', 'osom-multi-theme-switcher' ), 516 $post_type_obj->label 517 ) 518 : $rule['value']; 231 519 232 520 case 'url': … … 241 529 return $tag ? $tag->name : __( 'Unknown Tag', 'osom-multi-theme-switcher' ); 242 530 531 case 'taxonomy': 532 $taxonomy = isset( $rule['taxonomy'] ) ? $rule['taxonomy'] : ''; 533 $term = get_term( $rule['value'], $taxonomy ); 534 if ( $term && ! is_wp_error( $term ) ) { 535 $tax_obj = get_taxonomy( $taxonomy ); 536 $tax_label = $tax_obj ? $tax_obj->label : $taxonomy; 537 return $term->name . ' (' . $tax_label . ')'; 538 } 539 return __( 'Unknown Term', 'osom-multi-theme-switcher' ); 540 541 case 'cpt_item': 542 $post = get_post( $rule['value'] ); 543 return $post ? $post->post_title : sprintf( 544 /* translators: %d: Post ID */ 545 __( 'Unknown Item (ID: %d)', 'osom-multi-theme-switcher' ), 546 $rule['value'] 547 ); 548 243 549 case 'draft_page': 244 $page = get_post( $rule['value'] ); 245 return $page ? $page->post_title . ' (Draft)' : sprintf( 246 /* translators: %d: Page ID */ 247 __( 'Unknown Draft Page (ID: %d)', 'osom-multi-theme-switcher' ), 550 case 'draft_post': 551 case 'draft_cpt_item': 552 $post = get_post( $rule['value'] ); 553 return $post ? '(Draft) ' . $post->post_title : sprintf( 554 /* translators: %d: Post ID */ 555 __( 'Unknown Draft (ID: %d)', 'osom-multi-theme-switcher' ), 248 556 $rule['value'] 249 557 ); 250 558 251 case 'draft_post': 559 case 'pending_page': 560 case 'pending_post': 561 case 'pending_cpt_item': 252 562 $post = get_post( $rule['value'] ); 253 return $post ? $post->post_title . ' (Draft)': sprintf(563 return $post ? '(Pending) ' . $post->post_title : sprintf( 254 564 /* translators: %d: Post ID */ 255 __( 'Unknown Draft Post(ID: %d)', 'osom-multi-theme-switcher' ),565 __( 'Unknown Pending (ID: %d)', 'osom-multi-theme-switcher' ), 256 566 $rule['value'] 257 567 ); 258 568 259 case 'pending_page': 260 $page = get_post( $rule['value'] ); 261 return $page ? $page->post_title . ' (Pending)' : sprintf( 262 /* translators: %d: Page ID */ 263 __( 'Unknown Pending Page (ID: %d)', 'osom-multi-theme-switcher' ), 569 case 'private_page': 570 case 'private_post': 571 case 'private_cpt_item': 572 $post = get_post( $rule['value'] ); 573 return $post ? '(Private) ' . $post->post_title : sprintf( 574 /* translators: %d: Post ID */ 575 __( 'Unknown Private (ID: %d)', 'osom-multi-theme-switcher' ), 264 576 $rule['value'] 265 577 ); 266 578 267 case 'pending_post': 579 case 'future_page': 580 case 'future_post': 581 case 'future_cpt_item': 268 582 $post = get_post( $rule['value'] ); 269 return $post ? $post->post_title . ' (Pending)': sprintf(583 return $post ? '(Scheduled) ' . $post->post_title : sprintf( 270 584 /* translators: %d: Post ID */ 271 __( 'Unknown Pending Post (ID: %d)', 'osom-multi-theme-switcher' ), 272 $rule['value'] 273 ); 274 275 case 'private_page': 276 $page = get_post( $rule['value'] ); 277 return $page ? $page->post_title . ' (Private)' : sprintf( 278 /* translators: %d: Page ID */ 279 __( 'Unknown Private Page (ID: %d)', 'osom-multi-theme-switcher' ), 280 $rule['value'] 281 ); 282 283 case 'private_post': 284 $post = get_post( $rule['value'] ); 285 return $post ? $post->post_title . ' (Private)' : sprintf( 286 /* translators: %d: Post ID */ 287 __( 'Unknown Private Post (ID: %d)', 'osom-multi-theme-switcher' ), 288 $rule['value'] 289 ); 290 291 case 'future_page': 292 $page = get_post( $rule['value'] ); 293 return $page ? $page->post_title . ' (Scheduled)' : sprintf( 294 /* translators: %d: Page ID */ 295 __( 'Unknown Scheduled Page (ID: %d)', 'osom-multi-theme-switcher' ), 296 $rule['value'] 297 ); 298 299 case 'future_post': 300 $post = get_post( $rule['value'] ); 301 return $post ? $post->post_title . ' (Scheduled)' : sprintf( 302 /* translators: %d: Post ID */ 303 __( 'Unknown Scheduled Post (ID: %d)', 'osom-multi-theme-switcher' ), 585 __( 'Unknown Scheduled (ID: %d)', 'osom-multi-theme-switcher' ), 304 586 $rule['value'] 305 587 ); -
osom-multi-theme-switcher/trunk/includes/class-omts-theme-switcher.php
r3457914 r3464901 36 36 37 37 /** 38 * Option name for storing per-theme CPT/taxonomy registry. 39 * 40 * @var string 41 */ 42 private $theme_registry_option = 'omts_theme_object_registry'; 43 44 /** 38 45 * Flag to prevent recursion in filter_rest_url_prefix. 39 46 * … … 57 64 add_filter( 'rest_url_prefix', array( $this, 'filter_rest_url_prefix' ) ); 58 65 add_action( 'init', array( $this, 'add_custom_rest_rewrite_rules' ), 1 ); 66 add_action( 'init', array( $this, 'reregister_missing_cpts' ), 998 ); 67 add_action( 'init', array( $this, 'capture_theme_objects' ), 999 ); 59 68 add_filter( 'rest_pre_dispatch', array( $this, 'set_rest_theme_early' ), 1, 3 ); 60 69 } … … 75 84 } 76 85 77 add_filter( 'option_template', function() use ( $theme ) { 78 return $theme; 79 }, 1 ); 80 81 add_filter( 'option_stylesheet', function() use ( $theme ) { 82 return $theme; 83 }, 1 ); 86 $theme_obj = wp_get_theme( $theme ); 87 if ( ! $theme_obj->exists() ) { 88 return; 89 } 90 91 add_filter( 92 'option_template', 93 function() use ( $theme ) { 94 return $theme; 95 }, 96 1 97 ); 98 99 add_filter( 100 'option_stylesheet', 101 function() use ( $theme ) { 102 return $theme; 103 }, 104 1 105 ); 84 106 } 85 107 … … 94 116 public function switch_theme_template( $template ) { 95 117 $theme = $this->get_theme_for_current_request(); 118 119 if ( $theme ) { 120 $theme_obj = wp_get_theme( $theme ); 121 if ( ! $theme_obj->exists() ) { 122 return $template; 123 } 124 } 125 96 126 return $theme ? $theme : $template; 97 127 } … … 107 137 public function switch_theme_stylesheet( $stylesheet ) { 108 138 $theme = $this->get_theme_for_current_request(); 139 140 if ( $theme ) { 141 $theme_obj = wp_get_theme( $theme ); 142 if ( ! $theme_obj->exists() ) { 143 return $stylesheet; 144 } 145 } 146 109 147 return $theme ? $theme : $stylesheet; 110 148 } … … 228 266 229 267 /** 268 * Check if the WordPress query has run and conditional tags are available. 269 * 270 * Conditional query tags like is_page(), is_single(), etc. only work 271 * after the main query has been parsed. 272 * 273 * @since 1.0.4 274 * 275 * @return bool Whether conditional query tags can be used. 276 */ 277 private function is_query_ready() { 278 global $wp_query; 279 280 if ( did_action( 'wp' ) ) { 281 return true; 282 } 283 284 if ( isset( $wp_query ) && $wp_query instanceof WP_Query && ! empty( $wp_query->query ) ) { 285 return true; 286 } 287 288 return false; 289 } 290 291 /** 230 292 * Check if a rule matches the current request. 231 293 * … … 237 299 */ 238 300 private function rule_matches( $rule, $early = false ) { 301 if ( ! $early && ! $this->is_query_ready() ) { 302 $early = true; 303 } 304 239 305 // If called early, we can only check URL-based rules 240 306 if ( $early ) { … … 276 342 return $this->match_future_post_early( $rule['value'] ); 277 343 } 344 if ( 'cpt_item' === $rule['type'] ) { 345 return $this->match_cpt_item_early( $rule['value'], 'publish' ); 346 } 347 if ( 'draft_cpt_item' === $rule['type'] ) { 348 return $this->match_cpt_item_early( $rule['value'], 'draft' ); 349 } 350 if ( 'pending_cpt_item' === $rule['type'] ) { 351 return $this->match_cpt_item_early( $rule['value'], 'pending' ); 352 } 353 if ( 'private_cpt_item' === $rule['type'] ) { 354 return $this->match_cpt_item_early( $rule['value'], 'private' ); 355 } 356 if ( 'future_cpt_item' === $rule['type'] ) { 357 return $this->match_cpt_item_early( $rule['value'], 'future' ); 358 } 359 if ( 'category' === $rule['type'] ) { 360 return $this->match_category_early( $rule['value'] ); 361 } 362 if ( 'tag' === $rule['type'] ) { 363 return $this->match_tag_early( $rule['value'] ); 364 } 365 if ( 'taxonomy' === $rule['type'] ) { 366 $taxonomy = isset( $rule['taxonomy'] ) ? $rule['taxonomy'] : ''; 367 return $taxonomy ? $this->match_taxonomy_early( $rule['value'], $taxonomy, $rule ) : false; 368 } 369 if ( 'post_type' === $rule['type'] ) { 370 return $this->match_post_type_early( $rule ); 371 } 278 372 return false; 279 373 } … … 300 394 case 'tag': 301 395 return is_tag( $rule['value'] ) || ( is_single() && has_tag( $rule['value'] ) ); 396 397 case 'taxonomy': 398 $taxonomy = isset( $rule['taxonomy'] ) ? $rule['taxonomy'] : ''; 399 if ( ! $taxonomy ) { 400 return false; 401 } 402 return is_tax( $taxonomy, $rule['value'] ) || ( is_singular() && has_term( $rule['value'], $taxonomy ) ); 302 403 303 404 case 'draft_page': … … 317 418 return $post && is_single( $rule['value'] ); 318 419 420 case 'cpt_item': 421 return is_singular() && get_queried_object_id() === absint( $rule['value'] ); 422 423 case 'draft_cpt_item': 424 case 'pending_cpt_item': 425 case 'private_cpt_item': 426 case 'future_cpt_item': 427 $post = get_post( $rule['value'] ); 428 return $post && is_singular() && get_queried_object_id() === absint( $rule['value'] ); 429 319 430 default: 320 431 return false; … … 382 493 $theme = wp_get_theme( $stylesheet ); 383 494 return $theme->exists() ? $theme->get( 'Name' ) : $stylesheet; 495 } 496 497 /** 498 * Get human-readable display name for rule type. 499 * 500 * Shared by OMTS_Admin_Page and OMTS_Ajax_Handler to avoid duplication. 501 * 502 * @since 1.2.0 503 * 504 * @param string $type Rule type. 505 * @return string Display name. 506 */ 507 public static function get_rule_type_display( $type ) { 508 $type_map = array( 509 'page' => __( 'Page', 'osom-multi-theme-switcher' ), 510 'post' => __( 'Post', 'osom-multi-theme-switcher' ), 511 'post_type' => __( 'Custom Post Type', 'osom-multi-theme-switcher' ), 512 'url' => __( 'Custom URL', 'osom-multi-theme-switcher' ), 513 'category' => __( 'Category', 'osom-multi-theme-switcher' ), 514 'tag' => __( 'Tag', 'osom-multi-theme-switcher' ), 515 'taxonomy' => __( 'Taxonomy', 'osom-multi-theme-switcher' ), 516 'cpt_item' => __( 'CPT Item', 'osom-multi-theme-switcher' ), 517 'draft_page' => __( 'Page', 'osom-multi-theme-switcher' ), 518 'draft_post' => __( 'Post', 'osom-multi-theme-switcher' ), 519 'pending_page' => __( 'Page', 'osom-multi-theme-switcher' ), 520 'pending_post' => __( 'Post', 'osom-multi-theme-switcher' ), 521 'private_page' => __( 'Page', 'osom-multi-theme-switcher' ), 522 'private_post' => __( 'Post', 'osom-multi-theme-switcher' ), 523 'future_page' => __( 'Page', 'osom-multi-theme-switcher' ), 524 'future_post' => __( 'Post', 'osom-multi-theme-switcher' ), 525 'draft_cpt_item' => __( 'CPT Item', 'osom-multi-theme-switcher' ), 526 'pending_cpt_item' => __( 'CPT Item', 'osom-multi-theme-switcher' ), 527 'private_cpt_item' => __( 'CPT Item', 'osom-multi-theme-switcher' ), 528 'future_cpt_item' => __( 'CPT Item', 'osom-multi-theme-switcher' ), 529 ); 530 531 return isset( $type_map[ $type ] ) ? $type_map[ $type ] : ucfirst( str_replace( '_', ' ', $type ) ); 384 532 } 385 533 … … 1155 1303 1156 1304 /** 1157 * Match preview post against URL rules. 1305 * Match preview post against rules. 1306 * 1307 * Checks for page/post ID rules, status-based rules (draft_page, etc.), 1308 * and URL rules. 1158 1309 * 1159 1310 * @since 1.0.3 … … 1172 1323 $post = $wpdb->get_row( 1173 1324 $wpdb->prepare( 1174 "SELECT post_name, post_parent, post_type FROM {$wpdb->posts} WHERE ID = %d",1325 "SELECT post_name, post_parent, post_type, post_status FROM {$wpdb->posts} WHERE ID = %d", 1175 1326 $post_id 1176 1327 ) … … 1181 1332 } 1182 1333 1334 // First, check for direct page/post ID rules and status-based rules. 1335 foreach ( $rules as $rule ) { 1336 // Check for direct page rule (page type with matching ID). 1337 if ( 'page' === $rule['type'] && 'page' === $post->post_type && absint( $rule['value'] ) === $post_id ) { 1338 return $rule['theme']; 1339 } 1340 1341 // Check for direct post rule (post type with matching ID). 1342 if ( 'post' === $rule['type'] && 'post' === $post->post_type && absint( $rule['value'] ) === $post_id ) { 1343 return $rule['theme']; 1344 } 1345 1346 // Check for status-based rules (draft_page, pending_page, private_page, future_page, etc.). 1347 $status_type = $post->post_status . '_' . $post->post_type; 1348 if ( $rule['type'] === $status_type && absint( $rule['value'] ) === $post_id ) { 1349 return $rule['theme']; 1350 } 1351 1352 // Check for CPT item rules. 1353 if ( in_array( $rule['type'], array( 'cpt_item', 'draft_cpt_item', 'pending_cpt_item', 'private_cpt_item', 'future_cpt_item' ), true ) && absint( $rule['value'] ) === $post_id ) { 1354 return $rule['theme']; 1355 } 1356 } 1357 1358 // Then, check URL rules. 1183 1359 if ( 'page' === $post->post_type ) { 1184 1360 return $this->match_page_preview_against_rules( $post, $rules, $wpdb ); … … 1238 1414 */ 1239 1415 private function match_post_preview_against_rules( $post, $rules ) { 1240 $post_path = $post->post_name;1416 $post_path = $post->post_name; 1241 1417 $post_path_normalized = trim( $post_path, '/' ); 1242 1418 1243 1419 foreach ( $rules as $rule ) { 1244 1420 if ( 'url' === $rule['type'] ) { 1245 $rule_url = trim( $rule['value'], '/' );1421 $rule_url = trim( $rule['value'], '/' ); 1246 1422 $rule_url_segments = explode( '/', $rule_url ); 1247 1423 … … 1260 1436 return false; 1261 1437 } 1438 1439 /** 1440 * Match category rule early by checking URL against category base. 1441 * 1442 * @since 1.2.0 1443 * 1444 * @param int $term_id Category term ID. 1445 * @return bool Whether current URL matches the category. 1446 */ 1447 private function match_category_early( $term_id ) { 1448 global $wpdb; 1449 1450 if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { 1451 return false; 1452 } 1453 1454 $term_id = absint( $term_id ); 1455 $term = $wpdb->get_row( 1456 $wpdb->prepare( 1457 "SELECT t.slug FROM {$wpdb->terms} t 1458 INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id 1459 WHERE t.term_id = %d AND tt.taxonomy = 'category'", 1460 $term_id 1461 ) 1462 ); 1463 1464 if ( ! $term ) { 1465 return false; 1466 } 1467 1468 $sanitized_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); 1469 $request_uri = parse_url( $sanitized_uri, PHP_URL_PATH ); 1470 $path = trim( $request_uri, '/' ); 1471 1472 $category_base = get_option( 'category_base' ); 1473 if ( empty( $category_base ) ) { 1474 $category_base = 'category'; 1475 } 1476 1477 // Check if URL matches pattern: {category_base}/{slug} 1478 $expected_path = trim( $category_base, '/' ) . '/' . $term->slug; 1479 1480 return $path === $expected_path || 0 === strpos( $path, $expected_path . '/' ); 1481 } 1482 1483 /** 1484 * Match tag rule early by checking URL against tag base. 1485 * 1486 * @since 1.2.0 1487 * 1488 * @param int $term_id Tag term ID. 1489 * @return bool Whether current URL matches the tag. 1490 */ 1491 private function match_tag_early( $term_id ) { 1492 global $wpdb; 1493 1494 if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { 1495 return false; 1496 } 1497 1498 $term_id = absint( $term_id ); 1499 $term = $wpdb->get_row( 1500 $wpdb->prepare( 1501 "SELECT t.slug FROM {$wpdb->terms} t 1502 INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id 1503 WHERE t.term_id = %d AND tt.taxonomy = 'post_tag'", 1504 $term_id 1505 ) 1506 ); 1507 1508 if ( ! $term ) { 1509 return false; 1510 } 1511 1512 $sanitized_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); 1513 $request_uri = parse_url( $sanitized_uri, PHP_URL_PATH ); 1514 $path = trim( $request_uri, '/' ); 1515 1516 $tag_base = get_option( 'tag_base' ); 1517 if ( empty( $tag_base ) ) { 1518 $tag_base = 'tag'; 1519 } 1520 1521 // Check if URL matches pattern: {tag_base}/{slug} 1522 $expected_path = trim( $tag_base, '/' ) . '/' . $term->slug; 1523 1524 return $path === $expected_path || 0 === strpos( $path, $expected_path . '/' ); 1525 } 1526 1527 /** 1528 * Match custom taxonomy rule early by checking URL against taxonomy rewrite slug. 1529 * 1530 * Falls back to a stored rewrite_slug on the rule when get_taxonomy() is not 1531 * available yet (e.g., during setup_theme before taxonomies are registered). 1532 * 1533 * @since 1.2.0 1534 * 1535 * @param int $term_id Term ID. 1536 * @param string $taxonomy Taxonomy name. 1537 * @param array $rule Rule array with optional rewrite_slug. 1538 * @return bool Whether current URL matches the taxonomy term. 1539 */ 1540 private function match_taxonomy_early( $term_id, $taxonomy, $rule = array() ) { 1541 global $wpdb; 1542 1543 if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { 1544 return false; 1545 } 1546 1547 $term_id = absint( $term_id ); 1548 $term = $wpdb->get_row( 1549 $wpdb->prepare( 1550 "SELECT t.slug FROM {$wpdb->terms} t 1551 INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id 1552 WHERE t.term_id = %d AND tt.taxonomy = %s", 1553 $term_id, 1554 $taxonomy 1555 ) 1556 ); 1557 1558 if ( ! $term ) { 1559 return false; 1560 } 1561 1562 // Try get_taxonomy() first (available after init). 1563 $rewrite_slug = ''; 1564 $tax_obj = get_taxonomy( $taxonomy ); 1565 if ( $tax_obj && is_array( $tax_obj->rewrite ) && isset( $tax_obj->rewrite['slug'] ) ) { 1566 $rewrite_slug = $tax_obj->rewrite['slug']; 1567 } elseif ( ! empty( $rule['rewrite_slug'] ) ) { 1568 // Fallback to stored slug (works during setup_theme before taxonomies are registered). 1569 $rewrite_slug = $rule['rewrite_slug']; 1570 } else { 1571 return false; 1572 } 1573 1574 $sanitized_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); 1575 $request_uri = parse_url( $sanitized_uri, PHP_URL_PATH ); 1576 $path = trim( $request_uri, '/' ); 1577 1578 $rewrite_slug = trim( $rewrite_slug, '/' ); 1579 $expected_path = $rewrite_slug . '/' . $term->slug; 1580 1581 return $path === $expected_path || 0 === strpos( $path, $expected_path . '/' ); 1582 } 1583 1584 /** 1585 * Match post type rule early by checking URL against stored slugs. 1586 * 1587 * Uses slugs stored in the rule at save time, because CPTs may not be 1588 * registered yet during setup_theme hook. 1589 * 1590 * @since 1.2.0 1591 * 1592 * @param array $rule Rule array with optional archive_slug and rewrite_slug. 1593 * @return bool Whether current URL matches the post type. 1594 */ 1595 private function match_post_type_early( $rule ) { 1596 if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { 1597 return false; 1598 } 1599 1600 $sanitized_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); 1601 $request_uri = parse_url( $sanitized_uri, PHP_URL_PATH ); 1602 $path = trim( $request_uri, '/' ); 1603 1604 // First try stored slugs (works even before CPT is registered). 1605 if ( ! empty( $rule['archive_slug'] ) ) { 1606 $archive_slug = trim( $rule['archive_slug'], '/' ); 1607 if ( $path === $archive_slug || 0 === strpos( $path, $archive_slug . '/' ) ) { 1608 return true; 1609 } 1610 } 1611 1612 if ( ! empty( $rule['rewrite_slug'] ) ) { 1613 $rewrite_slug = trim( $rule['rewrite_slug'], '/' ); 1614 if ( 0 === strpos( $path, $rewrite_slug . '/' ) ) { 1615 return true; 1616 } 1617 } 1618 1619 // Fallback: try get_post_type_object if available (e.g., late calls). 1620 $post_type = $rule['value']; 1621 $post_type_obj = get_post_type_object( $post_type ); 1622 if ( $post_type_obj ) { 1623 if ( $post_type_obj->has_archive ) { 1624 $archive_slug = true === $post_type_obj->has_archive ? $post_type : $post_type_obj->has_archive; 1625 $archive_slug = trim( $archive_slug, '/' ); 1626 if ( $path === $archive_slug || 0 === strpos( $path, $archive_slug . '/' ) ) { 1627 return true; 1628 } 1629 } 1630 if ( is_array( $post_type_obj->rewrite ) && isset( $post_type_obj->rewrite['slug'] ) ) { 1631 $rewrite_slug = trim( $post_type_obj->rewrite['slug'], '/' ); 1632 if ( 0 === strpos( $path, $rewrite_slug . '/' ) ) { 1633 return true; 1634 } 1635 } 1636 } 1637 1638 return false; 1639 } 1640 1641 /** 1642 * Match CPT item rule early by querying database directly. 1643 * 1644 * @since 1.2.0 1645 * 1646 * @param int $post_id Post ID. 1647 * @param string $post_status Expected post status. 1648 * @return bool Whether current URL matches the CPT item. 1649 */ 1650 private function match_cpt_item_early( $post_id, $post_status = 'publish' ) { 1651 global $wpdb; 1652 1653 if ( ! isset( $_SERVER['REQUEST_URI'] ) ) { 1654 return false; 1655 } 1656 1657 $post_id = absint( $post_id ); 1658 $post = $wpdb->get_row( 1659 $wpdb->prepare( 1660 "SELECT post_name, post_type, post_status FROM {$wpdb->posts} WHERE ID = %d AND post_status = %s", 1661 $post_id, 1662 $post_status 1663 ) 1664 ); 1665 1666 if ( ! $post ) { 1667 return false; 1668 } 1669 1670 // Skip built-in page/post types — they have their own matchers. 1671 if ( in_array( $post->post_type, array( 'page', 'post' ), true ) ) { 1672 return false; 1673 } 1674 1675 $slug = $post->post_name; 1676 $sanitized_uri = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); 1677 $request_uri = parse_url( $sanitized_uri, PHP_URL_PATH ); 1678 $path = trim( $request_uri, '/' ); 1679 $path_segments = explode( '/', $path ); 1680 1681 // Determine the rewrite base for this CPT. 1682 $rewrite_base = $post->post_type; 1683 $post_type_obj = get_post_type_object( $post->post_type ); 1684 if ( $post_type_obj && is_array( $post_type_obj->rewrite ) && isset( $post_type_obj->rewrite['slug'] ) ) { 1685 $rewrite_base = trim( $post_type_obj->rewrite['slug'], '/' ); 1686 } 1687 1688 // Match only when the slug appears directly after the rewrite base. 1689 $base_segments = explode( '/', $rewrite_base ); 1690 $base_len = count( $base_segments ); 1691 1692 for ( $i = 0, $total = count( $path_segments ); $i < $total; $i++ ) { 1693 // Check if rewrite base starts at this position. 1694 if ( array_slice( $path_segments, $i, $base_len ) === $base_segments ) { 1695 $slug_index = $i + $base_len; 1696 if ( isset( $path_segments[ $slug_index ] ) && $path_segments[ $slug_index ] === $slug ) { 1697 return true; 1698 } 1699 } 1700 } 1701 1702 return false; 1703 } 1704 1705 /** 1706 * Capture registered CPTs and taxonomies for the currently active theme. 1707 * 1708 * Runs on `init` at priority 999 (after all CPTs/taxonomies are registered). 1709 * Stores full registration args so CPTs can be re-registered when a different 1710 * theme is active. 1711 * 1712 * @since 1.2.0 1713 */ 1714 public function capture_theme_objects() { 1715 // Get the currently active theme (may be the switched theme due to our rules). 1716 // We capture CPTs/taxonomies under this theme key so they can be 1717 // re-registered when a different theme is active via switching rules. 1718 $default_theme = get_option( 'stylesheet' ); 1719 $registry = get_option( $this->theme_registry_option, array() ); 1720 $installed_themes = wp_get_themes(); 1721 1722 // Prune registry entries for themes that are no longer installed. 1723 $registry = array_intersect_key( $registry, $installed_themes ); 1724 1725 // Collect public CPTs (excluding built-in page/post/attachment). 1726 $post_types = get_post_types( array( 'public' => true ), 'objects' ); 1727 $cpt_data = array(); 1728 foreach ( $post_types as $pt ) { 1729 if ( in_array( $pt->name, array( 'page', 'post', 'attachment' ), true ) ) { 1730 continue; 1731 } 1732 // Store args needed to re-register this CPT. 1733 $cpt_data[ $pt->name ] = array( 1734 'label' => $pt->label, 1735 'labels' => (array) $pt->labels, 1736 'public' => $pt->public, 1737 'has_archive' => $pt->has_archive, 1738 'rewrite' => $pt->rewrite, 1739 'supports' => get_all_post_type_supports( $pt->name ), 1740 'menu_icon' => $pt->menu_icon, 1741 'show_in_rest' => $pt->show_in_rest, 1742 'rest_base' => $pt->rest_base, 1743 'taxonomies' => get_object_taxonomies( $pt->name ), 1744 ); 1745 } 1746 1747 // Collect public taxonomies with registration args. 1748 $taxonomies = get_taxonomies( array( 'public' => true ), 'objects' ); 1749 $tax_data = array(); 1750 foreach ( $taxonomies as $tax ) { 1751 if ( in_array( $tax->name, array( 'category', 'post_tag', 'post_format' ), true ) ) { 1752 continue; 1753 } 1754 $tax_data[ $tax->name ] = array( 1755 'label' => $tax->label, 1756 'labels' => (array) $tax->labels, 1757 'public' => $tax->public, 1758 'hierarchical' => $tax->hierarchical, 1759 'rewrite' => $tax->rewrite, 1760 'show_in_rest' => $tax->show_in_rest, 1761 'rest_base' => $tax->rest_base, 1762 'object_type' => $tax->object_type, 1763 ); 1764 } 1765 1766 $new_entry = array( 1767 'post_types' => $cpt_data, 1768 'taxonomies' => $tax_data, 1769 ); 1770 1771 // Only update if data changed to avoid unnecessary DB writes. 1772 if ( ! isset( $registry[ $default_theme ] ) || $registry[ $default_theme ] !== $new_entry ) { 1773 $registry[ $default_theme ] = $new_entry; 1774 update_option( $this->theme_registry_option, $registry, false ); 1775 } 1776 } 1777 1778 /** 1779 * Re-register CPTs and taxonomies that are missing in the current theme 1780 * but are referenced by switching rules. 1781 * 1782 * Runs on `init` at priority 998, just before capture (999). 1783 * This ensures that when an alternative theme is active (via our rules), 1784 * CPTs from the original theme are still available so WordPress can 1785 * parse URLs and serve content correctly. 1786 * 1787 * @since 1.2.0 1788 */ 1789 public function reregister_missing_cpts() { 1790 $registry = get_option( $this->theme_registry_option, array() ); 1791 1792 if ( empty( $registry ) ) { 1793 return; 1794 } 1795 1796 // Collect all CPT slugs and taxonomy slugs referenced in rules. 1797 $rules = $this->get_rules(); 1798 $needed_cpts = array(); 1799 $needed_taxes = array(); 1800 1801 foreach ( $rules as $rule ) { 1802 if ( 'post_type' === $rule['type'] ) { 1803 $needed_cpts[ $rule['value'] ] = true; 1804 } 1805 if ( in_array( $rule['type'], array( 'cpt_item', 'draft_cpt_item', 'pending_cpt_item', 'private_cpt_item', 'future_cpt_item' ), true ) ) { 1806 if ( ! empty( $rule['post_type'] ) ) { 1807 $needed_cpts[ $rule['post_type'] ] = true; 1808 } 1809 } 1810 if ( 'taxonomy' === $rule['type'] && ! empty( $rule['taxonomy'] ) ) { 1811 $needed_taxes[ $rule['taxonomy'] ] = true; 1812 } 1813 } 1814 1815 if ( empty( $needed_cpts ) && empty( $needed_taxes ) ) { 1816 return; 1817 } 1818 1819 // Find CPT/taxonomy args from any theme in the registry. 1820 foreach ( $needed_cpts as $cpt_slug => $_ ) { 1821 if ( post_type_exists( $cpt_slug ) ) { 1822 continue; 1823 } 1824 1825 // Search all themes in registry for this CPT's args. 1826 $args = $this->find_cpt_args_in_registry( $cpt_slug, $registry ); 1827 if ( $args ) { 1828 register_post_type( $cpt_slug, $args ); 1829 } 1830 } 1831 1832 foreach ( $needed_taxes as $tax_slug => $_ ) { 1833 if ( taxonomy_exists( $tax_slug ) ) { 1834 continue; 1835 } 1836 1837 $tax_info = $this->find_taxonomy_args_in_registry( $tax_slug, $registry ); 1838 if ( $tax_info ) { 1839 register_taxonomy( $tax_slug, $tax_info['object_type'], $tax_info['args'] ); 1840 } 1841 } 1842 } 1843 1844 /** 1845 * Find CPT registration args from any theme in the registry. 1846 * 1847 * @since 1.2.0 1848 * 1849 * @param string $cpt_slug CPT slug to find. 1850 * @param array $registry Full theme object registry. 1851 * @return array|false Registration args or false if not found. 1852 */ 1853 private function find_cpt_args_in_registry( $cpt_slug, $registry ) { 1854 foreach ( $registry as $theme_data ) { 1855 if ( isset( $theme_data['post_types'][ $cpt_slug ] ) ) { 1856 $stored = $theme_data['post_types'][ $cpt_slug ]; 1857 return array( 1858 'label' => $stored['label'], 1859 'labels' => $stored['labels'], 1860 'public' => $stored['public'], 1861 'has_archive' => $stored['has_archive'], 1862 'rewrite' => $stored['rewrite'], 1863 'supports' => array_keys( array_filter( $stored['supports'] ) ), 1864 'menu_icon' => $stored['menu_icon'], 1865 'show_in_rest' => $stored['show_in_rest'], 1866 'rest_base' => $stored['rest_base'], 1867 'taxonomies' => $stored['taxonomies'], 1868 ); 1869 } 1870 } 1871 return false; 1872 } 1873 1874 /** 1875 * Find taxonomy registration args from any theme in the registry. 1876 * 1877 * @since 1.2.0 1878 * 1879 * @param string $tax_slug Taxonomy slug to find. 1880 * @param array $registry Full theme object registry. 1881 * @return array|false Array with 'object_type' and 'args' keys, or false if not found. 1882 */ 1883 private function find_taxonomy_args_in_registry( $tax_slug, $registry ) { 1884 foreach ( $registry as $theme_data ) { 1885 if ( isset( $theme_data['taxonomies'][ $tax_slug ] ) ) { 1886 $stored = $theme_data['taxonomies'][ $tax_slug ]; 1887 return array( 1888 'object_type' => $stored['object_type'], 1889 'args' => array( 1890 'label' => $stored['label'], 1891 'labels' => $stored['labels'], 1892 'public' => $stored['public'], 1893 'hierarchical' => $stored['hierarchical'], 1894 'rewrite' => $stored['rewrite'], 1895 'show_in_rest' => $stored['show_in_rest'], 1896 'rest_base' => $stored['rest_base'], 1897 ), 1898 ); 1899 } 1900 } 1901 return false; 1902 } 1262 1903 } -
osom-multi-theme-switcher/trunk/includes/class-osom-multi-theme-switcher.php
r3461829 r3464901 26 26 * @var string 27 27 */ 28 const VERSION = '1. 0.3';28 const VERSION = '1.2.1'; 29 29 30 30 /** … … 71 71 72 72 /** 73 * Status sync instance. 74 * 75 * @var OMTS_Status_Sync 76 */ 77 public $status_sync; 78 79 /** 73 80 * Get single instance. 74 81 * … … 105 112 require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-omts-ajax-handler.php'; 106 113 require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-omts-acf-loader.php'; 114 require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-omts-status-sync.php'; 107 115 } 108 116 … … 118 126 // Initialize ACF loader (loads ACF JSON from all themes). 119 127 $this->acf_loader = new OMTS_ACF_Loader(); 128 129 // Initialize status sync (automatically updates rules on post status changes). 130 $this->status_sync = new OMTS_Status_Sync( $this->theme_switcher ); 120 131 121 132 // Initialize admin components. -
osom-multi-theme-switcher/trunk/osom-multi-theme-switcher.php
r3461829 r3464901 4 4 * Plugin URI: https://github.com/osomstudio/osom-multi-theme-switcher 5 5 * Description: Allows you to use different themes for specific pages, posts, or URLs while keeping a main theme active. 6 * Version: 1. 0.36 * Version: 1.2.1 7 7 * Requires at least: 5.0 8 8 * Requires PHP: 7.0 … … 23 23 // Define plugin constants. 24 24 if ( ! defined( 'OMTS_VERSION' ) ) { 25 define( 'OMTS_VERSION', '1. 0.3' );25 define( 'OMTS_VERSION', '1.2.1' ); 26 26 } 27 27 -
osom-multi-theme-switcher/trunk/readme.txt
r3461835 r3464901 93 93 == Screenshots == 94 94 95 1. Theme Switcher rules panel — manage all your theme rules from one place96 2. Adding a new rule — select content type, target, and alternative theme97 3. Admin bar theme switcher — quickly access settings for any installed theme98 4. Different themes on different pages — same site, different designs95 1. Admin settings page - Add new theme rules and manage existing ones 96 2. Rule type selection - Choose from pages, posts, post types, URLs, categories, or tags 97 3. Admin bar theme switcher - Quickly switch themes in the WordPress dashboard 98 4. Side-by-side comparison of themes before/after theme switch 99 99 100 100 = Built by Osom Studio = … … 105 105 106 106 == Changelog == 107 108 = 1.2.1 = 109 * Added cascading selector UI for adding theme rules (Custom Post Type, Taxonomy support) 110 * Added automatic status synchronization (OMTS_Status_Sync) — rules update when post status changes 111 * Added CPT/taxonomy registry for re-registering missing CPTs across themes (OMTS_Theme_Switcher) 112 * Added early URL matching for categories, tags, custom taxonomies, post types, and CPT items 113 * Added is_query_ready() guard to prevent rule matching before WP_Query is available 114 * Added theme existence validation before switching template/stylesheet 115 * Added get_rule_type_display() shared static method for consistent rule type labels 116 * Added new AJAX handlers: omts_get_rule_objects, omts_get_rule_items 107 117 108 118 = 1.0.3 =
Note: See TracChangeset
for help on using the changeset viewer.