knowledge-8.x-1.x-dev/knowledge.module

knowledge.module
<?php

/**
 * @file
 * Enables users to knowledge on published content.
 *
 * When installed, the Knowledge module creates a field that facilitates a
 * discussion board for each Drupal entity to which a knowledge field is
 * attached. Users can post knowledge to discuss a forum topic, story,
 * collaborative book page, user etc.
 */

use Drupal\Component\Utility\DeprecationHelper;
use Drupal\Component\Utility\Html;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\Entity\EntityViewMode;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Link;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\Core\Utility\Error;
use Drupal\field\FieldConfigInterface;
use Drupal\field\FieldStorageConfigInterface;
use Drupal\knowledge\Entity\KnowledgeType;
use Drupal\knowledge\KnowledgeInterface;
use Drupal\knowledge\Plugin\Field\FieldType\KnowledgeItemInterface;
use Drupal\knowledge\Service\SequenceService;
use Drupal\node\NodeInterface;
use Drupal\user\RoleInterface;
use Drupal\user\UserInterface;
use Drupal\views\ViewExecutable;

/**
 * The time cutoff for knowledge marked as read for entity types other node.
 *
 * Knowledge changed before this time are always marked as read.
 * Knowledge changed after this time may be marked new, updated, or read,
 * depending on their state for the current user. Defaults to 30 days ago.
 *
 * @todo Remove when https://www.drupal.org/node/1029708 lands.
 */

/**
 * Implements hook_help().
 */
function knowledge_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.knowledge':
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Knowledge module allows users to knowledge on site content, set Knowledge defaults and permissions, and moderate Knowledge. For more information, see the <a href=":knowledge">online documentation for the Knowledge module</a>.', [':knowledge' => 'https://www.drupal.org/documentation/modules/knowledge']) . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Enabling Knowledge') . '</dt>';
      $output .= '<dd>' . t('Knowledge functionality can be enabled for any entity sub-type (for example, a <a href=":content-type">content type</a>) by adding a <em>Knowledge</em> field on its <em>Manage fields page</em>. Adding or removing knowledge links for an entity through the user interface requires the <a href=":field_ui">Field UI</a> module to be enabled, even though the knowledge functionality works without it. For more information on fields and entities, see the <a href=":field">Field module help page</a>.', [
        ':content-type' => (\Drupal::moduleHandler()->moduleExists('node')) ? Url::fromRoute('entity.node_type.collection')->toString() : '#',
        ':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString(),
        ':field_ui' => (\Drupal::moduleHandler()->moduleExists('field_ui')) ? Url::fromRoute('help.page', ['name' => 'field_ui'])->toString() : '#',
      ]) . '</dd>';
      $output .= '<dt>' . t('Configuring Knowledge settings') . '</dt>';
      $output .= '<dd>' . t('Knowledge settings can be configured by editing the <em>Knowledge</em> field on the <em>Manage fields page</em> of an entity type if the <em>Field UI module</em> is enabled. Configuration includes the label of the knowledge field, the amount of knowledge to be displayed, and whether they are shown in threaded list. Knowledge can be configured as: <em>Open</em> to allow new knowledge, <em>Closed</em> to view existing knowledge, but prevent new knowledge, or <em>Hidden</em> to hide existing knowledge and prevent new knowledge. Changing this configuration for an entity type will not change existing entity items.') . '</dd>';
      $output .= '<dt>' . t('Overriding default settings') . '</dt>';
      $output .= '<dd>' . t('Users with the appropriate permissions can override the default Knowledge settings of an entity type when they create an item of that type.') . '</dd>';
      $output .= '<dt>' . t('Adding knowledge types') . '</dt>';
      $output .= '<dd>' . t('Additional <em>knowledge types</em> can be created per entity sub-type and added on the <a href=":field">Knowledge types page</a>. If there are multiple knowledge types available you can select the appropriate one after adding a <em>Knowledge field</em>.', [':field' => Url::fromRoute('entity.knowledge_type.collection')->toString()]) . '</dd>';
      $output .= '<dt>' . t('Approving and managing knowledge') . '</dt>';
      $output .= '<dd>' . t('Knowledge from users who have the <em>Skip knowledge approval</em> permission are published immediately. All other Knowledge are placed in the <a href=":knowledge-approval">Unlinked knowledge</a> queue, until a user who has permission to <em>Administer knowledge and knowledge settings</em> publishes or deletes them. Published knowledge can be bulk managed on the <a href=":admin-knowledge">Published knowledge</a> administration page. When a knowledge has no replies, it remains editable by its author, as long as the author has <em>Edit own knowledge</em> permission.', [
        ':knowledge-approval' => Url::fromRoute('knowledge.admin_approval')->toString(),
        ':admin-knowledge' => Url::fromRoute('knowledge.admin')->toString(),
      ]) . '</dd>';
      $output .= '</dl>';
      return $output;

    case 'entity.knowledge_type.collection':
      $output = '<p>' . t('This page provides a list of all knowledge types on the site and allows you to manage the fields, form and display settings for each.') . '</p>';
      return $output;
  }
}

/**
 * Entity URI callback.
 */
function knowledge_uri(KnowledgeInterface $knowledge) {
  return new Url(
    'entity.knowledge.canonical',
    [
      'knowledge' => $knowledge->id(),
    ],
    ['fragment' => 'knowledge-' . $knowledge->id()]
  );
}

/**
 * Implements hook_entity_extra_field_info().
 */
function knowledge_entity_extra_field_info() {
  $return = [];
  foreach (KnowledgeType::loadMultiple() as $knowledge_type) {
    $return['knowledge'][$knowledge_type->id()] = [
      'form' => [
        'author' => [
          'label' => t('Author'),
          'description' => t('Author textfield'),
          'weight' => -2,
        ],
      ],
    ];
    $return['knowledge'][$knowledge_type->id()]['display']['links'] = [
      'label' => t('Links'),
      'description' => t('Knowledge operation links'),
      'weight' => 100,
      'visible' => TRUE,
    ];
  }

  return $return;
}

