media_duplicate_check-1.0.0/media_duplicate_check.module

media_duplicate_check.module
<?php

/**
 * @file
 * Primary module hooks for Media Duplicate Check module.
 */

declare(strict_types=1);

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\MessageCommand;
use Drupal\Core\Render\Markup;
use Drupal\file\Entity\File;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
 * Helper function to translate strings.
 */
function _media_duplicate_check_t($string, array $args = [], array $options = []) {
  return new TranslatableMarkup($string, $args, $options);
}

/**
 * Implements hook_form_alter().
 */
function media_duplicate_check_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Check if the module is enabled.
  $config = \Drupal::config('media_duplicate_check.settings');
  $enabled = $config->get('enabled');
  
  // Skip if module is disabled
  if (empty($enabled)) {
    return;
  }
  
  // Target media add/edit forms - check specific form IDs
  $media_form_ids = [
    'media_image_add_form',
    'media_image_edit_form',
    'media_document_add_form',
    'media_document_edit_form',
    'media_svg_image_add_form',
    'media_svg_image_edit_form',
  ];
  
  if (in_array($form_id, $media_form_ids)) {
    // Check if this media type should be checked.
    $media_type = NULL;
    if (preg_match('/^media_([^_]+)_/', $form_id, $matches)) {
      $media_type = $matches[1];
    }
    
    $check_media_types = $config->get('check_media_types') ?? ['image', 'document', 'svg_image'];
    // Handle both array formats (indexed and associative)
    if (is_array($check_media_types)) {
      // If it's an associative array from config, get the keys
      $check_media_types = array_filter(array_keys($check_media_types));
    }
    
    if ($media_type && in_array($media_type, $check_media_types)) {
      _media_duplicate_check_alter_media_form($form, $form_state, $form_id);
    }
  }
  
  // Also target inline entity forms for media.
  if ($form_id === 'media_library_add_form_upload') {
    _media_duplicate_check_alter_media_library_form($form, $form_state);
  }
  
  // Also target the media library upload form variants
  if (strpos($form_id, 'media_library_add_form') === 0) {
    _media_duplicate_check_alter_media_library_form($form, $form_state);
  }
}

/**
 * Alter media entity forms to add duplicate checking.
 */
function _media_duplicate_check_alter_media_form(&$form, FormStateInterface $form_state, $form_id) {
  // Attach our library.
  $form['#attached']['library'][] = 'media_duplicate_check/duplicate_check';
  
  // Add JavaScript translations.
  $form['#attached']['drupalSettings']['media_duplicate_check']['translations'] = [
    'existing_media' => _media_duplicate_check_t('Existing media:')->render(),
    'warning_message' => _media_duplicate_check_t('Warning message')->render(),
    'duplicate_file_detected' => _media_duplicate_check_t('Duplicate file detected: @filename')->render(),
    'yes_upload_anyway' => _media_duplicate_check_t('Yes, Upload Anyway')->render(),
    'cancel' => _media_duplicate_check_t('Cancel')->render(),
    'status_message' => _media_duplicate_check_t('Status message')->render(),
    'upload_confirmed_auto_name' => _media_duplicate_check_t('Upload confirmed. The file will be uploaded with an automatically generated name to avoid conflicts.')->render(),
    'information_message' => _media_duplicate_check_t('Information message')->render(),
    'upload_cancelled' => _media_duplicate_check_t('Upload cancelled. Please select a different file.')->render(),
    'upload_confirmed_save' => _media_duplicate_check_t('Upload confirmed. You may now save the media item.')->render(),
  ];
  
  // Find the file upload field.
  $file_fields = [
    'field_media_image',
    'field_media_document',
    'field_media_svg_image',
  ];
  
  foreach ($file_fields as $field_name) {
    if (isset($form[$field_name])) {
      // Add a container for duplicate warnings.
      $form['duplicate_check_container'] = [
        '#type' => 'container',
        '#attributes' => ['id' => 'duplicate-check-wrapper'],
        '#weight' => -100,
      ];
      
      // Add hidden field to track user confirmation.
      $form['duplicate_confirmed'] = [
        '#type' => 'hidden',
        '#default_value' => 0,
        '#attributes' => ['id' => 'duplicate-confirmed'],
      ];
      
      // Store the field name in form state for the validator
      $form_state->set('media_file_field', $field_name);
      
      break;
    }
  }
}

