cbr-1.0.0/cbr.module

cbr.module
<?php

use Drupal\cbr\Plugin\Field\FieldType\CBRCaseStatus;
use Drupal\cbr\Plugin\Field\FieldType\CBRFieldInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

//** Entity-API hooks **/
/** Implements hook_entity_insert() 
 * @param EntityInterface $entity The entity being inserted.
 */
function cbr_node_insert(EntityInterface $entity)
{
  if ($entity->getEntityTypeId() == 'node') {
    /** @var \Drupal\node\NodeInterface $entity */
    cbr_set_case_status($entity);
    if (cbr_is_enabled_on_entity($entity) && cbr_number_of_nodes($entity->getType()) > 1) {
      // cbr_calculate_similarity($entity); //Uncomment to calculate similarity on insert directly
      cbr_add_to_calculation_queue($entity);
    }
  }
}

/** Implements hook_entity_update() 
 * @param EntityInterface $entity The entity being updated.
 */
function cbr_node_update(EntityInterface $entity)
{
  if ($entity->getEntityTypeId() == 'node') {
    /** @var \Drupal\node\NodeInterface $entity */
    if (cbr_is_enabled_on_entity($entity) && cbr_number_of_nodes($entity->getType()) > 1) {
      // cbr_calculate_similarity($entity); //Uncomment to recalculate similarity on update directly
      cbr_add_to_calculation_queue($entity);
    }
  }
}

/** Implements hook_entity_delete()
 * @param EntityInterface $entity The entity being deleted.
 */
function cbr_node_delete(EntityInterface $entity)
{
  if ($entity->getEntityTypeId() == 'node') {

    //Delete similarity
    $query = \Drupal::database()->delete('cbr_similarity');
    $query->condition($query->orConditionGroup()->condition('nid1', $entity->id())->condition('nid2', $entity->id()));
    $query->execute();

    //Delete from calculation queue
    $query = \Drupal::database()->delete('cbr_calculation_queue');
    $query->condition($query->orConditionGroup()->condition('nid', $entity->id())->condition('nid', $entity->id()));
    $query->execute();
  }
}

/**
 * Implements hook_form_node_form_alter()
 * Add a second submit button to the node form to save the node and calculate the similarity.
 */
function cbr_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id)
{
  if (cbr_is_enabled_on_entity($form_state->getFormObject()->getEntity())) {
    $form['actions']['save_and_calculate_similarity'] = [
      '#button_type' => 'primary',
      '#type' => 'submit',
      '#value' => t('Save and calculate similarity'),
      '#weight' => 1,
      '#submit' => ['::submitForm', '::save', 'cbr_submit_node_form'],
    ];
  }
  return $form;
}

function cbr_submit_node_form(array &$form, FormStateInterface $form_state)
{
  if (cbr_number_of_nodes($form_state->getFormObject()->getEntity()->getType()) > 1) {
    cbr_calculate_similarity($form_state->getFormObject()->getEntity(), true);
  }
}

function cbr_add_to_calculation_queue(EntityInterface $entity)
{
  $query = \Drupal::database()->merge('cbr_calculation_queue');
  $query->key('nid', $entity->id());
  $query->fields(['updated' => time()]);
  $query->execute();
}

