PluginProbe
Yatra – Travel Booking & Tour Operator Software / trunk
Yatra – Travel Booking & Tour Operator Software vtrunk
3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 2.0.11 All 82 releases
yatra / app / Core / Handlers / BasePageHandler.php

BasePageHandler.php in Yatra – Travel Booking & Tour Operator Software trunk, at app/Core/Handlers/BasePageHandler.php

284 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Core\Handlers;
6
7 use Yatra\Core\Assets\BaseAssetManager;
8 use Yatra\Core\Routing\PageContext;
9
10 /**
11 * Base Page Handler
12 *
13 * Provides common functionality for all page handlers
14 */
15 abstract class BasePageHandler
16 {
17 /**
18 * Handle the page request
19 *
20 * @param array $route_data Route data from RouteMatcher
21 * @return bool True if handled successfully
22 */
23 abstract public function handle(array $route_data): bool;
24
25 /**
26 * Set query vars for backward compatibility
27 *
28 * @param array $vars Query vars to set
29 */
30 protected function setQueryVars(array $vars): void
31 {
32 global $wp_query;
33
34 foreach ($vars as $key => $value) {
35 $wp_query->set($key, $value);
36 }
37 }
38
39 /**
40 * Set 404 status
41 */
42 protected function set404(): void
43 {
44 global $wp_query;
45
46 $wp_query->set_404();
47 status_header(404);
48 }
49
50 /**
51 * Prevent 404 handling and set 200 status
52 */
53 protected function prevent404(): void
54 {
55 global $wp_query;
56
57 $wp_query->is_404 = false;
58 status_header(200);
59 }
60
61 /**
62 * Configure $wp_query and globals so the current request looks like a real WP page to
63 * the theme — required for FSE/block themes to resolve the correct block template
64 * (singular/archive/index) instead of falling back to 404.html.
65 *
66 * @param string $context 'singular' for single-trip / booking-confirmation / account, 'archive' for listings.
67 * @param array $args {
68 * @type string $title Page title used in <title> and queried-object stub.
69 * @type int $object_id Optional ID for the virtual queried object (defaults to 0).
70 * @type string $post_type Post type for the faux WP_Post (default 'page').
71 * @type string $post_name Slug for the faux WP_Post.
72 * @type string|null $content Optional post content placeholder.
73 * }
74 */
75 protected function setupPageEnvironment(string $context = 'singular', array $args = []): void
76 {
77 global $wp_query, $post;
78
79 $title = (string) ($args['title'] ?? '');
80 $object_id = (int) ($args['object_id'] ?? 0);
81 $post_type = (string) ($args['post_type'] ?? 'page');
82 $post_name = (string) ($args['post_name'] ?? '');
83 $content = (string) ($args['content'] ?? '');
84
85 // Clear the conditional-tag state — WP_Query keeps stale flags from the main query.
86 $wp_query->init_query_flags();
87 $wp_query->is_404 = false;
88 $wp_query->is_home = false;
89 $wp_query->is_front_page = false;
90
91 if ($context === 'archive') {
92 $wp_query->is_archive = true;
93 // Do NOT set is_post_type_archive — we are a virtual page, not a
94 // real WP post-type archive. Setting it triggers WP code paths
95 // (feed_links_extra, post_type_archive_title, …) that call
96 // get_post_type_object() and crash on null when no CPT is set.
97 } else {
98 $wp_query->is_singular = true;
99 $wp_query->is_page = ($post_type === 'page');
100 $wp_query->is_single = ($post_type !== 'page');
101 }
102
103 // Build a virtual WP_Post so the_post(), get_the_ID(), body_class(), and FSE
104 // block-template selection have something coherent to read.
105 $virtual = new \WP_Post((object) [
106 'ID' => $object_id,
107 'post_author' => 0,
108 'post_date' => current_time('mysql'),
109 'post_date_gmt' => current_time('mysql', true),
110 'post_content' => $content,
111 'post_title' => $title,
112 'post_excerpt' => '',
113 'post_status' => 'publish',
114 'comment_status' => 'closed',
115 'ping_status' => 'closed',
116 'post_password' => '',
117 'post_name' => $post_name,
118 'to_ping' => '',
119 'pinged' => '',
120 'post_modified' => current_time('mysql'),
121 'post_modified_gmt' => current_time('mysql', true),
122 'post_content_filtered' => '',
123 'post_parent' => 0,
124 'guid' => home_url($post_name ? '/' . $post_name : '/'),
125 'menu_order' => 0,
126 'post_type' => $post_type,
127 'post_mime_type' => '',
128 'comment_count' => 0,
129 'filter' => 'raw',
130 ]);
131
132 $wp_query->queried_object = $virtual;
133 $wp_query->queried_object_id = $object_id;
134 $wp_query->post = $virtual;
135 $wp_query->posts = [$virtual];
136 $wp_query->post_count = 1;
137 $wp_query->found_posts = 1;
138 $wp_query->max_num_pages = 1;
139 $wp_query->current_post = -1;
140
141 $post = $virtual;
142
143 // ── Strip WP core hooks that don't make sense for virtual pages ─────────────
144 //
145 // Our queried-object ID is a Yatra trips-table row id, NOT a wp_posts row id.
146 // Several WP core hooks call `get_post( get_queried_object_id() )` expecting a
147 // real WP_Post — they receive null and crash with
148 // "Attempt to read property 'post_type' on null".
149 //
150 // wp_shortlink_wp_head → calls wp_get_shortlink('query') → get_post(id) → null
151 // wp_shortlink_header → same code path, called on template_redirect
152 // adjacent_posts_rel_link_wp_head → walks $post->post_type for prev/next links
153 //
154 // None of these emit anything meaningful for a virtual Yatra page, so we strip
155 // them at the start of the render. Re-checking they exist (rather than calling
156 // remove_action blindly) keeps us forward-compatible if WP core retires a hook.
157 if (function_exists('wp_shortlink_wp_head')) {
158 remove_action('wp_head', 'wp_shortlink_wp_head', 10);
159 }
160 if (function_exists('wp_shortlink_header')) {
161 remove_action('template_redirect', 'wp_shortlink_header', 11);
162 }
163 if (function_exists('adjacent_posts_rel_link_wp_head')) {
164 remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10);
165 }
166
167 // WP admin-bar "Edit Page" link — wp_admin_bar_edit_menu reads our virtual
168 // $post->post_type ('page') and adds an Edit link pointing at
169 // wp-admin/post.php?post=<our_virtual_id>&action=edit. That ID is a Yatra trip
170 // row id, not a wp_posts row, so the link 404s (or worse, edits an unrelated
171 // post that happens to share the ID). Yatra registers its own "Edit Trip"
172 // admin-bar item via AdminBarProvider, which is the correct link for trips.
173 if (function_exists('wp_admin_bar_edit_menu')) {
174 remove_action('admin_bar_menu', 'wp_admin_bar_edit_menu', 80);
175 }
176
177 // Belt-and-braces: short-circuit `pre_get_shortlink` for THIS request so any
178 // other plugin/theme code that calls `wp_get_shortlink( 0, 'query' )` directly
179 // also bails out cleanly instead of feeding our virtual ID to `get_post()`.
180 if (!has_filter('pre_get_shortlink', [self::class, 'suppressVirtualShortlink'])) {
181 add_filter('pre_get_shortlink', [self::class, 'suppressVirtualShortlink'], 1);
182 }
183
184 status_header(200);
185 nocache_headers();
186 }
187
188 /**
189 * Returning an empty string from `pre_get_shortlink` short-circuits
190 * {@see wp_get_shortlink()} BEFORE it calls `get_post()` on our virtual ID. Used
191 * to silence "Attempt to read property 'post_type' on null" warnings on Yatra
192 * trip / listing / account pages where the queried-object ID is a Yatra row id,
193 * not a wp_posts row id.
194 */
195 public static function suppressVirtualShortlink($shortlink): string
196 {
197 return '';
198 }
199
200 /**
201 * Set global variable
202 *
203 * @param string $name Variable name
204 * @param mixed $value Variable value
205 */
206 protected function setGlobal(string $name, $value): void
207 {
208 $GLOBALS[$name] = $value;
209 }
210
211 /**
212 * Log error message
213 *
214 * @param string $message Error message
215 */
216 protected function logError(string $message): void
217 {
218 if (defined('WP_DEBUG') && WP_DEBUG) {
219 }
220 }
221
222 /**
223 * Hand off a Yatra template to the `template_include` filter and return
224 * a routing result. Replaces the legacy `include + exit` pattern.
225 *
226 * The handler should call this as the last step in handle(). It does NOT
227 * render the template — that happens later via WordPress's normal
228 * template-loader pipeline, so wp_head/wp_footer fire, other plugins can
229 * hook in, and child themes can override.
230 *
231 * @param string $templateName Template filename relative to /templates/, with or without .php.
232 * @param BaseAssetManager|null $assetManager Optional asset manager whose assets to enqueue.
233 * @param string $pageType Logical page type for body_class hints + FSE template slug.
234 * @return bool true if the template was queued, false if missing.
235 */
236 protected function selectTemplate(string $templateName, ?BaseAssetManager $assetManager = null, string $pageType = ''): bool
237 {
238 $name = ltrim($templateName, '/');
239 if (substr($name, -4) !== '.php') {
240 $name .= '.php';
241 }
242
243 $absolute = YATRA_PLUGIN_PATH . 'templates/' . $name;
244
245 // Allow themes/child-themes to override via templates/yatra/<name>.
246 $located = locate_template(['yatra/' . $name], false, false);
247 if (is_string($located) && $located !== '') {
248 $absolute = $located;
249 }
250
251 $absolute = (string) apply_filters('yatra_template_path', $absolute, $name, $pageType);
252
253 if (!is_readable($absolute)) {
254 $this->logError("Template not readable: {$absolute}");
255 return false;
256 }
257
258 if ($assetManager !== null) {
259 $assetManager->enqueueAssets();
260 }
261
262 $ctx = PageContext::instance();
263 $ok = $ctx->select($absolute, $pageType);
264 if ($ok && $pageType !== '') {
265 $ctx->addBodyClass('yatra-page-' . sanitize_html_class($pageType));
266 }
267 return $ok;
268 }
269
270 /**
271 * @deprecated Kept only for backwards-compat with handlers not yet migrated.
272 * New handlers should call selectTemplate() and return.
273 *
274 * @param string|null $message Optional exit message
275 */
276 protected function exit(?string $message = null): void
277 {
278 if ($message && defined('WP_DEBUG') && WP_DEBUG) {
279 echo '<!-- ' . esc_html($message) . ' -->';
280 }
281 exit;
282 }
283 }
284