annotate core/modules/media_library/src/MediaLibraryUiBuilder.php @ 5:12f9dff5fda9 tip

Update to Drupal core 8.7.1
author Chris Cannam
date Thu, 09 May 2019 15:34:47 +0100
parents
children
rev   line source
Chris@5 1 <?php
Chris@5 2
Chris@5 3 namespace Drupal\media_library;
Chris@5 4
Chris@5 5 use Drupal\Core\Access\AccessResult;
Chris@5 6 use Drupal\Core\Form\FormBuilderInterface;
Chris@5 7 use Drupal\Core\Form\FormState;
Chris@5 8 use Drupal\Core\Entity\EntityTypeManagerInterface;
Chris@5 9 use Drupal\Core\Session\AccountInterface;
Chris@5 10 use Drupal\Core\StringTranslation\StringTranslationTrait;
Chris@5 11 use Drupal\Core\Url;
Chris@5 12 use Drupal\views\ViewExecutableFactory;
Chris@5 13 use Symfony\Component\HttpFoundation\RequestStack;
Chris@5 14 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
Chris@5 15
Chris@5 16 /**
Chris@5 17 * Service which builds the media library.
Chris@5 18 *
Chris@5 19 * @internal
Chris@5 20 * Media Library is an experimental module and its internal code may be
Chris@5 21 * subject to change in minor releases. External code should not instantiate
Chris@5 22 * or extend this class.
Chris@5 23 */
Chris@5 24 class MediaLibraryUiBuilder {
Chris@5 25
Chris@5 26 use StringTranslationTrait;
Chris@5 27
Chris@5 28 /**
Chris@5 29 * The form builder.
Chris@5 30 *
Chris@5 31 * @var \Drupal\Core\Form\FormBuilderInterface
Chris@5 32 */
Chris@5 33 protected $formBuilder;
Chris@5 34
Chris@5 35 /**
Chris@5 36 * The entity type manager.
Chris@5 37 *
Chris@5 38 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
Chris@5 39 */
Chris@5 40 protected $entityTypeManager;
Chris@5 41
Chris@5 42 /**
Chris@5 43 * The currently active request object.
Chris@5 44 *
Chris@5 45 * @var \Symfony\Component\HttpFoundation\Request
Chris@5 46 */
Chris@5 47 protected $request;
Chris@5 48
Chris@5 49 /**
Chris@5 50 * The views executable factory.
Chris@5 51 *
Chris@5 52 * @var \Drupal\views\ViewExecutableFactory
Chris@5 53 */
Chris@5 54 protected $viewsExecutableFactory;
Chris@5 55
Chris@5 56 /**
Chris@5 57 * Constructs a MediaLibraryUiBuilder instance.
Chris@5 58 *
Chris@5 59 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
Chris@5 60 * The entity type manager.
Chris@5 61 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
Chris@5 62 * The request stack.
Chris@5 63 * @param \Drupal\views\ViewExecutableFactory $views_executable_factory
Chris@5 64 * The views executable factory.
Chris@5 65 * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
Chris@5 66 * The currently active request object.
Chris@5 67 */
Chris@5 68 public function __construct(EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack, ViewExecutableFactory $views_executable_factory, FormBuilderInterface $form_builder) {
Chris@5 69 $this->entityTypeManager = $entity_type_manager;
Chris@5 70 $this->request = $request_stack->getCurrentRequest();
Chris@5 71 $this->viewsExecutableFactory = $views_executable_factory;
Chris@5 72 $this->formBuilder = $form_builder;
Chris@5 73 }
Chris@5 74
Chris@5 75 /**
Chris@5 76 * Get media library dialog options.
Chris@5 77 *
Chris@5 78 * @return array
Chris@5 79 * The media library dialog options.
Chris@5 80 */
Chris@5 81 public static function dialogOptions() {
Chris@5 82 return [
Chris@5 83 'dialogClass' => 'media-library-widget-modal',
Chris@5 84 'title' => t('Add or select media'),
Chris@5 85 'height' => '75%',
Chris@5 86 'width' => '75%',
Chris@5 87 ];
Chris@5 88 }
Chris@5 89
Chris@5 90 /**
Chris@5 91 * Build the media library UI.
Chris@5 92 *
Chris@5 93 * @param \Drupal\media_library\MediaLibraryState $state
Chris@5 94 * (optional) The current state of the media library, derived from the
Chris@5 95 * current request.
Chris@5 96 *
Chris@5 97 * @return array
Chris@5 98 * The render array for the media library.
Chris@5 99 */
Chris@5 100 public function buildUi(MediaLibraryState $state = NULL) {
Chris@5 101 if (!$state) {
Chris@5 102 $state = MediaLibraryState::fromRequest($this->request);
Chris@5 103 }
Chris@5 104 // When navigating to a media type through the vertical tabs, we only want
Chris@5 105 // to load the changed library content. This is not only more efficient, but
Chris@5 106 // also provides a more accessible user experience for screen readers.
Chris@5 107 if ($state->get('media_library_content') === '1') {
Chris@5 108 return $this->buildLibraryContent($state);
Chris@5 109 }
Chris@5 110 else {
Chris@5 111 return [
Chris@5 112 '#type' => 'html_tag',
Chris@5 113 '#tag' => 'div',
Chris@5 114 '#attributes' => [
Chris@5 115 'id' => 'media-library-wrapper',
Chris@5 116 'class' => ['media-library-wrapper'],
Chris@5 117 ],
Chris@5 118 'menu' => $this->buildMediaTypeMenu($state),
Chris@5 119 'content' => $this->buildLibraryContent($state),
Chris@5 120 // Attach the JavaScript for the media library UI. The number of
Chris@5 121 // available slots needs to be added to make sure users can't select
Chris@5 122 // more items than allowed.
Chris@5 123 '#attached' => [
Chris@5 124 'library' => ['media_library/ui'],
Chris@5 125 'drupalSettings' => [
Chris@5 126 'media_library' => [
Chris@5 127 'selection_remaining' => $state->getAvailableSlots(),
Chris@5 128 ],
Chris@5 129 ],
Chris@5 130 ],
Chris@5 131 ];
Chris@5 132 }
Chris@5 133 }
Chris@5 134
Chris@5 135 /**
Chris@5 136 * Build the media library content area.
Chris@5 137 *
Chris@5 138 * @param \Drupal\media_library\MediaLibraryState $state
Chris@5 139 * The current state of the media library, derived from the current request.
Chris@5 140 *
Chris@5 141 * @return array
Chris@5 142 * The render array for the media library.
Chris@5 143 */
Chris@5 144 protected function buildLibraryContent(MediaLibraryState $state) {
Chris@5 145 return [
Chris@5 146 '#type' => 'html_tag',
Chris@5 147 '#tag' => 'div',
Chris@5 148 '#attributes' => [
Chris@5 149 'id' => 'media-library-content',
Chris@5 150 'class' => ['media-library-content'],
Chris@5 151 'tabindex' => -1,
Chris@5 152 ],
Chris@5 153 'form' => $this->buildMediaTypeAddForm($state),
Chris@5 154 'view' => $this->buildMediaLibraryView($state),
Chris@5 155 ];
Chris@5 156 }
Chris@5 157
Chris@5 158 /**
Chris@5 159 * Check access to the media library.
Chris@5 160 *
Chris@5 161 * @param \Drupal\Core\Session\AccountInterface $account
Chris@5 162 * (optional) Run access checks for this account.
Chris@5 163 * @param \Drupal\media_library\MediaLibraryState $state
Chris@5 164 * (optional) The current state of the media library, derived from the
Chris@5 165 * current request.
Chris@5 166 *
Chris@5 167 * @return \Drupal\Core\Access\AccessResult
Chris@5 168 * The access result.
Chris@5 169 */
Chris@5 170 public function checkAccess(AccountInterface $account = NULL, MediaLibraryState $state = NULL) {
Chris@5 171 if (!$state) {
Chris@5 172 try {
Chris@5 173 MediaLibraryState::fromRequest($this->request);
Chris@5 174 }
Chris@5 175 catch (BadRequestHttpException $e) {
Chris@5 176 return AccessResult::forbidden($e->getMessage());
Chris@5 177 }
Chris@5 178 catch (\InvalidArgumentException $e) {
Chris@5 179 return AccessResult::forbidden($e->getMessage());
Chris@5 180 }
Chris@5 181 }
Chris@5 182 // Deny access if the view or display are removed.
Chris@5 183 $view = $this->entityTypeManager->getStorage('view')->load('media_library');
Chris@5 184 if (!$view) {
Chris@5 185 return AccessResult::forbidden('The media library view does not exist.')
Chris@5 186 ->setCacheMaxAge(0);
Chris@5 187 }
Chris@5 188 if (!$view->getDisplay('widget')) {
Chris@5 189 return AccessResult::forbidden('The media library widget display does not exist.')
Chris@5 190 ->addCacheableDependency($view);
Chris@5 191 }
Chris@5 192 return AccessResult::allowedIfHasPermission($account, 'view media')
Chris@5 193 ->addCacheableDependency($view);
Chris@5 194 }
Chris@5 195
Chris@5 196 /**
Chris@5 197 * Get the media type menu for the media library.
Chris@5 198 *
Chris@5 199 * @param \Drupal\media_library\MediaLibraryState $state
Chris@5 200 * The current state of the media library, derived from the current request.
Chris@5 201 *
Chris@5 202 * @return array
Chris@5 203 * The render array for the media type menu.
Chris@5 204 */
Chris@5 205 protected function buildMediaTypeMenu(MediaLibraryState $state) {
Chris@5 206 // Add the menu for each type if we have more than 1 media type enabled for
Chris@5 207 // the field.
Chris@5 208 $allowed_type_ids = $state->getAllowedTypeIds();
Chris@5 209 if (count($allowed_type_ids) <= 1) {
Chris@5 210 return [];
Chris@5 211 }
Chris@5 212
Chris@5 213 // @todo: Add a class to the li element.
Chris@5 214 // https://www.drupal.org/project/drupal/issues/3029227
Chris@5 215 $menu = [
Chris@5 216 '#theme' => 'links',
Chris@5 217 '#links' => [],
Chris@5 218 '#attributes' => [
Chris@5 219 'class' => ['media-library-menu', 'js-media-library-menu'],
Chris@5 220 ],
Chris@5 221 ];
Chris@5 222
Chris@5 223 $allowed_types = $this->entityTypeManager->getStorage('media_type')->loadMultiple($allowed_type_ids);
Chris@5 224
Chris@5 225 $selected_type_id = $state->getSelectedTypeId();
Chris@5 226 foreach ($allowed_types as $allowed_type_id => $allowed_type) {
Chris@5 227 $link_state = MediaLibraryState::create($state->getOpenerId(), $state->getAllowedTypeIds(), $allowed_type_id, $state->getAvailableSlots());
Chris@5 228 // Add the 'media_library_content' parameter so the response will contain
Chris@5 229 // only the updated content for the tab.
Chris@5 230 // @see self::buildUi()
Chris@5 231 $link_state->set('media_library_content', 1);
Chris@5 232
Chris@5 233 $title = $allowed_type->label();
Chris@5 234 if ($allowed_type_id === $selected_type_id) {
Chris@5 235 $title = [
Chris@5 236 '#markup' => $this->t('@title<span class="active-tab visually-hidden"> (active tab)</span>', ['@title' => $title]),
Chris@5 237 ];
Chris@5 238 }
Chris@5 239
Chris@5 240 $menu['#links']['media-library-menu-' . $allowed_type_id] = [
Chris@5 241 'title' => $title,
Chris@5 242 'url' => Url::fromRoute('media_library.ui', [], [
Chris@5 243 'query' => $link_state->all(),
Chris@5 244 ]),
Chris@5 245 'attributes' => [
Chris@5 246 'class' => ['media-library-menu__link'],
Chris@5 247 ],
Chris@5 248 ];
Chris@5 249 }
Chris@5 250
Chris@5 251 // Set the active menu item.
Chris@5 252 $menu['#links']['media-library-menu-' . $selected_type_id]['attributes']['class'][] = 'active';
Chris@5 253
Chris@5 254 return $menu;
Chris@5 255 }
Chris@5 256
Chris@5 257 /**
Chris@5 258 * Get the add form for the selected media type.
Chris@5 259 *
Chris@5 260 * @param \Drupal\media_library\MediaLibraryState $state
Chris@5 261 * The current state of the media library, derived from the current request.
Chris@5 262 *
Chris@5 263 * @return array
Chris@5 264 * The render array for the media type add form.
Chris@5 265 */
Chris@5 266 protected function buildMediaTypeAddForm(MediaLibraryState $state) {
Chris@5 267 $selected_type_id = $state->getSelectedTypeId();
Chris@5 268
Chris@5 269 if (!$this->entityTypeManager->getAccessControlHandler('media')->createAccess($selected_type_id)) {
Chris@5 270 return [];
Chris@5 271 }
Chris@5 272
Chris@5 273 $selected_type = $this->entityTypeManager->getStorage('media_type')->load($selected_type_id);
Chris@5 274 $plugin_definition = $selected_type->getSource()->getPluginDefinition();
Chris@5 275
Chris@5 276 if (empty($plugin_definition['forms']['media_library_add'])) {
Chris@5 277 return [];
Chris@5 278 }
Chris@5 279
Chris@5 280 // After the form to add new media is submitted, we need to rebuild the
Chris@5 281 // media library with a new instance of the media add form. The form API
Chris@5 282 // allows us to do that by forcing empty user input.
Chris@5 283 // @see \Drupal\Core\Form\FormBuilder::doBuildForm()
Chris@5 284 $form_state = new FormState();
Chris@5 285 if ($state->get('_media_library_form_rebuild')) {
Chris@5 286 $form_state->setUserInput([]);
Chris@5 287 $state->remove('_media_library_form_rebuild');
Chris@5 288 }
Chris@5 289 $form_state->set('media_library_state', $state);
Chris@5 290 return $this->formBuilder->buildForm($plugin_definition['forms']['media_library_add'], $form_state);
Chris@5 291 }
Chris@5 292
Chris@5 293 /**
Chris@5 294 * Get the media library view.
Chris@5 295 *
Chris@5 296 * @param \Drupal\media_library\MediaLibraryState $state
Chris@5 297 * The current state of the media library, derived from the current request.
Chris@5 298 *
Chris@5 299 * @return array
Chris@5 300 * The render array for the media library view.
Chris@5 301 */
Chris@5 302 protected function buildMediaLibraryView(MediaLibraryState $state) {
Chris@5 303 // @todo Make the view configurable in
Chris@5 304 // https://www.drupal.org/project/drupal/issues/2971209
Chris@5 305 $view = $this->entityTypeManager->getStorage('view')->load('media_library');
Chris@5 306 $view_executable = $this->viewsExecutableFactory->get($view);
Chris@5 307 $display_id = 'widget';
Chris@5 308
Chris@5 309 // Make sure the state parameters are set in the request so the view can
Chris@5 310 // pass the parameters along in the pager, filters etc.
Chris@5 311 $view_request = $view_executable->getRequest();
Chris@5 312 $view_request->query->add($state->all());
Chris@5 313 $view_executable->setRequest($view_request);
Chris@5 314
Chris@5 315 $args = [$state->getSelectedTypeId()];
Chris@5 316
Chris@5 317 // Make sure the state parameters are set in the request so the view can
Chris@5 318 // pass the parameters along in the pager, filters etc.
Chris@5 319 $request = $view_executable->getRequest();
Chris@5 320 $request->query->add($state->all());
Chris@5 321 $view_executable->setRequest($request);
Chris@5 322
Chris@5 323 $view_executable->setDisplay($display_id);
Chris@5 324 $view_executable->preExecute($args);
Chris@5 325 $view_executable->execute($display_id);
Chris@5 326
Chris@5 327 return $view_executable->buildRenderable($display_id, $args, FALSE);
Chris@5 328 }
Chris@5 329
Chris@5 330 }