function cbr_cron()
{
  $cbr_similarity_calculator = \Drupal::service('cbr.similarity_calculator');
  $node_storage =  \Drupal::entityTypeManager()->getStorage('node');
  $logger = \Drupal::logger('cbr');

  $query = \Drupal::database()->select('cbr_calculation_queue', 'c');
  $query->fields('c', ['nid']);
  $query->orderBy('updated', 'DESC');
  $query->range(0, 100); //limit to 100 cases at a cron run to avoid timeouts
  $nids = $query->execute()->fetchCol();

  $done = [];

  foreach ($nids as $nid) {
    $entity = $node_storage->load($nid);

    if ($entity) {
      //Get all others Node-IDs
      $other_nids = \Drupal::entityQuery('node')
        ->condition('nid', $entity->id(), "<>")
        ->condition('type', $entity->bundle())
        ->execute();


      //Split other nids into chunks of 10
      $other_nids_chunks = array_chunk($other_nids, 10);

      foreach ($other_nids_chunks as $other_nids_chunk) {
        $entities = $node_storage->loadMultiple($other_nids_chunk);
        
        //Reset memory cache to avoid memory issues
        $node_storage->resetCache($other_nids_chunk);

        //loop through all entities of chunk and calculate similarity
        foreach ($entities as $other_entity) {

          //Skip if already calculated
          if ($done[$entity->id()][$other_entity->id()] ?? false) {
            continue;
          }

          $similarity = $cbr_similarity_calculator->calculate($entity, $other_entity);
          $cbr_similarity_calculator->saveSimilarity($entity->id(), $other_entity->id(), $similarity);

          $done[$entity->id()][$other_entity->id()] = true;
          $done[$other_entity->id()][$entity->id()] = true;
        }
      }
    }
    $logger->info('Calculated similarity between node nid @nid and @count other nodes', ['@nid' => $entity->id(), '@count' => count($other_nids)]);
  }
}



/** Returns true, if an entity has CBR enabled 
 *
 * @param EntityInterface $entity The entity
 * @return True, if CBR is active on this content type or false, if not
 */
function cbr_is_enabled_on_entity(EntityInterface $entity): bool
{
  $has_cbr_field = false;
  foreach ($entity->getFields(false) as $field_item) {
    if ($field_item->first() instanceof CBRFieldInterface) {
      $has_cbr_field = true;
      break;
    }
  }

  $has_cbr_status_field = false;
  foreach ($entity->getFieldDefinitions() as $field_item) {
    if ($field_item->getType() == 'cbr_case_status') {
      $has_cbr_status_field = true;
      break;
    }
  }

  if ($has_cbr_field && !$has_cbr_status_field) {
    \Drupal::messenger()->addWarning(t('This content type has CBR fields, but no CBR status field was found. Please add a CBR status field.', []));
    \Drupal::logger('cbr')->warning('CBR fields on @type, but no CBR status field is defined.', ['@type' => $entity->getEntityTypeId()]);
  }

  return $has_cbr_status_field;
}

/**
 * Return the number of nodes of a given type.
 * @param string $type The type of nodes to count.
 */
function cbr_number_of_nodes(string $type): int
{
  $connection = \Drupal::database();
  $query = $connection->select('node_field_data', 'n');
  $query->addExpression('COUNT(*)');
  $query->condition('n.type', $type);
  return $query->execute()->fetchField();
}

/**
 * Set the case status of an entity.
 * @param EntityInterface $entity The entity to set the status for.
 */
function cbr_set_case_status(EntityInterface $entity)
{
  foreach ($entity->getFieldDefinitions() as $field_item) {
    if ($field_item->getType() == 'cbr_case_status'  && !isset($entity->get($field_item->getName())->value)) {
      $entity->get($field_item->getName())->setValue(CBRCaseStatus::CASE_STATUS_NEW);
      $entity->save();
      break;
    }
  }
}

/**
 * Start the calculation of the similarity of a given entity. This will start a new batch job.
 * @param EntityInterface $entity The entity to calculate the similarity for.
 */
function cbr_calculate_similarity(EntityInterface $entity)
{
  $batch = [
    'operations' => [
      ['cbr_batch_calculation', [$entity, \Drupal::routeMatch()->getRouteName()]]
    ],
    'finished' => 'cbr_batch_finished',
    'title' => t('Processing CBR calculation'),
    'init_message' => t('CBR calculation is starting.'),
    'progress_message' => t('Processed @current out of @total.'),
    'error_message' => t('CBR core calculate has encountered an error.'),
  ];

  batch_set($batch);
}

/**
 * Batch callback to calculate the similarity of a given entity.
 * @param EntityInterface $entity The entity to calculate the similarity for.
 * @param string $src_route The current route.
 * @param array $context The batch context.
 */
