rorschach.theme 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. <?php
  2. /**
  3. * @file
  4. * Functions to support theming in the Rorschach theme.
  5. */
  6. use Drupal\rorschach\RorschachPreRender;
  7. use Drupal\Component\Utility\Html;
  8. use Drupal\Core\Form\FormStateInterface;
  9. use Drupal\Core\Template\Attribute;
  10. use Drupal\Core\Render\Element;
  11. use Drupal\user\UserInterface;
  12. /**
  13. * Implements hook_preprocess_HOOK() for HTML document templates.
  14. *
  15. * Adds body classes if certain regions have content.
  16. */
  17. function rorschach_preprocess_html(&$variables) {
  18. if (theme_get_setting('mobile_menu_all_widths') === 1) {
  19. $variables['attributes']['class'][] = 'is-always-mobile-nav';
  20. }
  21. // So fonts can be preloaded from base theme in the event Rorschach is used as a subtheme.
  22. $variables['rorschach_path'] = \Drupal::request()->getBasePath() . '/' . \Drupal::service('extension.list.theme')->getPath('rorschach');
  23. $query_string = \Drupal::state()->get('system.css_js_query_string') ?: '0';
  24. // Create render array with noscript tag to output non-JavaScript
  25. // stylesheet for primary menu.
  26. $variables['noscript_styles'] = [
  27. '#type' => 'html_tag',
  28. '#noscript' => TRUE,
  29. '#tag' => 'link',
  30. '#attributes' => [
  31. 'rel' => 'stylesheet',
  32. 'href' => $variables['rorschach_path'] . '/css/components/navigation/nav-primary-no-js.css?' . $query_string,
  33. ],
  34. ];
  35. }
  36. /**
  37. * Implements hook_preprocess_HOOK() for page title templates.
  38. */
  39. function rorschach_preprocess_page_title(&$variables) {
  40. // Since the title and the shortcut link are both block level elements,
  41. // positioning them next to each other is much simpler with a wrapper div.
  42. if (!empty($variables['title_suffix']['add_or_remove_shortcut']) && $variables['title']) {
  43. // Add a wrapper div using the title_prefix and title_suffix render
  44. // elements.
  45. $variables['title_prefix']['shortcut_wrapper'] = [
  46. '#markup' => '<div class="shortcut-wrapper">',
  47. '#weight' => 100,
  48. ];
  49. $variables['title_suffix']['shortcut_wrapper'] = [
  50. '#markup' => '</div>',
  51. '#weight' => -99,
  52. ];
  53. // Make sure the shortcut link is the first item in title_suffix.
  54. $variables['title_suffix']['add_or_remove_shortcut']['#weight'] = -100;
  55. }
  56. // Unset shortcut link on front page.
  57. $variables['is_front'] = \Drupal::service('path.matcher')->isFrontPage();
  58. if ($variables['is_front'] === TRUE) {
  59. unset($variables['title_suffix']['add_or_remove_shortcut']);
  60. }
  61. }
  62. /**
  63. * Implements hook_preprocess_HOOK() for maintenance-page.html.twig.
  64. */
  65. function rorschach_preprocess_maintenance_page(&$variables) {
  66. // By default, site_name is set to Drupal if no db connection is available
  67. // or during site installation. Setting site_name to an empty string makes
  68. // the site and update pages look cleaner.
  69. // @see template_preprocess_maintenance_page
  70. if (!$variables['db_is_active']) {
  71. $variables['site_name'] = '';
  72. }
  73. // Rorschach has custom styling for the maintenance page.
  74. $variables['#attached']['library'][] = 'rorschach/maintenance-page';
  75. }
  76. /**
  77. * Implements hook_preprocess_HOOK() for node.html.twig.
  78. */
  79. function rorschach_preprocess_node(&$variables) {
  80. // Remove the "Add new comment" link on teasers or when the comment form is
  81. // displayed on the page.
  82. if ($variables['teaser'] || !empty($variables['content']['comments']['comment_form'])) {
  83. unset($variables['content']['links']['comment']['#links']['comment-add']);
  84. }
  85. // Apply custom date formatter to "date" field.
  86. if (!empty($variables['date']) && !empty($variables['display_submitted']) && $variables['display_submitted'] === TRUE) {
  87. $variables['date'] = \Drupal::service('date.formatter')->format($variables['node']->getCreatedTime(), 'rorschach_medium');
  88. }
  89. // Pass layout variable to template if content type is article in full view
  90. // mode. This is then used in the template to create a BEM style CSS class to
  91. // control the layout.
  92. if ($variables['node']->bundle() === 'article' && $variables['view_mode'] === 'full') {
  93. $variables['layout'] = 'content-narrow';
  94. }
  95. }
  96. /**
  97. * Implements hook_preprocess_HOOK() for block.html.twig.
  98. */
  99. function rorschach_preprocess_block(&$variables) {
  100. if (!empty($variables['elements']['#id'])) {
  101. /** @var \Drupal\block\BlockInterface $block */
  102. $block = \Drupal::entityTypeManager()
  103. ->getStorage('block')
  104. ->load($variables['elements']['#id']);
  105. if ($block) {
  106. $region = $block->getRegion();
  107. if ($variables['base_plugin_id'] === 'system_menu_block') {
  108. $variables['content']['#attributes']['region'] = $region;
  109. if ($region === 'sidebar') {
  110. $variables['#attached']['library'][] = 'rorschach/menu-sidebar';
  111. }
  112. }
  113. if ($variables['base_plugin_id'] === 'search_form_block') {
  114. if ($region === 'primary_menu') {
  115. $variables['#attached']['library'][] = 'rorschach/search-narrow';
  116. $variables['content']['actions']['submit']['#theme_wrappers'] = ['input__submit__header_search'];
  117. }
  118. elseif ($region === 'secondary_menu') {
  119. $variables['#attached']['library'][] = 'rorschach/search-wide';
  120. $variables['content']['actions']['submit']['#theme_wrappers'] = ['input__submit__header_search'];
  121. }
  122. }
  123. }
  124. }
  125. if ($variables['plugin_id'] === 'system_branding_block') {
  126. $site_branding_color = theme_get_setting('site_branding_bg_color');
  127. if ($site_branding_color && $site_branding_color !== 'default') {
  128. $variables['attributes']['class'][] = 'site-branding--bg-' . $site_branding_color;
  129. }
  130. }
  131. // Add a primary-nav class to main menu navigation block.
  132. if ($variables['plugin_id'] === 'system_menu_block:main') {
  133. $variables['attributes']['class'][] = 'primary-nav';
  134. }
  135. }
  136. /**
  137. * Implements hook_theme_suggestions_HOOK_alter() for menu.
  138. */
  139. function rorschach_theme_suggestions_menu_alter(&$suggestions, array $variables) {
  140. if (isset($variables['attributes']['region'])) {
  141. $suggestions[] = 'menu__' . $variables['attributes']['region'];
  142. }
  143. }
  144. /**
  145. * Implements hook_preprocess_HOOK().
  146. */
  147. function rorschach_preprocess_menu(&$variables) {
  148. if (isset($variables['attributes']['region'])) {
  149. if ($variables['attributes']['region'] === 'sidebar') {
  150. $variables['attributes']['class'][] = 'menu--sidebar';
  151. }
  152. unset($variables['attributes']['region']);
  153. }
  154. }
  155. /**
  156. * Implements hook_theme_suggestions_HOOK_alter() for form templates.
  157. */
  158. function rorschach_theme_suggestions_form_alter(array &$suggestions, array $variables) {
  159. if ($variables['element']['#form_id'] === 'search_block_form') {
  160. $suggestions[] = 'form__search_block_form';
  161. }
  162. }
  163. /**
  164. * Implements hook_form_alter() for adding classes and placeholder text to the search forms.
  165. */
  166. function rorschach_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  167. if (isset($form['actions']['submit']) && (count($form['actions'])) <= 2) {
  168. $form['actions']['submit']['#attributes']['class'][] = 'button--primary';
  169. }
  170. switch ($form_id) {
  171. case 'search_block_form':
  172. // Add placeholder text to keys input.
  173. $form['keys']['#attributes']['placeholder'] = t('Search by keyword or phrase.');
  174. // Add classes to the search form submit input.
  175. $form['actions']['submit']['#attributes']['class'][] = 'search-form__submit';
  176. break;
  177. case 'search_form':
  178. $form['basic']['keys']['#attributes']['placeholder'] = t('Search by keyword or phrase.');
  179. $form['basic']['submit']['#attributes']['class'][] = 'button--primary';
  180. $form['advanced']['submit']['#attributes']['class'][] = 'button--primary';
  181. break;
  182. }
  183. }
  184. /**
  185. * Implements hook_theme_suggestions_HOOK_alter() for block().
  186. */
  187. function rorschach_theme_suggestions_block_alter(&$suggestions, array $variables) {
  188. if (!empty($variables['elements']['#id'])) {
  189. /** @var \Drupal\block\BlockInterface $block */
  190. $block = \Drupal::entityTypeManager()
  191. ->getStorage('block')
  192. ->load($variables['elements']['#id']);
  193. if ($block) {
  194. // Add region-specific block theme suggestions.
  195. $region = $block
  196. ->getRegion();
  197. $suggestions[] = 'block__' . $region;
  198. $suggestions[] = 'block__' . $region . '__' . 'plugin_id' . '__' . $variables['elements']['#plugin_id'];
  199. $suggestions[] = 'block__' . $region . '__' . 'id' . '__' . $variables['elements']['#id'];
  200. }
  201. }
  202. }
  203. /**
  204. * Implements hook_preprocess_HOOK() for menu-local-tasks templates.
  205. */
  206. function rorschach_preprocess_menu_local_tasks(&$variables) {
  207. foreach (Element::children($variables['primary']) as $key) {
  208. $variables['primary'][$key]['#level'] = 'primary';
  209. }
  210. foreach (Element::children($variables['secondary']) as $key) {
  211. $variables['secondary'][$key]['#level'] = 'secondary';
  212. }
  213. }
  214. /**
  215. * Implements hook_preprocess_form_element().
  216. */
  217. function rorschach_preprocess_form_element(&$variables) {
  218. if (in_array($variables['element']['#type'] ?? FALSE, ['checkbox', 'radio'], TRUE)) {
  219. $variables['attributes']['class'][] = 'form-type-boolean';
  220. }
  221. if (!empty($variables['description']['attributes'])) {
  222. $variables['description']['attributes']->addClass('form-item__description');
  223. }
  224. if ($variables['disabled']) {
  225. $variables['label']['#attributes']['class'][] = 'is-disabled';
  226. }
  227. }
  228. /**
  229. * Implements hook_preprocess_HOOK().
  230. */
  231. function rorschach_preprocess_form_element_label(&$variables) {
  232. $variables['attributes']['class'][] = 'form-item__label';
  233. }
  234. /**
  235. * Implements hook_preprocess_HOOK().
  236. */
  237. function rorschach_preprocess_input(&$variables) {
  238. if (
  239. !empty($variables['element']['#title_display']) &&
  240. $variables['element']['#title_display'] === 'attribute' &&
  241. !empty((string) $variables['element']['#title'])
  242. ) {
  243. $variables['attributes']['title'] = (string) $variables['element']['#title'];
  244. }
  245. $type_api = $variables['element']['#type'];
  246. $type_html = $variables['attributes']['type'];
  247. $text_types_html = [
  248. 'text',
  249. 'email',
  250. 'tel',
  251. 'number',
  252. 'search',
  253. 'password',
  254. 'date',
  255. 'time',
  256. 'file',
  257. 'color',
  258. 'datetime-local',
  259. 'url',
  260. 'month',
  261. 'week',
  262. ];
  263. if (in_array($type_html, $text_types_html, TRUE)) {
  264. $variables['attributes']['class'][] = 'form-element';
  265. $variables['attributes']['class'][] = Html::getClass('form-element--type-' . $type_html);
  266. $variables['attributes']['class'][] = Html::getClass('form-element--api-' . $type_api);
  267. // This logic is functioning as expected, but there is nothing in the theme that renders the result.
  268. // As a result it can't currently be covered by a functional test.
  269. if (!empty($variables['element']['#autocomplete_route_name'])) {
  270. $variables['autocomplete_message'] = t('Loading…');
  271. }
  272. }
  273. if (in_array($type_html, ['checkbox', 'radio'], TRUE)) {
  274. $variables['attributes']['class'][] = 'form-boolean';
  275. $variables['attributes']['class'][] = Html::getClass('form-boolean--type-' . $type_html);
  276. }
  277. }
  278. /**
  279. * Implements hook_preprocess_HOOK().
  280. */
  281. function rorschach_preprocess_textarea(&$variables) {
  282. $variables['attributes']['class'][] = 'form-element';
  283. $variables['attributes']['class'][] = 'form-element--type-textarea';
  284. $variables['attributes']['class'][] = 'form-element--api-textarea';
  285. }
  286. /**
  287. * Implements hook_preprocess_HOOK().
  288. */
  289. function rorschach_preprocess_select(&$variables) {
  290. $variables['attributes']['class'][] = 'form-element';
  291. $variables['attributes']['class'][] = $variables['element']['#multiple'] ?
  292. 'form-element--type-select-multiple' :
  293. 'form-element--type-select';
  294. }
  295. /**
  296. * Implements hook_preprocess_HOOK().
  297. */
  298. function rorschach_preprocess_checkboxes(&$variables) {
  299. $variables['attributes']['class'][] = 'form-boolean-group';
  300. }
  301. /**
  302. * Implements hook_preprocess_HOOK().
  303. */
  304. function rorschach_preprocess_radios(&$variables) {
  305. $variables['attributes']['class'][] = 'form-boolean-group';
  306. }
  307. /**
  308. * Implements hook_preprocess_HOOK().
  309. */
  310. function rorschach_preprocess_field(&$variables) {
  311. $rich_field_types = ['text_with_summary', 'text', 'text_long'];
  312. if (in_array($variables['field_type'], $rich_field_types, TRUE)) {
  313. $variables['attributes']['class'][] = 'text-content';
  314. }
  315. if ($variables['field_type'] == 'image' && $variables['element']['#view_mode'] == 'full' && !$variables["element"]["#is_multiple"] && $variables['field_name'] !== 'user_picture') {
  316. $variables['attributes']['class'][] = 'wide-image';
  317. }
  318. }
  319. /**
  320. * Implements hook_preprocess_HOOK().
  321. */
  322. function rorschach_preprocess_field_multiple_value_form(&$variables) {
  323. // Make disabled available for the template.
  324. $variables['disabled'] = !empty($variables['element']['#disabled']);
  325. if (!empty($variables['multiple'])) {
  326. // Add an additional CSS class for the field label table cell.
  327. // This repeats the logic of template_preprocess_field_multiple_value_form()
  328. // without using '#prefix' and '#suffix' for the wrapper element.
  329. //
  330. // If the field is multiple, we don't have to check the existence of the
  331. // table header cell.
  332. //
  333. // @see template_preprocess_field_multiple_value_form().
  334. $header_attributes = ['class' => ['form-item__label', 'form-item__label--multiple-value-form']];
  335. if (!empty($variables['element']['#required'])) {
  336. $header_attributes['class'][] = 'js-form-required';
  337. $header_attributes['class'][] = 'form-required';
  338. }
  339. // Using array_key_first() for addressing the first header cell would be
  340. // more elegant here, but we can rely on the related theme.inc preprocess.
  341. // @todo change this after https://www.drupal.org/node/3099026 has landed.
  342. $variables['table']['#header'][0]['data'] = [
  343. '#type' => 'html_tag',
  344. '#tag' => 'h4',
  345. '#value' => $variables['element']['#title'],
  346. '#attributes' => $header_attributes,
  347. ];
  348. if ($variables['disabled']) {
  349. $variables['table']['#attributes']['class'][] = 'tabledrag-disabled';
  350. $variables['table']['#attributes']['class'][] = 'js-tabledrag-disabled';
  351. // We will add the 'is-disabled' CSS class to the disabled table header
  352. // cells.
  353. $header_attributes['class'][] = 'is-disabled';
  354. foreach ($variables['table']['#header'] as &$cell) {
  355. if (is_array($cell) && isset($cell['data'])) {
  356. $cell = $cell + ['class' => []];
  357. $cell['class'][] = 'is-disabled';
  358. }
  359. else {
  360. // We have to modify the structure of this header cell.
  361. $cell = [
  362. 'data' => $cell,
  363. 'class' => ['is-disabled'],
  364. ];
  365. }
  366. }
  367. }
  368. }
  369. }
  370. /**
  371. * Implements hook_preprocess_HOOK() for menu-local-task templates.
  372. */
  373. function rorschach_preprocess_menu_local_task(&$variables) {
  374. $variables['link']['#options']['attributes']['class'][] = 'tabs__link';
  375. $variables['link']['#options']['attributes']['class'][] = 'js-tabs-link';
  376. // Ensure is-active class is set when the tab is active. The generic active
  377. // link handler applies stricter comparison rules than what is necessary for
  378. // tabs.
  379. if (isset($variables['is_active']) && $variables['is_active'] === TRUE) {
  380. $variables['link']['#options']['attributes']['class'][] = 'is-active';
  381. }
  382. if (isset($variables['element']['#level'])) {
  383. $variables['level'] = $variables['element']['#level'];
  384. }
  385. }
  386. /**
  387. * Implements template_preprocess_HOOK() for fieldset.
  388. */
  389. function rorschach_preprocess_fieldset(&$variables) {
  390. $element = $variables['element'];
  391. $composite_types = ['checkboxes', 'radios'];
  392. if (!empty($element['#type']) && in_array($element['#type'], $composite_types) && !empty($variables['element']['#children_errors'])) {
  393. $variables['legend_span']['attributes']->addClass('has-error');
  394. }
  395. if (!empty($element['#disabled'])) {
  396. $variables['legend_span']['attributes']->addClass('is-disabled');
  397. if (!empty($variables['description']) && !empty($variables['description']['attributes'])) {
  398. $variables['description']['attributes']->addClass('is-disabled');
  399. }
  400. }
  401. // Remove 'container-inline' class from the main attributes and add a flag
  402. // instead.
  403. // @todo remove this after https://www.drupal.org/node/3059593 has been
  404. // resolved.
  405. if (!empty($variables['attributes']['class'])) {
  406. $container_inline_key = array_search('container-inline', $variables['attributes']['class']);
  407. if ($container_inline_key !== FALSE) {
  408. unset($variables['attributes']['class'][$container_inline_key]);
  409. $variables['inline_items'] = TRUE;
  410. }
  411. }
  412. }
  413. /**
  414. * Implements hook_theme_suggestions_HOOK_alter().
  415. */
  416. function rorschach_theme_suggestions_user_alter(&$suggestions, $variables) {
  417. $suggestions[] = 'user__' . $variables['elements']['#view_mode'];
  418. }
  419. /**
  420. * Implements hook_preprocess_HOOK().
  421. */
  422. function rorschach_preprocess_field__node__created(&$variables) {
  423. foreach (Element::children($variables['items']) as $item) {
  424. unset($variables['items'][$item]['content']['#prefix']);
  425. }
  426. }
  427. /**
  428. * Implements hook_preprocess_HOOK() for setting classes.
  429. */
  430. function rorschach_preprocess_filter_caption(&$variables) {
  431. $variables['classes'] = isset($variables['classes']) && !empty($variables['classes']) ? $variables['classes'] . ' caption' : 'caption';
  432. }
  433. /**
  434. * Implements hook_form_FORM_ID_alter().
  435. */
  436. function rorschach_form_node_preview_form_select_alter(&$form, FormStateInterface $form_state, $form_id) {
  437. $form['backlink']['#options']['attributes']['class'][] = 'button';
  438. $form['backlink']['#options']['attributes']['class'][] = 'button--small';
  439. $form['backlink']['#options']['attributes']['class'][] = 'button--icon-back';
  440. $form['backlink']['#options']['attributes']['class'][] = 'button--primary';
  441. $form['view_mode']['#attributes']['class'][] = 'form-element--small';
  442. }
  443. /**
  444. * Implements hook_preprocess_HOOK() for comment.html.twig.
  445. */
  446. function rorschach_preprocess_comment(&$variables) {
  447. // Getting the node creation time stamp from the comment object.
  448. $date = $variables['comment']->getCreatedTime();
  449. // Formatting "created" as "X days ago".
  450. $variables['created'] = t('@time ago', ['@time' => \Drupal::service('date.formatter')->formatInterval(\Drupal::time()->getRequestTime() - $date)]);
  451. }
  452. /**
  453. * Implements hook_preprocess_HOOK() for field--comment.html.twig.
  454. */
  455. function rorschach_preprocess_field__comment(&$variables) {
  456. // Add a comment_count.
  457. $variables['comment_count'] = count(array_filter($variables['comments'], 'is_numeric', ARRAY_FILTER_USE_KEY));
  458. // Add user.compact to field-comment if profile's avatar of current user
  459. // exist.
  460. $user = \Drupal::currentUser();
  461. if ($user->isAuthenticated() && $user instanceof UserInterface) {
  462. if ($user->hasField('user_picture') && !$user->get('user_picture')->isEmpty()) {
  463. $variables['user_picture'] = \Drupal::entityTypeManager()
  464. ->getViewBuilder('user')
  465. ->view($user, 'compact');
  466. }
  467. $variables['#cache']['contexts'][] = 'user';
  468. }
  469. }
  470. /**
  471. * Implements hook_element_info_alter().
  472. */
  473. function rorschach_element_info_alter(&$info) {
  474. if (array_key_exists('text_format', $info)) {
  475. $info['text_format']['#pre_render'][] = [RorschachPreRender::class, 'textFormat'];
  476. }
  477. if (isset($info['status_messages'])) {
  478. $info['status_messages']['#pre_render'][] = [RorschachPreRender::class, 'messagePlaceholder'];
  479. }
  480. }
  481. /**
  482. * Implements template_preprocess_text_format_wrapper().
  483. *
  484. * @todo Remove when https://www.drupal.org/node/3016343 is fixed.
  485. */
  486. function rorschach_preprocess_text_format_wrapper(&$variables) {
  487. $description_attributes = [];
  488. if (!empty($variables['attributes']['id'])) {
  489. $description_attributes['id'] = $variables['attributes']['aria-describedby'] = $variables['attributes']['id'];
  490. unset($variables['attributes']['id']);
  491. }
  492. $variables['description_attributes'] = new Attribute($description_attributes);
  493. }
  494. /**
  495. * Implements hook_preprocess_search_result().
  496. */
  497. function rorschach_preprocess_search_result(&$variables) {
  498. // Apply custom date formatter to "date" field.
  499. if (!empty($variables['result']['date'])) {
  500. $variables['info_date'] = \Drupal::service('date.formatter')->format($variables['result']['node']->getCreatedTime(), 'rorschach_medium');
  501. }
  502. }
  503. /**
  504. * Implements hook_preprocess_links__comment().
  505. */
  506. function rorschach_preprocess_links__comment(&$variables) {
  507. foreach ($variables['links'] as &$link) {
  508. $link['link']['#options']['attributes']['class'][] = 'comment__links-link';
  509. }
  510. }
  511. /**
  512. * Implements hook_preprocess_table().
  513. */
  514. function rorschach_preprocess_table(&$variables) {
  515. // Mark the whole table and the first cells if rows are draggable.
  516. if (!empty($variables['rows'])) {
  517. $draggable_row_found = FALSE;
  518. foreach ($variables['rows'] as &$row) {
  519. /** @var \Drupal\Core\Template\Attribute $row['attributes'] */
  520. if (!empty($row['attributes']) && $row['attributes']->hasClass('draggable')) {
  521. if (!$draggable_row_found) {
  522. $variables['attributes']['class'][] = 'draggable-table';
  523. $draggable_row_found = TRUE;
  524. }
  525. }
  526. }
  527. }
  528. }
  529. /**
  530. * Implements hook_form_views_exposed_form_alter().
  531. */
  532. function rorschach_form_views_exposed_form_alter(&$form) {
  533. $form['#attributes']['class'][] = 'form--inline';
  534. }