improvements-2.x-dev/improvements.module
improvements.module
<?php use Drupal\block\BlockForm; use Drupal\block\BlockInterface; use Drupal\Core\Access\AccessResultInterface; use Drupal\Core\Block\BlockPluginInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Template\Attribute; use Drupal\Core\Template\AttributeHelper; use Drupal\Core\Url; use Drupal\druhels\ArrayHelper; use Drupal\file\FileInterface; use Drupal\improvements\ImprovementsHelper; /** * Implements hook_theme(). */ function improvements_theme(): array { return [ 'simple_field' => [ 'render element' => 'element', ], 'picture' => [ 'variables' => [ 'img' => [], 'source' => [], ], ], 'noindex' => [ 'render element' => 'element', ], ]; } /** * Implements hook_cron(). */ function improvements_cron(): void { // Trigger hook_daily_cron() $current_timestamp = \Drupal::time()->getRequestTime(); $cron_last_run_timestamp = \Drupal::state()->get('system.cron_last'); if (date('Ymd', $current_timestamp) != date('Ymd', $cron_last_run_timestamp)) { \Drupal::moduleHandler()->invokeAll('daily_cron'); } } /** * Implements hook_mail_alter(). */ function improvements_mail_alter(array &$message): void { // Invoke hook_mail_MESSAGE_ID_alter() \Drupal::moduleHandler()->alter('mail_' . $message['id'], $message); // Add debug headers to e-mail on dev environment if (str_ends_with(\Drupal::request()->getHost(), '.local')) { $message['headers']['X-Drupal-Mail-Id'] = $message['id']; $message['headers']['X-Drupal-Mail-Module'] = $message['module']; $message['headers']['X-Drupal-Mail-Key'] = $message['key']; } } /** * Implements hook_library_info_alter(). */ function improvements_library_info_alter(array &$libraries, string $extension): void { static $module_assets_path; if (!$module_assets_path) { $module_assets_path = '/' . \Drupal::service('extension.path.resolver')->getPath('module', 'improvements') . '/assets/'; } if ($extension == 'contextual') { $libraries['drupal.contextual-links']['js'][$module_assets_path . 'improvements.contextual.js'] = []; } } /** * Implements hook_element_info_alter(). */ function improvements_element_info_alter(array &$elements): void { // Add page #pre_render callback to delete "off canvas" wrapper $elements['page']['#pre_render'][] = '\Drupal\improvements\ImprovementsTrustedCallbacks::pagePreRender'; // Add support empty link fragment $elements['link']['#post_render'][] = '\Drupal\improvements\ImprovementsTrustedCallbacks::linkPostRender'; // Decrease pager items $elements['pager']['#quantity'] = 5; } /** * Implements hook_entity_operation_alter(). */ function improvements_entity_operation_alter(array &$operations, EntityInterface $entity): void { // Remove "destination" from entity operations links foreach ($operations as &$operation) { $operation_url = $operation['url']; /** @var Url $operation_url */ $operation_url_query = $operation_url->getOption('query'); if ($operation_url_query && isset($operation_url_query['destination'])) { unset($operation_url_query['destination']); $operation_url->setOption('query', $operation_url_query); } } } /** * Preprocess function for simple-field.html.twig. */ function improvements_preprocess_simple_field(array &$vars): void { $vars['content'] = $vars['element'] + [ '#entity_type' => '', '#bundle' => '', '#field_name' => '', '#field_type' => 'string', '#label_display' => 'above', '#title' => '', '#is_multiple' => FALSE, ]; $vars['content']['#theme'] = 'field'; if (isset($vars['element']['#entity']) && $vars['element']['#entity'] instanceof EntityInterface) { $vars['content']['#entity_type'] = $vars['element']['#entity']->getEntityTypeId(); $vars['content']['#bundle'] = $vars['element']['#entity']->bundle(); } if (is_array($vars['element']['#items'])) { $vars['content'] += $vars['element']['#items']; } elseif (is_string($vars['element']['#items'])) { $vars['content'][0] = [ '#markup' => $vars['element']['#items'], ]; } unset($vars['content']['#children']); unset($vars['content']['#render_children']); } /** * Implements hook_theme_suggestions_HOOK(): simple_field. */ function improvements_theme_suggestions_simple_field(array $vars): array { $suggestions = []; if (isset($vars['element']['#field_name'])) { $suggestions[] = 'simple_field__' . $vars['element']['#field_name']; } return $suggestions; } /** * Implements hook_editor_js_settings_alter(). */ function improvements_editor_js_settings_alter(array &$settings): void { // Remove H1 from CKEditor formats foreach ($settings['editor']['formats'] as &$format_settings) { if (isset($format_settings['editorSettings']['format_tags'])) { $format_settings['editorSettings']['format_tags'] = str_replace('h1;', '', $format_settings['editorSettings']['format_tags']); } } } /** * Implements hook_block_access(). * * @return ?AccessResultInterface[] */ function improvements_block_access(BlockInterface $block_config, string $operation, AccountInterface $account): ?array { if ($operation == 'view') { // Create hook hook_block_BLOCK_CONFIG_ID_view_access() $hook = 'block_' . $block_config->id() . '_view_access'; return array_merge( Drupal::moduleHandler()->invokeAll($hook, [$block_config, $account]), ImprovementsHelper::invokeAllFromThemes($hook, [$block_config, $account]) ); } return NULL; } /** * Implements hook_entity_insert(). */ function improvements_entity_insert(EntityInterface $entity): void { // Invoke hook_ENTITY_TYPE_manipulation() \Drupal::moduleHandler()->invokeAll($entity->getEntityTypeId() . '_manipulation', [$entity, 'insert']); } /** * Implements hook_entity_update(). */ function improvements_entity_update(EntityInterface $entity): void { // Invoke hook_ENTITY_TYPE_manipulation() \Drupal::moduleHandler()->invokeAll($entity->getEntityTypeId() . '_manipulation', [$entity, 'update']); } /** * Implements hook_entity_update(). */ function improvements_entity_delete(EntityInterface $entity): void { // Invoke hook_ENTITY_TYPE_manipulation() \Drupal::moduleHandler()->invokeAll($entity->getEntityTypeId() . '_manipulation', [$entity, 'delete']); } /** * Implements hook_entity_presave(). */ function improvements_entity_presave(EntityInterface $entity): void { // Save to field data table info about width/height of svg image if (function_exists('svg_image_get_image_file_dimensions') && $entity instanceof FieldableEntityInterface) { foreach ($entity->getFieldDefinitions() as $field_name => $field_definition) { if ($field_definition->getType() == 'image') { foreach ($entity->get($field_name) as $image_field_item) { /** @var FileInterface $image_file_entity */ $image_file_entity = $image_field_item->entity; if ( $image_file_entity && svg_image_is_file_svg($image_file_entity) && !$image_field_item->width && $image_file_dimensions = svg_image_get_image_file_dimensions($image_file_entity) ) { $image_field_item ->set('width', $image_file_dimensions['width']) ->set('height', $image_file_dimensions['height']); } } } } } } /** * Preprocess function for picture.html.twig. */ function improvements_preprocess_picture(array &$vars): void { $vars['img_attributes'] = new Attribute($vars['img']); foreach ($vars['source'] as &$source) { $source['attributes'] = new Attribute($source); } } /** * Preprocess fucntion for noindex.html.twig. */ function improvements_preprocess_noindex(array &$vars): void { $vars['children'] = $vars['element']['#children']; } /** * Implements hook_block_view_alter(). */ function improvements_block_view_alter(array &$build, BlockPluginInterface $block): void { // Re-add block config entity because \Drupal\block\BlockViewBuilder::buildBlock() removes it. $block_config_entity = $build['#block']; /** @var BlockInterface $block_config_entity */ $build['#block_config'] = $block_config_entity; } /** * Preprocess function for block.html.twig. */ function improvements_preprocess_block(array &$vars): void { $block_config_entity = $vars['elements']['#block_config'] ?? NULL; /** @var \Drupal\block\Entity\Block $block_config_entity */ // Html attributes if ($block_config_entity && ($block_attributes_raw = $block_config_entity->getThirdPartySetting('improvements', 'attributes'))) { $block_attributes = ArrayHelper::formatKeyValueListAsArray($block_attributes_raw, ': '); $vars['attributes'] = array_merge($vars['attributes'] ?? [], ArrayHelper::formatArrayAsAttributes($block_attributes)); } // Our way to preprocess block template by suggestion $preprocess_by_base_plugin_id = '_improvements_preprocess_block__' . $vars['base_plugin_id']; if (function_exists($preprocess_by_base_plugin_id)) { $preprocess_by_base_plugin_id($vars); } } /** * Implements hook_entity_base_field_info_alter(). * * @param BaseFieldDefinition[] $fields */ function improvements_entity_base_field_info_alter(array &$fields, EntityTypeInterface $entity_type): void { if ($entity_type->id() == 'paragraph') { $fields['created']->setLabel(t('Date of creation')); } } /** * Implements hook_cache_flush(). */ function improvements_cache_flush(): void { // Clear "flood" table $database = \Drupal::database(); if ($database->schema()->tableExists('flood')) { $database->truncate('flood')->execute(); } } /** * Implements hook_field_widget_info_alter(). */ function improvements_field_widget_info_alter(array &$info): void { $info['string_textarea']['field_types'][] = 'string'; $info['string_textfield']['field_types'][] = 'string_long'; } /** * Implements hook_field_formatter_info_alter(). */ function improvements_field_formatter_info_alter(array &$info): void { $info['file_default']['field_types'][] = 'image'; } /** * Implements hook_page_ROUTE_NAME_result_alter(): entity.paragraphs_type.collection. */ function improvements_page_entity_paragraphs_type_collection_result_alter(&$result): void { // Hide "icon" column if all icons is empty if (!empty($result['table']['#rows']) && ArrayHelper::columnIsEmpty($result['table']['#rows'], 'icon_file')) { unset($result['table']['#header']['icon_file']); foreach ($result['table']['#rows'] as &$row) { unset($row['icon_file']); } } } /** * Preprocess function for pager.html.twig. */ function improvements_preprocess_pager(array &$vars): void { // Add fragment to pager items url if (!empty($vars['items']) && !empty($vars['pager']['#route_fragment'])) { foreach ($vars['items'] as $item_name => &$item) { if ($item_name == 'pages') { foreach ($item as &$item_item) { _improvements_add_fragment_to_pager_item($item_item, $vars['pager']['#route_fragment']); } } else { _improvements_add_fragment_to_pager_item($item, $vars['pager']['#route_fragment']); } } } } /** * Add url fragment to pager item. */ function _improvements_add_fragment_to_pager_item(array &$item, string $fragment): void { $item['href'] .= '#' . $fragment; } /** * Implements hook_form_FORM_ID_alter(): block_form. * * @see \Drupal\block\BlockForm */ function improvements_form_block_form_alter(array &$form, FormStateInterface $form_state): void { $block_form_object = $form_state->getFormObject(); /** @var BlockForm $block_form_object */ $block_config = $block_form_object->getEntity(); /** @var BlockInterface $block_config */ $form['third_party_settings']['improvements']['attributes'] = [ '#type' => 'textarea', '#title' => t('Html attributes'), '#description' => t('Format') . ' <code>key: value</code>', '#default_value' => $block_config->getThirdPartySetting('improvements', 'attributes'), '#rows' => 2, ]; } /** * Remove "title" attribute from menu links. */ function improvements_unset_menu_items_description(array &$items): void { foreach ($items as $key => &$item) { $url = $item['url']; /** @var Url $url */ $url_attributes = $url->getOption('attributes'); if (isset($url_attributes['title'])) { unset($url_attributes['title']); $url->setOption('attributes', $url_attributes); } // Processing child items if (!empty($item['below'])) { improvements_unset_menu_items_description($item['below']); } } } /** * Implements hook_entity_view_alter(). */ function improvements_entity_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void { // Not using Element::children() to improve performance foreach ($build as $element) { if (is_array($element) && isset($element['#entity_attributes'])) { $build['#attributes'] = AttributeHelper::mergeCollections($build['#attributes'] ?? [], $element['#entity_attributes']); } } }