BannerVisibilityService.php 4.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. <?php
  2. namespace App\Services;
  3. use App\Models\Banner;
  4. use App\Support\ContentLink;
  5. use Illuminate\Support\Facades\DB;
  6. class BannerVisibilityService
  7. {
  8. /** Positive numeric links are activity IDs; empty/zero and other links are unbound. */
  9. public static function activityId($link): ?string
  10. {
  11. $link = trim((string) $link);
  12. if ($link === '' || !ctype_digit($link)) {
  13. return null;
  14. }
  15. $id = ltrim($link, '0');
  16. return $id === '' ? null : $id;
  17. }
  18. /** Only activity-type banners bind an activity; rows saved before click_type existed fall back to the link. */
  19. public static function boundActivityId(?string $clickType, $link): ?string
  20. {
  21. return $clickType === null || $clickType === 'activity' ? self::activityId($link) : null;
  22. }
  23. /** A stored click type wins; otherwise infer it from a legacy single `link` value. */
  24. public static function clickType(?string $clickType, $link): string
  25. {
  26. if (in_array($clickType, ['none', 'activity', 'external', 'internal'], true)) return $clickType;
  27. $link = trim((string) $link);
  28. if (self::activityId($link) !== null) return 'activity';
  29. if ($link === '' || ltrim($link, '0') === '') return 'none';
  30. return str_starts_with($link, '/') && !str_starts_with($link, '//') ? 'internal' : 'external';
  31. }
  32. /** Bound banners follow the activity; only unbound banners use the stored manual setting. */
  33. public static function decorate(iterable $banners, ?int $now = null): array
  34. {
  35. $now = $now ?? time();
  36. $items = [];
  37. $activityIds = [];
  38. foreach ($banners as $banner) {
  39. $item = $banner instanceof Banner ? $banner->toArray() : $banner;
  40. $item['status'] = (int) $item['status'];
  41. $item['activity_id'] = self::boundActivityId($item['click_type'] ?? null, $item['link'] ?? null);
  42. $item = array_replace($item, self::links($item));
  43. $item['can_manual_toggle'] = $item['activity_id'] === null;
  44. $item['manual_status'] = $item['can_manual_toggle'] ? $item['status'] : null;
  45. if ($item['activity_id'] !== null) {
  46. $activityIds[$item['activity_id']] = $item['activity_id'];
  47. }
  48. $items[] = $item;
  49. }
  50. // Read raw timestamps; ActivityReward's date accessors discard the time of day.
  51. $activities = empty($activityIds) ? collect() : DB::table('activity_rewards')
  52. ->whereIn('id', array_values($activityIds))
  53. ->get(['id', 'status', 'start_time', 'end_time'])->keyBy('id');
  54. foreach ($items as &$item) {
  55. $activity = $item['activity_id'] === null ? null : $activities->get($item['activity_id']);
  56. $reason = null;
  57. if ($item['activity_id'] !== null) {
  58. if ($activity === null) {
  59. $reason = 'activity_missing';
  60. } elseif ((int) $activity->status !== 1) {
  61. $reason = 'activity_offline';
  62. } elseif ((int) $activity->start_time > $now) {
  63. $reason = 'activity_not_started';
  64. } elseif ((int) $activity->end_time <= $now) {
  65. $reason = 'activity_ended';
  66. }
  67. } elseif ($item['manual_status'] !== 1) {
  68. $reason = 'manual';
  69. }
  70. $item['effective_status'] = $reason === null ? 1 : 0;
  71. $item['status'] = $item['effective_status'];
  72. $item['offline_reason'] = $reason;
  73. }
  74. unset($item);
  75. return $items;
  76. }
  77. /** Normalized jump fields for the admin form; internal rows always expose both platform paths. */
  78. public static function links(array $row): array
  79. {
  80. $type = self::clickType($row['click_type'] ?? null, $row['link'] ?? null);
  81. if ($type === 'activity') {
  82. return ['click_type' => $type, 'link' => self::activityId($row['link'] ?? null), 'link_pc' => null, 'link_h5' => null];
  83. }
  84. return ContentLink::normalize(['click_type' => $type] + $row);
  85. }
  86. }