| @@ -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); |