PluginProbe
Metricool – Social media and site statistics / 2.1.1
Metricool – Social media and site statistics v2.1.1
2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
← All changes | app/Features/UserSettings/UserSettingsService.php +140 -0 2.0.0 → 2.1.1 View file →
@@ -1,0 +1,140 @@
1 +<?php
2 +
3 +declare(strict_types=1);
4 +
5 +namespace Metricool\Features\UserSettings;
6 +
7 +use Metricool\Features\UserSettings\Exceptions\StorageSubmitException;
8 +use Metricool\Features\UserSettings\Exceptions\ValidationFailedExceptions;
9 +use Metricool\Features\UserSettings\Exceptions\ValidatorFailedException;
10 +use Metricool\Features\UserSettings\Fields\Field;
11 +use Metricool\Support\Helpers\Collection;
12 +
13 +/**
14 + * This service is responsible for storing and retrieving user settings.
15 + */
16 +class UserSettingsService
17 +{
18 + /**
19 + * The fields from config/user_settings.php converted to {@see Field}
20 + * instances grouped in a Collection
21 + * @var Collection|Field[]
22 + */
23 + public Collection $fields;
24 +
25 + /**
26 + * Property is used to keep track of unique field storages when storing
27 + * settings, so we can submit each storage only once.
28 + */
29 + private array $uniqueFieldStorages = [];
30 +
31 + public function __construct(Collection $fields)
32 + {
33 + $this->fields = $fields;
34 + }
35 +
36 + public function getSettings(): Collection
37 + {
38 + return $this->fields;
39 + }
40 +
41 + /**
42 + * Return an array with keys and values of all the settings, optionally
43 + * filtered by section.
44 + * @throws \Exception
45 + */
46 + public function getSettingsResponse(?string $section = null): array
47 + {
48 + $response = new UserSettingsResponse($this->fields);
49 +
50 + if (!empty($section)) {
51 + $response->setSection($section);
52 + }
53 +
54 + return $response->parse()->get();
55 + }
56 +
57 + /**
58 + * Validate and update settings and return their updated values. If any
59 + * field storage is submittable, it will be submitted after all fields are
60 + * validated.
61 + *
62 + * @throws ValidationFailedExceptions with all the validation errors when validation fails
63 + * @throws StorageSubmitException when it fails to store data to it's storage
64 + */
65 + public function storeSettings(array $requestData, \WP_REST_Request $request): Collection
66 + {
67 + $validationErrors = [];
68 + $userSettings = $this->fields->whereIn('name', array_keys($requestData));
69 +
70 + foreach ($userSettings as &$field) {
71 + $value = $requestData[$field->getName()];
72 +
73 + try {
74 + $field->setValue($value, $request);
75 + } catch (ValidatorFailedException $e) {
76 + $validationErrors[$field->getName()] = $e;
77 + continue;
78 + }
79 +
80 + // Store the validated setting for later submission
81 + $this->storeValidatedSetting($field, $value);
82 + }
83 +
84 + // After all fields are validated, save the settings through their
85 + // storages
86 + $this->saveValidatedSettings();
87 +
88 + // Still throw validation errors if any, this should not block storage
89 + // submission for valid fields
90 + if (count($validationErrors) > 0) {
91 + // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- $validationErrors contains ValidatorFailedException objects with pre-escaped messages.
92 + throw new ValidationFailedExceptions($validationErrors);
93 + }
94 +
95 + return $userSettings;
96 + }
97 +
98 + /**
99 + * Store the validated value into the field's storage and keep track of
100 + * unique storages to submit later with {@see saveSettings()}
101 + * @param mixed $value
102 + */
103 + private function storeValidatedSetting(Field $field, $value): void
104 + {
105 + $field->storage->set($field->getSettingName(), $value);
106 +
107 + // Remember the unique storages to submit later with saveSettings()
108 + if (empty($this->uniqueFieldStorages[$field->getStorageName()])) {
109 + $this->uniqueFieldStorages[$field->getStorageName()] = $field->getStorage();
110 + }
111 + }
112 +
113 + /**
114 + * Save all updated unique storages that were mapped in
115 + * {@see storeValidatedSetting}. The method collects any errors and throws a
116 + * single exception if any storage submission fails.
117 + *
118 + * @throws StorageSubmitException when any storage submission fails - is
119 + * caught in {@see UserSettingsEndpoint} and the message we return here is
120 + * only shown to the user has WP_DEBUG set to true.
121 + */
122 + private function saveValidatedSettings(): void
123 + {
124 + $requestErrors = [];
125 + foreach ($this->uniqueFieldStorages as $storage) {
126 + try {
127 + $storage->save();
128 + } catch (\Exception $e) {
129 + $requestErrors[$storage->name] = $e->getMessage();
130 + continue; // So we can try to submit other storages
131 + }
132 + }
133 +
134 + if (count($requestErrors) > 0) {
135 + $exception = new StorageSubmitException(__('Something went wrong while submitting the settings!', 'metricool'));
136 + $exception->setErrors($requestErrors);
137 + throw $exception;
138 + }
139 + }
140 +}