Chris@18
|
1 <?php
|
Chris@18
|
2
|
Chris@18
|
3 namespace Drupal\media_library;
|
Chris@18
|
4
|
Chris@18
|
5 use Drupal\Component\Utility\Crypt;
|
Chris@18
|
6 use Drupal\Core\Site\Settings;
|
Chris@18
|
7 use Symfony\Component\HttpFoundation\ParameterBag;
|
Chris@18
|
8 use Symfony\Component\HttpFoundation\Request;
|
Chris@18
|
9 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
Chris@18
|
10
|
Chris@18
|
11 /**
|
Chris@18
|
12 * A value object for the media library state.
|
Chris@18
|
13 *
|
Chris@18
|
14 * When the media library is opened it needs several parameters to work
|
Chris@18
|
15 * properly. The parameters are retrieved from the MediaLibraryState value
|
Chris@18
|
16 * object. Since the parameters are passed via the URL, the value object is
|
Chris@18
|
17 * extended from ParameterBag. This also allows an opener to add extra
|
Chris@18
|
18 * parameters if needed. The following parameters are needed to open the media
|
Chris@18
|
19 * library:
|
Chris@18
|
20 * - media_library_opener_id: The opener ID is used to describe the "thing" that
|
Chris@18
|
21 * opened the media library. Most of the time this is going to be a form
|
Chris@18
|
22 * field.
|
Chris@18
|
23 * - media_library_allowed_types: The media types available in the library can
|
Chris@18
|
24 * be restricted to a list of allowed types. This should be an array of media
|
Chris@18
|
25 * type IDs.
|
Chris@18
|
26 * - media_library_selected_type: The media library contains tabs to navigate
|
Chris@18
|
27 * between the different media types. The selected type contains the ID of the
|
Chris@18
|
28 * media type whose tab that should be opened.
|
Chris@18
|
29 * - media_library_remaining: When the opener wants to limit the amount of media
|
Chris@18
|
30 * items that can be selected, it can pass the number of remaining slots. When
|
Chris@18
|
31 * the number of remaining slots is a negative number, an unlimited amount of
|
Chris@18
|
32 * items can be selected.
|
Chris@18
|
33 *
|
Chris@18
|
34 * @internal
|
Chris@18
|
35 * Media Library is an experimental module and its internal code may be
|
Chris@18
|
36 * subject to change in minor releases. External code should not instantiate
|
Chris@18
|
37 * or extend this class.
|
Chris@18
|
38 */
|
Chris@18
|
39 class MediaLibraryState extends ParameterBag {
|
Chris@18
|
40
|
Chris@18
|
41 /**
|
Chris@18
|
42 * {@inheritdoc}
|
Chris@18
|
43 */
|
Chris@18
|
44 public function __construct(array $parameters = []) {
|
Chris@18
|
45 $this->validateParameters($parameters['media_library_opener_id'], $parameters['media_library_allowed_types'], $parameters['media_library_selected_type'], $parameters['media_library_remaining']);
|
Chris@18
|
46 parent::__construct($parameters);
|
Chris@18
|
47 // Add a hash to the state parameters.
|
Chris@18
|
48 $this->set('hash', $this->getHash());
|
Chris@18
|
49 }
|
Chris@18
|
50
|
Chris@18
|
51 /**
|
Chris@18
|
52 * Creates a new MediaLibraryState object.
|
Chris@18
|
53 *
|
Chris@18
|
54 * @param string $opener_id
|
Chris@18
|
55 * The opener ID.
|
Chris@18
|
56 * @param string[] $allowed_media_type_ids
|
Chris@18
|
57 * The allowed media type IDs.
|
Chris@18
|
58 * @param string $selected_type_id
|
Chris@18
|
59 * The selected media type ID.
|
Chris@18
|
60 * @param int $remaining_slots
|
Chris@18
|
61 * The number of remaining items the user is allowed to select or add in the
|
Chris@18
|
62 * library.
|
Chris@18
|
63 *
|
Chris@18
|
64 * @return \Drupal\media_library\MediaLibraryState
|
Chris@18
|
65 * A state object.
|
Chris@18
|
66 */
|
Chris@18
|
67 public static function create($opener_id, array $allowed_media_type_ids, $selected_type_id, $remaining_slots) {
|
Chris@18
|
68 $state = new static([
|
Chris@18
|
69 'media_library_opener_id' => $opener_id,
|
Chris@18
|
70 'media_library_allowed_types' => $allowed_media_type_ids,
|
Chris@18
|
71 'media_library_selected_type' => $selected_type_id,
|
Chris@18
|
72 'media_library_remaining' => $remaining_slots,
|
Chris@18
|
73 ]);
|
Chris@18
|
74 return $state;
|
Chris@18
|
75 }
|
Chris@18
|
76
|
Chris@18
|
77 /**
|
Chris@18
|
78 * Get the media library state from a request.
|
Chris@18
|
79 *
|
Chris@18
|
80 * @param \Symfony\Component\HttpFoundation\Request $request
|
Chris@18
|
81 * The request.
|
Chris@18
|
82 *
|
Chris@18
|
83 * @return \Drupal\media_library\MediaLibraryState
|
Chris@18
|
84 * A state object.
|
Chris@18
|
85 *
|
Chris@18
|
86 * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
|
Chris@18
|
87 * Thrown when the hash query parameter is invalid.
|
Chris@18
|
88 */
|
Chris@18
|
89 public static function fromRequest(Request $request) {
|
Chris@18
|
90 $query = $request->query;
|
Chris@18
|
91
|
Chris@18
|
92 // Create a MediaLibraryState object through the create method to make sure
|
Chris@18
|
93 // all validation runs.
|
Chris@18
|
94 $state = static::create(
|
Chris@18
|
95 $query->get('media_library_opener_id'),
|
Chris@18
|
96 $query->get('media_library_allowed_types'),
|
Chris@18
|
97 $query->get('media_library_selected_type'),
|
Chris@18
|
98 $query->get('media_library_remaining')
|
Chris@18
|
99 );
|
Chris@18
|
100
|
Chris@18
|
101 // The request parameters need to contain a valid hash to prevent a
|
Chris@18
|
102 // malicious user modifying the query string to attempt to access
|
Chris@18
|
103 // inaccessible information.
|
Chris@18
|
104 if (!$state->isValidHash($query->get('hash'))) {
|
Chris@18
|
105 throw new BadRequestHttpException("Invalid media library parameters specified.");
|
Chris@18
|
106 }
|
Chris@18
|
107
|
Chris@18
|
108 // Once we have validated the required parameters, we restore the parameters
|
Chris@18
|
109 // from the request since there might be additional values.
|
Chris@18
|
110 $state->replace($query->all());
|
Chris@18
|
111 return $state;
|
Chris@18
|
112 }
|
Chris@18
|
113
|
Chris@18
|
114 /**
|
Chris@18
|
115 * Validate the required parameters for a new MediaLibraryState object.
|
Chris@18
|
116 *
|
Chris@18
|
117 * @param string $opener_id
|
Chris@18
|
118 * The opener ID.
|
Chris@18
|
119 * @param string[] $allowed_media_type_ids
|
Chris@18
|
120 * The allowed media type IDs.
|
Chris@18
|
121 * @param string $selected_type_id
|
Chris@18
|
122 * The selected media type ID.
|
Chris@18
|
123 * @param int $remaining_slots
|
Chris@18
|
124 * The number of remaining items the user is allowed to select or add in the
|
Chris@18
|
125 * library.
|
Chris@18
|
126 *
|
Chris@18
|
127 * @throws \InvalidArgumentException
|
Chris@18
|
128 * If one of the passed arguments is missing or does not pass the
|
Chris@18
|
129 * validation.
|
Chris@18
|
130 */
|
Chris@18
|
131 protected function validateParameters($opener_id, array $allowed_media_type_ids, $selected_type_id, $remaining_slots) {
|
Chris@18
|
132 // The opener ID must be a non-empty string.
|
Chris@18
|
133 if (!is_string($opener_id) || empty(trim($opener_id))) {
|
Chris@18
|
134 throw new \InvalidArgumentException('The opener ID parameter is required and must be a string.');
|
Chris@18
|
135 }
|
Chris@18
|
136
|
Chris@18
|
137 // The allowed media type IDs must be an array of non-empty strings.
|
Chris@18
|
138 if (empty($allowed_media_type_ids) || !is_array($allowed_media_type_ids)) {
|
Chris@18
|
139 throw new \InvalidArgumentException('The allowed types parameter is required and must be an array of strings.');
|
Chris@18
|
140 }
|
Chris@18
|
141 foreach ($allowed_media_type_ids as $allowed_media_type_id) {
|
Chris@18
|
142 if (!is_string($allowed_media_type_id) || empty(trim($allowed_media_type_id))) {
|
Chris@18
|
143 throw new \InvalidArgumentException('The allowed types parameter is required and must be an array of strings.');
|
Chris@18
|
144 }
|
Chris@18
|
145 }
|
Chris@18
|
146
|
Chris@18
|
147 // The selected type ID must be a non-empty string.
|
Chris@18
|
148 if (!is_string($selected_type_id) || empty(trim($selected_type_id))) {
|
Chris@18
|
149 throw new \InvalidArgumentException('The selected type parameter is required and must be a string.');
|
Chris@18
|
150 }
|
Chris@18
|
151 // The selected type ID must be present in the list of allowed types.
|
Chris@18
|
152 if (!in_array($selected_type_id, $allowed_media_type_ids, TRUE)) {
|
Chris@18
|
153 throw new \InvalidArgumentException('The selected type parameter must be present in the list of allowed types.');
|
Chris@18
|
154 }
|
Chris@18
|
155
|
Chris@18
|
156 // The remaining slots must be numeric.
|
Chris@18
|
157 if (!is_numeric($remaining_slots)) {
|
Chris@18
|
158 throw new \InvalidArgumentException('The remaining slots parameter is required and must be numeric.');
|
Chris@18
|
159 }
|
Chris@18
|
160 }
|
Chris@18
|
161
|
Chris@18
|
162 /**
|
Chris@18
|
163 * Get the hash for the state object.
|
Chris@18
|
164 *
|
Chris@18
|
165 * @return string
|
Chris@18
|
166 * The hashed parameters.
|
Chris@18
|
167 */
|
Chris@18
|
168 public function getHash() {
|
Chris@18
|
169 // Create a hash from the required state parameters.
|
Chris@18
|
170 $hash = implode(':', [
|
Chris@18
|
171 $this->getOpenerId(),
|
Chris@18
|
172 implode(':', $this->getAllowedTypeIds()),
|
Chris@18
|
173 $this->getSelectedTypeId(),
|
Chris@18
|
174 $this->getAvailableSlots(),
|
Chris@18
|
175 ]);
|
Chris@18
|
176
|
Chris@18
|
177 return Crypt::hmacBase64($hash, \Drupal::service('private_key')->get() . Settings::getHashSalt());
|
Chris@18
|
178 }
|
Chris@18
|
179
|
Chris@18
|
180 /**
|
Chris@18
|
181 * Validate a hash for the state object.
|
Chris@18
|
182 *
|
Chris@18
|
183 * @param string $hash
|
Chris@18
|
184 * The hash to validate.
|
Chris@18
|
185 *
|
Chris@18
|
186 * @return string
|
Chris@18
|
187 * The hashed parameters.
|
Chris@18
|
188 */
|
Chris@18
|
189 public function isValidHash($hash) {
|
Chris@18
|
190 return Crypt::hashEquals($this->getHash(), $hash);
|
Chris@18
|
191 }
|
Chris@18
|
192
|
Chris@18
|
193 /**
|
Chris@18
|
194 * Returns the ID of the opener of the media library.
|
Chris@18
|
195 *
|
Chris@18
|
196 * @return string
|
Chris@18
|
197 * The opener ID.
|
Chris@18
|
198 */
|
Chris@18
|
199 public function getOpenerId() {
|
Chris@18
|
200 return $this->get('media_library_opener_id');
|
Chris@18
|
201 }
|
Chris@18
|
202
|
Chris@18
|
203 /**
|
Chris@18
|
204 * Returns the media type IDs which can be selected.
|
Chris@18
|
205 *
|
Chris@18
|
206 * @return string[]
|
Chris@18
|
207 * The media type IDs.
|
Chris@18
|
208 */
|
Chris@18
|
209 public function getAllowedTypeIds() {
|
Chris@18
|
210 return $this->get('media_library_allowed_types');
|
Chris@18
|
211 }
|
Chris@18
|
212
|
Chris@18
|
213 /**
|
Chris@18
|
214 * Returns the selected media type.
|
Chris@18
|
215 *
|
Chris@18
|
216 * @return string
|
Chris@18
|
217 * The selected media type.
|
Chris@18
|
218 */
|
Chris@18
|
219 public function getSelectedTypeId() {
|
Chris@18
|
220 return $this->get('media_library_selected_type');
|
Chris@18
|
221 }
|
Chris@18
|
222
|
Chris@18
|
223 /**
|
Chris@18
|
224 * Determines if additional media items can be selected.
|
Chris@18
|
225 *
|
Chris@18
|
226 * @return bool
|
Chris@18
|
227 * TRUE if additional items can be selected, otherwise FALSE.
|
Chris@18
|
228 */
|
Chris@18
|
229 public function hasSlotsAvailable() {
|
Chris@18
|
230 return $this->getAvailableSlots() !== 0;
|
Chris@18
|
231 }
|
Chris@18
|
232
|
Chris@18
|
233 /**
|
Chris@18
|
234 * Returns the number of additional media items that can be selected.
|
Chris@18
|
235 *
|
Chris@18
|
236 * When the value is not available in the URL the default is 0. When a
|
Chris@18
|
237 * negative integer is passed, an unlimited amount of media items can be
|
Chris@18
|
238 * selected.
|
Chris@18
|
239 *
|
Chris@18
|
240 * @return int
|
Chris@18
|
241 * The number of additional media items that can be selected.
|
Chris@18
|
242 */
|
Chris@18
|
243 public function getAvailableSlots() {
|
Chris@18
|
244 return $this->getInt('media_library_remaining');
|
Chris@18
|
245 }
|
Chris@18
|
246
|
Chris@18
|
247 }
|