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