You are here

node_authlink.module in Node authorize link 8

Same filename and directory in other branches
  1. 7 node_authlink.module

Node Authlink hooks and alters.

File

node_authlink.module
View source
<?php

/**
 * @file
 * Node Authlink hooks and alters.
 */
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use Drupal\node_authlink\Access\NodeAuthlinkNodeAccessControlHandler;
use Drupal\node_authlink\Plugin\NodeAuthlinkGroupContentAccessControlHandler;

/**
 * Alter of node_type_form.
 */
function node_authlink_form_node_type_form_alter(&$form, FormStateInterface &$form_state) {
  if (!\Drupal::currentUser()
    ->hasPermission('configure node_authlink module')) {
    return;
  }

  /** @var \Drupal\node\Entity\NodeType $node_type */
  $node_type = $form_state
    ->getFormObject();
  $type = $node_type
    ->getEntity()
    ->id();
  $config = \Drupal::config('node_authlink.settings');
  $config_enable = $config
    ->get('enable');
  $config_grants = $config
    ->get('grants');
  $config_expire = $config
    ->get('expire');
  $form['node_authlink'] = [
    '#type' => 'details',
    '#title' => t('Node authorize link'),
    '#group' => 'additional_settings',
  ];
  $form['node_authlink']['node_authlink_enable'] = [
    '#type' => 'checkbox',
    '#title' => t('Enable'),
    '#default_value' => isset($config_enable[$type]) ? $config_enable[$type] : 0,
    '#description' => t('Disable of this feature will cost erase of authorization keys of all nodes in this node type.'),
  ];
  $form['node_authlink']['node_authlink_grants'] = [
    '#type' => 'checkboxes',
    '#title' => t('Grants to give'),
    '#default_value' => isset($config_grants[$type]) ? $config_grants[$type] : [],
    '#options' => [
      'view' => t('View'),
      'update' => t('Update'),
      'delete' => t('Delete'),
    ],
    '#description' => t('What operations will be temporarily given to authorised user for the node. This not affect users who is authorised yet.'),
  ];

  // Time periods: none, 1 day, 1 week, 4 weeks, 3 months, 6 months, 1 year
  $time_periods = [
    0,
    86400,
    604800,
    2419200,
    7776000,
    15552000,
    31536000,
  ];
  $period = node_authlink_build_options($time_periods);
  $period[0] = '<' . t('disabled') . '>';
  $form['node_authlink']['node_authlink_expire'] = [
    '#type' => 'select',
    '#title' => t('Regenerate authkeys after'),
    '#default_value' => isset($config_expire[$type]) ? $config_expire[$type] : '',
    '#options' => $period,
    '#description' => t('Keys older than selected time will be regenerated by cron run.'),
  ];
  $form['node_authlink']['node_authlink_batch'] = [
    '#type' => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#title' => t('Batch operations'),
    '#description' => t('Affects all nodes in this node type.'),
  ];
  $form['node_authlink']['node_authlink_batch']['generate'] = [
    '#type' => 'submit',
    '#value' => t('Generate authkeys'),
    '#submit' => [
      'node_authlink_batch_generate',
    ],
  ];
  $form['node_authlink']['node_authlink_batch']['delete'] = [
    '#type' => 'submit',
    '#value' => t('Delete all authkeys'),
    '#submit' => [
      'node_authlink_batch_delete',
    ],
  ];
  $form['actions']['submit']['#submit'][] = 'node_authlink_form_node_type_form_alter_submit';
}

/**
 * Helper function to build expire options.
 *
 * @param array $time_intervals
 * @param int $granularity
 * @param null $langcode
 *
 * @return array
 */
function node_authlink_build_options(array $time_intervals, $granularity = 2, $langcode = NULL) {
  $callback = function ($value) use ($granularity, $langcode) {
    return \Drupal::service('date.formatter')
      ->formatInterval($value, $granularity, $langcode);
  };
  return array_combine($time_intervals, array_map($callback, $time_intervals));
}

/**
 * Submit for node_type_form.
 */
function node_authlink_form_node_type_form_alter_submit(&$form, FormStateInterface &$form_state) {

  // Disabled
  $type = $form_state
    ->getValue('type');
  $config_factory = \Drupal::configFactory();
  $config = $config_factory
    ->getEditable('node_authlink.settings');
  $enable = $config
    ->get('enable');
  $grants = $config
    ->get('grants');
  $expire = $config
    ->get('expire');
  if (!$form_state
    ->getValue('node_authlink_enable')) {
    unset($enable[$type]);
    unset($grants[$type]);
  }
  else {
    $enable[$type] = TRUE;
    $grants[$type] = $form_state
      ->getValue('node_authlink_grants');
  }
  $expire[$type] = $form_state
    ->getValue('node_authlink_expire');
  $config
    ->set('enable', $enable);
  $config
    ->set('grants', $grants);
  $config
    ->set('expire', $expire);
  $config
    ->save();
}

/**
 * Generate authkeys for all nodes in node type.
 */
function node_authlink_batch_generate(&$form, FormStateInterface &$form_state) {

  // Load NIDs that are not in the authkeys table
  $query = \Drupal::database()
    ->select('node', 'n');
  $query
    ->leftJoin('node_authlink_nodes', 'a', 'n.nid = a.nid');
  $query
    ->fields('n', [
    'nid',
  ])
    ->condition('type', $form_state
    ->getValue('type'))
    ->isNull('authkey');
  $nids = $query
    ->execute()
    ->fetchCol();

  // Create keys
  foreach ($nids as $nid) {
    node_authlink_create($nid);
  }
  \Drupal::messenger()
    ->addMessage(t('%num authkeys has been generated.', [
    '%num' => count($nids),
  ]));
}

/**
 * Delete authkeys for all nodes in node type.
 *
 * @param array $form
 *   Form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   Form state.
 */
function node_authlink_batch_delete(array &$form, FormStateInterface &$form_state) {

  // NIDs of nodes that are in this node type.
  $query = \Drupal::database()
    ->select('node', 'n');
  $query
    ->leftJoin('node_authlink_nodes', 'a', 'n.nid = a.nid');
  $query
    ->fields('n', [
    'nid',
  ])
    ->condition('type', $form_state
    ->getValue('type'))
    ->isNotNull('authkey');
  $nids = $query
    ->execute()
    ->fetchCol();
  foreach ($nids as $nid) {
    node_authlink_delete($nid);
  }
  \Drupal::messenger()
    ->addMessage(t('%num authkeys has been deleted.', [
    '%num' => count($nids),
  ]));
}

/**
 * Implements hook_node_load().
 *
 * Appends authke to loaded node object.
 */
function node_authlink_node_load($nodes) {
  foreach ($nodes as $nid => $node) {

    // TODO: check node type (performance)
    if ($authkey = node_authlink_load_authkey($nid)) {
      $nodes[$nid]->authkey = $authkey;
    }
  }
}

/**
 * Loads key from NID.
 */
function node_authlink_load_authkey($nid) {
  $result = \Drupal::database()
    ->query('SELECT authkey FROM {node_authlink_nodes} WHERE nid = :nid', [
    ':nid' => $nid,
  ]);
  return $result
    ->fetchField();
}

/**
 * Get edit URL of specified node.
 *
 * @param $node Node object or NID.
 * @param string $op Operation to do with node. view, update (default) or delete.
 * @param null $revision_id
 *
 * @return bool|\Drupal\Core\GeneratedUrl|string
 */
function node_authlink_get_url($node, $op = 'view', $revision_id = NULL) {
  if (is_numeric($node)) {
    $node = Node::load($node);
  }
  if (!isset($node->authkey)) {
    return FALSE;
  }
  switch ($op) {
    case 'view':
      if (is_numeric($revision_id)) {
        $route_name = 'entity.node.revision';
      }
      else {
        $route_name = 'entity.node.canonical';
      }
      break;
    case 'update':
      $route_name = 'entity.node.edit_form';
      break;
    case 'delete':
      $route_name = 'entity.node.delete_form';
      break;
    default:
      return FALSE;
  }
  $arguments = [
    'node' => $node
      ->id(),
  ];
  if (is_numeric($revision_id)) {
    $arguments['node_revision'] = $revision_id;
  }
  $url = Url::fromRoute($route_name, $arguments, [
    'absolute' => TRUE,
    'query' => [
      'authkey' => $node->authkey,
    ],
  ]);
  return $url
    ->toString();
}

/**
 * Implements hook_node_access().
 */
function node_authlink_node_access(NodeInterface $node, $op, AccountInterface $account) {

  // Ignore if just creating node
  if ($op == 'create') {
    return AccessResult::neutral();
  }

  // Ignore if node type is not enabled
  if (!node_authlink_node_is_enabled($node)) {
    return AccessResult::neutral();
  }
  if (node_authlink_check_authlink($node, $op, $account)) {
    return AccessResult::allowed();
  }
}

/**
 * Checks if a node is enabled for node_authlink
 *
 * @param \Drupal\node\NodeInterface $node
 *   The node to check.
 *
 * @return bool
 */
function node_authlink_node_is_enabled(NodeInterface $node) {
  $config = \Drupal::config('node_authlink.settings');
  $config_enable = $config
    ->get('enable');
  if (isset($config_enable[$node
    ->bundle()]) && $config_enable[$node
    ->bundle()]) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Check if a node has access to a node via authlink and grant it the query parameter is correct.
 *
 * @param \Drupal\node\NodeInterface $node
 * @param $op
 * @param \Drupal\Core\Session\AccountInterface $account
 *
 * @return bool
 */
function node_authlink_check_authlink(NodeInterface $node, $op, AccountInterface $account) {
  $config = \Drupal::config('node_authlink.settings');

  // Check key if:
  if (isset($_GET['authkey']) && isset($node->authkey)) {

    // authkey in node is setand
    if ($node->authkey == $_GET['authkey']) {

      // Start session
      if ($account
        ->isAnonymous() && !isset($_SESSION['node_authlink_nodes'])) {

        /** @var \Drupal\Core\Session\SessionManager $session_manager */
        $session_manager = \Drupal::service('session_manager');
        $session_manager
          ->start();
      }

      // Save allowed grants to session
      $config_grants = $config
        ->get('grants');
      $_SESSION['node_authlink_nodes'][$node
        ->id()] = $config_grants[$node
        ->bundle()];
    }
  }

  // Permit if checked
  if (isset($_SESSION['node_authlink_nodes'][$node
    ->id()]) && in_array($op, $_SESSION['node_authlink_nodes'][$node
    ->id()], TRUE)) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_ENTITY_TYPE_delete().
 */
function node_authlink_node_delete(Drupal\Core\Entity\EntityInterface $entity) {
  $config = \Drupal::config('node_authlink.settings');
  $enable = $config
    ->get('enable');

  // Ignore if node type is disabled
  if (isset($enable[$entity
    ->bundle()]) && $enable[$entity
    ->bundle()]) {
    node_authlink_delete($entity
      ->id());
  }
}

/**
 * Generate and save auth key for the new node.
 */
function node_authlink_create($node) {

  // Allow key generate without load node object
  if (is_numeric($node)) {
    $nid = $node;
  }
  else {
    $nid = $node
      ->id();
    $config = \Drupal::config('node_authlink.settings');

    // Ignore if node type is disabled
    if (!$config
      ->get('enable.' . $node
      ->bundle())) {
      return;
    }
  }

  // Generate key if not yet
  $authkey = isset($node->authkey) ? $node->authkey : hash('sha256', random_bytes(64));

  // Save to DB
  \Drupal::database()
    ->insert('node_authlink_nodes')
    ->fields([
    'nid' => $nid,
    'authkey' => $authkey,
    'created' => time(),
  ])
    ->execute();
}

/**
 * Deletes the node_authlink.
 *
 * @param $node
 */
function node_authlink_delete($node) {
  if (is_numeric($node)) {
    $nid = $node;
  }
  else {
    $nid = $node
      ->id();
  }

  // Delete keys
  $count = \Drupal::database()
    ->delete('node_authlink_nodes')
    ->condition('nid', $nid)
    ->execute();
}

/**
 * Implementation of hook_cron().
 */
function node_authlink_cron() {
  $node_types = \Drupal\node\Entity\NodeType::loadMultiple();
  $config = \Drupal::config('node_authlink.settings');
  foreach ($node_types as $type) {
    $expire = $config
      ->get('expire.' . $type
      ->id());
    if (!$expire) {
      continue;
    }

    // NIDs of expired keys
    $query = \Drupal::database()
      ->select('node', 'n');
    $query
      ->leftJoin('node_authlink_nodes', 'a', 'n.nid = a.nid');
    $query
      ->fields('n', [
      'nid',
    ])
      ->condition('n.type', $type
      ->id())
      ->condition('a.created', time() - $expire, '<');
    $nids = $query
      ->execute()
      ->fetchCol();

    // Regenerate keys
    foreach ($nids as $nid) {
      \Drupal::database()
        ->delete('node_authlink_nodes')
        ->condition('nid', $nid)
        ->execute();
      node_authlink_create($nid);
    }
  }
}

/**
 * Implementation of hook_token_info().
 */
function node_authlink_token_info() {
  $node['authlink:authkey'] = [
    'name' => t("Authorization key"),
    'description' => t("Key generated by Node authorize link module."),
  ];
  $node['authlink:view-url'] = [
    'name' => t("View URL"),
    'description' => t("URL with authorization key."),
  ];
  $node['authlink:edit-url'] = [
    'name' => t("Edit URL"),
    'description' => t("URL with authorization key."),
  ];
  $node['authlink:delete-url'] = [
    'name' => t("Delete URL"),
    'description' => t("URL with authorization key."),
  ];
  return [
    'tokens' => [
      'node' => $node,
    ],
  ];
}

/**
 * Implements hook_tokens().
 */
function node_authlink_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
  if ($type != 'node' || empty($data['node'])) {
    return;
  }
  $replacements = [];
  if ($type == 'node' && !empty($data['node']->authkey)) {
    $node = $data['node'];
    foreach ($tokens as $name => $original) {
      switch ($name) {
        case 'authlink:authkey':
          $replacements[$original] = $node->authkey;
          break;
        case 'authlink:view-url':
          $replacements[$original] = node_authlink_get_url($node, 'view');
          break;
        case 'authlink:edit-url':
          $replacements[$original] = node_authlink_get_url($node, 'update');
          break;
        case 'authlink:delete-url':
          $replacements[$original] = node_authlink_get_url($node, 'delete');
          break;
      }
    }
  }
  return $replacements;
}

/**
 * Implements hook_group_content_info_alter().
 */
function node_authlink_group_content_info_alter(array &$groupContentInfo) {

  // Override the access control handler so authlink can operate first.
  foreach ($groupContentInfo as &$info) {
    $info['handlers']['access'] = NodeAuthlinkGroupContentAccessControlHandler::class;
  }
}

/**
 * Implements hook_entity_type_alter().
 */
function node_authlink_entity_type_alter(array &$entity_types) {
  if ($entity_types['node'] instanceof ContentEntityTypeInterface) {
    $entity_types['node']
      ->setAccessClass(NodeAuthlinkNodeAccessControlHandler::class);
  }
}

Functions

Namesort descending Description
node_authlink_batch_delete Delete authkeys for all nodes in node type.
node_authlink_batch_generate Generate authkeys for all nodes in node type.
node_authlink_build_options Helper function to build expire options.
node_authlink_check_authlink Check if a node has access to a node via authlink and grant it the query parameter is correct.
node_authlink_create Generate and save auth key for the new node.
node_authlink_cron Implementation of hook_cron().
node_authlink_delete Deletes the node_authlink.
node_authlink_entity_type_alter Implements hook_entity_type_alter().
node_authlink_form_node_type_form_alter Alter of node_type_form.
node_authlink_form_node_type_form_alter_submit Submit for node_type_form.
node_authlink_get_url Get edit URL of specified node.
node_authlink_group_content_info_alter Implements hook_group_content_info_alter().
node_authlink_load_authkey Loads key from NID.
node_authlink_node_access Implements hook_node_access().
node_authlink_node_delete Implements hook_ENTITY_TYPE_delete().
node_authlink_node_is_enabled Checks if a node is enabled for node_authlink
node_authlink_node_load Implements hook_node_load().
node_authlink_tokens Implements hook_tokens().
node_authlink_token_info Implementation of hook_token_info().