Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
CRAP
100.00% covered (success)
100.00%
1 / 1
RemoteConfig
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
1 / 1
 hasAnySection
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace App\Http\Models;
4
5use Illuminate\Database\Eloquent\Factories\HasFactory;
6
7/**
8 * RemoteConfig model - single-row pattern for active remote configuration.
9 *
10 * Stores the current active configuration that the Chrome extension consumes
11 * via the meta-data endpoint. Only one row exists at a time.
12 *
13 * @property string $_id
14 * @property string $version ISO-8601 timestamp version identifier
15 * @property array|null $linkedin_selectors LinkedIn DOM selectors (18 top-level keys)
16 * @property array|null $outlook_domains Outlook domain detection config
17 * @property array|null $timing Tunable timing constants in ms
18 * @property array|null $feature_flags Feature flags for gradual rollout
19 * @property array|null $features Extension feature toggles
20 * @property array|null $playlists Playlist config for FlyEngage/FlyPosts
21 * @property array|null $blacklist_config Input blacklist rules
22 * @property array|null $ui_visibility UI element visibility toggles
23 * @property array|null $field_injection Field injection config per domain (selector, shadow DOM, offsets)
24 * @property array|null $force_plain_text Force plain-text paste config per domain (domain + optional field selectors)
25 * @property array|null $display_strategy_grammar Display strategy rules for Grammar feature per domain
26 * @property array|null $display_strategy_magic_wand Display strategy rules for Magic Wand feature per domain
27 * @property array|null $display_strategy_paragraph Display strategy rules for Paragraph feature per domain
28 * @property string|null $updated_by Who last updated this config
29 * @property \Carbon\Carbon|null $created_at
30 * @property \Carbon\Carbon|null $updated_at
31 */
32class RemoteConfig extends Moloquent
33{
34    use HasFactory;
35
36    /**
37     * The collection name.
38     *
39     * @var string
40     */
41    protected $table = 'remote_configs';
42
43    /**
44     * All config sections.
45     */
46    public const SECTIONS = [
47        'linkedin_selectors',
48        'outlook_domains',
49        'timing',
50        'feature_flags',
51        'features',
52        'playlists',
53        'blacklist_config',
54        'ui_visibility',
55        'field_injection',
56        'force_plain_text',
57        'display_strategy_grammar',
58        'display_strategy_magic_wand',
59        'display_strategy_paragraph',
60    ];
61
62    /**
63     * Required top-level keys for linkedin_selectors.
64     */
65    public const LINKEDIN_SELECTOR_KEYS = [
66        'detection',
67        'postContainer',
68        'postOwner',
69        'activityId',
70        'postContent',
71        'articleContainer',
72        'articleOwner',
73        'articleContent',
74        'commentContainer',
75        'replyContainer',
76        'nestedCommentIndicators',
77        'commentUrn',
78        'commentAuthor',
79        'commentContent',
80        'commentEditor',
81        'replyButton',
82        'commentList',
83        'reactions',
84    ];
85
86    /**
87     * Required sub-keys within linkedin_selectors.detection.
88     */
89    public const LINKEDIN_DETECTION_KEYS = [
90        'isInsideLinkedInPost',
91        'isCommentEditor',
92        'isReplyEditor',
93        'isInsideLinkedInArticle',
94        'isPostEditor',
95    ];
96
97    /**
98     * The attributes that are mass assignable.
99     *
100     * @var array<string>
101     */
102    protected $fillable = [
103        'version',
104        'linkedin_selectors',
105        'outlook_domains',
106        'timing',
107        'feature_flags',
108        'features',
109        'playlists',
110        'blacklist_config',
111        'ui_visibility',
112        'field_injection',
113        'force_plain_text',
114        'display_strategy_grammar',
115        'display_strategy_magic_wand',
116        'display_strategy_paragraph',
117        'updated_by',
118    ];
119
120    /**
121     * The attributes that should be cast.
122     *
123     * @var array<string, string>
124     */
125    protected $casts = [
126        'linkedin_selectors' => 'array',
127        'outlook_domains' => 'array',
128        'timing' => 'array',
129        'feature_flags' => 'array',
130        'features' => 'array',
131        'playlists' => 'array',
132        'blacklist_config' => 'array',
133        'ui_visibility' => 'array',
134        'field_injection' => 'array',
135        'force_plain_text' => 'array',
136        'display_strategy_grammar' => 'array',
137        'display_strategy_magic_wand' => 'array',
138        'display_strategy_paragraph' => 'array',
139    ];
140
141    /**
142     * Check if any of the 8 sections has a non-null value.
143     */
144    public function hasAnySection(): bool
145    {
146        foreach (self::SECTIONS as $section) {
147            if ($this->{$section} !== null) {
148                return true;
149            }
150        }
151
152        return false;
153    }
154}