claro.theme 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416
  1. <?php
  2. /**
  3. * @file
  4. * Functions to support theming in the Claro theme.
  5. */
  6. use Drupal\claro\ClaroPreRender;
  7. use Drupal\Component\Utility\Html;
  8. use Drupal\Component\Utility\NestedArray;
  9. use Drupal\Component\Utility\UrlHelper;
  10. use Drupal\Core\Entity\EntityForm;
  11. use Drupal\Core\Form\FormStateInterface;
  12. use Drupal\Core\GeneratedLink;
  13. use Drupal\Core\Link;
  14. use Drupal\Core\Render\Element;
  15. use Drupal\Core\Template\Attribute;
  16. use Drupal\Core\Url;
  17. use Drupal\media\MediaForm;
  18. use Drupal\file\FileInterface;
  19. use Drupal\views\ViewExecutable;
  20. /**
  21. * Implements hook_theme_suggestions_HOOK_alter() for form_element.
  22. */
  23. function claro_theme_suggestions_form_element_alter(&$suggestions, $variables) {
  24. if (!empty($variables['element']['#type'])) {
  25. $suggestions[] = 'form_element__' . $variables['element']['#type'];
  26. }
  27. }
  28. /**
  29. * Implements hook_theme_suggestions_HOOK_alter() for details.
  30. */
  31. function claro_theme_suggestions_details_alter(&$suggestions, $variables) {
  32. if (!empty($variables['element']['#vertical_tab_item'])) {
  33. $suggestions[] = 'details__vertical_tabs';
  34. }
  35. }
  36. /**
  37. * Implements hook_preprocess_HOOK() for menu-local-tasks templates.
  38. *
  39. * Use preprocess hook to set #attached to child elements
  40. * because they will be processed by Twig and drupal_render will
  41. * be invoked.
  42. */
  43. function claro_preprocess_menu_local_tasks(&$variables) {
  44. if (!empty($variables['primary'])) {
  45. $variables['primary']['#attached'] = [
  46. 'library' => [
  47. 'claro/drupal.nav-tabs',
  48. ],
  49. ];
  50. }
  51. elseif (!empty($variables['secondary'])) {
  52. $variables['secondary']['#attached'] = [
  53. 'library' => [
  54. 'claro/drupal.nav-tabs',
  55. ],
  56. ];
  57. }
  58. foreach (Element::children($variables['primary']) as $key) {
  59. $variables['primary'][$key]['#level'] = 'primary';
  60. }
  61. foreach (Element::children($variables['secondary']) as $key) {
  62. $variables['secondary'][$key]['#level'] = 'secondary';
  63. }
  64. }
  65. /**
  66. * Implements hook_preprocess_HOOK() for menu-local-task templates.
  67. */
  68. function claro_preprocess_menu_local_task(&$variables) {
  69. $variables['link']['#options']['attributes']['class'][] = 'tabs__link';
  70. $variables['link']['#options']['attributes']['class'][] = 'js-tabs-link';
  71. // Ensure is-active class is set when the tab is active. The generic active
  72. // link handler applies stricter comparison rules than what is necessary for
  73. // tabs.
  74. if (isset($variables['is_active']) && $variables['is_active'] === TRUE) {
  75. $variables['link']['#options']['attributes']['class'][] = 'is-active';
  76. }
  77. if (isset($variables['element']['#level'])) {
  78. $variables['level'] = $variables['element']['#level'];
  79. }
  80. }
  81. /**
  82. * Implements hook_preprocess_HOOK() for menu-local-task Views UI templates.
  83. */
  84. function claro_preprocess_menu_local_task__views_ui(&$variables) {
  85. // Remove 'tabs__link' without adding a new class because it couldn't be used
  86. // reliably.
  87. // @see https://www.drupal.org/node/3051605
  88. $link_class_index = array_search('tabs__link', $variables['link']['#options']['attributes']['class']);
  89. if ($link_class_index !== FALSE) {
  90. unset($variables['link']['#options']['attributes']['class'][$link_class_index]);
  91. }
  92. }
  93. /**
  94. * Implements template_preprocess_HOOK() for node_add_list.
  95. *
  96. * Makes node_add_list variables compatible with entity_add_list.
  97. */
  98. function claro_preprocess_node_add_list(&$variables) {
  99. if (!empty($variables['content'])) {
  100. /** @var \Drupal\node\NodeTypeInterface $type */
  101. foreach ($variables['content'] as $type) {
  102. $label = $type->label();
  103. $description = $type->getDescription();
  104. $type_id = $type->id();
  105. $add_url = Url::fromRoute('node.add', ['node_type' => $type_id]);
  106. $variables['bundles'][$type_id] = [
  107. 'label' => $label,
  108. 'add_link' => Link::fromTextAndUrl($label, $add_url),
  109. 'description' => [],
  110. ];
  111. if (!empty($description)) {
  112. $variables['bundles'][$type_id]['description'] = [
  113. '#markup' => $description,
  114. ];
  115. }
  116. }
  117. $variables['attributes']['class'][] = 'node-type-list';
  118. }
  119. }
  120. /**
  121. * Implements template_preprocess_HOOK() for block_content_add_list.
  122. *
  123. * Makes block_content_add_list variables compatible with entity_add_list.
  124. */
  125. function claro_preprocess_block_content_add_list(&$variables) {
  126. if (!empty($variables['content'])) {
  127. $query = \Drupal::request()->query->all();
  128. /** @var \Drupal\block_content\BlockContentTypeInterface $type */
  129. foreach ($variables['content'] as $type) {
  130. $label = $type->label();
  131. $description = $type->getDescription();
  132. $type_id = $type->id();
  133. $add_url = Url::fromRoute('block_content.add_form', [
  134. 'block_content_type' => $type_id,
  135. ], [
  136. 'query' => $query,
  137. ]);
  138. $variables['bundles'][$type_id] = [
  139. 'label' => $label,
  140. 'add_link' => Link::fromTextAndUrl($label, $add_url),
  141. 'description' => [],
  142. ];
  143. if (!empty($description)) {
  144. $variables['bundles'][$type_id]['description'] = [
  145. '#markup' => $description,
  146. ];
  147. }
  148. }
  149. }
  150. }
  151. /**
  152. * Implements template_preprocess_HOOK() for entity_add_list.
  153. */
  154. function claro_preprocess_entity_add_list(&$variables) {
  155. // Remove description if empty.
  156. foreach ($variables['bundles'] as $type_id => $values) {
  157. if (isset($values['description']['#markup']) && empty($values['description']['#markup'])) {
  158. $variables['bundles'][$type_id]['description'] = [];
  159. }
  160. }
  161. }
  162. /**
  163. * Implements hook_preprocess_block() for block content.
  164. *
  165. * Disables contextual links for all blocks except for layout builder blocks.
  166. */
  167. function claro_preprocess_block(&$variables) {
  168. if (isset($variables['title_suffix']['contextual_links']) && !isset($variables['elements']['#contextual_links']['layout_builder_block'])) {
  169. unset($variables['title_suffix']['contextual_links']);
  170. unset($variables['elements']['#contextual_links']);
  171. $variables['attributes']['class'] = array_diff($variables['attributes']['class'], ['contextual-region']);
  172. }
  173. }
  174. /**
  175. * Implements template_preprocess_HOOK() for admin_block.
  176. */
  177. function claro_preprocess_admin_block(&$variables) {
  178. if (!empty($variables['block']['content'])) {
  179. $variables['block']['content']['#attributes']['class'][] = 'admin-list--panel';
  180. }
  181. }
  182. /**
  183. * Implements template_preprocess_HOOK() for admin_block.
  184. */
  185. function claro_preprocess_admin_block_content(&$variables) {
  186. foreach ($variables['content'] as &$item) {
  187. $link_attributes = $item['url']->getOption('attributes') ?: [];
  188. $link_attributes['class'][] = 'admin-item__link';
  189. $item['url']->setOption('attributes', $link_attributes);
  190. $item['link'] = Link::fromTextAndUrl($item['title'], $item['url']);
  191. if (empty($item['description']) || empty($item['description']['#markup'])) {
  192. unset($item['description']);
  193. }
  194. }
  195. }
  196. /**
  197. * Implements hook_preprocess_HOOK() for menu-local-action templates.
  198. */
  199. function claro_preprocess_menu_local_action(array &$variables) {
  200. $variables['link']['#options']['attributes']['class'][] = 'button--primary';
  201. $variables['attributes']['class'][] = 'local-actions__item';
  202. $legacy_class_key = array_search('button-action', $variables['link']['#options']['attributes']['class']);
  203. if ($legacy_class_key !== FALSE) {
  204. $variables['link']['#options']['attributes']['class'][$legacy_class_key] = 'button--action';
  205. }
  206. }
  207. /**
  208. * Implements hook_element_info_alter().
  209. */
  210. function claro_element_info_alter(&$type) {
  211. // Add a pre-render function that handles the sidebar of the node form.
  212. // @todo Refactor when https://www.drupal.org/node/3056089 is in.
  213. if (isset($type['container'])) {
  214. $container_pre_renders = !empty($type['container']['#pre_render']) ? $type['container']['#pre_render'] : [];
  215. array_unshift($container_pre_renders, [ClaroPreRender::class, 'container']);
  216. $type['container']['#pre_render'] = $container_pre_renders;
  217. }
  218. // @todo Refactor when https://www.drupal.org/node/3016343 is fixed.
  219. if (isset($type['text_format'])) {
  220. $type['text_format']['#pre_render'][] = [ClaroPreRender::class, 'textFormat'];
  221. }
  222. // Add a pre-render function that handles dropbutton variants.
  223. if (isset($type['dropbutton'])) {
  224. $type['dropbutton']['#pre_render'][] = [ClaroPreRender::class, 'dropButton'];
  225. }
  226. if (isset($type['vertical_tabs'])) {
  227. $type['vertical_tabs']['#pre_render'][] = [ClaroPreRender::class, 'verticalTabs'];
  228. }
  229. // Add a pre-render to managed_file.
  230. if (isset($type['managed_file'])) {
  231. $type['managed_file']['#pre_render'][] = [ClaroPreRender::class, 'managedFile'];
  232. }
  233. // Add a pre-render to status_messages to alter the placeholder markup.
  234. if (isset($type['status_messages'])) {
  235. $type['status_messages']['#pre_render'][] = [ClaroPreRender::class, 'messagePlaceholder'];
  236. }
  237. }
  238. /**
  239. * Implements template_preprocess_filter_guidelines().
  240. */
  241. function claro_preprocess_filter_guidelines(&$variables) {
  242. // Fix filter guidelines selector issue of 'filter/drupal.filter'.
  243. // @todo Remove when https://www.drupal.org/node/2881212 is fixed.
  244. $variables['attributes']['class'][] = 'filter-guidelines-item';
  245. $variables['attributes']['class'][] = 'filter-guidelines-' . $variables['format']->id();
  246. }
  247. /**
  248. * Implements template_preprocess_text_format_wrapper().
  249. *
  250. * @todo Remove when https://www.drupal.org/node/3016343 is fixed.
  251. */
  252. function claro_preprocess_text_format_wrapper(&$variables) {
  253. $description_attributes = [];
  254. if (!empty($variables['attributes']['id'])) {
  255. $description_attributes['id'] = $variables['attributes']['aria-describedby'] = $variables['attributes']['id'];
  256. unset($variables['attributes']['id']);
  257. }
  258. $variables['description_attributes'] = new Attribute($description_attributes);
  259. }
  260. /**
  261. * Implements hook_theme_registry_alter().
  262. */
  263. function claro_theme_registry_alter(&$theme_registry) {
  264. if (!empty($theme_registry['admin_block_content'])) {
  265. $theme_registry['admin_block_content']['variables']['attributes'] = [];
  266. }
  267. // @todo Remove when https://www.drupal.org/node/3016346 is fixed.
  268. if (!empty($theme_registry['text_format_wrapper'])) {
  269. $theme_registry['text_format_wrapper']['variables']['disabled'] = FALSE;
  270. }
  271. }
  272. /**
  273. * Implements hook_preprocess_install_page().
  274. */
  275. function claro_preprocess_install_page(&$variables) {
  276. // Claro has custom styling for the install page.
  277. $variables['#attached']['library'][] = 'claro/install-page';
  278. }
  279. /**
  280. * Implements hook_preprocess_maintenance_page().
  281. */
  282. function claro_preprocess_maintenance_page(&$variables) {
  283. // Claro has custom styling for the maintenance page.
  284. $variables['#attached']['library'][] = 'claro/maintenance-page';
  285. }
  286. /**
  287. * Implements hook_preprocess_HOOK() for details.
  288. *
  289. * @todo Revisit when https://www.drupal.org/node/3056089 is in.
  290. */
  291. function claro_preprocess_details(&$variables) {
  292. $element = $variables['element'];
  293. if (!empty($element['#accordion_item'])) {
  294. // Details should appear as an accordion item.
  295. $variables['accordion_item'] = TRUE;
  296. }
  297. if (!empty($element['#accordion'])) {
  298. // Details should appear as a standalone accordion.
  299. $variables['accordion'] = TRUE;
  300. }
  301. if (!empty($element['#theme']) && $element['#theme'] === 'file_widget_multiple') {
  302. // Mark the details required if needed. If the file widget allows uploading
  303. // multiple files, the required state is checked by checking the state of
  304. // the first child.
  305. $variables['required'] = isset($element[0]['#required']) ?
  306. $element[0]['#required'] :
  307. !empty($element['#required']);
  308. // If the error is the same as the one in the multiple field widget element,
  309. // we have to avoid displaying it twice. Seven and Stark have this issue
  310. // as well.
  311. // @todo revisit when https://www.drupal.org/node/3084906 is fixed.
  312. if (isset($element['#errors']) && isset($variables['errors']) && $element['#errors'] === $variables['errors']) {
  313. unset($variables['errors']);
  314. }
  315. }
  316. $variables['disabled'] = !empty($element['#disabled']);
  317. }
  318. /**
  319. * Implements hook_form_alter().
  320. */
  321. function claro_form_alter(array &$form, FormStateInterface $form_state, $form_id) {
  322. $build_info = $form_state->getBuildInfo();
  323. $form_object = $form_state->getFormObject();
  324. // Make entity forms delete link use the action-link component.
  325. if (isset($form['actions']['delete']['#type']) && $form['actions']['delete']['#type'] === 'link' && !empty($build_info['callback_object']) && $build_info['callback_object'] instanceof EntityForm) {
  326. $form['actions']['delete'] = _claro_convert_link_to_action_link($form['actions']['delete'], 'trash', 'default', 'danger');
  327. }
  328. if ($form_object instanceof ViewsForm && strpos($form_object->getBaseFormId(), 'views_form_media_library') === 0) {
  329. if (isset($form['header'])) {
  330. $form['header']['#attributes']['class'][] = 'media-library-views-form__header';
  331. $form['header']['media_bulk_form']['#attributes']['class'][] = 'media-library-views-form__bulk_form';
  332. }
  333. $form['actions']['submit']['#attributes']['class'] = ['media-library-select'];
  334. }
  335. }
  336. /**
  337. * Implements hook_preprocess_HOOK() for links.
  338. */
  339. function claro_preprocess_links(&$variables) {
  340. foreach ($variables['links'] as $links_item) {
  341. if (!empty($links_item['link']) && !empty($links_item['link']['#url']) && $links_item['link']['#url'] instanceof Url) {
  342. if ($links_item['link']['#url']->isRouted()) {
  343. switch ($links_item['link']['#url']->getRouteName()) {
  344. case 'system.theme_settings_theme':
  345. $links_item['link'] = _claro_convert_link_to_action_link($links_item['link'], 'cog', 'small');
  346. break;
  347. case 'system.theme_uninstall':
  348. $links_item['link'] = _claro_convert_link_to_action_link($links_item['link'], 'ex', 'small');
  349. break;
  350. case 'system.theme_set_default':
  351. case 'system.theme_install':
  352. $links_item['link'] = _claro_convert_link_to_action_link($links_item['link'], 'checkmark', 'small');
  353. break;
  354. }
  355. }
  356. }
  357. }
  358. }
  359. /**
  360. * Converts a link render element to an action link.
  361. *
  362. * This helper merges every attributes from $link['#attributes'], from
  363. * $link['#options']['attributes'] and from the Url object's.
  364. *
  365. * @param array $link
  366. * Link renderable array.
  367. * @param string|null $icon_name
  368. * The name of the needed icon. When specified, a CSS class will be added with
  369. * the following pattern: 'action-link--icon-[icon_name]'. If the needed icon
  370. * is not implemented in CSS, no icon will be added.
  371. * Currently available icons are:
  372. * - checkmark,
  373. * - cog,
  374. * - ex,
  375. * - plus,
  376. * - trash.
  377. * @param string $size
  378. * Name of the small action link variant. Defaults to 'default'.
  379. * Supported sizes are:
  380. * - default,
  381. * - small,
  382. * - extrasmall.
  383. * @param string $variant
  384. * Variant of the action link. Supported variants are 'default' and 'danger'.
  385. * Defaults to 'default'.
  386. *
  387. * @return array
  388. * The link renderable converted to action link.
  389. */
  390. function _claro_convert_link_to_action_link(array $link, $icon_name = NULL, $size = 'default', $variant = 'default') {
  391. // Early opt-out if we cannot do anything.
  392. if (empty($link['#type']) || $link['#type'] !== 'link' || empty($link['#url'])) {
  393. return $link;
  394. }
  395. // \Drupal\Core\Render\Element\Link::preRenderLink adds $link['#attributes']
  396. // to $link[#options]['attributes'] if it is not empty, but it does not merges
  397. // the 'class' subkey deeply.
  398. // Because of this, when $link[#options]['attributes']['class'] is set, the
  399. // classes defined in $link['#attributes']['class'] are ignored.
  400. //
  401. // To keep this behavior we repeat this for action-link, which means that this
  402. // conversion happens a bit earlier. We unset $link['#attributes'] to prevent
  403. // Link::preRenderLink() doing the same, because for action-links, that would
  404. // be needless.
  405. $link += ['#options' => []];
  406. if (isset($link['#attributes'])) {
  407. $link['#options'] += [
  408. 'attributes' => [],
  409. ];
  410. $link['#options']['attributes'] += $link['#attributes'];
  411. unset($link['#attributes']);
  412. }
  413. $link['#options'] += ['attributes' => []];
  414. $link['#options']['attributes'] += ['class' => []];
  415. // Determine the needed (type) variant.
  416. $variants_supported = ['default', 'danger'];
  417. $variant = is_string($variant) && in_array($variant, $variants_supported) ? $variant : reset($variants_supported);
  418. // Remove button, button modifier CSS classes and other unwanted ones.
  419. $link['#options']['attributes']['class'] = array_diff($link['#options']['attributes']['class'], [
  420. 'button',
  421. 'button--action',
  422. 'button--primary',
  423. 'button--danger',
  424. 'button--small',
  425. 'button--extrasmall',
  426. 'link',
  427. ]);
  428. // Adding the needed CSS classes.
  429. $link['#options']['attributes']['class'][] = 'action-link';
  430. // Add the variant-modifier CSS class only if the variant is not the default.
  431. if ($variant !== reset($variants_supported)) {
  432. $link['#options']['attributes']['class'][] = Html::getClass("action-link--$variant");
  433. }
  434. // Add the icon modifier CSS class.
  435. if (!empty($icon_name)) {
  436. $link['#options']['attributes']['class'][] = Html::getClass("action-link--icon-$icon_name");
  437. }
  438. if ($size && in_array($size, ['small', 'extrasmall'])) {
  439. $link['#options']['attributes']['class'][] = Html::getClass("action-link--$size");
  440. }
  441. // If the provided $link is an item of the 'links' theme function, then only
  442. // the attributes of the Url object are processed during rendering.
  443. $url_attributes = $link['#url']->getOption('attributes') ?: [];
  444. $url_attributes = NestedArray::mergeDeep($url_attributes, $link['#options']['attributes']);
  445. $link['#url']->setOption('attributes', $url_attributes);
  446. return $link;
  447. }
  448. /**
  449. * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
  450. *
  451. * Changes vertical tabs to container.
  452. */
  453. function claro_form_node_form_alter(&$form, FormStateInterface $form_state) {
  454. $form['#theme'] = ['node_edit_form'];
  455. $form['#attached']['library'][] = 'claro/node-form';
  456. $form['advanced']['#type'] = 'container';
  457. $form['advanced']['#accordion'] = TRUE;
  458. $form['meta']['#type'] = 'container';
  459. $form['meta']['#access'] = TRUE;
  460. $form['revision_information']['#type'] = 'container';
  461. $form['revision_information']['#group'] = 'meta';
  462. $form['revision_information']['#attributes']['class'][] = 'entity-meta__revision';
  463. }
  464. /**
  465. * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\media\MediaForm.
  466. */
  467. function claro_form_media_form_alter(&$form, FormStateInterface $form_state) {
  468. // Only attach CSS from core if this form comes from Media core, and not from
  469. // the contrib Media Entity 1.x branch.
  470. if (\Drupal::moduleHandler()->moduleExists('media') && $form_state->getFormObject() instanceof MediaForm) {
  471. // @todo Revisit after https://www.drupal.org/node/2892304 is in. It
  472. // introduces a footer region to these forms which will allow for us to
  473. // display a top border over the published checkbox by defining a
  474. // media-edit-form.html.twig template the same way node does.
  475. $form['#attached']['library'][] = 'claro/media-form';
  476. }
  477. }
  478. /**
  479. * Implements hook_views_ui_display_top_alter().
  480. */
  481. function claro_views_ui_display_top_alter(&$element) {
  482. // @todo remove this after https://www.drupal.org/node/3051605 has been
  483. // solved.
  484. $element['tabs']['#prefix'] = preg_replace('/(class="(.+\s)?)tabs(\s.+"|")/', '$1views-tabs$3', $element['tabs']['#prefix']);
  485. $element['tabs']['#prefix'] = preg_replace('/(class="(.+\s)?)secondary(\s.+"|")/', '$1views-tabs--secondary$3', $element['tabs']['#prefix']);
  486. foreach (Element::children($element['tabs']) as $tab) {
  487. $element['tabs'][$tab]['#theme'] = 'menu_local_task__views_ui';
  488. }
  489. // Change top extra actions to use the small dropbutton variant.
  490. // @todo Revisit after https://www.drupal.org/node/3057581 is added.
  491. if (!empty($element['extra_actions'])) {
  492. $element['extra_actions']['#dropbutton_type'] = 'small';
  493. }
  494. }
  495. /**
  496. * Implements hook_views_ui_display_tab_alter().
  497. */
  498. function claro_views_ui_display_tab_alter(&$element) {
  499. // We process the dropbutton-like element on views edit form's
  500. // display settings top section.
  501. //
  502. // That element should be a regular Dropbutton.
  503. //
  504. // After that the reported issue is fixed and the element is rendered with
  505. // the Dropbutton type, we just have to set it's '#dropbutton_type' to
  506. // 'extrasmall'.
  507. //
  508. // @todo: revisit after https://www.drupal.org/node/3057577 is fixed.
  509. $dummy_dropbutton = &$element['details']['top']['actions'];
  510. if ($dummy_dropbutton) {
  511. $child_keys = Element::children($dummy_dropbutton);
  512. $prefix_regex = '/(<.*class\s*= *["\']?)([^"\']*)(.*)/i';
  513. $child_count = 0;
  514. foreach ($child_keys as $key) {
  515. if (in_array($key, ['prefix', 'suffix'])) {
  516. continue;
  517. }
  518. $nested_child_keys = Element::children($dummy_dropbutton[$key], TRUE);
  519. if (!empty($nested_child_keys)) {
  520. foreach ($nested_child_keys as $nested_key) {
  521. $child_count++;
  522. $prefix = $dummy_dropbutton[$key][$nested_key]['#prefix'];
  523. $dummy_dropbutton[$key][$nested_key]['#prefix'] = preg_replace($prefix_regex, '$1$2 dropbutton__item dropbutton__item--extrasmall$3', $prefix);
  524. }
  525. }
  526. else {
  527. $child_count++;
  528. $prefix = $dummy_dropbutton[$key]['#prefix'];
  529. $dummy_dropbutton[$key]['#prefix'] = preg_replace($prefix_regex, '$1$2 dropbutton__item dropbutton__item--extrasmall$3', $prefix);
  530. }
  531. }
  532. if (!empty($dummy_dropbutton['prefix']) && !empty($dummy_dropbutton['prefix']['#markup'])) {
  533. $classes = 'dropbutton--extrasmall ';
  534. $classes .= ($child_count > 1) ? 'dropbutton--multiple' : 'dropbutton--single';
  535. $prefix = $dummy_dropbutton['prefix']['#markup'];
  536. $dummy_dropbutton['prefix']['#markup'] = preg_replace($prefix_regex, '$1$2 ' . $classes . '$3', $prefix);
  537. }
  538. }
  539. }
  540. /**
  541. * Implements hook_preprocess_HOOK for views_exposed_form.
  542. */
  543. function claro_preprocess_views_exposed_form(&$variables) {
  544. $form = &$variables['form'];
  545. // Add BEM classes for items in the form.
  546. // Sorted keys.
  547. $child_keys = Element::children($form, TRUE);
  548. $last_key = NULL;
  549. $child_before_actions_key = NULL;
  550. foreach ($child_keys as $child_key) {
  551. if (!empty($form[$child_key]['#type'])) {
  552. if ($form[$child_key]['#type'] === 'actions') {
  553. // We need the key of the element that precedes the actions element.
  554. $child_before_actions_key = $last_key;
  555. $form[$child_key]['#attributes']['class'][] = 'views-exposed-form__item';
  556. $form[$child_key]['#attributes']['class'][] = 'views-exposed-form__item--actions';
  557. }
  558. if (!in_array($form[$child_key]['#type'], ['hidden', 'actions'])) {
  559. $form[$child_key]['#wrapper_attributes']['class'][] = 'views-exposed-form__item';
  560. $last_key = $child_key;
  561. }
  562. }
  563. }
  564. if ($child_before_actions_key) {
  565. // Add a modifier class to the item that precedes the form actions.
  566. $form[$child_before_actions_key]['#wrapper_attributes']['class'][] = 'views-exposed-form__item--preceding-actions';
  567. }
  568. }
  569. /**
  570. * Implements hook_form_FORM_ID_alter() for views_exposed_form.
  571. */
  572. function claro_form_views_exposed_form_alter(&$form, FormStateInterface $form_state) {
  573. $view = $form_state->getStorage()['view'];
  574. $view_title = $view->getTitle();
  575. // Add a label so screenreaders can identify the purpose of the exposed form
  576. // without having to scan content that appears further down the page.
  577. $form['#attributes']['aria-label'] = t('Filter the contents of the %view_title view', ['%view_title' => $view_title]);
  578. }
  579. /**
  580. * Implements hook_preprocess_form_element().
  581. */
  582. function claro_preprocess_form_element(&$variables) {
  583. if (!empty($variables['element']['#errors'])) {
  584. $variables['label']['#attributes']['class'][] = 'has-error';
  585. }
  586. if ($variables['disabled']) {
  587. $variables['label']['#attributes']['class'][] = 'is-disabled';
  588. if (!empty($variables['description']['attributes'])) {
  589. $variables['description']['attributes']->addClass('is-disabled');
  590. }
  591. }
  592. }
  593. /**
  594. * Implements template_preprocess_HOOK() for input.
  595. */
  596. function claro_preprocess_input(&$variables) {
  597. if (
  598. !empty($variables['element']['#title_display']) &&
  599. $variables['element']['#title_display'] === 'attribute' &&
  600. !empty((string) $variables['element']['#title'])
  601. ) {
  602. $variables['attributes']['title'] = (string) $variables['element']['#title'];
  603. }
  604. $type_api = $variables['element']['#type'];
  605. $type_html = $variables['attributes']['type'];
  606. $text_types_html = [
  607. 'text',
  608. 'email',
  609. 'tel',
  610. 'number',
  611. 'search',
  612. 'password',
  613. 'date',
  614. 'time',
  615. 'file',
  616. 'color',
  617. 'datetime-local',
  618. 'url',
  619. 'month',
  620. 'week',
  621. ];
  622. if (in_array($type_html, $text_types_html)) {
  623. $variables['attributes']['class'][] = 'form-element';
  624. $variables['attributes']['class'][] = Html::getClass('form-element--type-' . $type_html);
  625. $variables['attributes']['class'][] = Html::getClass('form-element--api-' . $type_api);
  626. if (!empty($variables['element']['#autocomplete_route_name'])) {
  627. $variables['autocomplete_message'] = t('Loading…');
  628. }
  629. }
  630. if (in_array($type_html, ['checkbox', 'radio'])) {
  631. $variables['attributes']['class'][] = 'form-boolean';
  632. $variables['attributes']['class'][] = Html::getClass('form-boolean--type-' . $type_html);
  633. }
  634. }
  635. /**
  636. * Implements template_preprocess_HOOK() for textarea.
  637. */
  638. function claro_preprocess_textarea(&$variables) {
  639. $variables['attributes']['class'][] = 'form-element';
  640. $variables['attributes']['class'][] = 'form-element--type-textarea';
  641. $variables['attributes']['class'][] = 'form-element--api-textarea';
  642. }
  643. /**
  644. * Implements template_preprocess_HOOK() for select.
  645. */
  646. function claro_preprocess_select(&$variables) {
  647. if (!empty($variables['element']['#title_display']) && $variables['element']['#title_display'] === 'attribute' && !empty((string) $variables['element']['#title'])) {
  648. $variables['attributes']['title'] = (string) $variables['element']['#title'];
  649. }
  650. $variables['attributes']['class'][] = 'form-element';
  651. $variables['attributes']['class'][] = $variables['element']['#multiple'] ?
  652. 'form-element--type-select-multiple' :
  653. 'form-element--type-select';
  654. if (in_array('block-region-select', $variables['attributes']['class'])) {
  655. $variables['attributes']['class'][] = 'form-element--extrasmall';
  656. }
  657. }
  658. /**
  659. * Implements template_preprocess_HOOK() for datetime_wrapper.
  660. */
  661. function claro_preprocess_datetime_wrapper(&$variables) {
  662. if (!empty($variables['element']['#errors'])) {
  663. $variables['title_attributes']['class'][] = 'has-error';
  664. }
  665. if (!empty($variables['element']['#disabled'])) {
  666. $variables['title_attributes']['class'][] = 'is-disabled';
  667. if (!empty($variables['description_attributes'])) {
  668. $variables['description_attributes']->addClass('is-disabled');
  669. }
  670. }
  671. }
  672. /**
  673. * Implements template_preprocess_HOOK() for fieldset.
  674. */
  675. function claro_preprocess_fieldset(&$variables) {
  676. $element = $variables['element'];
  677. $composite_types = ['checkboxes', 'radios'];
  678. if (!empty($element['#type']) && in_array($element['#type'], $composite_types) && !empty($variables['element']['#children_errors'])) {
  679. $variables['legend_span']['attributes']->addClass('has-error');
  680. }
  681. if (!empty($element['#disabled'])) {
  682. $variables['legend_span']['attributes']->addClass('is-disabled');
  683. if (!empty($variables['description']) && !empty($variables['description']['attributes'])) {
  684. $variables['description']['attributes']->addClass('is-disabled');
  685. }
  686. }
  687. // Remove 'container-inline' class from the main attributes and add a flag
  688. // instead.
  689. // @todo remove this after https://www.drupal.org/node/3059593 has been
  690. // resolved.
  691. if (!empty($variables['attributes']['class'])) {
  692. $container_inline_key = array_search('container-inline', $variables['attributes']['class']);
  693. if ($container_inline_key !== FALSE) {
  694. unset($variables['attributes']['class'][$container_inline_key]);
  695. $variables['inline_items'] = TRUE;
  696. }
  697. }
  698. }
  699. /**
  700. * Implements hook_preprocess_HOOK() for field_multiple_value_form.
  701. */
  702. function claro_preprocess_field_multiple_value_form(&$variables) {
  703. // Make disabled available for the template.
  704. $variables['disabled'] = !empty($variables['element']['#disabled']);
  705. if ($variables['multiple']) {
  706. // Add an additional CSS class for the field label table cell.
  707. // This repeats the logic of template_preprocess_field_multiple_value_form()
  708. // without using '#prefix' and '#suffix' for the wrapper element.
  709. //
  710. // If the field is multiple, we don't have to check the existence of the
  711. // table header cell.
  712. //
  713. // @see template_preprocess_field_multiple_value_form().
  714. $header_attributes = ['class' => ['form-item__label', 'form-item__label--multiple-value-form']];
  715. if (!empty($variables['element']['#required'])) {
  716. $header_attributes['class'][] = 'js-form-required';
  717. $header_attributes['class'][] = 'form-required';
  718. }
  719. // Using array_key_first() for addressing the first header cell would be
  720. // more elegant here, but we can rely on the related theme.inc preprocess.
  721. $variables['table']['#header'][0]['data'] = [
  722. '#type' => 'html_tag',
  723. '#tag' => 'h4',
  724. '#value' => $variables['element']['#title'],
  725. '#attributes' => $header_attributes,
  726. ];
  727. if ($variables['disabled']) {
  728. $variables['table']['#attributes']['class'][] = 'tabledrag-disabled';
  729. $variables['table']['#attributes']['class'][] = 'js-tabledrag-disabled';
  730. // We will add the 'is-disabled' CSS class to the disabled table header
  731. // cells.
  732. $header_attributes['class'][] = 'is-disabled';
  733. foreach ($variables['table']['#header'] as &$cell) {
  734. if (is_array($cell) && isset($cell['data'])) {
  735. $cell = $cell + ['class' => []];
  736. $cell['class'][] = 'is-disabled';
  737. }
  738. else {
  739. // We have to modify the structure of this header cell.
  740. $cell = [
  741. 'data' => $cell,
  742. 'class' => ['is-disabled'],
  743. ];
  744. }
  745. }
  746. }
  747. // Make add-more button smaller.
  748. if (!empty($variables['button'])) {
  749. $variables['button']['#attributes']['class'][] = 'button--small';
  750. }
  751. }
  752. }
  753. /**
  754. * Implements hook_preprocess_HOOK() for form_element__password_confirm.
  755. */
  756. function claro_preprocess_form_element__password_confirm(&$variables) {
  757. // Add CSS classes needed for theming the password confirm widget.
  758. $variables['attributes']['class'][] = 'password-confirm';
  759. $variables['attributes']['class'][] = 'is-initial';
  760. $variables['attributes']['class'][] = 'is-password-empty';
  761. $variables['attributes']['class'][] = 'is-confirm-empty';
  762. }
  763. /**
  764. * Implements hook_preprocess_HOOK() for form_element__password.
  765. */
  766. function claro_preprocess_form_element__password(&$variables) {
  767. if (!empty($variables['element']['#array_parents']) && in_array('pass1', $variables['element']['#array_parents'])) {
  768. // This is the main password form element.
  769. $variables['attributes']['class'][] = 'password-confirm__password';
  770. }
  771. if (!empty($variables['element']['#array_parents']) && in_array('pass2', $variables['element']['#array_parents'])) {
  772. // This is the password confirm form element.
  773. $variables['attributes']['class'][] = 'password-confirm__confirm';
  774. }
  775. }
  776. /**
  777. * Implements template_preprocess_HOOK() for filter_tips.
  778. */
  779. function claro_preprocess_filter_tips(&$variables) {
  780. $variables['#attached']['library'][] = 'filter/drupal.filter';
  781. }
  782. /**
  783. * Implements template_preprocess_HOOK() for table.
  784. */
  785. function claro_preprocess_table(&$variables) {
  786. // Adding table sort indicator CSS class for inactive sort link.
  787. // @todo Revisit after https://www.drupal.org/node/3025726 or
  788. // https://www.drupal.org/node/1973418 is in.
  789. if (!empty($variables['header'])) {
  790. foreach ($variables['header'] as &$header_cell) {
  791. // For 8.6.x and below.
  792. // @todo Remove this after 8.6.x is out of support.
  793. if ($header_cell['content'] instanceof GeneratedLink) {
  794. $dom_doc = Html::load($header_cell['content']->getGeneratedLink());
  795. $anchors = $dom_doc->getElementsByTagName('a');
  796. if (!empty($anchors)) {
  797. foreach ($anchors as $anchor) {
  798. $anchor_href = $anchor->getAttribute('href');
  799. $parsed_url = UrlHelper::parse($anchor_href);
  800. $query = !empty($parsed_url['query']) ? $parsed_url['query'] : [];
  801. if (isset($query['order']) && isset($query['sort'])) {
  802. $header_cell['attributes']->addClass('sortable-heading');
  803. }
  804. }
  805. }
  806. }
  807. // For 8.7.x and above.
  808. if ($header_cell['content'] instanceof Link) {
  809. $query = $header_cell['content']->getUrl()->getOption('query') ?: [];
  810. if (isset($query['order']) && isset($query['sort'])) {
  811. $header_cell['attributes']->addClass('sortable-heading');
  812. }
  813. }
  814. }
  815. }
  816. // Mark the whole table and the first cells if rows are draggable.
  817. if (!empty($variables['rows'])) {
  818. $draggable_row_found = FALSE;
  819. foreach ($variables['rows'] as &$row) {
  820. /** @var \Drupal\Core\Template\Attribute $row['attributes'] */
  821. if (!empty($row['attributes']) && $row['attributes']->hasClass('draggable')) {
  822. if (!$draggable_row_found) {
  823. $variables['attributes']['class'][] = 'draggable-table';
  824. $draggable_row_found = TRUE;
  825. }
  826. reset($row['cells']);
  827. $first_cell_key = key($row['cells']);
  828. // The 'attributes' key is always here and it is an
  829. // \Drupal\Core\Template\Attribute.
  830. // @see template_preprocess_table();
  831. $row['cells'][$first_cell_key]['attributes']->addClass('tabledrag-cell');
  832. // Check that the first cell is empty or not.
  833. if (empty($row['cells'][$first_cell_key]) || empty($row['cells'][$first_cell_key]['content'])) {
  834. $row['cells'][$first_cell_key]['attributes']->addClass('tabledrag-cell--only-drag');
  835. }
  836. }
  837. }
  838. }
  839. }
  840. /**
  841. * Implements template_preprocess_HOOK() for field_ui_table.
  842. */
  843. function claro_preprocess_field_ui_table(&$variables) {
  844. claro_preprocess_table($variables);
  845. }
  846. /**
  847. * Implements template_preprocess_HOOK() for views_view_table.
  848. *
  849. * @todo Revisit after https://www.drupal.org/node/3025726 or
  850. * https://www.drupal.org/node/1973418 is in.
  851. */
  852. function claro_preprocess_views_view_table(&$variables) {
  853. if (!empty($variables['header'])) {
  854. foreach ($variables['header'] as &$header_cell) {
  855. if (!empty($header_cell['url'])) {
  856. $parsed_url = UrlHelper::parse($header_cell['url']);
  857. $query = !empty($parsed_url['query']) ? $parsed_url['query'] : [];
  858. if (isset($query['order']) && isset($query['sort'])) {
  859. $header_cell['attributes']->addClass('sortable-heading');
  860. }
  861. }
  862. }
  863. }
  864. }
  865. /**
  866. * Implements hook_preprocess_HOOK() for links__dropbutton__operations.
  867. *
  868. * Operations always use the extra small dropbutton variant.
  869. */
  870. function claro_preprocess_links__dropbutton__operations(&$variables) {
  871. $item_classes = ['dropbutton__item', 'dropbutton__item--extrasmall'];
  872. $variables['attributes']['class'][] = 'dropbutton--extrasmall';
  873. foreach ($variables['links'] as &$link_data) {
  874. $link_data['attributes']->addClass($item_classes);
  875. }
  876. }
  877. /**
  878. * Implements hook_preprocess_HOOK() for links__dropbutton.
  879. */
  880. function claro_preprocess_links__dropbutton(&$variables) {
  881. // Add the right CSS class for the dropbutton list that helps reducing FOUC.
  882. if (!empty($variables['links'])) {
  883. $variables['attributes']['class'][] = count($variables['links']) > 1
  884. ? 'dropbutton--multiple'
  885. : 'dropbutton--single';
  886. }
  887. $item_classes = ['dropbutton__item'];
  888. // Check that the dropbutton has a supported variant class.
  889. // @todo Revisit after https://www.drupal.org/node/3057581 is added.
  890. if (!empty($variables['attributes']['class'])) {
  891. if (array_search('dropbutton--small', $variables['attributes']['class'])) {
  892. $item_classes[] = 'dropbutton__item--small';
  893. }
  894. elseif (array_search('dropbutton--extrasmall', $variables['attributes']['class'])) {
  895. $item_classes[] = 'dropbutton__item--extrasmall';
  896. }
  897. }
  898. foreach ($variables['links'] as &$link_data) {
  899. $link_data['attributes']->addClass($item_classes);
  900. }
  901. }
  902. /**
  903. * Implements hook_preprocess_HOOK() for views_ui_display_tab_bucket.
  904. */
  905. function claro_preprocess_views_ui_display_tab_bucket(&$variables) {
  906. // Instead of re-styling Views UI dropbuttons with module-specific CSS styles,
  907. // change dropbutton variants to the extra small version.
  908. // @todo Revisit after https://www.drupal.org/node/3057581 is added.
  909. if (!empty($variables['actions']) && $variables['actions']['#type'] === 'dropbutton') {
  910. $variables['actions']['#dropbutton_type'] = 'extrasmall';
  911. }
  912. }
  913. /**
  914. * Implements hook_preprocess_HOOK() for status_messages.
  915. */
  916. function claro_preprocess_status_messages(&$variables) {
  917. $variables['title_ids'] = [];
  918. foreach ($variables['message_list'] as $message_type => $messages) {
  919. $variables['title_ids'][$message_type] = Html::getUniqueId("message-$message_type-title");
  920. }
  921. }
  922. /**
  923. * Implements hook_preprocess_HOOK() for system_themes_page.
  924. */
  925. function claro_preprocess_system_themes_page(&$variables) {
  926. if (!empty($variables['theme_groups'])) {
  927. foreach ($variables['theme_groups'] as &$theme_group) {
  928. if (!empty($theme_group['themes'])) {
  929. foreach ($theme_group['themes'] as &$theme_card) {
  930. /**
  931. * @todo Remove dependency on attributes after
  932. * https://www.drupal.org/project/drupal/issues/2511548 has been
  933. * resolved.
  934. */
  935. if (isset($theme_card['screenshot']['#attributes']) && $theme_card['screenshot']['#attributes'] instanceof Attribute && $theme_card['screenshot']['#attributes']->hasClass('no-screenshot')) {
  936. unset($theme_card['screenshot']);
  937. }
  938. $theme_card['title_id'] = Html::getUniqueId($theme_card['name'] . '-label');
  939. $description_is_empty = empty((string) $theme_card['description']);
  940. // Set description_id only if the description is not empty.
  941. if (!$description_is_empty) {
  942. $theme_card['description_id'] = Html::getUniqueId($theme_card['name'] . '-description');
  943. }
  944. if (!empty($theme_card['operations']) && !empty($theme_card['operations']['#theme']) && $theme_card['operations']['#theme'] === 'links') {
  945. $theme_card['operations']['#theme'] = 'links__action_links';
  946. }
  947. }
  948. }
  949. }
  950. }
  951. }
  952. /**
  953. * Implements hook_preprocess_HOOK() for links__action_links.
  954. */
  955. function claro_preprocess_links__action_links(&$variables) {
  956. $variables['attributes']['class'][] = 'action-links';
  957. foreach ($variables['links'] as $delta => $link_item) {
  958. $variables['links'][$delta]['attributes']->addClass('action-links__item');
  959. }
  960. }
  961. /**
  962. * Implements hook_preprocess_HOOK() for file_managed_file.
  963. */
  964. function claro_preprocess_file_managed_file(&$variables) {
  965. // Produce the same renderable element structure as image widget has.
  966. $child_keys = Element::children($variables['element']);
  967. foreach ($child_keys as $child_key) {
  968. $variables['data'][$child_key] = $variables['element'][$child_key];
  969. }
  970. _claro_preprocess_file_and_image_widget($variables);
  971. }
  972. /**
  973. * Implements hook_preprocess_HOOK() for file_widget_multiple.
  974. */
  975. function claro_preprocess_file_widget_multiple(&$variables) {
  976. $has_upload = FALSE;
  977. if (isset($variables['table']['#type']) && $variables['table']['#type'] === 'table') {
  978. // Add a variant class for the table.
  979. $variables['table']['#attributes']['class'][] = 'table-file-multiple-widget';
  980. // Mark table disabled if the field widget is disabled.
  981. if (isset($variables['element']['#disabled']) && $variables['element']['#disabled']) {
  982. $variables['table']['#attributes']['class'][] = 'tabledrag-disabled';
  983. $variables['table']['#attributes']['class'][] = 'js-tabledrag-disabled';
  984. // We will add the 'is-disabled' CSS class to the disabled table header
  985. // cells.
  986. foreach ($variables['table']['#header'] as &$cell) {
  987. if (is_array($cell) && isset($cell['data'])) {
  988. $cell = $cell + ['class' => []];
  989. $cell['class'][] = 'is-disabled';
  990. }
  991. else {
  992. // We have to modify the structure of this header cell.
  993. $cell = [
  994. 'data' => $cell,
  995. 'class' => ['is-disabled'],
  996. ];
  997. }
  998. }
  999. }
  1000. // Mark operations column cells with a CSS class.
  1001. if (isset($variables['table']['#rows']) && is_array($variables['table']['#rows'])) {
  1002. foreach ($variables['table']['#rows'] as $row_key => $row) {
  1003. if (isset($row['data']) && is_array($row['data'])) {
  1004. $last_cell = end($row['data']);
  1005. $last_cell_key = key($row['data']);
  1006. if (is_array($last_cell['data'])) {
  1007. foreach ($last_cell['data'] as $last_cell_item) {
  1008. if (isset($last_cell_item['#attributes']['class']) && is_array($last_cell_item['#attributes']['class']) && in_array('remove-button', $last_cell_item['#attributes']['class'])) {
  1009. $variables['table']['#rows'][$row_key]['data'][$last_cell_key] += ['class' => []];
  1010. $variables['table']['#rows'][$row_key]['data'][$last_cell_key]['class'][] = 'file-operations-cell';
  1011. break 1;
  1012. }
  1013. }
  1014. }
  1015. }
  1016. }
  1017. }
  1018. // Add a CSS class to the table if an upload widget is present.
  1019. // This is required for removing the border of the last table row.
  1020. if (!empty($variables['element'])) {
  1021. $element_keys = Element::children($variables['element']);
  1022. foreach ($element_keys as $delta) {
  1023. if (!isset($variables['element'][$delta]['upload']['#access']) || $variables['element'][$delta]['upload']['#access'] !== FALSE) {
  1024. $has_upload = TRUE;
  1025. break 1;
  1026. }
  1027. }
  1028. }
  1029. $variables['table']['#attributes']['class'][] = $has_upload ? 'table-file-multiple-widget--has-upload' : 'table-file-multiple-widget--no-upload';
  1030. }
  1031. $table_is_not_empty = isset($variables['table']['#rows']) && !empty($variables['table']['#rows']);
  1032. $table_is_accessible = !isset($variables['table']['#access']) || (isset($variables['table']['#access']) && $variables['table']['#access'] !== FALSE);
  1033. $variables['has_table'] = $table_is_not_empty && $table_is_accessible;
  1034. }
  1035. /**
  1036. * Implements hook_preprocess_HOOK() for image_widget.
  1037. */
  1038. function claro_preprocess_image_widget(&$variables) {
  1039. // Stable adds the file size as #suffix for image file_link renderable array.
  1040. // We have to remove that because we will render it in our file_link template
  1041. // for every kind of files, and not just for images.
  1042. if (!empty($variables['element']['fids']['#value'])) {
  1043. $file = reset($variables['element']['#files']);
  1044. unset($variables['data']['file_' . $file->id()]['filename']['#suffix']);
  1045. }
  1046. _claro_preprocess_file_and_image_widget($variables);
  1047. }
  1048. /**
  1049. * Helper pre-process callback for file_managed_file and image_widget.
  1050. *
  1051. * @param array $variables
  1052. * The renderable array of image and file widgets, with 'element' and 'data'
  1053. * keys.
  1054. */
  1055. function _claro_preprocess_file_and_image_widget(array &$variables) {
  1056. $element = $variables['element'];
  1057. $main_item_keys = [
  1058. 'upload',
  1059. 'upload_button',
  1060. 'remove_button',
  1061. ];
  1062. // Calculate helper values for the template.
  1063. $upload_is_accessible = !isset($element['upload']['#access']) || $element['upload']['#access'] !== FALSE;
  1064. $is_multiple = !empty($element['#cardinality']) && $element['#cardinality'] !== 1;
  1065. $has_value = isset($element['#value']['fids']) && !empty($element['#value']['fids']);
  1066. // File widget properties.
  1067. $display_can_be_displayed = !empty($element['#display_field']);
  1068. // Display is rendered in a separate table cell for multiple value widgets.
  1069. $display_is_visible = $display_can_be_displayed && !$is_multiple && isset($element['display']['#type']) && $element['display']['#type'] !== 'hidden';
  1070. $description_can_be_displayed = !empty($element['#description_field']);
  1071. $description_is_visible = $description_can_be_displayed && isset($element['description']);
  1072. // Image widget properties.
  1073. $alt_can_be_displayed = !empty($element['#alt_field']);
  1074. $alt_is_visible = $alt_can_be_displayed && (!isset($element['alt']['#access']) || $element['alt']['#access'] !== FALSE);
  1075. $title_can_be_displayed = !empty($element['#title_field']);
  1076. $title_is_visible = $title_can_be_displayed && (!isset($element['title']['#access']) || $element['title']['#access'] !== FALSE);
  1077. $variables['multiple'] = $is_multiple;
  1078. $variables['upload'] = $upload_is_accessible;
  1079. $variables['has_value'] = $has_value;
  1080. $variables['has_meta'] = $alt_is_visible || $title_is_visible || $display_is_visible || $description_is_visible;
  1081. $variables['display'] = $display_is_visible;
  1082. // Render file upload input and upload button (or file name and remove button,
  1083. // if the field is not empty) in an emphasized div.
  1084. foreach ($variables['data'] as $key => $item) {
  1085. $item_is_filename = isset($item['filename']['#file']) && $item['filename']['#file'] instanceof FileInterface;
  1086. // Move filename to main items.
  1087. if ($item_is_filename) {
  1088. $variables['main_items']['filename'] = $item;
  1089. unset($variables['data'][$key]);
  1090. continue;
  1091. }
  1092. // Move buttons, upload input and hidden items to main items.
  1093. if (in_array($key, $main_item_keys)) {
  1094. $variables['main_items'][$key] = $item;
  1095. unset($variables['data'][$key]);
  1096. }
  1097. }
  1098. }
  1099. /**
  1100. * Implements hook_preprocess_views_view_fields().
  1101. *
  1102. * This targets each rendered media item in the grid display of the media
  1103. * library's modal dialog.
  1104. */
  1105. function claro_preprocess_views_view_fields__media_library(array &$variables) {
  1106. // Add classes to media rendered entity field so it can be targeted for
  1107. // styling. Adding this class in a template is very difficult to do.
  1108. if (isset($variables['fields']['rendered_entity']->wrapper_attributes)) {
  1109. $variables['fields']['rendered_entity']->wrapper_attributes->addClass('media-library-item__click-to-select-trigger');
  1110. }
  1111. }
  1112. /**
  1113. * Implements hook_form_BASE_FORM_ID_alter().
  1114. */
  1115. function claro_form_media_library_add_form_alter(array &$form, FormStateInterface $form_state) {
  1116. $form['#attributes']['class'][] = 'media-library-add-form';
  1117. $form['#attached']['library'][] = 'claro/media_library.theme';
  1118. // If there are unsaved media items, apply styling classes to various parts
  1119. // of the form.
  1120. if (isset($form['media'])) {
  1121. $form['#attributes']['class'][] = 'media-library-add-form--with-input';
  1122. // Put a wrapper around the informational message above the unsaved media
  1123. // items.
  1124. $form['description']['#template'] = '<p class="media-library-add-form__description">{{ text }}</p>';
  1125. }
  1126. else {
  1127. $form['#attributes']['class'][] = 'media-library-add-form--without-input';
  1128. }
  1129. }
  1130. /**
  1131. * Implements hook_form_FORM_ID_alter().
  1132. */
  1133. function claro_form_media_library_add_form_upload_alter(array &$form, FormStateInterface $form_state) {
  1134. $form['#attributes']['class'][] = 'media-library-add-form--upload';
  1135. if (isset($form['container'])) {
  1136. $form['container']['#attributes']['class'][] = 'media-library-add-form__input-wrapper';
  1137. }
  1138. }
  1139. /**
  1140. * Implements hook_form_FORM_ID_alter().
  1141. */
  1142. function claro_form_media_library_add_form_oembed_alter(array &$form, FormStateInterface $form_state) {
  1143. $form['#attributes']['class'][] = 'media-library-add-form--oembed';
  1144. // If no media items have been added yet, add a couple of styling classes
  1145. // to the initial URL form.
  1146. if (isset($form['container'])) {
  1147. $form['container']['#attributes']['class'][] = 'media-library-add-form__input-wrapper';
  1148. $form['container']['url']['#attributes']['class'][] = 'media-library-add-form-oembed-url';
  1149. $form['container']['submit']['#attributes']['class'][] = 'media-library-add-form-oembed-submit';
  1150. }
  1151. }
  1152. /**
  1153. * Implements hook_preprocess_item_list__media_library_add_form_media_list().
  1154. *
  1155. * This targets each new, unsaved media item added to the media library, before
  1156. * they are saved.
  1157. */
  1158. function claro_preprocess_item_list__media_library_add_form_media_list(array &$variables) {
  1159. foreach ($variables['items'] as &$item) {
  1160. $item['value']['preview']['#attributes']['class'][] = 'media-library-add-form__preview';
  1161. $item['value']['fields']['#attributes']['class'][] = 'media-library-add-form__fields';
  1162. $item['value']['remove_button']['#attributes']['class'][] = 'media-library-add-form__remove-button';
  1163. // #source_field_name is set by AddFormBase::buildEntityFormElement()
  1164. // to help themes and form_alter hooks identify the source field.
  1165. $fields = &$item['value']['fields'];
  1166. $source_field_name = $fields['#source_field_name'];
  1167. if (isset($fields[$source_field_name])) {
  1168. $fields[$source_field_name]['#attributes']['class'][] = 'media-library-add-form__source-field';
  1169. }
  1170. }
  1171. }
  1172. /**
  1173. * Implements hook_preprocess_media_library_item__widget().
  1174. *
  1175. * This targets each media item selected in an entity reference field.
  1176. */
  1177. function claro_preprocess_media_library_item__widget(array &$variables) {
  1178. $variables['content']['remove_button']['#attributes']['class'][] = 'media-library-item__remove';
  1179. }
  1180. /**
  1181. * Implements hook_preprocess_media_library_item__small().
  1182. *
  1183. * This targets each pre-selected media item selected when adding new media in
  1184. * the modal media library dialog.
  1185. */
  1186. function claro_preprocess_media_library_item__small(array &$variables) {
  1187. $variables['content']['select']['#attributes']['class'][] = 'media-library-item__click-to-select-checkbox';
  1188. }
  1189. /**
  1190. * @todo Remove this when https://www.drupal.org/project/drupal/issues/2999549
  1191. * lands.
  1192. *
  1193. * @see \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::formElement()
  1194. */
  1195. function claro_preprocess_fieldset__media_library_widget(array &$variables) {
  1196. if (isset($variables['prefix']['weight_toggle'])) {
  1197. $variables['prefix']['weight_toggle']['#attributes']['class'][] = 'media-library-widget__toggle-weight';
  1198. }
  1199. if (isset($variables['suffix']['open_button'])) {
  1200. $variables['suffix']['open_button']['#attributes']['class'][] = 'media-library-open-button';
  1201. }
  1202. }
  1203. /**
  1204. * Implements hook_views_pre_render().
  1205. */
  1206. function claro_views_pre_render(ViewExecutable $view) {
  1207. $add_classes = function (&$option, array $classes_to_add) {
  1208. $classes = preg_split('/\s+/', $option);
  1209. $classes = array_filter($classes);
  1210. $classes = array_merge($classes, $classes_to_add);
  1211. $option = implode(' ', array_unique($classes));
  1212. };
  1213. if ($view->id() === 'media_library') {
  1214. if ($view->display_handler->options['defaults']['css_class']) {
  1215. $add_classes($view->displayHandlers->get('default')->options['css_class'], ['media-library-view']);
  1216. }
  1217. else {
  1218. $add_classes($view->display_handler->options['css_class'], ['media-library-view']);
  1219. }
  1220. if ($view->current_display === 'page') {
  1221. if (array_key_exists('media_bulk_form', $view->field)) {
  1222. $add_classes($view->field['media_bulk_form']->options['element_class'], ['media-library-item__click-to-select-checkbox']);
  1223. }
  1224. if (array_key_exists('rendered_entity', $view->field)) {
  1225. $add_classes($view->field['rendered_entity']->options['element_class'], ['media-library-item__content']);
  1226. }
  1227. if (array_key_exists('edit_media', $view->field)) {
  1228. $add_classes($view->field['edit_media']->options['alter']['link_class'], ['media-library-item__edit']);
  1229. }
  1230. if (array_key_exists('delete_media', $view->field)) {
  1231. $add_classes($view->field['delete_media']->options['alter']['link_class'], ['media-library-item__remove']);
  1232. }
  1233. }
  1234. elseif (strpos($view->current_display, 'widget') === 0) {
  1235. if (array_key_exists('rendered_entity', $view->field)) {
  1236. $add_classes($view->field['rendered_entity']->options['element_class'], ['media-library-item__content']);
  1237. }
  1238. if (array_key_exists('media_library_select_form', $view->field)) {
  1239. $add_classes($view->field['media_library_select_form']->options['element_wrapper_class'], ['media-library-item__click-to-select-checkbox']);
  1240. }
  1241. if ($view->display_handler->options['defaults']['css_class']) {
  1242. $add_classes($view->displayHandlers->get('default')->options['css_class'], ['media-library-view--widget']);
  1243. }
  1244. else {
  1245. $add_classes($view->display_handler->options['css_class'], ['media-library-view--widget']);
  1246. }
  1247. }
  1248. }
  1249. }