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 / Controllers / ModuleController.php

ModuleController.php in Yatra – Travel Booking & Tour Operator Software trunk, at app/Controllers/ModuleController.php

275 lines 9.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\Controllers;
6
7 use WP_REST_Request;
8 use WP_REST_Response;
9 use WP_Error;
10 use Yatra\Core\Modules\ModuleManager;
11
12 class ModuleController extends BaseController
13 {
14 public function register_routes(): void
15 {
16 $namespace = 'yatra/v1';
17
18 register_rest_route($namespace, '/modules', [
19 [
20 'methods' => \WP_REST_Server::READABLE,
21 'callback' => [$this, 'get_modules'],
22 'permission_callback' => [$this, 'check_permission'],
23 ],
24 ]);
25
26 register_rest_route($namespace, '/modules/(?P<slug>[a-z0-9_\-]+)/toggle', [
27 [
28 'methods' => \WP_REST_Server::CREATABLE,
29 'callback' => [$this, 'toggle_module'],
30 'permission_callback' => [$this, 'check_permission'],
31 'args' => [
32 'slug' => [
33 'required' => true,
34 'sanitize_callback' => 'sanitize_key',
35 ],
36 'enabled' => [
37 'required' => true,
38 'validate_callback' => function ($value) {
39 return is_bool($value) || $value === 'true' || $value === 'false' || $value === 1 || $value === 0;
40 },
41 ],
42 ],
43 ],
44 ]);
45
46 register_rest_route($namespace, '/modules/bulk-toggle', [
47 [
48 'methods' => \WP_REST_Server::CREATABLE,
49 'callback' => [$this, 'bulk_toggle'],
50 'permission_callback' => [$this, 'check_permission'],
51 ],
52 ]);
53 }
54
55 /**
56 * Module toggle — critical-sensitivity cap. Enabling or disabling
57 * a Pro module flips significant feature surfaces (Webhooks,
58 * Channel Manager, Team & Access, etc.) on or off, so only the
59 * Owner role holds `yatra_manage_modules` by default. The
60 * previous implementation gated this on `yatra_edit_trips` which
61 * meant any trip editor could toggle modules — that was the wrong
62 * cap entirely. WP admins pass via the Team module's admin-
63 * fallback filter.
64 */
65 public function check_permission(?WP_REST_Request $request = null): bool
66 {
67 if (!is_user_logged_in()) {
68 return false;
69 }
70 return current_user_can('yatra_manage_modules');
71 }
72
73 public function get_modules(): WP_REST_Response
74 {
75 return $this->success_response([
76 'data' => ModuleManager::getModules(),
77 ]);
78 }
79
80 public function toggle_module(WP_REST_Request $request)
81 {
82 $slug = sanitize_key($request->get_param('slug'));
83 $enabled = filter_var($request->get_param('enabled'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
84
85 if ($enabled === null) {
86 return $this->error_response(__('Enabled flag is required.', 'yatra'), 400);
87 }
88
89 $modules = ModuleManager::getModules();
90 $module_exists = false;
91 $target_module = null;
92
93 foreach ($modules as $module) {
94 if ($module['slug'] === $slug) {
95 $module_exists = true;
96 $target_module = $module;
97 break;
98 }
99 }
100
101 if (!$module_exists || !$target_module) {
102 return $this->error_response(__('Module not found.', 'yatra'), 404);
103 }
104
105 // Check if trying to enable a premium module without Pro
106 if ($enabled && !empty($target_module['is_premium']) && !$target_module['is_available']) {
107 return $this->error_response(
108 sprintf(
109 /* translators: %s: module name. */
110 __('%s is a premium module. Yatra Pro is required to enable this module.', 'yatra'),
111 $target_module['name']
112 ),
113 403
114 );
115 }
116
117 // Check if trying to enable a module that requires Pro but Pro is not active
118 if ($enabled && !empty($target_module['requires_pro'])) {
119 $pro_active = apply_filters('yatra_is_pro_active', false);
120 if (!$pro_active) {
121 return $this->error_response(
122 sprintf(
123 /* translators: %s: module name. */
124 __('%s requires Yatra Pro. Please install and activate Yatra Pro to enable this module.', 'yatra'),
125 $target_module['name']
126 ),
127 403
128 );
129 }
130 }
131
132 // Agency-tier gate: even with Pro active, the white-label/agency-only
133 // modules need an Agency Yearly or Lifetime license.
134 if ($enabled && !empty($target_module['requires_agency'])) {
135 if (!apply_filters('yatra_is_agency_active', false)) {
136 return $this->error_response(
137 sprintf(
138 /* translators: %s: module name. */
139 __('%s is available on the Yatra Pro Scale plan only. Upgrade your license to enable it.', 'yatra'),
140 $target_module['name']
141 ),
142 403
143 );
144 }
145 }
146
147 // Growth-or-Agency gate (e.g. AI Assistant).
148 if ($enabled && !empty($target_module['requires_growth_or_agency'])) {
149 if (!apply_filters('yatra_is_ai_eligible', false)) {
150 return $this->error_response(
151 sprintf(
152 /* translators: %s: module name. */
153 __('%s requires a Growth or Scale license. Upgrade your plan to enable it.', 'yatra'),
154 $target_module['name']
155 ),
156 403
157 );
158 }
159 }
160
161 $updated = ModuleManager::setModuleStatus($slug, (bool) $enabled);
162
163 return $this->success_response([
164 'data' => $updated,
165 ]);
166 }
167
168 public function bulk_toggle(WP_REST_Request $request)
169 {
170 $items = $request->get_param('items');
171 if (!is_array($items) || empty($items)) {
172 return $this->error_response(__('No module changes supplied.', 'yatra'), 400);
173 }
174
175 $modules = ModuleManager::getModules();
176 $module_map = [];
177 foreach ($modules as $module) {
178 $module_map[$module['slug']] = $module;
179 }
180
181 $sanitized = [];
182 $blocked_modules = [];
183
184 foreach ($items as $item) {
185 if (empty($item['slug'])) {
186 continue;
187 }
188
189 $slug = sanitize_key($item['slug']);
190 $enabled = filter_var($item['enabled'] ?? false, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
191 if ($enabled === null) {
192 continue;
193 }
194
195 // Check if module exists
196 if (!isset($module_map[$slug])) {
197 continue;
198 }
199
200 $target_module = $module_map[$slug];
201
202 // Check if trying to enable a premium module without Pro
203 if ($enabled && !empty($target_module['is_premium']) && !$target_module['is_available']) {
204 $blocked_modules[] = $target_module['name'];
205 continue;
206 }
207
208 // Check if trying to enable a module that requires Pro but Pro is not active
209 if ($enabled && !empty($target_module['requires_pro'])) {
210 $pro_active = apply_filters('yatra_is_pro_active', false);
211 if (!$pro_active) {
212 $blocked_modules[] = $target_module['name'];
213 continue;
214 }
215 }
216
217 if ($enabled && !empty($target_module['requires_agency'])) {
218 if (!apply_filters('yatra_is_agency_active', false)) {
219 $blocked_modules[] = $target_module['name'];
220 continue;
221 }
222 }
223
224 if ($enabled && !empty($target_module['requires_growth_or_agency'])) {
225 if (!apply_filters('yatra_is_ai_eligible', false)) {
226 $blocked_modules[] = $target_module['name'];
227 continue;
228 }
229 }
230
231 $sanitized[] = [
232 'slug' => $slug,
233 'enabled' => (bool) $enabled,
234 ];
235 }
236
237 if (empty($sanitized) && !empty($blocked_modules)) {
238 return $this->error_response(
239 sprintf(
240 /* translators: %s: comma-separated list of module names. */
241 __('The following modules require Yatra Pro: %s', 'yatra'),
242 implode(', ', $blocked_modules)
243 ),
244 403
245 );
246 }
247
248 if (!empty($blocked_modules)) {
249 // Partial success - some modules processed, some blocked
250 $updated = ModuleManager::setMultipleStatuses($sanitized);
251
252 return $this->success_response([
253 'data' => $updated,
254 'message' => sprintf(
255 /* translators: %s: comma-separated list of module names. */
256 __('Some modules were skipped because they require Yatra Pro: %s', 'yatra'),
257 implode(', ', $blocked_modules)
258 ),
259 ]);
260 }
261
262 if (empty($sanitized)) {
263 return $this->error_response(__('No valid module changes supplied.', 'yatra'), 400);
264 }
265
266 $updated = ModuleManager::setMultipleStatuses($sanitized);
267
268 return $this->success_response([
269 'data' => $updated,
270 ]);
271 }
272 }
273
274
275