annotate core/modules/media_library/src/MediaLibraryState.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
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 }