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'] = [];
}
if ($extension == 'dialog_native') {
$libraries['dialogAdapter']['js'][$module_assets_path . 'improvements.dialog-native.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']);
}
}
}
