• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

waynestate / base-site / 17217076603

25 Aug 2025 06:15PM UTC coverage: 99.81% (-0.2%) from 100.0%
17217076603

push

github

breakdancingcat
Merge branch 'hotfix/8.14.13' into develop

6 of 8 new or added lines in 1 file covered. (75.0%)

1050 of 1052 relevant lines covered (99.81%)

6.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.05
/app/Repositories/ModularPageRepository.php
1
<?php
2

3
namespace App\Repositories;
4

5
use Contracts\Repositories\ModularPageRepositoryContract;
6
use Contracts\Repositories\EventRepositoryContract;
7
use Contracts\Repositories\ArticleRepositoryContract;
8
use Illuminate\Cache\Repository;
9
use Illuminate\Support\Str;
10
use Waynestate\Api\Connector;
11
use Waynestate\Promotions\ParsePromos;
12

13
class ModularPageRepository implements ModularPageRepositoryContract
14
{
15
    /** @var Connector */
16
    protected $wsuApi;
17

18
    /** @var ParsePromos */
19
    protected $parsePromos;
20

21
    /** @var Repository */
22
    protected $cache;
23

24
    /** @var ArticleRepositoryContract */
25
    protected $article;
26

27
    /** @var EventRepositoryContract */
28
    protected $event;
29

30
    /**
31
     * Construct the repository.
32
     *
33
     * @param Connector $wsuApi
34
     * @param ParsePromos $parsePromos
35
     * @param Repository $cache
36
     * @param ArticleRepositoryContract $article
37
     * @param EventRepositoryContract $event
38
     *
39
     */
40
    public function __construct(
41
        Connector $wsuApi,
42
        ParsePromos $parsePromos,
43
        Repository $cache,
44
        ArticleRepositoryContract $article,
45
        EventRepositoryContract $event
46
    ) {
47
        $this->wsuApi = $wsuApi;
37✔
48
        $this->parsePromos = $parsePromos;
37✔
49
        $this->cache = $cache;
37✔
50
        $this->article = $article;
37✔
51
        $this->event = $event;
37✔
52
    }
53

54
    /**
55
     * {@inheritdoc}
56
     */
57
    public function getModularComponents(array $data): array
58
    {
59
        if (empty($data['data'])) {
34✔
60
            return [];
11✔
61
        }
62

63
        $components = [];
25✔
64

65
        $data = $this->legacyPageFieldSupport($data);
25✔
66

67
        $rawComponents = $this->parseComponentJSON($data);
25✔
68

69
        $promos = $this->getPromos($rawComponents, $data['site']['id'] ?? '');
25✔
70

71
        $components = $this->configureComponents($rawComponents, $promos, $data);
25✔
72

73
        $components = $this->componentClasses($components);
25✔
74

75
        $components = $this->componentStyles($components);
25✔
76

77
        return $components;
25✔
78
    }
79

80
    /**
81
     * {@inheritdoc}
82
     */
83
    public function parseComponentJSON(array $data)
84
    {
85
        $components = [];
25✔
86
        $group_reference = [];
25✔
87
        $group_config = [];
25✔
88

89
        foreach ($data['data'] as $pageField => $componentConfig) {
25✔
90

91
            // Only care about page fields starting with modular
92
            if (Str::startsWith($pageField, 'modular-')) {
25✔
93

94
                // Remove modular from component key
95
                $name = Str::replaceFirst('modular-', '', $pageField);
25✔
96

97
                // Remove all spaces and line breaks
98
                $componentConfig = preg_replace('/\s*\R\s*/', '', $componentConfig);
25✔
99

100
                // Prevent trailing comma
101
                $componentConfig = preg_replace('(,})', '}', $componentConfig);
25✔
102

103
                // Interpret component config
104
                if (Str::startsWith($componentConfig, '{')) {
25✔
105
                    $components[$name] = json_decode($componentConfig, true);
24✔
106

107
                    // Ensure promo config exists as a string
108
                    if (empty($components[$name]['config'])) {
24✔
109
                        $components[$name]['config'] = '';
17✔
110
                    }
111

112
                    // Modify promo config
113
                    $promoConfig = explode('|', $components[$name]['config']) ?? [];
24✔
114

115
                    foreach ($promoConfig as $key => $value) {
24✔
116
                        // Insert correct page id into config
117
                        if (Str::startsWith($value, 'page_id')) {
24✔
118
                            $promoConfig[$key] = 'page_id:'.$data['page']['id'];
3✔
119
                        }
120

121
                        // Prevent 'first' in the promo config
122
                        // Return must be an array of promo items
123
                        if (Str::startsWith($value, 'first')) {
24✔
124
                            unset($promoConfig[$key]);
4✔
125
                        }
126
                    }
127

128
                    // Assume support for youtube links
129
                    if (strpos($components[$name]['config'], 'youtube') === false) {
24✔
130
                        array_push($promoConfig, 'youtube');
24✔
131
                    }
132

133
                    // Return config to correct format for API
134
                    $components[$name]['config'] = implode('|', $promoConfig);
24✔
135

136
                    // Identify the component filename, remove dash and number from page field label
137
                    $components[$name]['filename'] = preg_replace('/-\d+$/', '', $name);
24✔
138
                } else {
139
                    // Support modular components using a promo_group_id without JSON config
140
                    $components[$name]['id'] = $componentConfig;
1✔
141
                }
142

143
                // Create group_reference and group_config from components with promo data for API call
144
                if (!Str::contains($name, ['events', 'news']) && !empty($components[$name]['id'])) {
25✔
145
                    $group_reference[$components[$name]['id']] = $name;
14✔
146
                    if (!empty($components[$name]['config'])) {
14✔
147
                        $group_config[$name] = $components[$name]['config'];
13✔
148
                    }
149
                }
150
            }
151
        }
152

153
        return [
25✔
154
            'components' => $components,
25✔
155
            'group_reference' => $group_reference,
25✔
156
            'group_config' => $group_config,
25✔
157
        ];
25✔
158
    }
159

160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function getPromos($components, $site_id)
164
    {
165
        $params = [
25✔
166
            'method' => 'cms.promotions.listing',
25✔
167
            'promo_group_id' => array_keys($components['group_reference']),
25✔
168
            'filename_url' => true,
25✔
169
            'is_active' => '1',
25✔
170
        ];
25✔
171

172
        $promos = $this->cache->remember($params['method'] . md5(serialize($params)), config('cache.ttl'), function () use ($params) {
25✔
173
            return $this->wsuApi->sendRequest($params['method'], $params);
25✔
174
        });
25✔
175

176
        // Use another site's promo items only from Base
177
        if (!empty($site_id) && $site_id === 1561) {
25✔
178
            $promos['promotions'] = collect($promos['promotions'])->map(function ($promo) {
1✔
179
                if (!empty($promo['filename_url'])) {
1✔
180
                    $promo['relative_url'] = $promo['filename_url'];
1✔
181
                }
182

183
                if (!empty($promo['secondary_filename_url'])) {
1✔
184
                    $promo['secondary_relative_url'] = $promo['secondary_filename_url'];
1✔
185
                }
186

187
                return $promo;
1✔
188
            })->toArray();
1✔
189
        }
190

191
        $promos = $this->parsePromos->parse($promos, $components['group_reference'], $components['group_config']);
25✔
192

193
        foreach ($promos as $name => $data) {
25✔
194
            // Adjust promo data
195
            $data = collect($data)->map(function ($item) use ($components, $name) {
14✔
196
                return $this->adjustPromoData($item, $components['components'][$name]);
13✔
197
            })->toArray();
14✔
198

199
            // Organize by option
200
            if (!empty($components['components'][$name]['groupByOptions']) && $components['components'][$name]['groupByOptions'] === true && Str::startsWith($name, 'catalog')) {
14✔
201
                $data = $this->organizePromoItemsByOption($data);
1✔
202
            }
203

204
            // Build the return
205
            $promos[$name] = [
14✔
206
                'data' => $data,
14✔
207
                'component' => $components['components'][$name],
14✔
208
            ];
14✔
209
        }
210

211
        return $promos;
25✔
212
    }
213

214
    /**
215
     * {@inheritdoc}
216
     */
217
    public function configureComponents(array $components, array $promos, array $data): array
218
    {
219
        $modularComponents = [];
25✔
220

221
        foreach ($components['components'] as $name => $component) {
25✔
222
            if (Str::contains($name, 'events') || Str::contains($name, 'news')) {
25✔
223
                if (Str::contains($name, 'events')) {
10✔
224
                    $components['components'][$name]['id'] = $component['events_id'] ?? $component['id'] ?? $data['site']['id'];
8✔
225

226
                    // Restrict events row to 3 items
227
                    if (Str::contains($name, 'events-row') && !Str::contains($name, 'featured-events-row')) {
8✔
228
                        $limit = $components['components'][$name]['limit'] ?? 3;
4✔
229
                    } else {
230
                        $limit = $components['components'][$name]['limit'] ?? 4;
4✔
231
                    }
232

233
                    // Use full listing if the name contains featured, or events-row
234
                    // TODO Find better naming convention
235
                    if (!Str::contains($name, 'featured') && Str::contains($name, 'column') || Str::contains($name, 'news-and-events')) {
8✔
236
                        $events = $this->event->getEvents($components['components'][$name]['id'] ?? $data['site']['id'], $limit);
7✔
237
                    } else {
238
                        $events = $this->event->getEventsFullListing($components['components'][$name]['id'] ?? $data['site']['id'], $limit);
1✔
239
                    }
240

241
                    // Special data structure for news-and-events component
242
                    if (Str::contains($name, 'news-and-events')) {
8✔
243
                        $modularComponents[$name]['data']['events'] = $events['events'] ?? [];
4✔
244
                    } else {
245
                        $modularComponents[$name]['data'] = $events['events'] ?? [];
4✔
246
                    }
247

248
                    // Provide a default Events heading
249
                    if (!array_key_exists('heading', $component)) {
8✔
250
                        $components['components'][$name]['heading'] = 'Events';
8✔
251
                    }
252

253
                    // Assign the component data
254
                    $modularComponents[$name]['component'] = $components['components'][$name];
8✔
255

256
                    // Set featured events default columns
257
                    if (Str::contains($name, 'events-featured-row')) {
8✔
NEW
258
                        if (empty($components['components'][$name]['columns'])) {
×
NEW
259
                            $modularComponents[$name]['component']['columns'] = 4;
×
260
                        }
261
                    }
262

263
                    // Set the calendar link
264
                    if (empty($modularComponents[$name]['component']['cal_name']) && !empty($data['site']['events']['path'])) {
8✔
265
                        $modularComponents[$name]['component']['cal_name'] = $data['site']['events']['path'];
4✔
266
                    }
267
                }
268
                if (Str::contains($name, 'news')) {
10✔
269
                    $components['components'][$name]['id'] = $component['news_id'] ?? $component['id'] ?? $data['site']['news']['application_id'];
6✔
270
                    $limit = $component['limit'] ?? 4;
6✔
271

272
                    // Set the news route
273
                    $components['components'][$name]['news_route'] = $component['news_route'] ?? config('base.news_listing_route');
6✔
274

275
                    // Use featured news
276
                    if (!empty($component['featured']) && $component['featured'] === true) {
6✔
277
                        $articles = $this->article->listing($components['components'][$name]['id'], 50, 1, $component['topics'] ?? []);
1✔
278
                        $articles['articles']['data'] = collect($articles['articles']['data'])->filter(function ($article) {
1✔
279
                            return !empty($article['featured']['featured']) && $article['featured']['featured'] === 1;
1✔
280
                        })->take($limit)->toArray();
1✔
281
                    } else {
282
                        $articles = $this->article->listing($components['components'][$name]['id'], $limit, 1, $component['topics'] ?? []);
5✔
283
                    }
284

285
                    // Special data structure for news-and-events component
286
                    if (Str::contains($name, 'news-and-events')) {
6✔
287
                        $modularComponents[$name]['data']['news'] = $articles['articles']['data'] ?? [];
4✔
288
                    } else {
289
                        $modularComponents[$name]['data'] = $articles['articles']['data'] ?? [];
2✔
290
                    }
291

292
                    // Provide a default News heading
293
                    if (!array_key_exists('heading', $component)) {
6✔
294
                        $components['components'][$name]['heading'] = 'News';
6✔
295
                    }
296

297
                    // Assign the component data
298
                    $modularComponents[$name]['component'] = $components['components'][$name];
6✔
299
                    $modularComponents[$name]['meta'] = $articles['articles']['meta'] ?? [];
6✔
300

301
                    if (Str::startsWith($name, 'news-and-events')) {
6✔
302
                        // Clear any set heading
303
                        // Headings are set in the component blade
304
                        $modularComponents[$name]['component']['heading'] = '';
4✔
305
                    }
306
                }
307
            } elseif (Str::startsWith($name, 'page-content') || Str::startsWith($name, 'heading')) {
16✔
308
                // If there's JSON but no news, events or promo data, assign the component array as data
309
                // Page-content and heading components
310
                $modularComponents[$name]['data'][] = $components['components'][$name] ?? [];
1✔
311
                $modularComponents[$name]['component'] = $components['components'][$name] ?? [];
1✔
312
                unset($modularComponents[$name]['component']['heading']);
1✔
313
            } else {
314
                $modularComponents[$name]['data'] = $promos[$name]['data'] ?? [];
15✔
315
                $modularComponents[$name]['component'] = $promos[$name]['component'] ?? [];
15✔
316
            }
317
        }
318

319
        return $modularComponents;
25✔
320
    }
321

322
    public function adjustPromoData($data, $component)
323
    {
324
        if (isset($component['singlePromoView']) && $component['singlePromoView'] === true) {
13✔
325
            $data['link'] = 'view/'.Str::slug($data['title']).'-'.$data['promo_item_id'];
5✔
326
        }
327

328
        if (isset($component['showExcerpt']) && $component['showExcerpt'] === false) {
13✔
329
            unset($data['excerpt']);
1✔
330
        }
331

332
        if (isset($component['showDescription']) && $component['showDescription'] === false) {
13✔
333
            unset($data['description']);
1✔
334
        }
335

336
        // Override promo item option with component option
337
        if (isset($component['option'])) {
13✔
338
            $data['option'] = $component['option'];
1✔
339
        }
340

341
        return $data;
13✔
342
    }
343

344
    /**
345
    * {@inheritdoc}
346
    */
347
    public function organizePromoItemsByOption(array $data)
348
    {
349
        $options_exist = collect($data)->filter(function ($item) {
3✔
350
            return !empty($item['option']);
2✔
351
        })->isNotEmpty();
3✔
352

353
        if ($options_exist === true) {
3✔
354
            $data = collect($data)->groupBy('option')->toArray();
2✔
355

356
            if (!empty($data[''])) {
2✔
357
                $no_option_moved_to_bottom = $data[''];
2✔
358
                unset($data['']);
2✔
359
                $data[''] = $no_option_moved_to_bottom;
2✔
360
            }
361
        }
362

363
        return $data;
3✔
364
    }
365

366
    public function componentClasses($components)
367
    {
368
        foreach ($components as $componentName => $component) {
25✔
369
            // Establishing final arrays so they will always exist
370
            $components[$componentName]['component']['containerClass'] = $component['component']['containerClass'] ?? [];
25✔
371
            $components[$componentName]['component']['backgroundClass'] = $component['component']['backgroundClass'] ?? [];
25✔
372
            $components[$componentName]['component']['componentClass'] = $component['component']['componentClass'] ?? [];
25✔
373

374
            // containerClass => filename
375
            if (!empty($component['component']['filename'])) {
25✔
376
                $components[$componentName]['component']['containerClass'][] = $component['component']['filename'];
23✔
377
            }
378

379
            // containerClass => columnSpan
380
            if (!empty($component['component']['columnSpan'])) {
25✔
381
                // Inject the column span class
382
                array_push($components[$componentName]['component']['containerClass'], 'px-4', 'mt:colspan-'.$component['component']['columnSpan']);
2✔
383
            } elseif (!empty($component['component']['filename']) && strpos($component['component']['filename'], 'column') !== false) {
25✔
384
                // Inject the column span class
385
                array_push($components[$componentName]['component']['containerClass'], 'px-4', 'mt:colspan-6');
8✔
386
            } else {
387
                // Default width
388
                $components[$componentName]['component']['containerClass'][] = 'px-container';
19✔
389
            }
390

391
            // Collect all legacy class names
392
            $classes = ($component['component']['sectionClass'] ?? '').' '.($component['component']['componentClass'] ?? '').' '.($component['component']['classes'] ?? '');
25✔
393

394
            // Group the classes based on the container they will be applied to
395
            // Set backgroundClass, containerClass, componentClass
396
            if (!empty($classes)) {
25✔
397
                $classes = explode(' ', $classes);
25✔
398

399
                foreach ($classes as $class) {
25✔
400
                    if (strpos($class, 'bg-') !== false) {
25✔
401
                        // backgroundClass
402
                        $components[$componentName]['component']['backgroundClass'][] = $class;
2✔
403
                    } elseif (strpos($class, 'my-') !== false | strpos($class, 'mt-') !== false | strpos($class, 'mb-') !== false | strpos($class, 'end') !== false | strpos($class, 'left') !== false | strpos($class, 'right') !== false) {
25✔
404
                        // containerClass
405
                        $components[$componentName]['component']['containerClass'][] = $class;
2✔
406
                    } else {
407
                        // componentClass
408
                        $components[$componentName]['component']['componentClass'][] = $class;
25✔
409
                    }
410
                }
411
            }
412

413
            // Default background image positioning classes, won't overwrite existing backgroundClass values
414
            if (!empty($component['component']['backgroundImageUrl']) && empty($component['component']['backgroundClass'])) {
25✔
415
                $components[$componentName]['component']['backgroundClass'] = ['bg-cover', 'bg-top'];
2✔
416
            }
417

418
            // Section gutters, bottom padding
419
            // - No gutter if component uses margin-bottom class
420
            // - No gutter on heading component
421
            if (empty(preg_grep('/mb-/', $components[$componentName]['component']['containerClass']))
25✔
422
                && !Str::contains($componentName, 'heading')
25✔
423
            ) {
424
                $components[$componentName]['component']['containerClass'] [] = 'mb-gutter';
25✔
425
            }
426

427
            // Implode party, assign all classes to their respective container
428
            $components[$componentName]['component']['containerClass'] = implode(' ', $components[$componentName]['component']['containerClass']);
25✔
429
            $components[$componentName]['component']['backgroundClass'] = implode(' ', $components[$componentName]['component']['backgroundClass']);
25✔
430
            $components[$componentName]['component']['componentClass'] = implode(' ', $components[$componentName]['component']['componentClass']);
25✔
431
        }
432

433
        return $components;
25✔
434
    }
435

436
    public function componentStyles($components)
437
    {
438
        $expected_styles = [
25✔
439
            'backgroundImageUrl',
25✔
440
            //'sectionStyle',
25✔
441
        ];
25✔
442

443
        foreach ($components as $componentName => $component) {
25✔
444
            if (!empty($component['component']['backgroundImageUrl'])) {
25✔
445
                //$component['component']['backgroundImageUrl'] = "background-image:url('".$component['component']['backgroundImageUrl']."');";
446
                $components[$componentName]['component']['backgroundImageUrl'] = "style=\"background-image:url('".$component['component']['backgroundImageUrl']."');\"";
2✔
447
            }
448

449
            // Forcing a space delimeter
450
            foreach ($component['component'] as $option => $style) {
25✔
451
                if (in_array($option, $expected_styles)) {
25✔
452
                    $styles[$componentName][] = $style;
2✔
453
                    //$components[$componentName]['component']['componentStyle'] = "style=\"".implode(' ', $styles[$componentName])."\"";
454
                }
455
            }
456
        }
457

458
        return $components;
25✔
459
    }
460

461
    /**
462
     * {@inheritdoc}
463
     */
464
    public function legacyPageFieldSupport(array $data)
465
    {
466
        // Legacy support for accordion
467
        if (!empty($data['data']['accordion_promo_group_id'])) {
25✔
468
            $data['data']['modular-accordion-999'] = json_encode([
1✔
469
                'id' => $data['data']['accordion_promo_group_id']
1✔
470
            ]);
1✔
471
        }
472

473
        // Legacy support for listing
474
        if (!empty($data['data']['listing_promo_group_id'])) {
25✔
475
            if (!empty($data['data']['promotion_view_boolean']) && $data['data']['promotion_view_boolean'] === "true") {
2✔
476
                $data['data']['modular-catalog-998'] = json_encode([
1✔
477
                    'id' => $data['data']['listing_promo_group_id'],
1✔
478
                    'columns' => 1,
1✔
479
                    'singlePromoView' => true
1✔
480
                ]);
1✔
481
            } else {
482
                $data['data']['modular-catalog-998'] = json_encode([
1✔
483
                    'id' => $data['data']['listing_promo_group_id'],
1✔
484
                    'columns' => 1
1✔
485
                ]);
1✔
486
            }
487
        }
488

489
        // Legacy support for grid
490
        if (!empty($data['data']['grid_promo_group_id'])) {
25✔
491
            if (!empty($data['data']['promotion_view_boolean']) && $data['data']['promotion_view_boolean'] === "true") {
2✔
492
                $data['data']['modular-catalog-999'] = json_encode([
1✔
493
                    'id' => $data['data']['grid_promo_group_id'],
1✔
494
                    'columns' => 3,
1✔
495
                    'singlePromoView' => true
1✔
496
                ]);
1✔
497
            } else {
498
                $data['data']['modular-catalog-999'] = json_encode([
1✔
499
                    'id' => $data['data']['grid_promo_group_id'],
1✔
500
                    'columns' => 3
1✔
501
                ]);
1✔
502
            }
503
        }
504

505
        return $data;
25✔
506
    }
507
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc