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);
}
