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
← All changes | app/Controllers/ModuleController.php +149 -9 3.0.2.7trunk View file →
@@ -51,19 +51,24 @@
51 51 ],
52 52 ]);
53 53 }
54 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 + */
55 65 public function check_permission(?WP_REST_Request $request = null): bool
56 66 {
57 67 if (!is_user_logged_in()) {
58 68 return false;
59 69 }
60 -
61 - if (current_user_can('manage_options')) {
62 - return true;
63 - }
64 -
65 - return current_user_can('yatra_edit_trips');
70 + return current_user_can('yatra_manage_modules');
66 71 }
67 72
68 73 public function get_modules(): WP_REST_Response
69 74 {
@@ -81,14 +86,79 @@
81 86 return $this->error_response(__('Enabled flag is required.', 'yatra'), 400);
82 87 }
83 88
84 89 $modules = ModuleManager::getModules();
85 - $exists = array_filter($modules, static fn ($module) => $module['slug'] === $slug);
90 + $module_exists = false;
91 + $target_module = null;
86 92
87 - if (empty($exists)) {
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) {
88 102 return $this->error_response(__('Module not found.', 'yatra'), 404);
89 103 }
90 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 +
91 161 $updated = ModuleManager::setModuleStatus($slug, (bool) $enabled);
92 162
93 163 return $this->success_response([
94 164 'data' => $updated,
@@ -101,23 +171,93 @@
101 171 if (!is_array($items) || empty($items)) {
102 172 return $this->error_response(__('No module changes supplied.', 'yatra'), 400);
103 173 }
104 174
175 + $modules = ModuleManager::getModules();
176 + $module_map = [];
177 + foreach ($modules as $module) {
178 + $module_map[$module['slug']] = $module;
179 + }
180 +
105 181 $sanitized = [];
182 + $blocked_modules = [];
183 +
106 184 foreach ($items as $item) {
107 185 if (empty($item['slug'])) {
108 186 continue;
109 187 }
110 188
189 + $slug = sanitize_key($item['slug']);
111 190 $enabled = filter_var($item['enabled'] ?? false, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
112 191 if ($enabled === null) {
113 192 continue;
114 193 }
115 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 +
116 231 $sanitized[] = [
117 - 'slug' => sanitize_key($item['slug']),
232 + 'slug' => $slug,
118 233 'enabled' => (bool) $enabled,
119 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 + ]);
120 260 }
121 261
122 262 if (empty($sanitized)) {
123 263 return $this->error_response(__('No valid module changes supplied.', 'yatra'), 400);