function cbr_batch_calculation(EntityInterface $entity, $src_route, &$context)
{
  //Set some information on first batch call
  if (!isset($context['sandbox']['progress'])) {
    $context['sandbox']['progress'] = 0;
    $context['sandbox']['current_node'] = 0;
    $context['sandbox']['sum_weight'] = 0;
    $context['results']['nid'] = $entity->id();
    $context['results']['src_route'] = $src_route;
    $context['results']['start_time'] = microtime(TRUE);

    //Get all Node-IDs
    $context['sandbox']['nids'] = \Drupal::entityQuery('node')
      ->condition('nid', $entity->id(), "<>")
      ->condition('type', $entity->bundle())
      ->execute();
    $context['sandbox']['max'] = count($context['sandbox']['nids']);
  }

  $cbr_similarity_calculator = \Drupal::service('cbr.similarity_calculator');

  // With each pass through the callback, we calculate the similarity of one node to another node of the same type.
  $nid2 = array_shift($context['sandbox']['nids']);
  if ($nid2 != NULL && !isset($context['results']['similarity'][$entity->id()][$nid2])) {
    $entity2 = \Drupal::EntityTypeManager()->getStorage('node')->load($nid2);
    $similarity = $cbr_similarity_calculator->calculate($entity, $entity2);
    $context['results']['similarity'][$entity->id()][$entity2->id()] = $similarity;
    $context['results']['similarity'][$entity2->id()][$entity->id()] = $similarity;
    $cbr_similarity_calculator->saveSimilarity($entity->id(), $entity2->id(), $similarity);
  }

  //If nids is empty, we are done
  if (count($context['sandbox']['nids']) == 0) {
    $context['finished'] = 1;
  } else {
    // Update our progress information.
    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
    $context['sandbox']['progress']++;
    $context['message'] = t('Now processing Node @nid', array('@nid' => $context['sandbox']['nids'][0]));
  }
}

/**
 * Batch 'finished' callback
 * @param bool $success Whether the batch was successful.
 * @param array $results The results of the batch.
 * @param array $operations The operations that were run.
 */
function cbr_batch_finished($success, $results, $operations)
{
  if ($success) {
    if ($results['src_route'] != 'cbr.merge') {
      $count = count($results['similarity']);
      //druation of batch process in seconds with two decimals
      $duration = round((microtime(TRUE) - $results['start_time']) * 100) / 100;
      Drupal::messenger()->addMessage(t('Calculated similarity for @count cases in @duration seconds.', ['@count' => $count, '@duration' => $duration]));
    }
  } else {
    // An error occurred.
    // $operations contains the operations that remained unprocessed.
    $error_operation = reset($operations);
    Drupal::messenger()
      ->addMessage(t('An error occurred while processing @operation with arguments: @args', [
        '@operation' => $error_operation[0],
        '@args' => print_r($error_operation[0], TRUE),
      ]));
  }

  // Redirect to the list of cases
  $link_to_similar_cases = \Drupal\Core\Url::fromUri("internal:/node/{$results['nid']}/similar-cases");
  return new RedirectResponse($link_to_similar_cases->toString());
}

/**
 * Views hook to show the similarity via the views module.
 */
function cbr_views_data()
{
  //Field CBR_SIMILARITY
  $data = [];
  $data['cbr_similarity'] = [];
  $data['cbr_similarity']['table']['group'] = t('Case Based Reasoning');


  $data['cbr_similarity']['table']['join'] = [
    'node_field_data' => [
      'left_field' => 'nid',
      'field' => 'nid2',
    ],
  ];

  $data['cbr_similarity']['nid1'] = array(
    'title' => t('Base Case Node ID'),
    'help' => t('Base case for which similar cases should be found.'),
    'argument' => [
      // ID of argument handler plugin to use.
      'id' => 'numeric',
    ]
  );

  $data['cbr_similarity']['similarity'] = [
    'title' => t('Similarity'),
    'help' => t('Similarity between two cases. A higher similarity means a more similar case.'),
    'field' => [
      // ID of field handler plugin to use.
      'id' => 'numeric',
    ],
    'sort' => [
      // ID of sort handler plugin to use.
      'id' => 'standard',
    ],
    'filter' => [
      // ID of filter handler plugin to use.
      'id' => 'numeric',
    ]
  ];

  return $data;
}

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

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