PluginProbe
Booking Calendar / 11.8
Booking Calendar v11.8
11.9 11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 All 205 releases
booking / includes / booking_modes_v3 / class-wpbc-booking-modes-v3-source-index.php

class-wpbc-booking-modes-v3-source-index.php in Booking Calendar 11.8, at includes/booking_modes_v3/class-wpbc-booking-modes-v3-source-index.php

198 lines 5.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Lazy source metadata index for Booking Modes V3.
4 *
5 * @package Booking Calendar
6 * @since 11.8.0
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 /**
14 * Capture the fully contributed page tree once and index routes without HTML.
15 */
16 final class WPBC_Booking_Modes_V3_Source_Index {
17
18 /** @var int Maximum accepted contributor nodes per request. */
19 const MAX_SOURCE_NODES = 2048;
20
21 /** @var int Maximum accepted contributor navigation depth. */
22 const MAX_SOURCE_DEPTH = 8;
23
24 /** @var WPBC_Booking_Modes_V3_Source_Index|null */
25 private static $instance = null;
26
27 /** @var array<string,array<string,mixed>> */
28 private $navigation = array();
29
30 /** @var array<string,array<string,mixed>> */
31 private $routes = array();
32
33 /** @var int */
34 private $capture_count = 0;
35
36 /**
37 * Prevent direct construction.
38 */
39 private function __construct() {}
40
41 /**
42 * Return the shared request-local source index.
43 *
44 * @return WPBC_Booking_Modes_V3_Source_Index Source index.
45 */
46 public static function get_instance() {
47 if ( null === self::$instance ) {
48 self::$instance = new self();
49 }
50
51 return self::$instance;
52 }
53
54 /**
55 * Capture the first complete non-empty contributed tree.
56 *
57 * The shared page structure remains authoritative. This method never calls a
58 * page controller, content callback, save handler, or domain constructor.
59 *
60 * @param array $navigation Registered page, tab, and subtab metadata.
61 *
62 * @return void
63 */
64 public function capture( $navigation ) {
65 if ( ! empty( $this->navigation ) || ! is_array( $navigation ) || empty( $navigation ) ) {
66 return;
67 }
68
69 ++$this->capture_count;
70 $remaining_nodes = self::MAX_SOURCE_NODES;
71 $this->navigation = $this->bound_navigation( $navigation, 1, $remaining_nodes );
72
73 foreach ( $this->navigation as $page_slug => $tabs ) {
74 if ( ! is_array( $tabs ) ) {
75 continue;
76 }
77 foreach ( $tabs as $tab_slug => $tab ) {
78 if ( ! is_array( $tab ) ) {
79 continue;
80 }
81 $route = array( 'page' => $page_slug, 'tab' => $tab_slug, 'subtab' => '' );
82 $this->routes[ self::get_route_key( $route ) ] = array(
83 'page' => $page_slug,
84 'tab' => $tab_slug,
85 'subtab' => '',
86 'node' => $tab,
87 );
88
89 $subtabs = isset( $tab['subtabs'] ) && is_array( $tab['subtabs'] ) ? $tab['subtabs'] : array();
90 foreach ( $subtabs as $subtab_slug => $subtab ) {
91 if ( ! is_array( $subtab ) ) {
92 continue;
93 }
94 $route = array( 'page' => $page_slug, 'tab' => $tab_slug, 'subtab' => $subtab_slug );
95 $this->routes[ self::get_route_key( $route ) ] = array(
96 'page' => $page_slug,
97 'tab' => $tab_slug,
98 'subtab' => $subtab_slug,
99 'node' => $subtab,
100 'parent_node' => $tab,
101 );
102 }
103 }
104 }
105 }
106
107 /**
108 * Bound contributor navigation while preserving each accepted source node.
109 *
110 * Only recursive `subtabs` collections are traversed. Other source metadata,
111 * including callbacks, URLs, capabilities, and domain presentation fields,
112 * remains byte-for-byte in the accepted node.
113 *
114 * @param array $nodes Contributor navigation at the current depth.
115 * @param int $depth Current tree depth.
116 * @param int $remaining Remaining node budget passed by reference.
117 *
118 * @return array Bounded navigation in original contributor order.
119 */
120 private function bound_navigation( $nodes, $depth, &$remaining ) {
121 $bounded = array();
122 if ( ! is_array( $nodes ) || $depth > self::MAX_SOURCE_DEPTH || $remaining <= 0 ) {
123 return $bounded;
124 }
125
126 foreach ( array_slice( $nodes, 0, WPBC_Booking_Modes_V3_Definition_Validator::MAX_COLLECTION_ENTRIES, true ) as $node_key => $node ) {
127 if ( $remaining <= 0 ) {
128 break;
129 }
130 if ( 1 === $depth ) {
131 if ( ! is_array( $node ) ) {
132 continue;
133 }
134 $bounded[ $node_key ] = $this->bound_navigation( $node, $depth + 1, $remaining );
135 continue;
136 }
137 if ( ! is_array( $node ) ) {
138 continue;
139 }
140
141 --$remaining;
142 $bounded_node = $node;
143 if ( isset( $node['subtabs'] ) ) {
144 $bounded_node['subtabs'] = $this->bound_navigation( $node['subtabs'], $depth + 1, $remaining );
145 }
146 $bounded[ $node_key ] = $bounded_node;
147 }
148
149 return $bounded;
150 }
151
152 /**
153 * Return one indexed route record.
154 *
155 * @param array $route Normalized route with page, tab, and subtab keys.
156 *
157 * @return array<string,mixed>|null Source record or null when unavailable.
158 */
159 public function get_record( $route ) {
160 $route_key = self::get_route_key( $route );
161
162 return isset( $this->routes[ $route_key ] ) ? $this->routes[ $route_key ] : null;
163 }
164
165 /**
166 * Return the immutable source navigation snapshot.
167 *
168 * @return array<string,array<string,mixed>> Source navigation.
169 */
170 public function get_navigation() {
171 return $this->navigation;
172 }
173
174 /**
175 * Return the number of source captures in this request.
176 *
177 * @return int Capture count.
178 */
179 public function get_capture_count() {
180 return $this->capture_count;
181 }
182
183 /**
184 * Build a collision-safe key for one normalized route.
185 *
186 * @param array $route Route fields.
187 *
188 * @return string Route index key.
189 */
190 public static function get_route_key( $route ) {
191 $page = isset( $route['page'] ) && is_scalar( $route['page'] ) ? (string) $route['page'] : '';
192 $tab = isset( $route['tab'] ) && is_scalar( $route['tab'] ) ? (string) $route['tab'] : '';
193 $subtab = isset( $route['subtab'] ) && is_scalar( $route['subtab'] ) ? (string) $route['subtab'] : '';
194
195 return strlen( $page ) . ':' . $page . '|' . strlen( $tab ) . ':' . $tab . '|' . strlen( $subtab ) . ':' . $subtab;
196 }
197 }
198