PluginProbe ʕ •ᴥ•ʔ
FAPI Member / 2.2.37
FAPI Member v2.2.37
2.2.37 2.2.36 2.2.35 2.2.34 2.2.33 2.2.32 trunk 1.9.47 2.1.18 2.2.24 2.2.25 2.2.26 2.2.28 2.2.29 2.2.30 2.2.31
fapi-member / src / Service / MembershipService.php
fapi-member / src / Service Last commit date
ApiService.php 2 days ago ElementService.php 2 days ago EmailService.php 2 days ago FormService.php 2 days ago LevelOrderService.php 2 days ago LevelService.php 2 days ago MembershipService.php 2 days ago RedirectService.php 2 days ago SanitizationService.php 2 days ago StatisticsService.php 2 days ago UserService.php 2 days ago
MembershipService.php
475 lines
1 <?php declare(strict_types=1);
2
3 namespace FapiMember\Service;
4
5 use DateInterval;
6 use DateTimeImmutable;
7 use FapiMember\Container\Container;
8 use FapiMember\Model\Enums\Format;
9 use FapiMember\Model\Enums\Keys\MetaKey;
10 use FapiMember\Model\Enums\Types\LevelUnlockType;
11 use FapiMember\Model\Membership;
12 use FapiMember\Repository\LevelRepository;
13 use FapiMember\Repository\MembershipRepository;
14 use FapiMember\Repository\UserRepository;
15 use FapiMember\Utils\DateTimeHelper;
16
17 class MembershipService
18 {
19 private MembershipRepository $membershipRepository;
20 private LevelRepository $levelRepository;
21 private UserRepository $userRepository;
22
23 public function __construct()
24 {
25 $this->membershipRepository = Container::get(MembershipRepository::class);
26 $this->levelRepository = Container::get(LevelRepository::class);
27 $this->userRepository = Container::get(UserRepository::class);
28 }
29
30 /**
31 * @return array<Membership>
32 */
33 public function getActiveWithAccessByUserId(int $userId): array
34 {
35 return $this->membershipRepository->getActiveByUserId($userId, true);
36 }
37
38 /**
39 * @return array<Membership>
40 */
41 public function getActiveByUserIdAndUpdate(int $userId): array
42 {
43 $memberships = $this->membershipRepository->getActiveByUserId($userId);
44 $this->membershipRepository->saveAll($userId, $memberships);
45
46 return $memberships;
47 }
48
49 /**
50 * @param array<Membership> $memberships
51 */
52 public function saveAll(int $userId, array $memberships): void
53 {
54 $this->membershipRepository->saveAll($userId, $memberships);
55 $this->fixMemberships($userId);
56 }
57
58 public function fixMemberships(int $userId): void
59 {
60 $this->extendMembershipsToSections($userId);
61 $this->timeUnlockLevelsForUser($userId);
62 $this->extendSectionsDates($userId);
63 }
64
65 public function saveOne(Membership $newMembership): void
66 {
67 if (
68 $this->membershipRepository->getOneByUserIdAndLevelId(
69 $newMembership->getUserId(),
70 $newMembership->getLevelId(),
71 ) !== null
72 ) {
73 return;
74 }
75
76 $memberships = $this->getActiveByUserIdAndUpdate($newMembership->getUserId());
77 $memberships[] = $newMembership;
78
79 $this->membershipRepository->saveAll($newMembership->getUserId(), $memberships);
80 }
81
82 public function update(Membership $updatedMembership): void
83 {
84 $memberships = $this->getActiveByUserIdAndUpdate($updatedMembership->getUserId());
85 $found = false;
86
87 foreach ($memberships as $key => $membership) {
88 if ($membership->getLevelId() === $updatedMembership->getLevelId()) {
89 $memberships[$key] = $updatedMembership;
90 $found = true;
91 break;
92 }
93 }
94
95 if ($found) {
96 $this->saveAll($updatedMembership->getUserId(), $memberships);
97 }
98 }
99
100 /** @return array<mixed> */
101 public function createOrUpdateMembership(
102 int $userId,
103 int $levelId,
104 DateTimeImmutable|null $registered,
105 DateTimeImmutable|null $until = null,
106 ): array
107 {
108 $oldMembership = $this->membershipRepository->getOneByUserIdAndLevelId($userId, $levelId);
109
110 $newMembership = new Membership([
111 'level_id' => $levelId,
112 'user_id' => $userId,
113 'registered' => $registered,
114 'until' => $until,
115 'is_unlimited' => $until === null,
116 ]);
117
118 if ($oldMembership !== null) {
119 $this->update($newMembership);
120 } else {
121 $this->saveOne($newMembership);
122 }
123
124 $this->fixMemberships($userId);
125
126 return [
127 'oldMembership' => $oldMembership,
128 'newMembership' => $newMembership,
129 ];
130 }
131
132 /** @return array<mixed> */
133 public function createOrProlongMembershipByDays(
134 int $userId,
135 int $levelId,
136 int|null $days = null,
137 ): array
138 {
139 $membership = $this->membershipRepository->getOneByUserIdAndLevelId($userId, $levelId);
140
141 $isUnlimited = $days === null;
142
143 if ($membership !== null) {
144 $props = $this->prolongMembership($membership, $isUnlimited, $days);
145 } else {
146 $props = $this->createMembershipFromDays($userId, $levelId, $isUnlimited, $days);
147 }
148
149 $this->fixMemberships($userId);
150
151 return $props;
152 }
153
154 /**
155 * @return array<mixed>
156 * @throws \Exception
157 */
158 public function createMembershipFromDays(
159 int $userId,
160 int $levelId,
161 $isUnlimited,
162 int|null $days = null,
163 ): array
164 {
165 $props = [];
166 $props['oldMembership'] = null;
167
168 $registered = DateTimeHelper::getNow();
169
170 if ($isUnlimited) {
171 $until = null;
172 } else {
173 $until = $registered;
174 $until = $until->modify(sprintf('+ %s days', $days ));
175 }
176
177 $newMembership = new Membership([
178 'level_id' => $levelId,
179 'user_id' => $userId,
180 'registered' => $registered,
181 'until' => $until,
182 'is_unlimited' => $isUnlimited
183 ]);
184
185 $props['newMembership'] = $newMembership;
186
187 $this->saveOne($newMembership);
188
189 return $props;
190 }
191
192 /** @return array<mixed> */
193 public function prolongMembership(
194 Membership $membership,
195 bool $isUnlimited,
196 int|null $days = null,
197 ): array
198 {
199 $props = [];
200 $props['oldMembership'] = $membership;
201
202 if ($isUnlimited || $membership->isUnlimited()) {
203 $membership->setIsUnlimited(true);
204 } else {
205 $membership->setUntil(
206 $membership->getUntil()->modify(sprintf('+ %s days', $days))
207 );
208 }
209
210 $props['newMembership'] = $membership;
211
212 $this->update($membership);
213
214 return $props;
215 }
216
217 public function extendMembershipsToSections(int $userId): void
218 {
219 $activeMemberships = $this->getActiveByUserIdAndUpdate($userId);
220
221 if ($activeMemberships === []) {
222 return;
223 }
224
225 /** @var array<Membership> $extendedMembership */
226 $extendedMemberships = $activeMemberships;
227 $sectionsToExtend = [];
228
229 foreach ($activeMemberships as $activeMembership) {
230 $level = $this->levelRepository->getLevelById($activeMembership->getLevelId());
231
232 if ($level !== null && !$level->isSection()) {
233 $sectionsToExtend[$level->getParentId()] = $this->levelRepository->getSectionById($level->getParentId());
234 }
235 }
236
237 foreach ($sectionsToExtend as $section) {
238 $childLevelIds = [];
239
240 foreach ($section->getLevels() as $level) {
241 $childLevelIds[] = $level->getId();
242 }
243
244 $LevelMemberships = array_filter(
245 $activeMemberships,
246 static function ( $membership ) use ( $childLevelIds ) {
247 return in_array( $membership->getLevelId(), $childLevelIds, true );
248 }
249 );
250
251 $childIsUnlimited = false;
252 $childMaxUntil = null;
253 $childMinRegistered = null;
254
255 foreach ($LevelMemberships as $levelMembership ) {
256 if ( $levelMembership->isUnlimited() === true ) {
257 $childIsUnlimited = true;
258 }
259
260 if ($levelMembership->getUntil() !== null) {
261 $childMaxUntil = max($childMaxUntil, $levelMembership->getUntil());
262 }
263
264 if ($levelMembership->getRegistered() !== null) {
265 if ($childMinRegistered === null) {
266 $childMinRegistered = $levelMembership->getRegistered();
267 } else {
268 $childMinRegistered = min(
269 $childMinRegistered,
270 $levelMembership->getRegistered(),
271 );
272 }
273 }
274 }
275
276 $wasParentTermExtended = null;
277
278 foreach ($extendedMemberships as $extendedMembership) {
279 if ($extendedMembership->getLevelId() === $section->getId()) {
280 if ($extendedMembership->isUnlimited()) {
281 $wasParentTermExtended = true;
282 break;
283 }
284
285 if ($childIsUnlimited) {
286 $extendedMembership->setIsUnlimited(true);
287 $extendedMembership->setUntil(null);
288 $wasParentTermExtended = true;
289 break;
290 }
291
292 $extendedMembership->setUntil(
293 max($extendedMembership->getUntil(), $childMaxUntil),
294 );
295 $wasParentTermExtended = true;
296 }
297 }
298
299 if (!$wasParentTermExtended) {
300 $newMembership = new Membership([
301 'level_id' => $section->getId(),
302 'user_id' => $userId,
303 'registered' => $childMinRegistered,
304 'until' => $childMaxUntil,
305 'is_unlimited' => $childIsUnlimited,
306 ]);
307
308 $this->saveOne($newMembership);
309 }
310 }
311 }
312
313 private function extendSectionsDates(int $userId): void
314 {
315 $sections = $this->levelRepository->getAllSections();
316
317 foreach ($sections as $section) {
318 $parentMembership = $this->membershipRepository->getOneByUserIdAndLevelId($userId, $section->getId());
319
320 $memberships = $this->membershipRepository->getAllLevelMembershipsByUserIdAndSectionId($userId, $section->getId());
321 $newRegistered = null;
322 $newUntil = null;
323 $newIsUnlimited = null;
324
325
326 foreach ($memberships as $membership) {
327 if ($membership?->getRegistered() === null) {
328 continue;
329 }
330
331 if (
332 $membership->getRegistered() < $parentMembership->getRegistered()
333 && ($newRegistered === null || $membership->getRegistered() < $newRegistered)
334 ) {
335 $newRegistered = $membership->getRegistered();
336 }
337
338 if (
339 $parentMembership->isUnlimited() !== true
340 && $membership->getUntil() !== null
341 && $membership->getUntil() > $parentMembership->getUntil()
342 && ($newUntil === null || $membership->getUntil() > $newUntil)
343 ) {
344 $newUntil = $membership->getUntil();
345 }
346
347 if ($membership->isUnlimited() && !$parentMembership->isUnlimited()) {
348 $newIsUnlimited = true;
349 }
350 }
351
352 if ($newRegistered !== null) {
353 $parentMembership->setRegistered($newRegistered);
354 }
355
356 if ($newIsUnlimited === true) {
357 $parentMembership->setIsUnlimited(true);
358 $parentMembership->setUntil(null);
359 } else if ($newUntil !== null) {
360 $parentMembership->setUntil($newUntil);
361 }
362
363 if ($newUntil !== null || $newRegistered !== null || $newIsUnlimited === true) {
364 $this->update($parentMembership);
365 }
366 }
367 }
368
369 public function timeUnlockLevelsForAllUsers(): void
370 {
371 $batchSize = 100;
372 $offset = 0;
373
374 do {
375 $userIds = $this->userRepository->getUserIdsBatch($batchSize, $offset);
376
377 foreach ($userIds as $userId) {
378 $this->timeUnlockLevelsForUser($userId);
379 }
380
381 $offset += count($userIds);
382 } while (count($userIds) === $batchSize);
383 }
384
385 public function timeUnlockLevelsForUser(int $userId): void
386 {
387 $memberships = $this->getActiveByUserIdAndUpdate($userId);
388
389 foreach ($memberships as $membership){
390 $section = $this->levelRepository->getSectionById($membership->getLevelId());
391
392 if ($section === null) {
393 continue;
394 }
395
396 foreach ($section->getLevels() as $level) {
397 $unlockDate = null;
398 $unlockHour = $this->levelRepository->getHourUnlock($level->getId());
399
400 if ($level->getUnlockType() === LevelUnlockType::DAYS) {
401 $daysToUnlock = (int) get_term_meta($level->getId(), MetaKey::DAYS_TO_UNLOCK, true);
402 $date = $membership->getRegistered()->getTimestamp();
403 $unlockDate = $date - ($date % 86400) + (86400 * $daysToUnlock) + (3600 * $unlockHour);
404 } elseif ($level->getUnlockType() === LevelUnlockType::DATE) {
405 $date = strtotime(get_term_meta($level->getId(), MetaKey::DATE_UNLOCK, true));
406 $date = $date - ($date % 86400) + (3600 * $unlockHour);
407
408 if (
409 $this->levelRepository->getAfterDateUnlock($level->getId()) === false
410 || $membership->getRegistered()->getTimestamp() < $date
411 ) {
412 $unlockDate = $date;
413 }
414 }
415
416 if ($unlockDate === null) {
417 continue;
418 }
419
420 $unlockDate -= 60;
421
422 if (DateTimeHelper::getNowTimestamp() >= $unlockDate) {
423 $this->saveOne(new Membership([
424 'level_id' => $level->getId(),
425 'user_id' => $userId,
426 'registered' => DateTimeHelper::getNow()->format(Format::DATE_TIME),
427 'until' => $membership->getUntil()?->format(Format::DATE_TIME),
428 'is_unlimited' => $membership->isUnlimited(),
429 ]));
430 }
431 }
432 }
433 }
434
435 /**
436 * @throws \Exception
437 */
438 public function getUnlockDate(int $levelId, int $userId, DateTimeImmutable|null $registrationDate = null): DateTimeImmutable|null
439 {
440 $level = $this->levelRepository->getLevelById($levelId);
441
442 if ($level?->getParentId() === null || $level->getUnlockType() === null || $level->getUnlockType() === LevelUnlockType::NONE) {
443 return null;
444 }
445
446 $date = null;
447
448 if ($level->getUnlockType() === LevelUnlockType::DATE) {
449 $date = DateTimeHelper::createOrNull($this->levelRepository->getDateUnlock($level->getId()), Format::DATE);
450
451 } elseif ($level->getUnlockType() === LevelUnlockType::DAYS) {
452 if ($registrationDate === null) {
453 $parentMembership = $this->membershipRepository->getOneByUserIdAndLevelId($userId, $level->getParentId());
454
455 if ($parentMembership === null) {
456 return null;
457 }
458 $registrationDate = $parentMembership->getRegistered();
459 }
460
461 $date = $registrationDate->add(
462 new DateInterval('P' . $this->levelRepository->getDaysUnlock($level->getId()) . 'D'),
463 );
464 }
465
466 if ($date !== null) {
467 $hour = $this->levelRepository->getHourUnlock($level->getId());
468 $date = $date->setTime($hour, 0);
469 }
470
471 return $date;
472 }
473
474 }
475