/**
 * Alter media library upload form.
 */
function _media_duplicate_check_alter_media_library_form(&$form, FormStateInterface $form_state) {
  // Attach our library.
  $form['#attached']['library'][] = 'media_duplicate_check/duplicate_check';
  
  // Add JavaScript translations.
  $form['#attached']['drupalSettings']['media_duplicate_check']['translations'] = [
    'existing_media' => _media_duplicate_check_t('Existing media:')->render(),
    'warning_message' => _media_duplicate_check_t('Warning message')->render(),
    'duplicate_file_detected' => _media_duplicate_check_t('Duplicate file detected: @filename')->render(),
    'yes_upload_anyway' => _media_duplicate_check_t('Yes, Upload Anyway')->render(),
    'cancel' => _media_duplicate_check_t('Cancel')->render(),
    'status_message' => _media_duplicate_check_t('Status message')->render(),
    'upload_confirmed_auto_name' => _media_duplicate_check_t('Upload confirmed. The file will be uploaded with an automatically generated name to avoid conflicts.')->render(),
    'information_message' => _media_duplicate_check_t('Information message')->render(),
    'upload_cancelled' => _media_duplicate_check_t('Upload cancelled. Please select a different file.')->render(),
    'upload_confirmed_save' => _media_duplicate_check_t('Upload confirmed. You may now save the media item.')->render(),
  ];
  
  // Add a container for duplicate warnings.
  $form['duplicate_check_container'] = [
    '#type' => 'container',
    '#attributes' => ['id' => 'duplicate-check-wrapper'],
    '#weight' => -100,
  ];
  
  // Add hidden field to track user confirmation.
  $form['duplicate_confirmed'] = [
    '#type' => 'hidden',
    '#default_value' => 0,
    '#attributes' => ['id' => 'duplicate-confirmed'],
  ];
  
  // The media library uses different field structures
  // Look for upload fields in various possible locations
  if (isset($form['container']['upload'])) {
    // This is for the main upload field in media library
    $form['media_library_file_field'] = [
      '#type' => 'value',
      '#value' => 'container][upload',
    ];
  }
  
  // Also check for media type specific uploads
  $media_types = ['image', 'document', 'video', 'audio'];
  foreach ($media_types as $type) {
    if (isset($form['container'][$type])) {
      $form['media_library_file_field'] = [
        '#type' => 'value',
        '#value' => "container][$type",
      ];
      break;
    }
  }
}

/**
 * AJAX callback for checking duplicate files.
 */
