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