Lite
3 months ago
ApplicationService.php
1 month ago
DataModifier.php
1 month ago
DomainService.php
1 month ago
EventListener.php
1 month ago
InfrastructureService.php
1 month ago
Licence.php
2 weeks ago
LicenceConstants.php
6 months ago
Licence.php
414 lines
| 1 | <?php |
| 2 | |
| 3 | namespace AmeliaBooking\Infrastructure\Licence; |
| 4 | |
| 5 | use AmeliaBooking\Domain\Services\Settings\SettingsService; |
| 6 | use AmeliaBooking\Infrastructure\WP\SettingsService\SettingsStorage; |
| 7 | |
| 8 | /** |
| 9 | * Class Licence |
| 10 | * |
| 11 | * @package AmeliaBooking\Infrastructure\Licence |
| 12 | */ |
| 13 | class Licence extends Lite\Licence |
| 14 | { |
| 15 | /** |
| 16 | * License hierarchy mapping - higher numbers mean higher access levels |
| 17 | */ |
| 18 | private static $licenseHierarchy = [ |
| 19 | LicenceConstants::LITE => 1, |
| 20 | LicenceConstants::STARTER => 2, |
| 21 | LicenceConstants::BASIC => 3, |
| 22 | LicenceConstants::PRO => 4, |
| 23 | LicenceConstants::DEVELOPER => 5 |
| 24 | ]; |
| 25 | |
| 26 | /** |
| 27 | * Feature to minimum required license mapping |
| 28 | * Maps feature codes to the minimum license level required to access them |
| 29 | */ |
| 30 | private static $featureLicenseRequirements = [ |
| 31 | 'apis' => LicenceConstants::DEVELOPER, |
| 32 | 'appleCalendar' => LicenceConstants::BASIC, |
| 33 | 'barion' => LicenceConstants::BASIC, |
| 34 | 'buddyboss' => LicenceConstants::BASIC, |
| 35 | 'cart' => LicenceConstants::PRO, |
| 36 | 'coupons' => LicenceConstants::STARTER, |
| 37 | 'customFields' => LicenceConstants::BASIC, |
| 38 | 'customNotifications' => LicenceConstants::BASIC, |
| 39 | 'customPricing' => LicenceConstants::BASIC, |
| 40 | 'depositPayment' => LicenceConstants::BASIC, |
| 41 | 'employeeBadge' => LicenceConstants::BASIC, |
| 42 | 'eTickets' => LicenceConstants::PRO, |
| 43 | 'extras' => LicenceConstants::STARTER, |
| 44 | 'facebookPixel' => LicenceConstants::STARTER, |
| 45 | 'facebookSocialLogin' => LicenceConstants::BASIC, |
| 46 | 'googleAnalytics' => LicenceConstants::STARTER, |
| 47 | 'googleCalendar' => LicenceConstants::BASIC, |
| 48 | 'googleSocialLogin' => LicenceConstants::BASIC, |
| 49 | 'invoices' => LicenceConstants::BASIC, |
| 50 | 'lessonSpace' => LicenceConstants::STARTER, |
| 51 | 'locations' => LicenceConstants::BASIC, |
| 52 | 'mailchimp' => LicenceConstants::BASIC, |
| 53 | 'mobileApp' => LicenceConstants::PRO, |
| 54 | 'mollie' => LicenceConstants::BASIC, |
| 55 | 'mycred' => LicenceConstants::BASIC, |
| 56 | 'noShowTag' => LicenceConstants::BASIC, |
| 57 | 'outlookCalendar' => LicenceConstants::BASIC, |
| 58 | 'packages' => LicenceConstants::PRO, |
| 59 | 'payPal' => LicenceConstants::BASIC, |
| 60 | 'razorpay' => LicenceConstants::BASIC, |
| 61 | 'recaptcha' => LicenceConstants::STARTER, |
| 62 | 'recurringAppointments' => LicenceConstants::BASIC, |
| 63 | 'recurringEvents' => LicenceConstants::BASIC, |
| 64 | 'resources' => LicenceConstants::PRO, |
| 65 | 'square' => LicenceConstants::LITE, |
| 66 | 'stripe' => LicenceConstants::BASIC, |
| 67 | 'tax' => LicenceConstants::BASIC, |
| 68 | 'tickets' => LicenceConstants::BASIC, |
| 69 | 'timezones' => LicenceConstants::BASIC, |
| 70 | 'waitingList' => LicenceConstants::PRO, |
| 71 | 'waitingListAppointments' => LicenceConstants::PRO, |
| 72 | 'wc' => LicenceConstants::BASIC, |
| 73 | 'webhooks' => LicenceConstants::BASIC, |
| 74 | 'whatsapp' => LicenceConstants::PRO, |
| 75 | 'zoom' => LicenceConstants::BASIC, |
| 76 | ]; |
| 77 | |
| 78 | /** |
| 79 | * Map of license names to their corresponding class names |
| 80 | */ |
| 81 | private static $licenseClassMap = [ |
| 82 | LicenceConstants::LITE => 'AmeliaBooking\Infrastructure\Licence\Lite\Licence', |
| 83 | LicenceConstants::STARTER => 'AmeliaBooking\Infrastructure\Licence\Lite\Licence', |
| 84 | LicenceConstants::BASIC => 'AmeliaBooking\Infrastructure\Licence\Lite\Licence', |
| 85 | LicenceConstants::PRO => 'AmeliaBooking\Infrastructure\Licence\Lite\Licence', |
| 86 | LicenceConstants::DEVELOPER => 'AmeliaBooking\Infrastructure\Licence\Lite\Licence', |
| 87 | ]; |
| 88 | |
| 89 | /** |
| 90 | * Get the appropriate license class based on settings (only in development mode) |
| 91 | * |
| 92 | * @return string The fully qualified class name of the license |
| 93 | */ |
| 94 | private static function getLicenseClass() |
| 95 | { |
| 96 | // In production, always use the parent class (Lite) |
| 97 | if (!AMELIA_DEV) { |
| 98 | return 'AmeliaBooking\Infrastructure\Licence\Lite\Licence'; |
| 99 | } |
| 100 | |
| 101 | // In development, get the license from settings |
| 102 | $settingsService = new SettingsService(new SettingsStorage()); |
| 103 | $currentLicense = $settingsService->getSetting('activation', 'licence'); |
| 104 | $currentLicense = !empty($currentLicense) ? $currentLicense : LicenceConstants::DEVELOPER; |
| 105 | |
| 106 | // Return the appropriate license class |
| 107 | return self::$licenseClassMap[$currentLicense] ?? 'AmeliaBooking\Infrastructure\Licence\Lite\Licence'; |
| 108 | } |
| 109 | |
| 110 | /** |
| 111 | * Checks if the current license grants access to the specified license level. |
| 112 | * |
| 113 | * For example, if the user's license is 'Lite' and the function is called with 'Basic', it will return true. |
| 114 | * If the user's license is 'Lite' and the function is called with 'Lite', it will return false. |
| 115 | * |
| 116 | * Usage examples: |
| 117 | * - Licence::hasLicenseAccess(LicenseConstants::BASIC) |
| 118 | * - Licence::hasLicenseAccess(LicenseConstants::PRO) |
| 119 | * |
| 120 | * @param string $requiredLicense The license level required for access (use class constants) |
| 121 | * @return bool True if current license has access, false otherwise |
| 122 | */ |
| 123 | public static function hasLicenseAccess($requiredLicense) |
| 124 | { |
| 125 | $currentLicense = self::$licence; |
| 126 | |
| 127 | if (AMELIA_DEV) { |
| 128 | /** @var SettingsService $settingsService */ |
| 129 | $settingsService = new SettingsService(new SettingsStorage()); |
| 130 | |
| 131 | $currentLicense = $settingsService->getSetting('activation', 'licence'); |
| 132 | $currentLicense = !empty($currentLicense) ? $currentLicense : 'Lite'; |
| 133 | } |
| 134 | |
| 135 | // If either license is not in our hierarchy, fall back to exact match |
| 136 | if (!isset(self::$licenseHierarchy[$currentLicense]) || !isset(self::$licenseHierarchy[$requiredLicense])) { |
| 137 | return $currentLicense === $requiredLicense; |
| 138 | } |
| 139 | |
| 140 | // Check if current license level is greater than or equal to required level |
| 141 | return self::$licenseHierarchy[$currentLicense] >= self::$licenseHierarchy[$requiredLicense]; |
| 142 | } |
| 143 | |
| 144 | /** |
| 145 | * Checks if the current license has access to a specific feature. |
| 146 | * |
| 147 | * @param string $featureCode The feature code (e.g., 'googleCalendar', 'packages', etc.) |
| 148 | * @return bool True if current license has access to the feature, false otherwise |
| 149 | */ |
| 150 | public static function hasFeatureAccess($featureCode) |
| 151 | { |
| 152 | // If feature doesn't have a license requirement, assume it's available to all |
| 153 | if (!isset(self::$featureLicenseRequirements[$featureCode])) { |
| 154 | return true; |
| 155 | } |
| 156 | |
| 157 | $requiredLicense = self::$featureLicenseRequirements[$featureCode]; |
| 158 | return self::hasLicenseAccess($requiredLicense); |
| 159 | } |
| 160 | |
| 161 | /** |
| 162 | * Features that are not stored in featuresIntegrations settings |
| 163 | * These are license-gated features without an on/off toggle |
| 164 | */ |
| 165 | private static $nonToggleableFeatures = ['locations']; |
| 166 | |
| 167 | /** |
| 168 | * Checks if a feature should be shown in the UI (menu, pages, etc.) |
| 169 | * |
| 170 | * Logic for regular features: |
| 171 | * 1. If feature is enabled in settings → show it |
| 172 | * 2. If feature is disabled in settings: |
| 173 | * - If user has license access → hide it (don't show disabled features they can enable) |
| 174 | * - If user doesn't have license access → check hideUnavailableFeatures setting |
| 175 | * |
| 176 | * Logic for non-toggleable features (locations): |
| 177 | * - These don't have an on/off toggle in featuresIntegrations |
| 178 | * - If user has license access → always show |
| 179 | * - If user doesn't have license access → check hideUnavailableFeatures setting |
| 180 | * |
| 181 | * Note: For Lite license, unavailable features are always shown (hideUnavailableFeatures is ignored) |
| 182 | * |
| 183 | * @param string $featureCode The feature code (e.g., 'packages', 'customFields', 'locations') |
| 184 | * @return bool True if feature should be shown, false if it should be hidden |
| 185 | */ |
| 186 | public static function shouldShowFeature($featureCode) |
| 187 | { |
| 188 | $settingsService = new SettingsService(new SettingsStorage()); |
| 189 | |
| 190 | // Special handling for non-toggleable features (locations) |
| 191 | // These are not stored in featuresIntegrations and only have license restrictions |
| 192 | if (in_array($featureCode, self::$nonToggleableFeatures)) { |
| 193 | // If user has license access, always show it |
| 194 | if (self::hasFeatureAccess($featureCode)) { |
| 195 | return true; |
| 196 | } |
| 197 | |
| 198 | // User doesn't have license access |
| 199 | // Check hideUnavailableFeatures setting (Lite always shows unavailable features) |
| 200 | $hideUnavailableFeatures = self::getHideUnavailableFeatures($settingsService); |
| 201 | return !$hideUnavailableFeatures; |
| 202 | } |
| 203 | |
| 204 | // Standard feature handling (stored in featuresIntegrations) |
| 205 | // Check if feature is enabled in settings |
| 206 | $isFeatureEnabled = $settingsService->isFeatureEnabled($featureCode); |
| 207 | |
| 208 | // If feature is enabled in settings, always show it |
| 209 | if ($isFeatureEnabled) { |
| 210 | return true; |
| 211 | } |
| 212 | |
| 213 | // Feature is disabled in settings |
| 214 | // Check if user has license access to this feature |
| 215 | $hasLicenseAccess = self::hasFeatureAccess($featureCode); |
| 216 | |
| 217 | // If user has license access, don't show disabled features |
| 218 | if ($hasLicenseAccess) { |
| 219 | return false; |
| 220 | } |
| 221 | |
| 222 | // User doesn't have license access to this disabled feature |
| 223 | // Check hideUnavailableFeatures setting (Lite always shows unavailable features) |
| 224 | $hideUnavailableFeatures = self::getHideUnavailableFeatures($settingsService); |
| 225 | |
| 226 | // If hideUnavailableFeatures is false/null, show locked features |
| 227 | // If hideUnavailableFeatures is true, hide locked features |
| 228 | return !$hideUnavailableFeatures; |
| 229 | } |
| 230 | |
| 231 | /** |
| 232 | * Get the effective hideUnavailableFeatures value |
| 233 | * For Lite license, always return false (always show unavailable features) |
| 234 | * For other licenses, use the setting value |
| 235 | * |
| 236 | * @param SettingsService $settingsService |
| 237 | * @return bool |
| 238 | */ |
| 239 | private static function getHideUnavailableFeatures($settingsService) |
| 240 | { |
| 241 | // For Lite license, always show unavailable features |
| 242 | if (self::getLicence() === LicenceConstants::LITE) { |
| 243 | return false; |
| 244 | } |
| 245 | |
| 246 | return (bool) $settingsService->getSetting('activation', 'hideUnavailableFeatures'); |
| 247 | } |
| 248 | |
| 249 | /** |
| 250 | * Checks if a feature is locked (visible but not accessible due to license restrictions) |
| 251 | * |
| 252 | * A feature is locked when: |
| 253 | * - It should be shown (shouldShowFeature returns true) |
| 254 | * - BUT the user doesn't have license access to it |
| 255 | * |
| 256 | * For non-toggleable features (locations): |
| 257 | * - Locked = visible but no license access |
| 258 | * |
| 259 | * For regular features: |
| 260 | * - Locked = visible but not enabled (which implies no license access since |
| 261 | * users with license access who have disabled features won't see them) |
| 262 | * |
| 263 | * @param string $featureCode The feature code (e.g., 'packages', 'customFields', 'locations') |
| 264 | * @return bool True if feature is locked, false otherwise |
| 265 | */ |
| 266 | public static function isFeatureLocked($featureCode) |
| 267 | { |
| 268 | // Feature must be visible to be considered "locked" |
| 269 | if (!self::shouldShowFeature($featureCode)) { |
| 270 | return false; |
| 271 | } |
| 272 | |
| 273 | // Feature is locked if user doesn't have license access |
| 274 | return !self::hasFeatureAccess($featureCode); |
| 275 | } |
| 276 | |
| 277 | /** |
| 278 | * Filters feature settings array to disable features not available in current license. |
| 279 | * Preserves the original structure but sets 'enabled' to false for unavailable features. |
| 280 | * |
| 281 | * @param array $featuresIntegrations Array of feature settings |
| 282 | * @return array Filtered feature settings with license checks applied |
| 283 | */ |
| 284 | public static function filterFeaturesByLicense($featuresIntegrations) |
| 285 | { |
| 286 | if (!is_array($featuresIntegrations)) { |
| 287 | return $featuresIntegrations; |
| 288 | } |
| 289 | |
| 290 | foreach ($featuresIntegrations as $featureCode => $featureSettings) { |
| 291 | // Check if this feature has license restrictions and if user has access |
| 292 | if (!self::hasFeatureAccess($featureCode)) { |
| 293 | // Feature is not available in current license - force disable it |
| 294 | if (is_array($featureSettings) && isset($featureSettings['enabled'])) { |
| 295 | $featuresIntegrations[$featureCode]['enabled'] = false; |
| 296 | } |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | return $featuresIntegrations; |
| 301 | } |
| 302 | |
| 303 | /** |
| 304 | * Checks if a feature is both enabled in settings AND available in current license. |
| 305 | * This is a convenience method for use in SettingsStorage and other places. |
| 306 | * |
| 307 | * @param string $featureCode The feature code (e.g., 'googleCalendar', 'packages') |
| 308 | * @param array|null $featureSettings The feature settings array from database |
| 309 | * @return bool True if feature is enabled and license has access, false otherwise |
| 310 | */ |
| 311 | public static function isFeatureEnabledWithLicense($featureCode, $featureSettings) |
| 312 | { |
| 313 | // Check if feature settings exist and are valid |
| 314 | if (!is_array($featureSettings) || !isset($featureSettings['enabled'])) { |
| 315 | return false; |
| 316 | } |
| 317 | |
| 318 | // Feature must be enabled in settings AND license must have access to it |
| 319 | return $featureSettings['enabled'] && self::hasFeatureAccess($featureCode); |
| 320 | } |
| 321 | |
| 322 | /** |
| 323 | * Override getCommands to delegate to the appropriate license class in development mode |
| 324 | * |
| 325 | * @param \AmeliaBooking\Infrastructure\Common\Container $c |
| 326 | * @return array |
| 327 | */ |
| 328 | public static function getCommands($c) |
| 329 | { |
| 330 | // In production, use normal inheritance (class extends were changed by build scripts) |
| 331 | if (!AMELIA_DEV) { |
| 332 | return parent::getCommands($c); |
| 333 | } |
| 334 | |
| 335 | // In development, dynamically load the appropriate license class |
| 336 | $licenseClass = self::getLicenseClass(); |
| 337 | return $licenseClass::getCommands($c); |
| 338 | } |
| 339 | |
| 340 | /** |
| 341 | * Override setRoutes to delegate to the appropriate license class in development mode |
| 342 | * |
| 343 | * @param \Slim\App $app |
| 344 | * @param \AmeliaBooking\Infrastructure\Common\Container $container |
| 345 | * @return void |
| 346 | */ |
| 347 | public static function setRoutes($app, $container) |
| 348 | { |
| 349 | // In production, use normal inheritance (class extends were changed by build scripts) |
| 350 | if (!AMELIA_DEV) { |
| 351 | parent::setRoutes($app, $container); |
| 352 | return; |
| 353 | } |
| 354 | |
| 355 | // In development, dynamically load the appropriate license class |
| 356 | $licenseClass = self::getLicenseClass(); |
| 357 | $licenseClass::setRoutes($app, $container); |
| 358 | } |
| 359 | |
| 360 | /** |
| 361 | * Override getPaddleUrl to delegate to the appropriate license class in development mode |
| 362 | * |
| 363 | * @return string |
| 364 | */ |
| 365 | public static function getPaddleUrl() |
| 366 | { |
| 367 | // In production, use normal inheritance (class extends were changed by build scripts) |
| 368 | if (!AMELIA_DEV) { |
| 369 | return parent::getPaddleUrl(); |
| 370 | } |
| 371 | |
| 372 | // In development, dynamically load the appropriate license class |
| 373 | $licenseClass = self::getLicenseClass(); |
| 374 | return $licenseClass::getPaddleUrl(); |
| 375 | } |
| 376 | |
| 377 | /** |
| 378 | * Get the current license name |
| 379 | * This is kept for backward compatibility but should use hasLicenseAccess() instead |
| 380 | * |
| 381 | * @return string |
| 382 | */ |
| 383 | public static function getLicence() |
| 384 | { |
| 385 | if (AMELIA_DEV) { |
| 386 | $settingsService = new SettingsService(new SettingsStorage()); |
| 387 | $currentLicense = $settingsService->getSetting('activation', 'licence'); |
| 388 | return !empty($currentLicense) ? $currentLicense : LicenceConstants::DEVELOPER; |
| 389 | } |
| 390 | |
| 391 | return self::$licence; |
| 392 | } |
| 393 | |
| 394 | /** |
| 395 | * Check if current license is premium |
| 396 | * This is kept for backward compatibility but should use hasLicenseAccess() instead |
| 397 | * |
| 398 | * @return bool |
| 399 | */ |
| 400 | public static function isPremium() |
| 401 | { |
| 402 | if (AMELIA_DEV) { |
| 403 | $settingsService = new SettingsService(new SettingsStorage()); |
| 404 | $currentLicense = $settingsService->getSetting('activation', 'licence'); |
| 405 | $currentLicense = !empty($currentLicense) ? $currentLicense : LicenceConstants::DEVELOPER; |
| 406 | |
| 407 | // Lite is the only non-premium license |
| 408 | return $currentLicense !== LicenceConstants::LITE; |
| 409 | } |
| 410 | |
| 411 | return self::$premium; |
| 412 | } |
| 413 | } |
| 414 |