PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / trunk
Booking for Appointments and Events Calendar – Amelia vtrunk
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / src / Infrastructure / Licence / Licence.php
ameliabooking / src / Infrastructure / Licence Last commit date
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