function media_duplicate_check_ajax_callback(array &$form, FormStateInterface $form_state) {
  $response = new AjaxResponse();
  
  // Get the uploaded file information.
  $triggering_element = $form_state->getTriggeringElement();
  
  // Try to find the field name from the triggering element's parents
  $field_name = NULL;
  if (isset($triggering_element['#parents'])) {
    $parents = $triggering_element['#parents'];
    if (!empty($parents[0])) {
      $field_name = $parents[0];
    }
  }
  
  // If we still don't have a field name, check common media fields
  if (!$field_name) {
    $file_fields = ['field_media_image', 'field_media_document', 'field_media_svg_image'];
    foreach ($file_fields as $field) {
      if ($form_state->hasValue([$field, 0, 'fids'])) {
        $field_name = $field;
        break;
      }
    }
  }
  
  if (!$field_name) {
    return $response;
  }
  
  // Get the file from form state.
  $file_ids = $form_state->getValue([$field_name, 0, 'fids']);
  
  if (empty($file_ids)) {
    // Clear any previous warnings.
    $response->addCommand(new HtmlCommand('#duplicate-check-wrapper', ''));
    return $response;
  }
  
  $file = File::load(reset($file_ids));
  if (!$file) {
    return $response;
  }
  
  $filename = $file->getFilename();
  $file_uri = $file->getFileUri();
  
  // Check for duplicates by hash (if the file exists) or by filename.
  $duplicate_checker = \Drupal::service('media_duplicate_check.duplicate_checker');
  $duplicates = $duplicate_checker->findDuplicatesByFilename($filename, NULL, $file_uri);
  
  if (!empty($duplicates)) {
    // Build the warning message.
    $warning_content = _media_duplicate_check_build_warning($duplicates, $filename);
    
    // Add the warning to the form.
    $response->addCommand(new HtmlCommand('#duplicate-check-wrapper', $warning_content));
    
    // Add visual warning to file upload area
    $response->addCommand(new InvokeCommand('.file-upload-js', 'addClass', ['file-upload-duplicate-warning']));
    
    // Show modal with duplicate media preview.
    $modal_content = _media_duplicate_check_build_modal_content($duplicates, $filename);
    
    $response->addCommand(new OpenModalDialogCommand(
      _media_duplicate_check_t('Duplicate File Detected'),
      $modal_content,
      [
        'width' => 700,
        'height' => 'auto',
        'dialogClass' => 'duplicate-check-modal',
        'modal' => TRUE,
        'draggable' => FALSE,
        'resizable' => FALSE,
      ]
    ));
    
    // Disable form submission until confirmed
    $response->addCommand(new InvokeCommand('form input[type="submit"], form button[type="submit"]', 'prop', ['disabled', true]));
  }
  else {
    // Clear any previous warnings.
    $response->addCommand(new HtmlCommand('#duplicate-check-wrapper', ''));
    $response->addCommand(new InvokeCommand('#duplicate-confirmed', 'val', ['1']));
    $response->addCommand(new InvokeCommand('.file-upload-js', 'removeClass', ['file-upload-duplicate-warning']));
    
    // Re-enable form submission
    $response->addCommand(new InvokeCommand('form input[type="submit"], form button[type="submit"]', 'prop', ['disabled', false]));
  }
  
  return $response;
}

/**
 * AJAX callback for media library upload.
 */
function media_duplicate_check_library_ajax_callback(array &$form, FormStateInterface $form_state) {
  $response = new AjaxResponse();
  
  // Get uploaded files.
  $files = $form_state->getValue(['container', 'upload']);
  
  if (empty($files)) {
    return $response;
  }
  
  $duplicate_checker = \Drupal::service('media_duplicate_check.duplicate_checker');
  $all_duplicates = [];
  
  foreach ($files as $file_id) {
    $file = File::load($file_id);
    if ($file) {
      $filename = $file->getFilename();
      $duplicates = $duplicate_checker->findDuplicatesByFilename($filename);
      if (!empty($duplicates)) {
        $all_duplicates[$filename] = $duplicates;
      }
    }
  }
  
  if (!empty($all_duplicates)) {
    $warning_content = '';
    foreach ($all_duplicates as $filename => $duplicates) {
      $warning_content .= _media_duplicate_check_build_warning($duplicates, $filename);
    }
    
    $response->addCommand(new HtmlCommand('#duplicate-check-wrapper', $warning_content));
    
    // Build combined modal content.
    $modal_content = '<div class="duplicate-check-modal-content">';
    foreach ($all_duplicates as $filename => $duplicates) {
      $modal_content .= _media_duplicate_check_build_modal_content($duplicates, $filename);
    }
    $modal_content .= '</div>';
    
    $response->addCommand(new OpenModalDialogCommand(
      _media_duplicate_check_t('Duplicate Files Detected'),
      $modal_content,
      [
        'width' => 700,
        'height' => 'auto',
        'dialogClass' => 'duplicate-check-modal',
      ]
    ));
  }
  else {
    $response->addCommand(new HtmlCommand('#duplicate-check-wrapper', ''));
    $response->addCommand(new InvokeCommand('#duplicate-confirmed', 'val', ['1']));
  }
  
  return $response;
}

/**
 * Build warning message for duplicate files.
 */