/**
 * Implements hook_theme().
 */
function knowledge_theme() {
  return [
    'knowledge' => [
      'render element' => 'elements',
    ],
    'field__knowledge' => [
      'base hook' => 'field',
    ],
    'knowledge_adherence_form' => [
      'render element' => 'form',
    ],
    'knowledge_quality' => [
      'render element' => 'elements',
    ],
    'block__social__plugin_id__knowledge_suffa' => [
      'render element' => 'elements',
      'base hook' => 'block',
    ],
    'knowledge_wave' => ['render element' => 'elements'],
  ];
}

/**
 * Implements hook_ENTITY_TYPE_create() for 'field_config'.
 */
function knowledge_field_config_create(FieldConfigInterface $field) {
  if ($field->getType() == 'knowledge' && !$field->isSyncing()) {
    // Assign default values for the field.
    $default_value = $field->getDefaultValueLiteral();
    $default_value += [[]];
    $default_value[0] += [
      'status' => KnowledgeItemInterface::OPEN,
      'first_knowledge' => 0,
      'first_knowledge_timestamp' => 0,
      'first_knowledge_uid' => 0,
      'last_knowledge' => 0,
      'last_knowledge_timestamp' => 0,
      'last_knowledge_uid' => 0,
      'total_count' => 0,
      'long_count' => 0,
      'medium_count' => 0,
      'short_count' => 0,
      'total_citation' => 0,
      'long_citation' => 0,
      'medium_citation' => 0,
      'short_citation' => 0,
    ];
    $field->setDefaultValue($default_value);
  }
}

/**
 * Implements hook_ENTITY_TYPE_update() for 'field_config'.
 */
function knowledge_field_config_update(FieldConfigInterface $field) {
  if ($field->getType() == 'knowledge') {
    // Knowledge field settings also affects the rendering of *knowledge*
    // entities, not only the *linked* entities.
    \Drupal::entityTypeManager()->getViewBuilder('knowledge')->resetCache();
  }
}

/**
 * Implements hook_ENTITY_TYPE_insert() for 'field_storage_config'.
 */
function knowledge_field_storage_config_insert(FieldStorageConfigInterface $field_storage) {
  if ($field_storage->getType() == 'knowledge') {
    // Check that the target entity type uses an integer ID.
    $entity_type_id = $field_storage->getTargetEntityTypeId();
    if (!_knowledge_entity_uses_integer_id($entity_type_id)) {
      throw new \UnexpectedValueException('You cannot attach a knowledge field to an entity with a non-integer ID field');
    }
  }
}

/**
 * Implements hook_ENTITY_TYPE_delete() for 'field_config'.
 */
function knowledge_field_config_delete(FieldConfigInterface $field) {
  if ($field->getType() == 'knowledge') {
    // Delete all knowledge that used by the entity bundle.
    $entity_query = \Drupal::entityQuery('knowledge')->accessCheck(FALSE);
    $entity_query->condition('entity_type', $field->getEntityTypeId());
    $entity_query->condition('field_name', $field->getName());
    $kids = $entity_query->execute();
    $knowledge_storage = \Drupal::entityTypeManager()->getStorage('knowledge');
    $knowledge_links = $knowledge_storage->loadMultiple($kids);
    $knowledge_storage->delete($knowledge_links);
  }
}

/**
 * Implements hook_node_links_alter().
 */
function knowledge_node_links_alter(array &$links, NodeInterface $node, array &$context) {
  // Knowledge links are only added to node entity type for backwards
  // compatibility. Should you require knowledge links for other entity types
  // you can do so by implementing a new field formatter.
  // @todo Make this configurable from the formatter. See
  //   https://www.drupal.org/node/1901110.
  $knowledge_links = \Drupal::service('knowledge.link_builder')->buildKnowledgeedEntityLinks($node, $context);
  $links += $knowledge_links;
}

/**
 * Implements hook_ENTITY_TYPE_view_alter() for node entities.
 */
function knowledge_node_view_alter(array &$build, EntityInterface $node, EntityViewDisplayInterface $display) {
  if (\Drupal::moduleHandler()->moduleExists('history')) {
    $build['#attributes']['data-history-node-id'] = $node->id();
  }
}

/**
 * Implements hook_form_FORM_ID_alter() for field_ui_field_storage_add_form.
 */
