Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
58.91% covered (warning)
58.91%
76 / 129
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
MetaDataController
58.91% covered (warning)
58.91%
76 / 129
20.00% covered (danger)
20.00%
1 / 5
77.56
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 checkQuota
90.00% covered (success)
90.00%
45 / 50
0.00% covered (danger)
0.00%
0 / 1
11.12
 applyParameterDefaults
93.75% covered (success)
93.75%
30 / 32
0.00% covered (danger)
0.00%
0 / 1
8.02
 publicMetaData
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 checkOnlyQuota
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace App\Http\Controllers\v2;
4
5use App\Http\Controllers\Controller;
6use App\Http\Models\FlyGrammarLanguage;
7use App\Http\Services\InAppNotificationService;
8use App\Http\Services\ParameterService;
9use App\Http\Services\RemoteConfigService;
10use App\Http\Services\UserFieldInjectionService;
11use App\Http\Services\WPSService;
12use App\Services\FlyMsgAI\FlyMsgAIService;
13use App\Traits\SubscriptionTrait;
14use Illuminate\Http\JsonResponse;
15use Illuminate\Http\Request;
16
17class MetaDataController extends Controller
18{
19    use SubscriptionTrait;
20
21    public function __construct(
22        private readonly FlyMsgAIService $ai_service,
23        private readonly WPSService $wpsService,
24        private readonly ParameterService $parameterService,
25        private readonly RemoteConfigService $remoteConfigService,
26        private readonly UserFieldInjectionService $userFieldInjectionService,
27        private readonly InAppNotificationService $inAppNotificationService
28    ) {}
29
30    public function checkQuota(Request $request): JsonResponse
31    {
32        $user = $request->user();
33
34        $currentSubscriptionPlan = $this->getCurrentPlan($user);
35        $quota = $this->getFlyAIQuota($user);
36        $flycuts_quota = $this->getFlyCutsQuota($user);
37        $promptsQuota = $this->getPromptQuota($user);
38        $grammarQuota = $this->getGrammarQuota($user);
39
40        $flyGrammarLanguagesAvailable = FlyGrammarLanguage::select(['value', 'description'])->orderBy('index')
41            ->get()->map(function ($item) {
42                return [
43                    'id' => $item->value,
44                    'value' => $item->description,
45                ];
46            })->toArray();
47
48        $plan = $this->getCurrentPlan($user);
49        $show_upgrade_button = empty($user->company_id) && $plan->identifier !== 'sales-pro-monthly' && $plan->identifier !== 'sales-pro-yearly';
50
51        $meta = [
52            'is_enterprise' => ! empty($user->company_id) && $user->status !== 'Invited',
53            'show_upgrade_button' => $show_upgrade_button,
54            'quota' => $quota,
55            'prompts_quota' => $promptsQuota,
56            'flycuts_quota' => $flycuts_quota,
57            'grammar_quota' => $grammarQuota,
58            'plan' => $currentSubscriptionPlan,
59            'fly_grammar_languages_available' => $flyGrammarLanguagesAvailable,
60            'user_dictionary' => $this->wpsService->getUserDictionary($user),
61        ];
62
63        // Load all metadata parameters dynamically (single cached query)
64        $metadataParams = $this->parameterService->getMetadataParameters();
65        foreach ($metadataParams as $param) {
66            $meta[$param->metadata_key] = $param->value;
67        }
68
69        // Expose per-user developer mode flag so the client can override global remote config
70        $meta['developer_mode'] = (bool) ($user->developer_mode ?? false);
71
72        // Apply special handling for parameters with default fallbacks
73        $this->applyParameterDefaults($meta);
74
75        // Grammar quota override: disable autocorrect/autocomplete when quota is limited
76        if ($grammarQuota['remaining'] >= 0 && $grammarQuota['total'] !== -1) {
77            $meta['wsc_autocorrect'] = false;
78            $meta['wsc_autocomplete'] = false;
79        }
80
81        $remoteConfig = $this->remoteConfigService->getForMetaData();
82        if ($remoteConfig !== null) {
83            $remoteConfig = $this->userFieldInjectionService->mergeWithGlobalConfig(
84                $remoteConfig, $user->_id
85            );
86            $meta['remote_config'] = $remoteConfig;
87        } else {
88            $userOverrides = $this->userFieldInjectionService->getUserFieldInjectionFresh($user->_id);
89            if ($userOverrides && ! empty($userOverrides->field_injection)) {
90                $meta['remote_config'] = ['field_injection' => $userOverrides->field_injection];
91            }
92        }
93
94        $notifications = $this->inAppNotificationService->getAndMarkForUser(
95            (string) $user->_id,
96            $currentSubscriptionPlan['identifier'] ?? ''
97        );
98        if (! empty($notifications)) {
99            $meta['in_app_notifications'] = $notifications;
100        }
101
102        return response()->json($meta);
103    }
104
105    /**
106     * Apply default values for parameters that require special handling.
107     *
108     * @param  array<string, mixed>  $meta  The metadata array to modify
109     */
110    private function applyParameterDefaults(array &$meta): void
111    {
112        // fly_grammar_update_prompt has a default message if not set
113        if (! isset($meta['fly_grammar_update_prompt']) || $meta['fly_grammar_update_prompt'] === null) {
114            $meta['fly_grammar_update_prompt'] = ['Work smarter, not harder.'];
115        }
116
117        // fly_grammar_disabled_on_flymsg defaults to false if not set
118        if (! isset($meta['fly_grammar_disabled_on_flymsg'])) {
119            $meta['fly_grammar_disabled_on_flymsg'] = false;
120        }
121
122        // fly_cut_update_coupons has a default coupon array if not set
123        if (isset($meta['fly_cut_update_coupons']) && $meta['fly_cut_update_coupons'] !== null) {
124            $meta['fly_cut_update_coupons'] = collect($meta['fly_cut_update_coupons'])->sortBy('order')->values()->all();
125        } else {
126            $meta['fly_cut_update_coupons'] = [
127                [
128                    'title' => 'Never Hit a Limit Again!',
129                    'description' => "You've hit your {quota} daily text expansion limit. Your next refill is in {next_refill}. See how many times we've deployed a FlyCut:",
130                    'code' => '30OFF',
131                    'cta' => 'Get 30% Off on Year 1!',
132                    'cta_instruction' => 'On Page 2 of Checkout, Use this Code (click code to copy):',
133                    'cta_url' => config('romeo.frontend-base-url').'/new-plan/YearlyGrowth',
134                    'color' => '#2A1E36',
135                    'order' => 1,
136                ],
137            ];
138        }
139
140        // fly_grammar_update_coupons has a default coupon array if not set
141        if (isset($meta['fly_grammar_update_coupons']) && $meta['fly_grammar_update_coupons'] !== null) {
142            $meta['fly_grammar_update_coupons'] = collect($meta['fly_grammar_update_coupons'])->sortBy('order')->values()->all();
143        } else {
144            $meta['fly_grammar_update_coupons'] = [
145                [
146                    'title' => 'Unlock Unlimited Grammar Perfection!',
147                    'description' => "You've hit your {quota} daily spelling and grammar fixes. Your next refill is in {next_refill}. See how many improvements we've helped you make:",
148                    'code' => '30OFF',
149                    'cta' => 'Get 30% Off on Year 1!',
150                    'cta_instruction' => 'On Page 2 of Checkout, Use this Code (click code to copy):',
151                    'cta_url' => config('romeo.frontend-base-url').'/new-plan/YearlyGrowth',
152                    'color' => '#F15A2A',
153                    'order' => 1,
154                ],
155            ];
156        }
157    }
158
159    /**
160     * Get public metadata that does not require authentication.
161     *
162     * Returns general, non-user-specific configuration data intended for
163     * use by the extension before a user session is established.
164     * Includes grammar languages, metadata parameters, parameter defaults,
165     * and the global remote config (without user field injection overrides).
166     */
167    public function publicMetaData(): JsonResponse
168    {
169        $flyGrammarLanguagesAvailable = FlyGrammarLanguage::select(['value', 'description'])->orderBy('index')
170            ->get()->map(function ($item) {
171                return [
172                    'id' => $item->value,
173                    'value' => $item->description,
174                ];
175            })->toArray();
176
177        $meta = [
178            'fly_grammar_languages_available' => $flyGrammarLanguagesAvailable,
179        ];
180
181        $metadataParams = $this->parameterService->getMetadataParameters();
182        foreach ($metadataParams as $param) {
183            $meta[$param->metadata_key] = $param->value;
184        }
185
186        $this->applyParameterDefaults($meta);
187
188        $remoteConfig = $this->remoteConfigService->getForMetaData();
189        if ($remoteConfig !== null) {
190            $meta['remote_config'] = $remoteConfig;
191        }
192
193        return response()->json($meta);
194    }
195
196    public function checkOnlyQuota(Request $request): JsonResponse
197    {
198        $user = $request->user();
199
200        $currentSubscriptionPlan = $this->getCurrentPlan($user);
201        $quota = $this->getFlyAIQuota($user);
202        $flycuts_quota = $this->getFlyCutsQuota($user);
203        $promptsQuota = $this->getPromptQuota($user);
204        $grammarQuota = $this->getGrammarQuota($user);
205
206        $flyGrammarLanguagesAvailable = FlyGrammarLanguage::select(['value', 'description'])->orderBy('index')
207            ->get()->map(function ($item) {
208                return [
209                    'id' => $item->value,
210                    'value' => $item->description,
211                ];
212            })->toArray();
213
214        $plan = $this->getCurrentPlan($user);
215        $show_upgrade_button = empty($user->company_id) && $plan->identifier !== 'sales-pro-monthly' && $plan->identifier !== 'sales-pro-yearly';
216
217        $meta = [
218            'is_enterprise' => ! empty($user->company_id) && $user->status !== 'Invited',
219            'show_upgrade_button' => $show_upgrade_button,
220            'quota' => $quota,
221            'prompts_quota' => $promptsQuota,
222            'flycuts_quota' => $flycuts_quota,
223            'grammar_quota' => $grammarQuota,
224            'developer_mode' => (bool) ($user->developer_mode ?? false),
225            'plan' => $currentSubscriptionPlan,
226            'fly_grammar_languages_available' => $flyGrammarLanguagesAvailable,
227            'user_dictionary' => $this->wpsService->getUserDictionary($user),
228        ];
229
230        return response()->json($meta);
231    }
232}