function _media_duplicate_check_build_warning(array $duplicates, string $filename) {
  $count = count($duplicates);
  $message = \Drupal::translation()->formatPlural(
    $count,
    'A media item with the filename "@filename" already exists.',
    '@count media items with the filename "@filename" already exist.',
    ['@filename' => $filename]
  );
  
  // Build existing media links
  $existing_media_links = [];
  foreach ($duplicates as $media) {
    $duplicate_checker = \Drupal::service('media_duplicate_check.duplicate_checker');
    $preview_data = $duplicate_checker->getMediaPreviewData($media);
    
    $existing_media_links[] = [
      '#type' => 'link',
      '#title' => $preview_data['name'] . ' (' . $preview_data['type'] . ', ' . $preview_data['created_formatted'] . ')',
      '#url' => \Drupal\Core\Url::fromUri($preview_data['url']),
      '#attributes' => [
        'target' => '_blank',
        'class' => ['existing-media-link'],
      ],
    ];
  }
  
  $build = [
    '#type' => 'html_tag',
    '#tag' => 'details',
    '#attributes' => [
      'class' => ['duplicate-warning', 'claro-details'],
      'open' => TRUE,
      'data-once' => 'details',
    ],
    'summary' => [
      '#type' => 'html_tag',
      '#tag' => 'summary',
      '#value' => _media_duplicate_check_t('Duplicate file detected: @filename', ['@filename' => $filename]),
      '#attributes' => [
        'class' => ['claro-details__summary'],
        'role' => 'button',
        'aria-expanded' => 'true',
      ],
    ],
    'wrapper' => [
      '#type' => 'container',
      '#attributes' => [
        'class' => ['claro-details__wrapper', 'details-wrapper'],
      ],
        'message' => [
          '#type' => 'html_tag',
          '#tag' => 'p',
          '#value' => $message,
          '#attributes' => [
            'class' => ['duplicate-warning__message'],
          ],
        ],
        'existing_media' => [
          '#type' => 'container',
          '#attributes' => [
            'class' => ['existing-media-links', 'duplicate-warning__existing-media'],
          ],
          'header' => [
            '#type' => 'html_tag',
            '#tag' => 'p',
            '#value' => _media_duplicate_check_t('Existing media:'),
            '#attributes' => [
              'class' => ['existing-media-links__header'],
            ],
          ],
          'list' => [
            '#type' => 'html_tag',
            '#tag' => 'ul',
            '#attributes' => [
              'class' => ['existing-media-links__list'],
            ],
            'items' => array_map(function($link) {
              return [
                '#type' => 'html_tag',
                '#tag' => 'li',
                '#value' => \Drupal::service('renderer')->render($link),
                '#attributes' => [
                  'class' => ['existing-media-links__item'],
                ],
              ];
            }, $existing_media_links),
          ],
        ],
        'actions' => [
          '#type' => 'container',
          '#attributes' => [
            'class' => ['duplicate-warning-actions', 'form-actions'],
          ],
          'proceed' => [
            '#type' => 'html_tag',
            '#tag' => 'button',
            '#value' => _media_duplicate_check_t('Yes, Upload Anyway'),
            '#attributes' => [
              'type' => 'button',
              'class' => ['button', 'button--danger', 'duplicate-proceed-btn'],
              'data-filename' => $filename,
            ],
          ],
          'cancel' => [
            '#type' => 'html_tag',
            '#tag' => 'button',
            '#value' => _media_duplicate_check_t('Cancel'),
            '#attributes' => [
              'type' => 'button',
              'class' => ['button', 'button--primary', 'duplicate-cancel-btn' ],
            ],
          ],
        ],
      ],
  ];
  
  return \Drupal::service('renderer')->render($build);
}

/**
 * Build modal content showing existing media.
 */