function knowledge_form_field_ui_field_storage_add_form_alter(&$form, FormStateInterface $form_state) {
  $route_match = \Drupal::routeMatch();
  if ($form_state->get('entity_type_id') == 'knowledge' && $route_match->getParameter('knowledge_entity_type')) {
    $form['#title'] = \Drupal::service('knowledge.manager')->getFieldUIPageTitle($route_match->getParameter('knowledge_entity_type'), $route_match->getParameter('field_name'));
  }
  if (!_knowledge_entity_uses_integer_id($form_state->get('entity_type_id'))) {
    $optgroup = (string) t('General');
    // You cannot use knowledge fields on entity types with non-integer IDs.
    unset($form['add']['new_storage_type']['#options'][$optgroup]['knowledge']);
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function knowledge_form_field_ui_form_display_overview_form_alter(&$form, FormStateInterface $form_state) {
  $route_match = \Drupal::routeMatch();
  if ($form['#entity_type'] == 'knowledge' && $route_match->getParameter('knowledge_entity_type')) {
    $form['#title'] = \Drupal::service('knowledge.manager')->getFieldUIPageTitle($route_match->getParameter('knowledge_entity_type'), $route_match->getParameter('field_name'));
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function knowledge_form_field_ui_display_overview_form_alter(&$form, FormStateInterface $form_state) {
  $route_match = \Drupal::routeMatch();
  if ($form['#entity_type'] == 'knowledge' && $route_match->getParameter('knowledge_entity_type')) {
    $form['#title'] = \Drupal::service('knowledge.manager')->getFieldUIPageTitle($route_match->getParameter('knowledge_entity_type'), $route_match->getParameter('field_name'));
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function knowledge_form_node_form_alter(&$form, $form_state) {
  if (array_key_exists('knowledge_audience', $form)) {
    $form['knowledge_audience']['#group'] = 'meta';
    $form['knowledge_audience']['widget']['#wrapper_attributes'] = [
      'class' => ['container-inline'],
    ];
  }
  if (array_key_exists('status', $form)) {
    $form['status']['#group'] = 'meta';
  }
  if (array_key_exists('moderation_state', $form)) {
    $form['moderation_state']['#group'] = 'meta';
  }

  if (array_key_exists('knowledge_governance', $form) && \Drupal::currentUser()->hasPermission('edit knowledge governance')) {
    $form['#validate'][] = '_knowledge_governance_form_validate';
    $entity = $form_state->getFormObject()->getEntity();

    $knowledge_governance = (bool) $entity->get('knowledge_governance')->value;
    $form['governance'] = [
      '#type' => 'details',
      '#title' => t('Governance'),
      // Open the details when the selected value is different to the stored
      // default values for the field.
      '#open' => ($knowledge_governance),
      '#group' => 'advanced',
      '#attributes' => [
        'class' => ['knowledge-governance-' . Html::getClass($entity->getEntityTypeId()) . '-settings-form'],
      ],
      '#attached' => [
        // 'library' => ['knowledge/drupal.knowledge'], create library.
      ],
    ];
    $form['knowledge_governance']['#group'] = 'governance';
    if (array_key_exists('knowledge_governance_user', $form)) {
      $form['knowledge_governance_user']['#group'] = 'governance';
      $form['knowledge_governance_user']['#states']['visible'] = [
        ':input[name="knowledge_governance"]' => ['value' => 1],
      ];

    }
    if (array_key_exists('knowledge_governance_user_role', $form)) {
      $governance_settings = \Drupal::config('knowledge.governance.settings');
      $roles = $governance_settings->get('roles');
      $roles = array_filter($roles);

      $form['knowledge_governance_user_role']['#group'] = 'governance';
      $form['knowledge_governance_user_role']['#states']['visible'] = [
        ':input[name="knowledge_governance"]' => ['value' => 1],
      ];

      $roles_remove = [];
      $role_options = $form['knowledge_governance_user_role']['widget']['#options'] ?? [];
      foreach ($role_options as $role => $role_label) {

        $remove = !empty($roles) && !in_array($role, $roles);
        if ($role == '_none') {
          $remove = TRUE;
        }
        if ($remove) {
          $roles_remove[] = $role;
        }
      }

      foreach ($roles_remove as $role) {
        unset($form['knowledge_governance_user_role']['widget']['#options'][$role]);
      }
    }

  }
}

/**
 * Validation for the governance field.
 */
function _knowledge_governance_form_validate(&$form, $form_state) {

  $governance = (bool) ($form_state->getValue('knowledge_governance')[0]['value']);

  if ($governance) {
    $user_roles = $form_state->getValue('knowledge_governance_user_role');

    $users = $form_state->getValue('knowledge_governance_user');
    $users = $users['target_id'] ?? NULL;

    if (empty($user_roles) && empty($users)) {
      $form_state->setErrorByName('knowledge_governance_user_role', t('User or Role must have a value when Governance is Compliance.'));
      $form_state->setErrorByName('knowledge_governance_user', t('User or Role must have a value when Governance is Compliance.'));
    }
  }
}

/**
 * Implements hook_entity_storage_load().
 *
 * @see \Drupal\knowledge\Plugin\Field\FieldType\KnowledgeItem::propertyDefinitions()
 */
function knowledge_entity_storage_load($entities, $entity_type) {
  // Knowledge can only be attached to content entities, so skip others.
  if (!\Drupal::entityTypeManager()->getDefinition($entity_type)->entityClassImplements(FieldableEntityInterface::class)) {
    return;
  }
  if (!\Drupal::service('knowledge.manager')->getFields($entity_type)) {
    // Do not query database when entity has no knowledge fields.
    return;
  }
  // Load knowledge information from the database and update the entity's
  // knowledge statistics properties, which are defined on each KnowledgeItem
  // field.
  $result = \Drupal::service('knowledge.statistics')->read($entities, $entity_type);
  foreach ($result as $record) {
    // Skip fields that entity does not have.
    if (!$entities[$record->entity_id]->hasField($record->field_name)) {
      continue;
    }
    $knowledge_statistics = $entities[$record->entity_id]->get($record->field_name);
    $knowledge_statistics->first_knowledge = $record->first_knowledge;
    $knowledge_statistics->first_knowledge_timestamp = $record->first_knowledge_timestamp;
    $knowledge_statistics->first_knowledge_uid = $record->first_knowledge_uid;
    $knowledge_statistics->last_knowledge = $record->last_knowledge;
    $knowledge_statistics->last_knowledge_timestamp = $record->last_knowledge_timestamp;
    $knowledge_statistics->last_knowledge_uid = $record->last_knowledge_uid;
    $knowledge_statistics->total_count = $record->total_count;
    $knowledge_statistics->long_count = $record->long_count;
    $knowledge_statistics->medium_count = $record->medium_count;
    $knowledge_statistics->short_count = $record->short_count;
    $knowledge_statistics->total_citation = $record->total_citation;
    $knowledge_statistics->long_citation = $record->long_citation;
    $knowledge_statistics->medium_citation = $record->medium_citation;
    $knowledge_statistics->short_citation = $record->short_citation;
  }
}

/**
 * Implements hook_entity_insert().
 */
function knowledge_entity_insert(EntityInterface $entity) {
  // Allow bulk updates and inserts to temporarily disable the
  // maintenance of the {knowledge_entity_statistics} table.
  if (\Drupal::state()->get('knowledge.maintain_entity_statistics') &&
    $fields = \Drupal::service('knowledge.manager')->getFields($entity->getEntityTypeId())) {
    \Drupal::service('knowledge.statistics')->create($entity, $fields);
  }

}

/**
 * Implements hook_entity_predelete().
 */
function knowledge_entity_predelete(EntityInterface $entity) {
  // Entities can have non-numeric IDs, but {knowledge} and
  // {knowledge_entity_statistics} tables have integer columns for entity ID,
  // and PostgreSQL throws exceptions if you attempt query conditions with
  // mismatched types. So, we need to verify that the ID is numeric (even for an
  // entity type that has an integer ID, $entity->id() might be a string
  // containing a number), and then cast it to an integer when querying.
  if ($entity instanceof FieldableEntityInterface && is_numeric($entity->id())) {
    $entity_query = \Drupal::entityQuery('knowledge')->accessCheck(FALSE);
    $entity_query->condition('entity_id', (int) $entity->id());
    $entity_query->condition('entity_type', $entity->getEntityTypeId());
    $kids = $entity_query->execute();
    $knowledge_storage = \Drupal::entityTypeManager()->getStorage('knowledge');
    $knowledge_links = $knowledge_storage->loadMultiple($kids);
    $knowledge_storage->delete($knowledge_links);
    \Drupal::service('knowledge.statistics')->delete($entity);
  }
}

/**
 * Determines if an entity type is using an integer-based ID definition.
 *
 * @param string $entity_type_id
 *   The ID the represents the entity type.
 *
 * @return bool
 *   Returns TRUE if the entity type has an integer-based ID definition and
 *   FALSE otherwise.
 */
function _knowledge_entity_uses_integer_id($entity_type_id) {
  $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
  $entity_type_id_key = $entity_type->getKey('id');
  if ($entity_type_id_key === FALSE) {
    return FALSE;
  }
  $field_definitions = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions($entity_type->id());
  $entity_type_id_definition = $field_definitions[$entity_type_id_key];
  return $entity_type_id_definition->getType() === 'integer';
}

/**
 * Implements hook_node_update_index().
 */
function knowledge_node_update_index(EntityInterface $node) {
  $index_knowledge = &drupal_static(__FUNCTION__);

  if ($index_knowledge === NULL) {
    // Do not index in the following three cases:
    // 1. 'Authenticated user' can search content but can't access knowledge.
    // 2. 'Anonymous user' can search content but can't access knowledge.
    // 3. Any role can search content but can't access knowledge and access
    // knowledge is not granted by the 'authenticated user' role. In this case
    // all users might have both permissions from various roles but it is also
    // possible to set up a user to have only search content and so a user
    // edit could change the security situation so it is not safe to index the
    // knowledge.
    $index_knowledge = TRUE;
    $roles = \Drupal::entityTypeManager()->getStorage('user_role')->loadMultiple();
    $authenticated_can_access = $roles[RoleInterface::AUTHENTICATED_ID]->hasPermission('access knowledge');
    foreach ($roles as $rid => $role) {
      if ($role->hasPermission('search content') && !$role->hasPermission('access knowledge')) {
        if ($rid == RoleInterface::AUTHENTICATED_ID || $rid == RoleInterface::ANONYMOUS_ID || !$authenticated_can_access) {
          $index_knowledge = FALSE;
          break;
        }
      }
    }
  }

  $build = [];

  if ($index_knowledge) {
    foreach (\Drupal::service('knowledge.manager')->getFields('node') as $field_name => $info) {
      // Skip fields that entity does not have.
      if (!$node->hasField($field_name)) {
        continue;
      }
      $field_definition = $node->getFieldDefinition($field_name);
      $mode = $field_definition->getSetting('default_mode');
      $knowledge_per_page = $field_definition->getSetting('per_page');
      if ($node->get($field_name)->status) {
        /** @var \Drupal\knowledge\KnowledgeStorageInterface $knowledge_storage */
        $knowledge_storage = \Drupal::entityTypeManager()->getStorage('knowledge');
        /** @var \Drupal\knowledge\KnowledgeInterface[] $knowledge_links */
        $knowledge_links = $knowledge_storage
          ->loadThread($node, $field_name, $mode, $knowledge_per_page);
        if ($knowledge_links) {
          $build[] = \Drupal::entityTypeManager()->getViewBuilder('knowledge')->viewMultiple($knowledge_links);
        }
      }
    }
  }
  return \Drupal::service('renderer')->renderInIsolation($build);
}

/**
 * Implements hook_cron().
 */
function knowledge_cron() {
  // Store the maximum possible knowledge per thread (used for node search
  // ranking by reply count).
  \Drupal::state()->set('knowledge.node_knowledge_statistics_scale', 1.0 / max(1, \Drupal::service('knowledge.statistics')->getMaximumCount('node')));
  $today = strtotime('today');
  $statistics_calculated = \Drupal::state()->get('knowledge.knowledge_statistics_calculated', 0);
  if ($today > $statistics_calculated) {
    $database = \Drupal::database();
    $query = $database->query('SELECT last_knowledge
    FROM knowledge_entity_statistics
    WHERE last_knowledge <> 0');
    $results = $query->fetchAll();
    $ids = [];
    foreach ($results as $result) {
      $ids[] = $result->last_knowledge;
    }
    $knowledge_links = \Drupal::service('entity_type.manager')
      ->getStorage('knowledge')
      ->loadMultiple($ids);
    foreach ($knowledge_links as $link) {
      \Drupal::service('knowledge.statistics')->update($link);
    }
    \Drupal::state()->set('knowledge.knowledge_statistics_calculated', \Drupal::time()->getRequestTime());
  }
}

/**
 * Implements hook_node_search_result().
 *
 * Formats a knowledge count string and returns it, for display with search
 * results.
 */
function knowledge_node_search_result(EntityInterface $node) {
  /** @var \Drupal\node\NodeInterface $node */
  $knowledge_fields = \Drupal::service('knowledge.manager')->getFields('node');
  $knowledge_links = 0;
  $open = FALSE;
  foreach ($knowledge_fields as $field_name => $info) {
    // Skip fields that entity does not have.
    if (!$node->hasField($field_name)) {
      continue;
    }
    // Do not make a string if knowledge is hidden.
    $status = $node->get($field_name)->status;
    if (\Drupal::currentUser()->hasPermission('access knowledge') && $status != KnowledgeItemInterface::HIDDEN) {
      if ($status == KnowledgeItemInterface::OPEN) {
        // At least one knowledge field is open.
        $open = TRUE;
      }
      $knowledge_links += $node->get($field_name)->total_count;
    }
  }
  // Do not make a string if there are no knowledge fields, or no knowledge
  // exist or all knowledge fields are hidden.
  if ($knowledge_links > 0 || $open) {
    return ['knowledge' => \Drupal::translation()->formatPlural($knowledge_links, '1 knowledge', '@count knowledge')];
  }
}

/**
 * Implements hook_user_cancel().
 */
function knowledge_user_cancel($edit, UserInterface $account, $method) {
  switch ($method) {
    case 'user_cancel_block_unpublish':
      /** @var \Drupal\knowledge\KnowledgeInterface[] $knowledge_links */
      $knowledge_links = \Drupal::entityTypeManager()->getStorage('knowledge')->loadByProperties(['uid' => $account->id()]);
      foreach ($knowledge_links as $know) {
        $know->setUnpublished();
        $know->save();
      }
      break;

    case 'user_cancel_reassign':
      /** @var \Drupal\knowledge\KnowledgeInterface[] $knowledge_links */
      $knowledge_links = \Drupal::entityTypeManager()->getStorage('knowledge')->loadByProperties(['uid' => $account->id()]);
      foreach ($knowledge_links as $know) {
        $know->setOwnerId(0);
        $know->save();
      }
      break;
  }
}

/**
 * Implements hook_ENTITY_TYPE_predelete() for user entities.
 */
function knowledge_user_predelete($account) {
  $entity_query = \Drupal::entityQuery('knowledge')->accessCheck(FALSE);
  $entity_query->condition('uid', $account->id());
  $kids = $entity_query->execute();
  $knowledge_storage = \Drupal::entityTypeManager()->getStorage('knowledge');
  $knowledge = $knowledge_storage->loadMultiple($kids);
  $knowledge_storage->delete($knowledge);
}

/**
 * Generates a knowledge preview.
 *
 * @param \Drupal\knowledge\KnowledgeInterface $knowledge
 *   The knowledge entity to preview.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The current state of the form.
 *
 * @return array
 *   An array as expected by \Drupal\Core\Render\RendererInterface::render().
 */
function knowledge_preview(KnowledgeInterface $knowledge, FormStateInterface $form_state) {
  $preview_build = [];
  $entity = $knowledge->getKnowledgeedEntity();

  if (!$form_state->getErrors()) {
    $knowledge->in_preview = TRUE;
    $knowledge_build = \Drupal::entityTypeManager()->getViewBuilder('knowledge')->view($knowledge);
    $knowledge_build['#weight'] = -100;

    $preview_build['knowledge_preview'] = $knowledge_build;
  }

  // The knowledge field output includes rendering the parent entity of the
  // thread to which the knowledge is a reply. The rendered entity output
  // includes the knowledge reply form, which contains the knowledge preview and
  // therefore the rendered parent entity. This results in an infinite loop of
  // parent entity output rendering the knowledge form and the knowledge form
  // rendering the parent entity. To prevent this infinite loop we temporarily
  // set the value of the knowledge field on a clone of the entity to hidden
  // before calling entity_view(). That way when the output of the linked
  // entity is rendered, it excludes the knowledge field output.
  $field_name = $knowledge->getFieldName();
  $entity = clone $entity;
  $entity->$field_name->status = KnowledgeItemInterface::HIDDEN;
  $build = \Drupal::entityTypeManager()
    ->getViewBuilder($entity->getEntityTypeId())
    ->view($entity, 'full');

  $preview_build['knowledge_output_below'] = $build;
  $preview_build['knowledge_output_below']['#weight'] = 200;

  return $preview_build;
}

/**
 * Implements hook_preprocess_HOOK() for block templates.
 */
function knowledge_preprocess_block(&$variables) {
  if (array_key_exists('provider', $variables['configuration']) && $variables['configuration']['provider'] == 'knowledge') {
    $variables['attributes']['role'] = 'navigation';
  }
}

/**
 * Prepares variables for knowledge templates.
 *
 * By default this function performs special preprocessing of some base fields
 * so they are available as variables in the template. For example 'subject'
 * appears as 'title'. This preprocessing is skipped if:
 * - a module makes the field's display configurable via the field UI by means
 *   of BaseFieldDefinition::setDisplayConfigurable()
 * - AND the additional entity type property
 *   'enable_base_field_custom_preprocess_skipping' has been set using
 *   hook_entity_type_build().
 *
 * Default template: knowledge.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the knowledge
 *      and entity objects.
 *     Array keys: #knowledge, #linked_entity.
 */
function template_preprocess_knowledge(array &$variables) {
  /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
  $date_formatter = \Drupal::service('date.formatter');
  /** @var \Drupal\knowledge\KnowledgeInterface $knowledge */
  $knowledge = $variables['elements']['#knowledge'];
  $linked_entity = $knowledge->getKnowledgeedEntity();
  $variables['knowledge'] = $knowledge;
  $variables['linked_entity'] = $linked_entity;

  $skip_custom_preprocessing = $knowledge->getEntityType()->get('enable_base_field_custom_preprocess_skipping');

  // Make created, uid and subject fields available separately. Skip this
  // custom preprocessing if the field display is configurable and skipping has
  // been enabled.
  // @todo https://www.drupal.org/project/drupal/issues/3015623
  //   Eventually delete this code and matching template lines. Using
  //   $variables['content'] is more flexible and consistent.
  $submitted_configurable = $knowledge
    ->getFieldDefinition('created')
    ->isDisplayConfigurable('view')
    ||
    $knowledge->getFieldDefinition('uid')->isDisplayConfigurable('view');

  if (!$skip_custom_preprocessing || !$submitted_configurable) {
    $account = $knowledge->getOwner();
    $username = [
      '#theme' => 'username',
      '#account' => $account,
    ];
    $variables['author'] = \Drupal::service('renderer')->render($username);
    $variables['author_id'] = $knowledge->getOwnerId();
    $variables['new_indicator_timestamp'] = $knowledge->getChangedTime();
    $variables['created'] = $date_formatter->format($knowledge->getCreatedTime());
    // Avoid calling DateFormatterInterface::format() twice on the
    // same timestamp.
    if ($knowledge->getChangedTime() == $knowledge->getCreatedTime()) {
      $variables['changed'] = $variables['created'];
    }
    else {
      $variables['changed'] = $date_formatter->format($knowledge->getChangedTime());
    }

    $show_user_picture = DeprecationHelper::backwardsCompatibleCall(
      currentVersion: \Drupal::VERSION,
      deprecatedVersion: '11.3.0',
      currentCallable: fn() => \Drupal::service('theme.settings_provider')->getSetting('features.knowledge_user_picture'),
      deprecatedCallable: fn() => theme_get_setting('features.knowledge_user_picture')
    );
    if ($show_user_picture) {
      // To change user picture settings (for instance, image style), edit the
      // 'compact' view mode on the User entity.
      $variables['user_picture'] = \Drupal::entityTypeManager()
        ->getViewBuilder('user')
        ->view($account, 'compact');
    }
    else {
      $variables['user_picture'] = [];
    }

    $variables['submitted'] = t('by @username on @datetime', [
      '@username' => $variables['author'],
      '@datetime' => $variables['created'],
    ]);
  }

  if (isset($knowledge->in_preview)) {
    $variables['permalink'] = Link::fromTextAndUrl(t('Permalink'), Url::fromRoute('<front>'))->toString();
  }
  else {
    $variables['permalink'] = Link::fromTextAndUrl(t('Permalink'), $knowledge->permalink())->toString();
  }

  if (!$skip_custom_preprocessing || !$knowledge->getFieldDefinition('subject')->isDisplayConfigurable('view')) {
    if (isset($knowledge->in_preview)) {
      $variables['title'] = Link::fromTextAndUrl($knowledge->label(), Url::fromRoute('<front>'))->toString();
    }
    else {
      $uri = $knowledge->permalink();
      $attributes = $uri->getOption('attributes') ?: [];
      $attributes += ['class' => ['permalink'], 'rel' => 'bookmark'];
      $uri->setOption('attributes', $attributes);
      $variables['title'] = Link::fromTextAndUrl($knowledge->label(), $uri)->toString();
    }
  }

  // Helpful $content variable for templates.
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }

  // Set status to a string representation of knowledge->status.
  if (isset($knowledge->in_preview)) {
    $variables['status'] = 'preview';
  }
  else {
    $variables['status'] = $knowledge->isPublished() ? 'published' : 'unpublished';
  }

  // Add knowledge author user ID. Necessary for the
  // knowledge-by-viewer library.
  $variables['attributes']['data-knowledge-user-id'] = $knowledge->getOwnerId();
  // Add anchor for each knowledge.
  $variables['attributes']['id'] = 'knowledge-' . $knowledge->id();
}

/**
 * Prepares variables for knowledge field templates.
 *
 * Default template: field--knowledge.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - element: An associative array containing render arrays for the list of
 *     knowledge, and the knowledge form. Array keys: knowledge, knowledge_form.
 *
 * @todo Rename to template_preprocess_field__knowledge() once
 *   https://www.drupal.org/node/939462 is resolved.
 */
function knowledge_preprocess_field(array &$variables) {
  $element = $variables['element'];
  if ($element['#field_type'] == 'knowledge') {
    // Provide contextual information.
    $variables['knowledge_display_mode'] = $element[0]['#knowledge_display_mode'];
    $variables['knowledge_type'] = $element[0]['#knowledge_type'];

    // Append additional attributes (eg. RDFa) from the first field item.
    $variables['attributes'] += $variables['items'][0]['attributes']->storage();

    // Create separate variables for the knowledge and knowledge form.
    $variables['knowledge'] = $element[0]['knowledge'];
    $variables['knowledge_form'] = $element[0]['knowledge_form'];
  }
}

/**
 * Prepares variables for Quality templates.
 *
 * Default template: knowledge_quality.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the user information and any
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_knowledge_quality(array &$variables) {
  // $variables['score'] = $knowledge_quality->getScore();
  // Helpful $content variable for templates.
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}

/**
 * Implements hook_ranking().
 */
function knowledge_ranking() {
  return \Drupal::service('knowledge.statistics')->getRankingInfo();
}

/**
 * Implements hook_ENTITY_TYPE_presave() for entity_view_display entities.
 */
function knowledge_entity_view_display_presave(EntityViewDisplayInterface $display) {
  // Act only on knowledge view displays being disabled.
  if ($display->isNew() || $display->getTargetEntityTypeId() !== 'knowledge' || $display->status()) {
    return;
  }
  $storage = \Drupal::entityTypeManager()->getStorage('entity_view_display');
  if (!$storage->loadUnchanged($display->getOriginalId())->status()) {
    return;
  }

  // Disable the knowledge field formatter when the used view display
  // is disabled.
  foreach ($storage->loadMultiple() as $view_display) {
    /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $view_display */
    $changed = FALSE;

    foreach ($view_display->getComponents() as $field => $component) {
      if (isset($component['type']) && ($component['type'] === 'knowledge_default')) {
        if ($component['settings']['view_mode'] === $display->getMode()) {
          $view_display->removeComponent($field);
          /** @var \Drupal\Core\Entity\EntityViewModeInterface $mode */
          $mode = EntityViewMode::load($display->getTargetEntityTypeId() . '.' . $display->getMode());
          $arguments = [
            '@id' => $view_display->id(),
            '@name' => $field,
            '@display' => $mode->label(),
            '@mode' => $display->getMode(),
          ];
          \Drupal::logger('system')->warning("View display '@id': Knowledge field formatter '@name' was disabled because it is using the knowledge view display '@display' (@mode) that was just disabled.", $arguments);
          $changed = TRUE;
        }
      }
    }
    if ($changed) {
      $view_display->save();
    }
  }
}

/**
 * Implements hook_library_info_alter().
 */
function knowledge_library_info_alter(&$libraries, $extension) {
  if ($extension === 'knowledge' && isset($libraries['activity'])) {
    $libraries['activity']['dependencies'][] = \Drupal::config('moderation_dashboard.settings')->get('chart_js_cdn')
      ? 'moderation_dashboard/chart.js.external'
      : 'moderation_dashboard/chart.js.internal';
  }
}

/**
 * Allowed values for the audience field.
 */
function knowledge_audience_allowed_values(FieldStorageDefinitionInterface $definition, ?FieldableEntityInterface $entity = NULL) {

  $has_permission = \Drupal::currentUser()
    ->hasPermission('create knowledge audience external');
  $config = \Drupal::config('knowledge.settings');
  $options = [];
  if ($has_permission && $config->get('audience.public.enabled')) {
    $options['public'] = $config->get('audience.public.label');
  }
  if ($has_permission && $config->get('audience.customer.enabled')) {
    $options['customer'] = $config->get('audience.customer.label');
  }
  if ($has_permission && $config->get('audience.partner.enabled')) {
    $options['partner'] = $config->get('audience.partner.label');
  }
  if ($config->get('audience.internal.enabled')) {
    $options['internal'] = $config->get('audience.internal.label');
  }

  return $options;
}

/**
 * Implements hook_ENTITY_TYPE_access().
 */
function knowledge_node_access(NodeInterface $node, $op, AccountInterface $account) {
  $user_roles = [];
  if ($node->hasField('knowledge_governance_user_role') && $node_roles = $node->get('knowledge_governance_user_role')) {
    foreach ($node_roles as $role) {
      $user_roles[] = $role->target_id;
    }
  }

  $users = [];
  if ($node->hasField('knowledge_governance_user') && $node_users = $node->get('knowledge_governance_user')) {
    foreach ($node_users as $user) {
      $users[] = $user->target_id;
    }
  }

  $access = AccessResult::neutral();
  if (!$node->hasField('knowledge_audience')) {
    return $access;
  }
  $audience = $node->knowledge_audience->value;

  switch ($op) {
    case 'update':
      if ($audience != 'internal') {
        if (!$account->hasPermission('edit knowledge audience external')) {
          return AccessResult::forbidden();
        }
      }

      if ($node->hasField('knowledge_governance') && $node->get('knowledge_governance')->value) {
        if (!empty(array_intersect($account->getRoles(TRUE), $user_roles))) {
          return AccessResult::allowed();
        }
        elseif (in_array($account->id(), $users)) {
          return AccessResult::allowed();
        }
        else {
          return AccessResult::forbidden();
        }
      }
      break;

    case 'view':
      if ($audience != 'public') {
        if (!$account->hasPermission('view knowledge audience ' . $audience)) {
          return AccessResult::forbidden();
        }

      }
      break;
  }

  return $access;
}

/**
 * Implements hook_ENTITY_TYPE_access().
 */
function knowledge_user_role_access(EntityInterface $entity, $op, AccountInterface $account) {
  if ($op != 'view') {
    return AccessResult::neutral();
  }

  return AccessResult::allowedIfHasPermission($account, 'view knowledge governance');
}

/**
 * Implements hook_ENTITY_TYPE_ID_presave().
 */
function knowledge_node_presave(EntityInterface $entity) {

  if (!$entity->hasField('knowledge_governance')) {
    return;
  }

  $governance = $entity->get('knowledge_governance')->value;
  if ($governance) {
    return;
  }

  if ($entity->hasField('knowledge_governance_user')) {
    $entity->set('knowledge_governance_user', NULL);
  }

  if ($entity->hasField('knowledge_governance_user_role')) {
    $entity->set('knowledge_governance_user_role', NULL);
  }

}

/**
 * Implements hook_ENTITY_TYPE_ID_presave().
 */
function knowledge_user_presave(EntityInterface $entity) {
  /** @var \Drupal\user\UserInterface $user */
  $user = $entity;

  \Drupal::service('knowledge.competency')->doRoleRemoval($user);

  try {

    $leadership_changed = FALSE;
    if ($user->isNew()) {
      $leadership_changed = TRUE;
    }
    else {
      $old = $user->original->get('knowledge_leader')->target_id;
      $new = $user->get('knowledge_leader')->target_id;
      $leadership_changed = ($new != $old);
    }
    if ($leadership_changed) {
      $leader_svc = \Drupal::service('knowledge.leader');

      if ($user->get('knowledge_leader')->target_id) {
        $leader = $user->get('knowledge_leader')->entity;
        $roles = $leader->getRoles(TRUE);
        if (!in_array('knowledge_leader', $roles)) {
          $leader_svc->addKnowledgeLeader($leader);
        }
      }
      if (!$user->isNew()) {
        if ($user->original->get('knowledge_leader')->target_id) {
          $original_leader = $user->original->get('knowledge_leader')->entity;
          if ($original_leader) {
            $leader_svc->updateKnowledgeLeader($user, $original_leader);
          }
        }
      }
    }
  }
  catch (\Exception $e) {
    $logger = \Drupal::logger('knowledge');
    Error::logException($logger, $e);
  }

}

/**
 * Implements hook_ENTITY_TYPE_ID_delete().
 */
function knowledge_user_delete(EntityInterface $entity) {
  try {
    /** @var \Drupal\user\UserInterface $user */
    $user = $entity;
    if ($user->get('knowledge_leader')->target_id) {
      $leader = $user->get('knowledge_leader')->entity;
      $leader_svc = \Drupal::service('knowledge.leader');
      $leader_svc->removeKnowledgeLeader($leader);
    }
  }
  catch (\Exception $e) {
    $logger = \Drupal::logger('knowledge');
    Error::logException($logger, $e);
  }
}

/**
 * Implements hook_entity_field_access().
 */
function knowledge_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) {
  $permission = NULL;
  $field_name = $field_definition->getName();

  $view_fields = [
    'knowledge_governance',
    'knowledge_governance_user',
    'knowledge_governance_user_role',
    'knowledge_audience',
    'moderation_state',
  ];
  $edit_fields = [
    'knowledge_leader',
    'knowledge_coach',
    'knowledge_wave',
    'knowledge_governance',
    'knowledge_governance_user',
    'knowledge_governance_user_role',
  ];
  if ($operation == 'view') {
    if (!in_array($field_name, $view_fields)) {
      return AccessResult::neutral();
    }
    switch ($field_name) {
      case 'knowledge_governance':
      case 'knowledge_governance_user':
      case 'knowledge_governance_user_role':
        $permission = 'view knowledge governance';
        break;

      case 'knowledge_audience':
        $permission = 'view knowledge audience';
        break;

      case 'moderation_state':
        $permission = 'view knowledge confidence';
        break;
    }
  }
  if ($operation == 'edit') {
    if (!in_array($field_name, $edit_fields)) {
      return AccessResult::neutral();
    }
    switch ($field_name) {
      case 'knowledge_leader':
        $permission = 'edit knowledge leader';
        break;

      case 'knowledge_coach':
        $permission = 'edit knowledge coach';
        break;

      case 'knowledge_wave':
        $permission = 'edit wave field';
        break;

      case 'knowledge_governance':
      case 'knowledge_governance_user':
      case 'knowledge_governance_user_role':
        $permission = 'edit knowledge governance';
        break;
    }

  }

  if ($permission && $account->hasPermission($permission)) {
    return AccessResult::allowed();
  }

  return AccessResult::forbidden();
}

/**
 * Prepares variables for wave templates.
 *
 * Default template: knowledge-wave.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the wave information and any
 *     fields attached to the entity.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_knowledge_wave(array &$variables): void {
  $variables['view_mode'] = $variables['elements']['#view_mode'];
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}

/**
 * Implements hook_views_post_execute().
 */
function knowledge_views_post_execute(ViewExecutable $view) {
  // Only alter the knowledge views.
  if (strpos($view->id(), 'knowledge') !== 0) {
    return;
  }

  $view->result = SequenceService::fill($view->result);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function knowledge_form_field_config_edit_form_alter(&$form, FormStateInterface $form_state) {

  $entity = $form['#entity'];
  $entity_type = $entity->getEntityTypeId();
  if ($entity_type != 'knowledge_quality') {
    return;
  }

  $field_config = $form_state->getFormObject()->getEntity();
  $field_type = $field_config->getType();
  if ($field_type != 'boolean') {
    return;
  }

  $quality_settings = \Drupal::config('knowledge.quality.settings');
  $quality_categories = $quality_settings->get('categories');
  $options_pipe = explode(PHP_EOL, $quality_categories);

  $options = [
    '_none' => t('None'),
  ];
  foreach ($options_pipe as $option) {
    $option = explode('|', $option);
    $options[$option[0]] = $option[1] ?? $option[0];
  }

  $form['third_party_settings']['knowledge'] = [
    '#type' => 'details',
    '#title' => t('Knowledge Quality'),
    '#open' => TRUE,
  ];

  $form['third_party_settings']['knowledge']['quality_category'] = [
    '#type' => 'select',
    '#title' => t('Category'),
    '#description' => t('Enter a custom setting value.'),
    '#default_value' => $field_config->getThirdPartySetting('knowledge', 'quality_category', '_none'),
    '#options' => $options,
  ];

  $form['actions']['submit']['#submit'][] = 'knowledge_quality_field_config_edit_form_submit';
}

/**
 * Submit handler for the knowledge field config edit form.
 */
function knowledge_quality_field_config_edit_form_submit(&$form, FormStateInterface $form_state) {

  $field_config = $form_state->getFormObject()->getEntity();

  $quality_category = $form_state->getValue(['third_party_settings', 'knowledge', 'quality_category']);

  $field_config->setThirdPartySetting('knowledge', 'quality_category', $quality_category);
}

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

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