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