function _media_duplicate_check_build_modal_content(array $duplicates, string $filename) {
  $duplicate_checker = \Drupal::service('media_duplicate_check.duplicate_checker');
  
  // Build existing media items using render arrays
  $existing_media_items = [];
  foreach ($duplicates as $media) {
    $preview_data = $duplicate_checker->getMediaPreviewData($media);
    
    $media_item = [
      '#type' => 'container',
      '#attributes' => [
        'class' => ['existing-media-item', 'claro-details__item'],
      ],
      'content' => [
        '#type' => 'container',
        '#attributes' => [
          'class' => ['existing-media-item__content'],
        ],
        'thumbnail' => NULL,
        'info' => [
          '#type' => 'container',
          '#attributes' => [
            'class' => ['existing-media-item__info'],
          ],
          'name' => [
            '#type' => 'html_tag',
            '#tag' => 'h4',
            '#value' => $preview_data['name'],
            '#attributes' => [
              'class' => ['existing-media-item__name'],
            ],
          ],
          'details' => [
            '#type' => 'container',
            '#attributes' => [
              'class' => ['existing-media-item__details'],
            ],
            'type' => [
              '#type' => 'html_tag',
              '#tag' => 'div',
              '#value' => _media_duplicate_check_t('Type: @type', ['@type' => $preview_data['type']]),
              '#attributes' => [
                'class' => ['existing-media-item__type'],
              ],
            ],
            'created' => [
              '#type' => 'html_tag',
              '#tag' => 'div',
              '#value' => _media_duplicate_check_t('Created: @date', ['@date' => $preview_data['created_formatted']]),
              '#attributes' => [
                'class' => ['existing-media-item__created'],
              ],
            ],
          ],
          'actions' => [
            '#type' => 'container',
            '#attributes' => [
              'class' => ['existing-media-item__actions'],
            ],
            'view_link' => [
              '#type' => 'link',
              '#title' => _media_duplicate_check_t('View Media'),
              '#url' => \Drupal\Core\Url::fromUri($preview_data['url']),
              '#attributes' => [
                'class' => ['button', 'button--small', 'button--danger'],
                'target' => '_blank',
              ],
            ],
          ],
        ],
      ],
    ];
    
    // Add thumbnail if available
    if (!empty($preview_data['thumbnail'])) {
      $media_item['content']['thumbnail'] = [
        '#type' => 'container',
        '#attributes' => [
          'class' => ['existing-media-item__thumbnail'],
        ],
        'image' => [
          '#type' => 'html_tag',
          '#tag' => 'img',
          '#attributes' => [
            'src' => $preview_data['thumbnail'],
            'alt' => $preview_data['name'],
            'class' => ['existing-media-item__image'],
          ],
        ],
      ];
    }
    
    // Add file size if available
    if (!empty($preview_data['file_size'])) {
      $media_item['content']['info']['details']['size'] = [
        '#type' => 'html_tag',
        '#tag' => 'div',
        '#value' => _media_duplicate_check_t('Size: @size', ['@size' => $preview_data['file_size']]),
        '#attributes' => [
          'class' => ['existing-media-item__size'],
        ],
      ];
    }
    
    $existing_media_items[] = $media_item;
  }
  
  // Build the complete modal content using render arrays
  $build = [
    '#type' => 'container',
    '#attributes' => [
      'class' => ['duplicate-media-preview'],
      'data-filename' => $filename,
    ],
    'description' => [
      '#type' => 'html_tag',
      '#tag' => 'p',
      '#value' => _media_duplicate_check_t('The following media items already exist with the filename "@filename":', ['@filename' => $filename]),
      '#attributes' => [
        'class' => ['duplicate-media-preview__description'],
      ],
    ],
    'existing_media' => [
      '#type' => 'container',
      '#attributes' => [
        'class' => ['existing-media-list', 'claro-details'],
      ],
      'items' => $existing_media_items,
    ],
    'actions' => [
      '#type' => 'container',
      '#attributes' => [
        'class' => ['duplicate-actions', 'form-actions'],
      ],
      'question' => [
        '#type' => 'html_tag',
        '#tag' => 'p',
        '#value' => _media_duplicate_check_t('Do you want to continue uploading this file?'),
        '#attributes' => [
          'class' => ['duplicate-actions__question'],
        ],
      ],
      'buttons' => [
        '#type' => 'container',
        '#attributes' => [
          'class' => ['duplicate-actions__buttons'],
        ],
        'confirm' => [
          '#type' => 'html_tag',
          '#tag' => 'button',
          '#value' => _media_duplicate_check_t('Yes, Continue Upload'),
          '#attributes' => [
            'type' => 'button',
            'class' => ['button', 'button--danger', 'duplicate-confirm-btn'],
            'onclick' => 'mediaDuplicateCheck.confirmUpload()',
          ],
        ],
        'cancel' => [
          '#type' => 'html_tag',
          '#tag' => 'button',
          '#value' => _media_duplicate_check_t('Cancel'),
          '#attributes' => [
            'type' => 'button',
            'class' => ['button', 'button--primary', 'duplicate-cancel-btn' ],
            'onclick' => 'mediaDuplicateCheck.cancelUpload()',
          ],
        ],
      ],
    ],
  ];
  
  return \Drupal::service('renderer')->render($build);
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc