| @@ -602,4 +602,104 @@ | ||
| 602 | 602 | 'editUrl' => (string) get_edit_post_link( $new_post_id, 'raw' ), |
| 603 | 603 | ) |
| 604 | 604 | ); |
| 605 | 605 | } |
| 606 | + | |
| 607 | +/** | |
| 608 | + * Whether the current user's desktop would show any pinned notes. | |
| 609 | + * | |
| 610 | + * The presence hint the boot config ships as `hasNotes`: the notes | |
| 611 | + * bundle is presence-gated client-side (`src/notes/sentinel.ts`), so | |
| 612 | + * a user with no notes never downloads it — and never fires the | |
| 613 | + * boot-time list request the layer used to make unconditionally. | |
| 614 | + * | |
| 615 | + * **Steady-state cost: zero queries.** The computed answer is cached | |
| 616 | + * in user meta, stamped with a revision the site bumps whenever any | |
| 617 | + * note changes (`openstation_notes_bump_rev()` below). The revision | |
| 618 | + * lives in an autoloaded option and the user's meta cache is already | |
| 619 | + * primed on every admin request, so a boot between note changes | |
| 620 | + * reads two warm caches and touches the database not at all. Only | |
| 621 | + * the first boot after a note is created / deleted / re-scoped runs | |
| 622 | + * the probes again — two `fields => ids`, one-row queries at most: | |
| 623 | + * anyone's public note first, the user's own private ones second, | |
| 624 | + * mirroring the visibility rule the list route enforces. | |
| 625 | + * | |
| 626 | + * @return bool | |
| 627 | + */ | |
| 628 | +function openstation_notes_user_has_any() { | |
| 629 | + $rev = (string) get_option( 'desktop_mode_notes_rev', '0' ); | |
| 630 | + $user_id = get_current_user_id(); | |
| 631 | + $cached = (string) get_user_meta( $user_id, '_desktop_mode_has_notes', true ); | |
| 632 | + if ( '' !== $cached ) { | |
| 633 | + list( $cached_rev, $cached_value ) = array_pad( explode( ':', $cached, 2 ), 2, '' ); | |
| 634 | + if ( $cached_rev === $rev ) { | |
| 635 | + return '1' === $cached_value; | |
| 636 | + } | |
| 637 | + } | |
| 638 | + | |
| 639 | + $public = new WP_Query( | |
| 640 | + array( | |
| 641 | + 'post_type' => OPENSTATION_NOTES_POST_TYPE, | |
| 642 | + 'post_status' => 'publish', | |
| 643 | + 'posts_per_page' => 1, | |
| 644 | + 'fields' => 'ids', | |
| 645 | + 'no_found_rows' => true, | |
| 646 | + ) | |
| 647 | + ); | |
| 648 | + $has = (bool) $public->posts; | |
| 649 | + if ( ! $has ) { | |
| 650 | + $own = new WP_Query( | |
| 651 | + array( | |
| 652 | + 'post_type' => OPENSTATION_NOTES_POST_TYPE, | |
| 653 | + 'post_status' => 'private', | |
| 654 | + 'author' => $user_id, | |
| 655 | + 'posts_per_page' => 1, | |
| 656 | + 'fields' => 'ids', | |
| 657 | + 'no_found_rows' => true, | |
| 658 | + ) | |
| 659 | + ); | |
| 660 | + $has = (bool) $own->posts; | |
| 661 | + } | |
| 662 | + | |
| 663 | + update_user_meta( $user_id, '_desktop_mode_has_notes', $rev . ':' . ( $has ? '1' : '0' ) ); | |
| 664 | + return $has; | |
| 665 | +} | |
| 666 | + | |
| 667 | +/** | |
| 668 | + * Invalidate every user's cached `hasNotes` answer. | |
| 669 | + * | |
| 670 | + * One autoloaded revision counter instead of per-user cache deletes: | |
| 671 | + * a public note's existence changes the answer for EVERY user, and | |
| 672 | + * enumerating users to clear meta would be the expensive thing this | |
| 673 | + * cache exists to avoid. Bumping the rev makes all stamped answers | |
| 674 | + * stale at the cost of one option write per note change — and note | |
| 675 | + * changes are rare next to boots. | |
| 676 | + * | |
| 677 | + * @param int|WP_Post $post Post id or object being changed. | |
| 678 | + * @return void | |
| 679 | + */ | |
| 680 | +function openstation_notes_bump_rev( $post ) { | |
| 681 | + $post = get_post( $post ); | |
| 682 | + if ( ! $post instanceof WP_Post || OPENSTATION_NOTES_POST_TYPE !== $post->post_type ) { | |
| 683 | + return; | |
| 684 | + } | |
| 685 | + update_option( 'desktop_mode_notes_rev', (string) time() . '.' . wp_rand( 0, 999 ), true ); | |
| 686 | +} | |
| 687 | + | |
| 688 | +/** | |
| 689 | + * Every path a note can change through: status moves (create, | |
| 690 | + * publish/private flips, trash, restore) fire | |
| 691 | + * `transition_post_status`; hard deletes fire `deleted_post`. | |
| 692 | + * | |
| 693 | + * @param string $new_status New status. | |
| 694 | + * @param string $old_status Old status. | |
| 695 | + * @param WP_Post $post The post. | |
| 696 | + * @return void | |
| 697 | + */ | |
| 698 | +function openstation_notes_bump_rev_on_transition( $new_status, $old_status, $post ) { | |
| 699 | + if ( $new_status === $old_status ) { | |
| 700 | + return; | |
| 701 | + } | |
| 702 | + openstation_notes_bump_rev( $post ); | |
| 703 | +} | |
| 704 | +add_action( 'transition_post_status', 'openstation_notes_bump_rev_on_transition', 10, 3 ); | |
| 705 | +add_action( 'deleted_post', 'openstation_notes_bump_rev' ); | |