safedelete-1.0.0/safedelete.module

safedelete.module
<?php

/**
 * @file
 * Module safedelete.
 */

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Url;
use Drupal\safedelete\AdminHelper;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Drupal\Core\Routing\RouteMatchInterface;

use Drupal\Core\File\FileSystemInterface;
use Drupal\file\Entity\File;
use Drupal\Core\Render\Markup;

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function safedelete_node_insert(EntityInterface $entity) {
  $entity_id = $entity->id();
  drupal_register_shutdown_function('_handleInsertFileUsage_PageCreation', $entity, $entity_id);
}

/**
 * Implements hook_ENTITY_delete().
 * Cleanup file_usage when we delete this node.
 */
function safedelete_entity_delete(EntityInterface $entity) {
  // Exit early if possible.
  if ($entity->getEntityTypeId() != 'node') {
    return;
  }
  $entityid = $entity->id();
  AdminHelper::deleteRecordsInFileUsage($entityid);
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function safedelete_node_presave(EntityInterface $entity) {
  // Exit early if possible.
  if ($entity->getEntityTypeId() != 'node') {
    return;
  }
  $bundles = \Drupal::config('safedelete.settings')->get('bundle');
  if (!in_array($entity->bundle(), $bundles)) {
    // This bundle is not enabled, do not perform validation checks.
    return;
  }

  if ((!($entity->isNew())) && $entity->hasField('body')) {
    // Handle existing node (page).
    $entity_id = $entity->id();
    $qeryarray = NULL;
    $qeryarray = \Drupal::entityTypeManager()->getStorage('node')->getQuery()
    ->accessCheck(FALSE)
    ->latestRevision()
    ->condition('nid' , $entity_id, '=')
    ->execute();
    $latestver = key($qeryarray);
    $db_nodeenity = NULL;
    if (isset($latestver)) {
      $db_nodeenity = \Drupal::entityTypeManager()
        ->getStorage('node')
        ->loadRevision($latestver);
    }

    $all_languages = AdminHelper::getAllEnabledLanguages();
    foreach ($all_languages as $other_langcode => $other_language) {
      if ($db_nodeenity->hasTranslation($other_langcode) && $entity->hasTranslation($other_langcode)) {
        $filearray = [];  // stores fid and number of the same file count for the usage definition change
        $dbfilearray = [];
        $dbbody_field = NULL;
        $body_field = NULL;
        $regex_mediaobj = "/data-entity-type=\"media\" data-entity-uuid=\"([a-z]|[0-9]){8}-(([0-9]|[a-z]){4}-){3}([0-9]|[a-z]){12}\"/";
        $regex_uuid = "/([a-z]|[0-9]){8}-(([0-9]|[a-z]){4}-){3}([0-9]|[a-z]){12}/";
        $body_field = $entity->getTranslation($other_langcode)->get('body')->value;
        $dbbody_field = $db_nodeenity->getTranslation($other_langcode)->get('body')->value;
        if (!is_null($body_field) || empty(!$body_field)) {
          // Using PHP 8.1 or higher, do not do a preg_match_all on null.
          preg_match_all($regex_mediaobj, $body_field, $matches, PREG_SET_ORDER);
        }
        else {
          $matches = [];
        }
        foreach ($matches as $match) {
          $file = reset($match);
          preg_match_all($regex_uuid, $file, $id_match, PREG_SET_ORDER);
          $muuid = $id_match[0][0];
          $mentity = \Drupal::service('entity.repository')->loadEntityByUuid('media', $muuid);
          if (is_null($mentity)) {
            continue;
          }
          $fid = $mentity->getSource()->getSourceFieldValue($mentity);
          if ((array_key_exists($fid, $filearray))) {
            $filearray[$fid] = $filearray[$fid] + 1;
          }
          else {
            $filearray[$fid] = 1;
          }
        }

        if ($dbbody_field != $body_field) {
          $matches = [];
          if (!is_null($dbbody_field) && !empty($dbbody_field)) {
            // Using PHP 8.1 or higher, do not do a preg_match_all on null.
            preg_match_all($regex_mediaobj, $dbbody_field, $matches, PREG_SET_ORDER);
          }
          foreach ($matches as $match) {
            $file = reset($match);
            preg_match_all($regex_uuid, $file, $id_match, PREG_SET_ORDER);
            $muuid = $id_match[0][0];
            $mentity = \Drupal::service('entity.repository')->loadEntityByUuid('media', $muuid);
            if (is_null($mentity)) {
              continue;
            }
            $fid = $mentity->getSource()->getSourceFieldValue($mentity);
            if ((array_key_exists($fid, $dbfilearray))) {
              $dbfilearray[$fid] = $dbfilearray[$fid] + 1;
            }
            else {
              $dbfilearray[$fid] = 1;
            }
          }

          // Check if the media object(s) are removed or added.
          if (count($filearray) > 0) {
            $file_usage = \Drupal::service('file.usage');
            // Current editing content has embedded file(s)
            foreach ($filearray as $fileid => $refcount) {
              if (count($dbfilearray) > 0 && (array_key_exists($fileid, $dbfilearray))) {
                // Do nothing due to the file usage simple definition - only count 1 for the same file inserted into the editor multi-times.
                $dbrefcount = $dbfilearray[$fileid];
                $difcount = $refcount - $dbrefcount;
                // @todo if we change the file usage defintion, we count m file usage for the same file is embedded into the same content
              }
              else {
                // New media reference
                // Assumption: only add the file once.
                $fentity = File::load($fileid);
                if ($fentity) {
                  $file_usage->add($fentity, 'editor', 'node', $entity_id);
                }
              }
            }//end for loop  -$filearray - default

            if (count($dbfilearray) > 0) {
              foreach ($dbfilearray as $fileid => $refcount) {
                if (!(array_key_exists($fileid, $filearray))) {
                  // Assumption: only remove the file once.
                  // @todo Change the logic as a loop with count- $refcount.
                  $fentity = File::load($fileid);
                  if ($fentity) {
                    $file_usage->delete($fentity, 'editor', 'node', $entity_id);
                  }
                }
              }
            }
          }
          // Current editing content has no any embedded files.
          else {
            if (count($dbfilearray) > 0) {
              // Check if the media object(s) are total removed from the content.
              foreach ($dbfilearray as $fileid => $refcount) {
                $fentity = File::load($fileid);
                $file_usage = \Drupal::service('file.usage');
                $file_usage->delete($fentity, 'editor', 'node', $entity_id);
              }
            }
          } //End Processing the default body
        }
      }
    }  // end of the loop - lang
  } // end of the custom file handler for embedding the files
}

/**
 * Implements hook_entity_type_predelete().
 */
function safedelete_file_predelete(EntityInterface $entity) {

  $has_show_delete_permission = \Drupal::currentUser()->hasPermission('safedelete show delete button');
  if (!$has_show_delete_permission) {
    $has_show_delete_permission = \Drupal::config('safedelete.settings')->get('delete_button');
  }
  if ($has_show_delete_permission) {
    return;
  }

  $file_usage = \Drupal::service('file.usage');
  $usage = $file_usage->listUsage($entity);

  if (!empty($usage)) {
    // Set error message
    // display error.
    // Prevent delete.
    $entity_type = $entity->getEntityTypeId();
    $id = $entity->id();
    $response = new RedirectResponse(Url::fromUri('internal:/' . $entity_type . '/' . $id . '/delete')->toString());
    $response->send();
    \Drupal::messenger()->addMessage(t('Safe delete prevented you from deleting "%title" entity_id %id due to file usage.', [
      '%id' => $entity->id(),
      '%title' => $entity->getFilename(),
    ]), 'warning', TRUE);
    exit;
  }

}

/**
 * Implements hook_entity_type_predelete().
 */
function safedelete_media_predelete(EntityInterface $entity) {
  $node_storage = \Drupal::entityTypeManager()->getStorage('node');
  $query = \Drupal::entityQuery('node');
  $and1 = $query->accessCheck(FALSE)
    ->orConditionGroup()
    ->condition('body', $entity->uuid(), 'CONTAINS');
  $query->accessCheck(FALSE)
   ->condition($and1);
  $nids = $query->execute();
  if (!empty($nids)) {
    // Set error message
    // display error.
    // Prevent delete.
    $entity_type = $entity->getEntityTypeId();
    $id = $entity->id();
    $response = new RedirectResponse(Url::fromUri('internal:/' . $entity_type . '/' . $id . '/delete')->toString());
    $response->send();
    \Drupal::messenger()->addMessage(t('Safe delete prevented you from deleting "%title" entity_id %id due to existing uuid references in other entities body content.', [
      '%id' => $entity->id(),
      '%title' => $entity->getName(),
    ]), 'warning', TRUE);
    exit;
  }
}

/**
 * Implements hook_entity_type_predelete().
 */
function safedelete_node_predelete(EntityInterface $entity) {
  $node_storage = \Drupal::entityTypeManager()->getStorage('node');
  $query = \Drupal::entityQuery('node');
  $and1 = $query->accessCheck(FALSE)
    ->orConditionGroup()
    ->condition('body', $entity->uuid(), 'CONTAINS');
  $query->accessCheck(FALSE)
    ->condition($and1);
  $nids = $query->execute();
  if (!empty($nids)) {
    // Set error message
    // display error.
    // Prevent delete.
    $entity_type = $entity->getEntityTypeId();
    $id = $entity->id();
    $response = new RedirectResponse(Url::fromUri('internal:/' . $entity_type . '/' . $id . '/delete')->toString());
    $response->send();
    \Drupal::messenger()->addMessage(t('Safe delete prevented you from deleting "%title" entity_id %id due to existing linkit references in other entities body content.', [
      '%id' => $entity->id(),
      '%title' => $entity->getTitle(),
    ]), 'warning', TRUE);
    exit;
  }
}

/**
 * Implements safedelete_form_validate_HOOK().
 */
function safedelete_form_validate_enable_bulkpublish(array $form, FormStateInterface $form_state) {
  $username = \Drupal::currentUser()->getAccountName();
  if ($form_state->hasValue('enable_bulk_publish_validate')) {
    $tmpvar = $form_state->getValue('enable_bulk_publish_validate');
    $tmpconfig = \Drupal::config('safedelete.settings')->get('enforce_bulk_publish_verify_checkpagepublished');
    if ($tmpvar != $tmpconfig) {
      // Update SafeDelete settings enforce_bulk_publish_verify_checkpagepublished.
      $config = \Drupal::service('config.factory')->getEditable('safedelete.settings');
      if ($tmpvar == 0) {
        \Drupal::logger('safedelete')->notice($username . " disable the checkbox (Enable bulk publish validation to check if the linked pages are unpublished.)");
        $config->set('enforce_bulk_publish_verify_checkpagepublished', $tmpvar)->save();
      }
    }
  }
}

/**
 * Implements safedelete_form_submit_HOOK().
 */
function safedelete_form_submit_enable_bulkpublish(array $form, FormStateInterface $form_state) {
  $tmpconfig = \Drupal::config('safedelete.settings')->get('enforce_bulk_publish_verify_checkpagepublished');
  if (!$tmpconfig) {
    // Update Settings.
    $config = \Drupal::service('config.factory')->getEditable('safedelete.settings');
    \Drupal::logger('safedelete')->notice("Enable the checkbox (Enable bulk publish validation to check if the linked pages are unpublished.)");
    $config->set('enforce_bulk_publish_verify_checkpagepublished', TRUE)->save();
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function safedelete_form_views_form_moderated_content_moderated_content_alter(&$form, FormStateInterface $form_state, $form_id) {
  $route_name = \Drupal::routeMatch()->getRouteName();
  if ($route_name != 'content_moderation.admin_moderated_content') {
    return;
  }
  if (isset($form['header']['node_bulk_form']['action']['#options']['publish_latest'])) {
    // Only add this option when publish latest version action is available.
    if (($form_id == 'views_form_moderated_content_moderated_content')) {
      if ((!isset($form['enable_bulk_publish_validate']))) {
        $form['enable_bulk_publish_validate'] = [
          '#type' => 'checkbox',
          '#title' => t('Enable bulk publish validation to check if the linked pages are unpublished.'),
          '#default_value' => \Drupal::config('safedelete.settings')->get('enforce_bulk_publish_verify_checkpagepublished') ? \Drupal::config('safedelete.settings')->get('enforce_bulk_publish_verify_checkpagepublished') : 0,
          '#required' => FALSE,
        ];
        $form['enable_bulk_publish_validate']['#weight'] = -1;
        $form['#validate'][] = 'safedelete_form_validate_enable_bulkpublish';
        $form['#submit'][] = 'safedelete_form_submit_enable_bulkpublish';
      }
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function safedelete_form_views_form_content_page_1_alter(&$form, FormStateInterface $form_state, $form_id) {
  $route_name = \Drupal::routeMatch()->getRouteName();
  if ($route_name != 'system.admin_content') {
    return;
  }
  if (isset($form['header']['node_bulk_form']['action']['#options']['publish_latest'])) {
    // Only add this option when publish latest version actoin is available.
    if ($form_id == 'views_form_content_page_1') {
      if ((!isset($form['enable_bulk_publish_validate']))) {
        $form['enable_bulk_publish_validate']['widget'][0] = [
          '#type' => 'checkbox',
          '#title' => t('Enable bulk publish validation to check if the linked pages are unpublished.'),
          '#default_value' => \Drupal::config('safedelete.settings')->get('enforce_bulk_publish_verify_checkpagepublished') ? \Drupal::config('safedelete.settings')->get('enforce_bulk_publish_verify_checkpagepublished') : 0,
          '#required' => FALSE,
        ];
        $form['enable_bulk_publish_validate']['#weight'] = -1;
        $form['#validate'][] = 'safedelete_form_validate_enable_bulkpublish';
        $form['#submit'][] = 'safedelete_form_submit_enable_bulkpublish';
      }
    }
  }
}

/**
 * Implements hook_form_alter().
 */
function safedelete_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  if ((strpos($form_id, 'media') !== FALSE && strpos($form_id, 'delete_form') !== FALSE && $form_id != "media_type_delete_form")
      ||
      ($form_id == 'file_delete_form')
  ) {
    $limit = 20;
    $path = \Drupal::request()->getpathInfo();
    $arg = explode('/', $path);
    $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
    $index_arg = 0;
    $idx = 0;
    foreach ($arg as $segment) {
      if (is_numeric($segment) && !is_bool($segment)) {
        $index_arg = $idx;
      }
      $idx++;
    }
    if (isset($arg[$index_arg]) && is_numeric($arg[$index_arg])) {
      $mid = $arg[$index_arg];
      $fid = $arg[$index_arg];
    }
    else {
      // This should never happen.
      return;
    }
    if ($form_id == 'file_delete_form') {
      AdminHelper::checkFileReferencesMessage($mid, $bundle, $show_button, $markup, 'delete', $limit = 20);
      if ($show_button) {
        $form['description']['#markup'] = $markup;
        $form['#attached']['library'][] = 'safedelete/safedelete';
        $form['actions']['submit']['#access'] = $show_button ? TRUE : FALSE;
      }
      else {
        $form['description']['#markup'] = $markup;
        // $form['actions']['submit']['#access'] = $show_button ? TRUE : FALSE;
        $has_show_delete_permission = \Drupal::currentUser()->hasPermission('safedelete show delete button');
        if (!$has_show_delete_permission) {
          $has_show_delete_permission = \Drupal::config('safedelete.settings')->get('delete_button');
        }
        if (!$has_show_delete_permission) {
          $form['actions']['submit']['#attributes'] = ['disabled' => $show_button ? 'enabled' : 'disabled'];
        }
      }
    }
    elseif (strpos($form_id, 'media') !== FALSE) {
      AdminHelper::checkMediaReferencesMessage($mid, $bundle, $show_button, $markup, 'delete', $limit = 20);
      if ($show_button) {
        $form['description']['#markup'] = $markup;
        $form['#attached']['library'][] = 'safedelete/safedelete';
        $form['actions']['submit']['#access'] = $show_button ? TRUE : FALSE;
      }
      else {
        $form['description']['#markup'] = $markup;
        // $form['actions']['submit']['#access'] = $show_button ? TRUE : FALSE;
        $form['actions']['submit']['#attributes'] = ['disabled' => $show_button ? 'enabled' : 'disabled'];
      }
    }
  }
  if (strpos($form_id, 'node') !== FALSE &&
      strpos($form_id, 'delete_form') !== FALSE &&
      $form_id != "node_type_delete_form") {
    $limit = 20;
    $path = \Drupal::request()->getpathInfo();
    $arg = explode('/', $path);
    $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
    $index_arg = 0;
    $idx = 0;
    foreach ($arg as $segment) {
      if (is_numeric($segment) && !is_bool($segment)) {
        $index_arg = $idx;
      }
      $idx++;
    }
    if (isset($arg[$index_arg]) && is_numeric($arg[$index_arg])) {
      $nid = $arg[$index_arg];
    }
    else {
      // This should never happen.
      return;
    }
    AdminHelper::checkNodeReferencesMessage($nid, $bundle, $show_button, $markup, 'delete', $limit = 20);
    if ($show_button) {
      $form['description']['#markup'] = $markup;
      $form['#attached']['library'][] = 'safedelete/safedelete';
      $form['actions']['submit']['#access'] = $show_button ? TRUE : FALSE;
    }
    else {
      $form['description']['#markup'] = $markup;
      // $form['actions']['submit']['#access'] = $show_button ? TRUE : FALSE;
      $form['actions']['submit']['#attributes'] = ['disabled' => $show_button ? 'enabled' : 'disabled'];
    }
  }
  // This section must stay at the end of the form_alter.
  // Handle form entity add/edit.
  $form_object = $form_state->getFormObject();

  if (!$form_object instanceof EntityForm) {
    return;
  }

  $entity = $form_object->getEntity();
  $entity_type_id = $entity->getEntityTypeId();
  $bundle = $entity->bundle();

  if (!empty($entity) && $entity_type_id == 'node') {
    // Add our validation handler here.
    if (str_starts_with($form_id, 'node_' . $bundle . '_edit_form') !== FALSE ||
    str_starts_with($form_id, 'node_' . $bundle . '_form')) {
      $safe_anchors = \Drupal::config('safedelete.settings')->get('safeanchors');
      if ($safe_anchors && !in_array('safedelete_entity_form_safe_anchors_validate', $form['#validate'])) {
        $form['#validate'][] = 'safedelete_entity_form_safe_anchors_validate';
      }
    }
  }
  if (!empty($entity) && $entity_type_id == 'block') {
    // Disable this for now as it's not fully developped or tested.
    // @todo ,finish this.
    return;
    $safe_anchors = \Drupal::config('safedelete.settings')->get('safeanchors');
    if ($safe_anchors && !in_array('safedelete_entity_form_safe_anchors_validate', $form['#validate'])) {
      $form['#validate'][] = 'safedelete_entity_form_safe_anchors_validate';
    }
  }
}

/**
 * Submit handler for enforcing use of linkit style anchors.
 *
 * @param array $form
 *   Form definition array.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state object.
 */
function safedelete_entity_form_safe_anchors_validate(array &$form, FormStateInterface $form_state) {
  $language = \Drupal::languageManager()->getCurrentLanguage();
  $langcode = $language->getId();

  $moderation_state = '';
  $form_object = $form_state->getFormObject();
  $entity = NULL;
  if ($form_object instanceof EntityForm) {
    $entity = $form_object->getEntity();
    $entity_id = NULL;
    if (!$entity->isNew()) {
      $entity_id = $entity->id();
    }
    $entity_type = $entity->getEntityTypeId();
    $bundle = $entity->bundle();
    if ($bundle == 'block') {
      return;
    }
    $entityFieldManager = \Drupal::service('entity_field.manager');
    $fields = $entityFieldManager->getFieldDefinitions($entity_type, $bundle);
    $validate = TRUE;
    if (isset($fields['moderation_state'])) {
      $moderation_state = $form_state->getValue('moderation_state')[0]['value'];
      $validate = $moderation_state == 'published' ? TRUE : FALSE;
      if (!$validate) {
        $validate = $moderation_state == 'archived' ? TRUE : FALSE;
      }
    }
    // Process the translated values.
    $values = $form_state->getValues();
    $original_language = $entity->language()->getId();

    // Handle moderation_state value.
    if ($validate) {
      if ($entity->hasField('body') && !empty($entity->body) && isset($values['body'])) {
        $show_button = TRUE;
        $archived_validation = FALSE;
        $msgdetail_isArchived = NULL;
        $body_field_val = '';
        foreach ($values['body'] as $body_item) {
          $body_field_val .= $body_item['value'];
        }
        $field_name = 'body';
        if ($moderation_state == 'archived') {
          $found_token_href = FALSE;
          $found_absolute_url = FALSE;
          $found_internal_anchor = FALSE;
          $nid = $entity->id();
          $bundle = $entity->bundle();
          $found_suspect_node = FALSE;
          AdminHelper::checkNodeReferencesMessage($nid, $bundle, $show_button, $msgdetail_isPublished, 'archived_archived', 20);
          if (!$show_button) {
            $found_suspect_node = TRUE;
            $error_message = t('Unable to archive due to published or draft content linking to this archive candidate node.  See warning message above');
            $ms = &$form['moderation_state']['widget'][0];
            $key = isset($ms['state']) ? 'state' : (isset($ms['target_state']) ? 'target_state' : NULL);
            if ($key) {
              // Attach the error ONLY to the “Change to” select.
              $form_state->setError($ms[$key], $error_message);
            }
            else {
              // Fallback (rare): target by name using parents syntax.
              $form_state->setErrorByName('moderation_state][widget][0][state', $error_message);
            }
          }
          else {
            $found_suspect_node = FALSE;
            $archived_validation = TRUE;
            $msgdetail_isArchived = $msgdetail_isPublished;
          }
        }
        else {
          AdminHelper::checkPagePublished($body_field_val, $found_suspect_node, $msgdetail_isPublished);
          AdminHelper::checkUrlToken($body_field_val, $found_token_href, $msgdetail_isToken);
          AdminHelper::checkAbsoluteUrlPlus($body_field_val, $found_absolute_url, $msgdetail_isAbsoluteURL);
          AdminHelper::checkAbsoluteUrl($body_field_val, $found_absolute_url, $msgdetail_isAbsoluteURL);
          AdminHelper::checkInternalAnchors($body_field_val, $found_internal_anchor, $msgdetail_isInternal);
        }
        $markup_head_head = '<div data-drupal-messages="" class="messages-list"><div role="contentinfo" aria-labelledby="message-warning-title" class="messages-list__item messages messages--warning">';
        $markup_inner_foot = '</div></div>';
        $markup_foot = "</li></ul></div></div>" . $markup_inner_foot;
        if ($found_token_href || $found_suspect_node || $found_absolute_url || $found_internal_anchor) {
          $lang = $langcode;
          if ($show_button) {
            $markup_head = $markup_head_head . '<div role="alert"><div class="messages__header"><h2 id="message-warning-title" class="messages__title">' .
            t('Safedelete validation (%lang)', ['%lang' => $lang]) . '</h2></div><div class="messages__content"><ul class="messages__list"><li>';
          }
          if (!$show_button) {
            $markup_head = '';
            $markup_foot = '';
          }
          $markup = '';
          if ($found_token_href) {
            $markup .= $msgdetail_isToken;
          }
          if ($found_suspect_node) {
            $markup .= $msgdetail_isPublished;
          }
          if ($found_absolute_url) {
            $markup .= $msgdetail_isAbsoluteURL;
          }
          if ($found_internal_anchor) {
            $markup .= $msgdetail_isInternal;
          }
          $markup = Markup::create($markup_head . $markup . $markup_foot);
          $form['description']['#markup'] = $markup;
          $form['description']['#weight'] = '-99';
          $error_message = t('Unable to publish node %entity_id. See warning message(s).', ['%entity_id' => $entity_id]);
          if ($moderation_state != 'archived') {
            $form_state->setErrorByName($field_name, $error_message);
            \Drupal::messenger()->addMessage(t('Safe delete prevented you from publishing "%title" entity_id %id.', [
              '%id' => $entity_id,
              '%title' => $entity->getTitle(),
            ]), 'error', TRUE);
          }
          $found_token_href = FALSE;
          $found_suspect_node = FALSE;
          $found_absolute_url = FALSE;
          $found_internal_anchor = FALSE;
        }
        else if ($archived_validation) {
	  \Drupal::messenger()->addMessage($msgdetail_isArchived, 'warning', TRUE);
        }
        $all_languages = AdminHelper::getAllEnabledLanguages();
        foreach ($all_languages as $other_langcode => $other_language) {
          if (isset($values[$field_name . '-etuf-' . $other_langcode])) {
            $body_other_array = $values[$field_name . '-etuf-' . $other_langcode][0]['value'];
            AdminHelper::checkPagePublished($body_other_array, $found_suspect_node, $msgdetail_isPublished);
            AdminHelper::checkUrlToken($body_other_array, $found_token_href, $msgdetail_isToken);
            AdminHelper::checkAbsoluteUrlPlus($body_other_array, $found_absolute_url, $msgdetail_isAbsoluteURL, $other_langcode);
            AdminHelper::checkAbsoluteUrl($body_other_array, $found_absolute_url, $msgdetail_isAbsoluteURL);
            AdminHelper::checkInternalAnchors($body_other_array, $found_internal_anchor, $msgdetail_isInternal, $other_langcode);
            if ($found_token_href || $found_suspect_node || $found_absolute_url || $found_internal_anchor) {
               // Reset markup.
               $markup = '';
               if ($found_token_href) {
                 $markup .= $msgdetail_isToken;
               }
               if ($found_suspect_node) {
                 $markup .= $msgdetail_isPublished;
               }
               if ($found_absolute_url) {
                 $markup .= $msgdetail_isAbsoluteURL;
               }
               if ($found_internal_anchor) {
                 $markup .= $msgdetail_isInternal;
               }
               $markup_head = $markup_head_head . '<div role="alert"><div class="messages__header"><h2 id="message-error-title" class="messages__title">' .
               t('Safedelete validation (%lang)', ['%lang' => $other_langcode]) . '</h2></div><div class="messages__content"><ul class="messages__list"><li>';
               $markup = Markup::create($markup_head . $markup . $markup_foot);
               $markup = Markup::create($markup);
               // $form['description']['#markup'] .= $markup;
               // $form['body' . '-etuf-' . $other_langcode]['#weight'] = '-99';
               $error_message = t('Unable to publish node %entity_id. See warning message(s).', ['%entity_id' => $entity_id]);
               $form_state->setErrorByName($field_name . '-etuf-' . $other_langcode, $error_message);
               \Drupal::messenger()->addMessage(t('Safe delete prevented you from publishing "%title" entity_id %id.', [
                 '%id' => $entity_id,
                 '%title' => $entity->getTitle(),
               ]), 'error', TRUE);
            }
          }
        }
      }
    }
  }
}

/**
 * Form submission handler for node_form().
 *
 * Configure entity translation node object for entity_translation_unified_form.
 */
function safedelete_node_form_safe_archive_validate(array &$form, FormStateInterface $form_state) {
  // @todo Review possibility of moving validate to a field constraint instead.
  $language = \Drupal::languageManager()->getCurrentLanguage();
  $langcode = $language->getId();

  $form_object = $form_state->getFormObject();
  $entity = NULL;
  if ($form_object instanceof EntityForm) {
    $entity = $form_object->getEntity();
    if ($entity->isNew()) {
      // Skipping checks because it hasn't been referenced yet.
      return;
    }
    $entity_type = $entity->getEntityTypeId();
    $bundle = $entity->bundle();

    $entityFieldManager = \Drupal::service('entity_field.manager');
    $fields = $entityFieldManager->getFieldDefinitions($entity_type, $bundle);

    // Process the translated values.
    $values = $form_state->getValues();
    foreach ($fields as $field_name => $field_definition) {
      if ($field_name == 'moderation_state' && is_array($values[$field_name])) {
        // Handle moderation_state value.
        $moderationArray = reset($values[$field_name]);
        $state = $moderationArray['value'];
        if ($state == 'archived') {
          $nid = $entity->id();
          AdminHelper::checkNodeReferencesMessage($nid, $bundle, $show_button, $markup, 'archived', $limit = 20);
          if (!$show_button) {
            $markup = Markup::create($markup);
            $form['description']['#markup'] = $markup;
            $form['description']['#weight'] = '-99';
            $error_message = t('Unable to archive due to other content linking to this content.  See warning message above');
            $form_state->setErrorByName($field_name, $error_message);
          }
        }
      }
      $all_languages = AdminHelper::getAllEnabledLanguages();
      foreach ($all_languages as $other_langcode => $other_language) {
        if ($entity->hasTranslation($other_langcode)) {
          $field_type = $field_definition->getType();
          if ($field_name == 'moderation_state' && is_array($values[$field_name])) {
            $field_name_test = $field_name;
            if (isset($values[$field_name . '-etuf-' . $other_langcode])) {
              $field_name_test = $field_name . '-etuf-' . $other_langcode;
            }
            // Handle moderation_state value.
            $moderationArray = reset($values[$field_name_test]);
            $state = $moderationArray['value'];
            if ($state == 'archived') {
              if (!$show_button) {
                $form_state->setErrorByName($field_name_test, $error_message);
              }
            }
          }
        }
      }
    }
  }
}

/**
 * Implements hook_help().
 */
function safedelete_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.safedelete':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Safe Delete module is built to check for links in body fields and if there are links that will be b roken prevent the Node deletion') . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';

      $output .= '<dt>' . t('Configuring Safe Delete') . '</dt>';
      $output .= '<dd>' . t('The Safe Delete module provides page for configuring the show delete button, limit, format  <a href=":config">Safe Delete settings</a>.', [':config' =>  Url::fromRoute('safedelete.settings')->toString()]) . '</dd>';

      $output .= '</dl>';
      return $output;

    case 'safedelete.settings':
      return '<p>' . t('This page shows you all available administration tasks for Safe Delete module.') . '</p>';

  }
}

/**
 * Implements custom file usage hanlder for the page creation case.
 */
function _handleInsertFileUsage_PageCreation($entity, $entity_id) {
  if ($entity->hasField('body')) {
    $all_languages = AdminHelper::getAllEnabledLanguages();
    foreach ($all_languages as $other_langcode => $other_language) {
      if ($entity->hasTranslation($other_langcode)) {
        // Stores fid and number of the same file count for the usage definition change.
        $filearray = [];
        $body_field = NULL;
        $regex_mediaobj = "/data-entity-type=\"media\" data-entity-uuid=\"([a-z]|[0-9]){8}-(([0-9]|[a-z]){4}-){3}([0-9]|[a-z]){12}\"/";
        $regex_uuid = "/([a-z]|[0-9]){8}-(([0-9]|[a-z]){4}-){3}([0-9]|[a-z]){12}/";
        $body_field = $entity->getTranslation($other_langcode)->get('body')->value;
        if (!is_null($body_field) && !empty($body_field)) {
          // Using PHP 8.1 or higher, do not do a preg_match_all on null.
          preg_match_all($regex_mediaobj, $body_field, $matches, PREG_SET_ORDER);
        }
        else {
          $matches = [];
        }

        foreach ($matches as $match) {
          $file = reset($match);
          preg_match_all($regex_uuid, $file, $id_match, PREG_SET_ORDER);
          $muuid = $id_match[0][0];
          $mentity = \Drupal::service('entity.repository')->loadEntityByUuid('media', $muuid);
          if (is_null($mentity)) {
            continue;
          }
          $fid = $mentity->getSource()->getSourceFieldValue($mentity);
          if ((array_key_exists($fid, $filearray))) {
            $filearray[$fid] = $filearray[$fid] + 1;
          }
          else {
            $filearray[$fid] = 1;
          }
        }

        if (count($filearray) > 0) {
          foreach ($filearray as $fileid => $refcount) {
            $fentity = File::load($fileid);
            $file_usage = \Drupal::service('file.usage');
            $file_usage->add($fentity, 'editor', 'node', $entity_id);
          }
        }
      } // end of each lang
    } // end of for loop
  }
}

/**
 * Implements hook_theme().
 */
function safedelete_theme($existing, $type, $theme, $path) {
  return [
    'safedelete_orphanedpages' => [
      'variables' => [
        'orphanedpages_container' => NULL,
        'showconfigsettinglink' => NULL,
        'vars_array' => NULL,
      ],
    ],
  ];
}

/**
 *
 */
function batch_ProcessNodes_Op($id, $operation_details, $nid, $filedirectory, $exceptionarray, $super_type, &$context) {

  // Store some results for post-processing in the 'finished' callback.
  // The contents of 'results' will be available as $results in the
  // 'finished' function (in this example, batch_example_finished()).
  $context['results'][] = $id;
  $database = \Drupal::database();
  // Reset flag.
  $resultflag = FALSE;
  $entity_manager = \Drupal::entityTypeManager();
  $node = $entity_manager->getStorage('node')->load($nid);
  if (isset($node) && sizeof($exceptionarray) >= 0 && !in_array($nid, $exceptionarray)) {
    $nmstate = 'published';
    if (!$node->isPublished()) {
      $nmstate = 'unpublished';
    }
    if ($node->hasField('moderation_state')) {
      $nmstate = $node->get('moderation_state')->getString();
    }
    if ($nmstate == 'published') {
      $uuid = AdminHelper::getUuidFromNid($nid, $database);
      $type = AdminHelper::getNodeTypeFromNid($nid, $database);
      $resultreason = "";
      if ($type == $super_type && !AdminHelper::hasMenuLink($nid)) {
        // Do further check if the page is linked by other pages.
        if (!AdminHelper::findLinkitReferences($uuid, $nid)) {
          $resultflag = TRUE;
          $resultreason = t('No Menu Link and No Other Pages linked');
        }
      }
      elseif ($type != $super_type) {
        // Check if the special page type is linked by other pages.
        if (!AdminHelper::findLinkitReferences($uuid, $nid)) {
          $resultflag = TRUE;
          $resultreason = t('No Other Pages linked to this.');
        }
      }
    }
    if ($resultflag) {
      // @todo Performance and scalability improvement suggestion:
      // Build a custom pager and do the node load only for the current page.
      // Refactor this so that the nid is stored in an array in this forloop,
      // then just after the for loop process the nid array do the node load for only the current 'page'.
      $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
      $ntype = $node->type->entity->label();
      $ntitle = $node->get('title')->value;
      $reporteddateobj = new \DateTime('now');
      // array_shift($datearray);
      $reporteddate = $reporteddateobj->format('Y-m-d H:i:s');
      $orphanedpages_results[$nid]['nid'] = $nid;
      $orphanedpages_results[$nid]['title'] = $ntitle;
      $orphanedpages_results[$nid]['href'] = AdminHelper::getUrlForNode($nid, $langcode);
      $orphanedpages_results[$nid]['type'] = $node->type->entity->label();
      $orphanedpages_results[$nid]['reason'] = $resultreason;
      $orphanedpages_results[$nid]['reporteddate'] = $reporteddate;
      // Save date into the json file.
      $arrayforjsonobj = [
        'nid' => $nid,
        'title' => $ntitle,
        'type' => $node->type->entity->label(),
        'href' => AdminHelper::getUrlForNode($nid, $langcode),
        'reason' => $resultreason,
        'reporteddate' => $reporteddate,
      ];
      $json = json_encode($arrayforjsonobj, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
      $fp = $filedirectory . $nid . '.json';
      $file = \Drupal::service('file.repository')->writeData($json, $fp, FileSystemInterface::EXISTS_REPLACE);
    }
  }
  // Optional message displayed under the progressbar.
  $context['message'] = t('Running Batch "@id" @details',
    ['@id' => $id, '@details' => $operation_details]
  );
}

/**
 * Batch 'finished' callback used by batch.
 */
function batch_ProcessNodes_finished($success, $results, $operations) {
  $messenger = \Drupal::messenger();
  if ($success) {
    // Here we could do something meaningful with the results.
    // We just display the number of nodes we processed...
    $messenger->addMessage(t('Checking orphaned nodes proccess are successfully finished.'));
    // $messenger->addMessage(t('@count nodes are processed.', ['@count' => count($results)]));
    // $messenger->addMessage(t('The final result was "%final"', ['%final' => end($results)]));
  }
  else {
    // An error occurred.
    // $operations contains the operations that remained unprocessed.
    $error_operation = reset($operations);
    $messenger->addMessage(
      t('An error occurred while processing @operation with arguments : @args',
        [
          '@operation' => $error_operation[0],
          '@args' => print_r($error_operation[0], TRUE),
        ]
      )
    );
  }
}

/**
 * Implements hook_cron().
 */
function safedelete_cron() {
  $reporteddate = '';
  $orphanedpages_results = [];
  $filedirectory = 'private://orphaned_pages_files/';
  $database = \Drupal::database();
  AdminHelper::listOrphanedNodes($reporteddate, $orphanedpages_results, $filedirectory, $database);
}

/**
 * Implements hook_moderated_content_bulk_publish_verify_archived().
 *
 * @param \Drupal\moderated_content_bulk_publish\HookObject.
 *   An object with properties by reference.
 */
function safedelete_moderated_content_bulk_publish_verify_archived($hookObject) {
  $state = 'archived';
  $limit = 20;
  $nid = $hookObject->nid;
  $bundle = $hookObject->bundle;
  $show_button = $hookObject->show_button;
  $markup = $hookObject->markup;
  $error_message = $hookObject->error_message;
  AdminHelper::checkNodeReferencesMessage($nid, $bundle, $show_button, $markup, $state, $limit);
  // Pass references back.
  $hookObject->show_button = $show_button;
  $hookObject->markup = $markup;
  // Echo print_r($hookObject, TRUE);.
  if (!$hookObject->show_button) {
    $hookObject->error_message = t('Unable to archive due to other content linking to this content.  See warning message(s)');
  }
}

/**
 * Implements hook_moderated_content_bulk_publish_verify_publish().
 *
 * @param \Drupal\moderated_content_bulk_publish\HookObject.
 *   An object with properties by reference.
 */
function safedelete_moderated_content_bulk_publish_verify_publish($hookObject) {
  $state = 'publish';
  $body_field_val = $hookObject->body_field_val;
  $nid = $hookObject->nid;
  $enforce_bulk_publish_verify_checkpagepublished = \Drupal::config('safedelete.settings')->get('enforce_bulk_publish_verify_checkpagepublished');
  $found_unpublished_node = FALSE;
  $msgdetail_isPublished = '';
  if ($enforce_bulk_publish_verify_checkpagepublished) {
    AdminHelper::checkPagePublished($body_field_val, $found_unpublished_node, $msgdetail_isPublished);
    AdminHelper::checkAbsoluteUrlPlus($body_field_val, $found_absolute_url, $msgdetail_isAbsoluteURL);
    if ($found_absolute_url) {
      $msgdetail_isPublished .= $msgdetail_isAbsoluteURL;
    }
    AdminHelper::checkInternalAnchors($body_field_val, $found_internal_anchor, $msgdetail_isInternal);
    if ($found_internal_anchor) {
      $msgdetail_isPublished .= $msgdetail_isInternal;
    }
  }
  AdminHelper::checkUrlToken($body_field_val, $found_token_href, $msgdetail_isToken);
  AdminHelper::checkAbsoluteUrl($body_field_val, $found_absolute_url, $msgdetail_isAbsoluteURL);
  if ($found_token_href || $found_unpublished_node || $found_absolute_url) {
    $msgtitle = t('Unable to publish node %nid. See warning message(s).', ['%nid' => $nid]);
    $hookObject->error_message = $msgtitle;
    $hookObject->msgdetail_isToken = $msgdetail_isToken;
    $hookObject->msgdetail_isPublished = $msgdetail_isPublished;
    $hookObject->msgdetail_isAbsoluteURL = $msgdetail_isAbsoluteURL;
    $hookObject->validate_failure = TRUE;
  }
}

/**
 * Implements hook_preprocess_status_messages().
 *
 * Updates or removes the status message (type = status),
 * and displays the warning message (type = warning) if bulk action process fails.
 */
function safedelete_preprocess_status_messages(&$variables) {
  $msg = '';
  $msgCount = 0;
  $action = 'Archive current revision';
  if (isset($variables['message_list']['status'])) {
    $msgCount = count($variables['message_list']['status']);
    if ($msgCount > 0) {
      // Get the last item of Status Message for the archive bulk action.
      $msg = $variables['message_list']['status'][($msgCount - 1)];
    };
  }
  // Only handles Archive current revision action.
  if (isset($variables['message_list']['warning']) && (stripos($msg, $action) !== FALSE)) {
    $warning_messages = $variables['message_list']['warning'];
    $warningCount = count($variables['message_list']['warning']);
    $totalCount = 0;
    if ($warningCount > 0) {
      $int_str = preg_replace('/[^0-9]/', '', $msg);
      $totalCount = intval($int_str);
      $diffCount = $totalCount - ($warningCount - 1);
      $diff_str = strval($diffCount);
      if ($diffCount == 0) {
        unset($variables['message_list']['status']);
      }
      elseif ($diffCount == 1) {
        $new_msg = t(str_replace($int_str . ' items', $diff_str . ' item', $msg));
        $variables['message_list']['status'][($msgCount - 1)] = Markup::create($new_msg);
      }
      else {
        $diff_str = strval($diffCount);
        $new_msg = t(str_replace($int_str, $diff_str, $msg));
        $variables['message_list']['status'][($msgCount - 1)] = Markup::create($new_msg);
      }
    }
  }

  $bulk_publish_action = 'Publish latest revision';
  // Only handles Publish latest revision action.
  if (isset($variables['message_list']['warning']) && (stripos($msg, $bulk_publish_action) !== FALSE)) {
    $warningCount = 0;
    $warning_messages = $variables['message_list']['warning'];
    foreach ($warning_messages as $delta => $message) {
      $pattern = '/Unable to publish node \<em class=\"placeholder\"\>\d*\<\/em\>./';
      if (preg_match_all($pattern, $message, $matches)) {
        $warningCount = $warningCount + 1;
      }
    }
    if ($warningCount > 0) {
      $int_str = preg_replace('/[^0-9]/', '', $msg);
      $totalCount = intval($int_str);
      $diffCount = $totalCount - $warningCount;
      $diff_str = strval($diffCount);
      if ($diffCount == 0) {
        unset($variables['message_list']['status']);
      }
      elseif ($diffCount == 1) {
        $new_msg = t(str_replace($int_str . ' items', $diff_str . ' item', $msg));
        $variables['message_list']['status'][0] = Markup::create($new_msg);
      }
      else {
        $diff_str = strval($diffCount);
        $new_msg = t(str_replace($int_str, $diff_str, $msg));
        $variables['message_list']['status'][0] = Markup::create($new_msg);
      }
    }
  }
}

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

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