monster_menus-9.0.x-dev/modules/mm_workflow_access/mm_workflow_access.module

modules/mm_workflow_access/mm_workflow_access.module
<?php

/**
 * @file
 *   Provides node access permissions based on workflow states.
 */

use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Markup;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\monster_menus\Constants;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Drupal\workflow\Entity\Workflow;
use Drupal\workflow\Entity\WorkflowState;

const MM_WORKFLOW_PERMS_DELETE = 'd';
const MM_WORKFLOW_AUTHOR_GID = -1;
const MM_WORKFLOW_EVERYONE = 0;

/**
 * Implements hook_hook_info().
 */
function mm_workflow_access_module_implements_alter(&$implementations, $hook) {
  if (isset($implementations['mm_workflow_access']) && in_array($hook, ['form_alter', 'form_node_form_alter', 'workflow', 'mm_node_access', 'node_links_alter'])) {
    unset($implementations['mm_workflow_access']);
    $implementations['mm_workflow_access'] = FALSE;
  }
}

/**
 * Implements hook_theme().
 */
function mm_workflow_access_theme() {
  return [
    'mm_workflow_access_form' => [
      'render element' => 'form',
    ],
  ];
}

function mm_workflow_access_preprocess_mm_workflow_access_form(&$variables) {
  $form = &$variables['form'];
  $variables['no_read'] = $form['no_read'];
  unset($form['no_read']);
  $rows = [];
  foreach (Element::children($form) as $sid) {
    $row = [['data' => $form[$sid]['#title']]];
    foreach (Element::children($form[$sid]) as $mode) {
      $row[] = ['data' => $form[$sid][$mode], 'class' => ['align-top']];
    }
    $rows[] = ['data' => $row];
  }

  $header = [
    t('Workflow state'),
    t('Who can <strong>read</strong> posts in this state'),
    t('Who can <strong>edit/read posts</strong> in this state'),
    t('Who can <strong>delete</strong> posts in this state'),
  ];
  $form = [
    '#type' => 'table',
    '#header' => $header,
    '#rows' => $rows,
  ];
}

/**
 * Implements hook_form_alter() for workflow_admin_ui_edit_form.
 */
function mm_workflow_access_form_workflow_state_form_alter(&$form, FormStateInterface $form_state) {
  /** @var RouteMatchInterface $route */
  $route = \Drupal::service('current_route_match');
  /** @var Workflow $wf */
  $wf = $route->getParameters()->get('workflow_type');
  $states = $wf->getStates();

  $form['workflow_access'] = [
    '#type' => 'details',
    '#title' => t('Access control'),
    '#open' => TRUE,
    '#tree' => TRUE,
    '#theme' => 'mm_workflow_access_form',
  ];

  foreach ($states as $state) {
    $sid = $state->id();
    $access = $state->getThirdPartySetting('mm_workflow_access', 'mm_workflow_access');
    $form['workflow_access'][$sid]['#title'] = Markup::create($state->label());
    foreach ([Constants::MM_PERMS_READ, Constants::MM_PERMS_WRITE, MM_WORKFLOW_PERMS_DELETE] as $mode) {
      $everyone = FALSE;
      $author = FALSE;
      $groups = [];
      if (isset($access[$mode])) {
        $everyone = in_array(0, $access[$mode]);
        $author = in_array(MM_WORKFLOW_AUTHOR_GID, $access[$mode]);
        foreach (array_diff($access[$mode], [0, MM_WORKFLOW_AUTHOR_GID]) as $gid) {
          $groups[$gid] = mm_content_get_name($gid);
        }
      }
      $form['workflow_access'][$sid][$mode]['everyone'] = [
        '#type' => 'checkbox',
        '#default_value' => $everyone,
        '#attributes' => ['class' => ['wfe-everyone']],
        '#title' => t('everyone'),
        '#weight' => 1,
      ];
      $form['workflow_access'][$sid][$mode]['author'] = [
        '#type' => 'checkbox',
        '#default_value' => $author,
        '#title' => t('the author'),
        '#attributes' => ['class' => ['wfe-author']],
        '#weight' => 2,
      ];
      $form['workflow_access'][$sid][$mode]['groups'] = [
        '#type' => 'mm_grouplist',
        '#mm_list_popup_start' => mm_content_groups_mmtid(),
        '#mm_list_other_name' => "workflow_access[$sid][$mode][everyone]",
        '#default_value' => $groups,
        '#weight' => 3,
      ];
    }
  }

  $form['#attached']['library'][] = 'mm_workflow_access/edit_form';

  $form['workflow_access']['no_read'] = [
    '#type' => 'textarea',
    '#title' => t('Message to users who aren\'t permitted to read the content'),
    '#default_value' => $wf->options['mm_workflow_access_no_read'] ?? '',
    '#rows' => 4,
  ];

  // Place our block comfortably down the page.
  $form['submit']['#weight'] = 10;
  $form['permissions']['#weight'] = 11;
  $form['#submit'][] = '_mm_workflow_access_form_workflow_state_form_submit';
}

function _mm_workflow_access_form_workflow_state_form_submit($form, FormStateInterface $form_state) {
  /** @var RouteMatchInterface $route */
  $route = \Drupal::service('current_route_match');
  /** @var Workflow $wf */
  $wf = $route->getParameters()->get('workflow_type');
  $wf->options['mm_workflow_access_no_read'] = $form_state->getValue(['workflow_access', 'no_read']);
  $wf->save();

  foreach ($form_state->getValue('workflow_access') as $sid => $access) {
    // Ignore irrelevant keys.
    /** @var WorkflowState $state */
    if ($sid == 'no_read' || !($state = WorkflowState::load($sid)) || $state->getWorkflowId() != $wf->id()) {
      continue;
    }

    $groups_w = [];
    $everyone = $author = FALSE;
    $new_access = [];
    foreach ($access as $mode => $perms) {
      if ($perms['everyone']) {
        $new_access[$mode][] = MM_WORKFLOW_EVERYONE;
        if ($mode == Constants::MM_PERMS_WRITE) {
          $everyone = TRUE;
        }
      }
      else {
        $new_access[$mode] = [];
        if ($perms['author']) {
          $new_access[$mode][] = MM_WORKFLOW_AUTHOR_GID;
          if ($mode == Constants::MM_PERMS_WRITE) {
            $author = TRUE;
          }
        }
        $new_access[$mode] = array_merge($new_access[$mode], array_keys($perms['groups']));
        if ($mode == Constants::MM_PERMS_WRITE) {
          $groups_w = $perms['groups'];
        }
      }
    }
    $state->setThirdPartySetting('mm_workflow_access', 'mm_workflow_access', $new_access);
    $state->save();

    // Find all nodes using workflow fields
    foreach (_workflow_info_fields() as $field_info) {
      $field_name = $field_info->getName();
      // Find all nodes using this field with the state being edited.
      $nids = \Drupal::entityQuery($field_info->getTargetEntityTypeId())
        ->accessCheck(FALSE)
        ->condition($field_name, $sid, '=')
        ->execute();
      /** @var NodeInterface $node */
      foreach (Node::loadMultiple($nids) as $node) {
        // Update the node's permissions to match the new state perms.
        $node->__set('users_w', NULL);
        $node->__set('groups_w', $groups_w);
        $node->__set('others_w', $everyone);
        $node->__set('mm_others_w_force', TRUE);
        if ($author) {
          $node->setOwnerId($node->__get('workflow_author'));
        }
        $node->save();
      }
    }
  }
  \Drupal::messenger()->addStatus(t('Workflow access permissions updated.'));
}

/**
 * Implements hook_form_alter() for workflow_type_edit_form.
 */
function mm_workflow_access_form_workflow_type_edit_form_alter(&$form, FormStateInterface $form_state) {
  /** @var Workflow $wf */
  $wf = $form_state->getBuildInfo()['callback_object']->getEntity();
  $form['basic']['instructions'] = [
    '#type' => 'textarea',
    '#title' => t('Help text'),
    '#default_value' => $wf->options['mm_workflow_access_instructions'] ?? '',
    '#rows' => 4,
    '#description' => t('Instructions to the user editing a node, describing what to do with the workflow field'),
  ];

  $form['actions']['submit']['#submit'][] = function ($form, FormStateInterface $form_state) {
    /** @var Workflow $wf */
    $wf = $form_state->getBuildInfo()['callback_object']->getEntity();
    $wf->options['mm_workflow_access_instructions'] = $form_state->getValue('instructions');
    $wf->save();
  };
}

/**
 * Implements hook_config_schema_info_alter().
 */
function mm_workflow_access_config_schema_info_alter(&$definitions) {
  if (isset($definitions['workflow.workflow.*']['mapping'])) {
    // Store instructions and read-only message in the Workflow config entity.
    $definitions['workflow.workflow.*']['mapping']['options']['mapping'] += [
      'mm_workflow_access_instructions' => ['label' => 'Help Text', 'type' => 'string'],
      'mm_workflow_access_no_read' => ['label' => 'Instructions to users without read access', 'type' => 'string'],
    ];
  }
}

/**
 * Implements hook_ENTITY_TYPE_presave() for node entities.
 */
function mm_workflow_access_node_presave(NodeInterface $node) {
  if (($state = _mm_workflow_access_get_current_state($node)) && ($wf_data = _mm_workflow_access_node_has_workflow($node)) && ($node->isNew() || !isset($node->__get($wf_data['field'])[0]) || $node->__get($wf_data['field'])[0]->get('value')->getString() != $state->id())) {
    $node->__set('users_w', []);
    $node->__set('groups_w', []);
    $node->__set('others_w', FALSE);
    $node->setOwnerId(1);
    $access = $state->getThirdPartySetting('mm_workflow_access', 'mm_workflow_access');
    if (!empty($access[Constants::MM_PERMS_WRITE])) {
      foreach ($access[Constants::MM_PERMS_WRITE] as $gid) {
        if ($gid == MM_WORKFLOW_EVERYONE) {
          // Setting others_w_force makes the change apply in MM, even though
          // this user might not normally have permission
          $node->__set('others_w', TRUE);
          $node->__set('mm_others_w_force', TRUE);
          break;
        }
        elseif ($gid > 0) {
          $groups_w = $node->__get('groups_w');
          $groups_w[$gid] = '';
          $node->__set('groups_w', $groups_w);
        }
        elseif (!is_null($node->__get('workflow_author'))) {
          $node->setOwnerId($node->__get('workflow_author'));
        }
      }
    }
  }
}

/**
 * Implements hook_form_BASE_FORM_ID_alter() for node_form.
 */
function mm_workflow_access_form_node_form_alter(&$form, FormStateInterface $form_state) {
  /** @var Node $node */
  $node = $form_state->getFormObject()->getEntity();
  if ($wf_data = _mm_workflow_access_node_has_workflow($node)) {
    $wf = Workflow::load($wf_data['id']);
    if (isset($wf->options['mm_workflow_access_instructions']) && isset($form[$wf_data['field']])) {
      $form[$wf_data['field']]['help'] = [
        '#type' => 'item',
        '#input' => FALSE,
        '#description' => $wf->options['mm_workflow_access_instructions'],
      ];
    }

    $form['settings_perms']['help1'] = [
      '#weight' => -2,
      '#type' => 'item',
      '#input' => FALSE,
      '#description' => t('This content\'s permissions are controlled by a workflow.'),
    ];
    $form['settings_perms']['#title'] = t('Who can edit this content');
    $uid = $node->__get('workflow_author') ?: \Drupal::currentUser()->id();
    $group = &$form[$wf_data['field']];
    if ($form['settings_perms']['table']['#perms']['allow_everyone']) {
      $group['#type'] = 'details';
      $group['#open'] = TRUE;
      $group['#title'] = t('Workflow');
      $group['_workflow_author-choose'] = [
        '#type' => 'textfield',
        '#title' => t('Choose the author'),
        '#autocomplete_route_name' => 'monster_menus.autocomplete',
        '#description' => mm_autocomplete_desc(),
        '#size' => 30, '#maxlength' => 40,
      ];
      $group['_workflow_author'] = [
        '#type' => 'mm_userlist',
        '#description' => t('In addition to appearing in the attribution, the content\'s author can be given special permissions within a workflow.'),
        '#title' => t('Original Author'),
        '#default_value' => [$uid => mm_ui_uid2name($uid)],
        '#mm_list_autocomplete_name' => '_workflow_author-choose',
        '#mm_list_min' => 1,
        '#mm_list_max' => 1,
        '#element_validate' => ['mm_workflow_access_validate_workflow_author'],
      ];
      $form['settings_perms']['owner'] = [
        '#type' => 'value',
        '#value' => 1,
      ];
    }
    else {
      $group['_workflow_author'] = [
        '#type' => 'value',
        '#value' => $uid,
      ];
    }

    if (!empty($form['settings_perms']['table']['everyone'][0][0]['node-everyone']['#default_value'])) {
      $help2 = t('Everyone can edit this content while it is in the current workflow state.');
    }
    else {
      unset($form['settings_perms']['table']['everyone']);
      $help2 = t('These users and groups can edit this content while it is at the current stage of the workflow:');
      $form['settings_perms']['table']['#readonly'] = TRUE;
      $form['settings_perms']['table']['indiv_tbl'][0]['#mm_owner']['show'] = FALSE;
      if (empty($form['settings_perms']['table']['groups_tbl'][0]['#mm_groups']) && empty($form['settings_perms']['table']['indiv_tbl'][0]['#mm_users'])) {
        unset($form['settings_perms']['table']);
        unset($form['#attached']['js']['settings_perms_summary']);
        $help2 = t('Only administrators can edit this content while it is at the current stage of the workflow.');
      }
    }
    if (!empty($help2)) {
      $form['settings_perms']['help2'] = [
        '#weight' => -1,
        '#type' => 'item',
        '#input' => FALSE,
        '#description' => $help2,
      ];
    }
  }
}

function mm_workflow_access_validate_workflow_author(&$element, FormStateInterface $form_state) {
  $value = $form_state->getValue('_workflow_author');
  $form_state->setValue('_workflow_author', [0 => ['target_id' => array_key_first($value)]]);
}

function mm_workflow_access_form_entity_form_display_edit_form_alter(&$form, FormStateInterface $form_state) {
  if (isset($form['fields']['_workflow_author'])) {
    // Don't allow the widget or order to be changed.
    $form['fields']['_workflow_author']['#attributes']['class'] = ['tabledrag-hide'];
    $form['fields']['_workflow_author']['plugin']['type']['#access'] = FALSE;
    $form['fields']['_workflow_author']['parent_wrapper']['parent']['#access'] = FALSE;
    $form['fields']['_workflow_author']['region']['#access'] = FALSE;
    $form['fields']['_workflow_author']['weight']['#access'] = FALSE;
  }
}

/**
 * Implements hook_mm_delete().
 */
function mm_workflow_access_mm_delete($mmtids) {
  // First, filter just the groups.
  $groups = [];
  foreach (mm_content_get($mmtids, Constants::MM_GET_PARENTS) as $tree) {
    if (isset($tree->parents[1]) && $tree->parents[1] == mm_content_groups_mmtid()) {
      $groups[] = $tree->mmtid;
    }
  }

  if ($groups) {
    foreach (WorkflowState::loadMultiple() as $state) {
      $changed = FALSE;
      if ($access = $state->getThirdPartySetting('mm_workflow_access', 'mm_workflow_access')) {
        foreach ($access as $mode => $gids) {
          if (array_intersect($gids, $groups)) {
            $access[$mode] = array_diff($gids, $groups);
            $changed = TRUE;
          }
        }
      }

      if ($changed) {
        $state->setThirdPartySetting('mm_workflow_access', 'mm_workflow_access', $access);
        $state->save();
      }
    }
  }
}

/**
 * Implements hook_user_delete().
 */
function mm_workflow_access_user_delete(UserInterface $account) {
  // Find all nodes where the workflow author is being deleted.
  $nids = \Drupal::entityQuery('node')
    ->accessCheck(FALSE)
    ->condition('_workflow_author', $account->id())
    ->execute();
  foreach (Node::loadMultiple($nids) as $node) {
    // Change to the admin user.
    $node->set('_workflow_author', [0 => ['target_id' => 1]])->save();
  }
}

function mm_workflow_access_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
  if ($entity_type->id() == 'node') {
    $have_workflow_field = FALSE;
    $have_workflow_author_field = isset($fields['_workflow_author']);

    /** @var FieldConfig $field_config */
    foreach ($fields as $field_config) {
      $storage = $field_config->getFieldStorageDefinition();
      if ($storage->getType() == 'workflow') {
        $have_workflow_field = TRUE;
        break;
      }
    }

    if ($have_workflow_field != $have_workflow_author_field) {
      if ($have_workflow_field) {
        $field_config_storage = \Drupal::getContainer()
          ->get('entity_type.manager')
          ->getStorage('field_config');
        $existing_field = $field_config_storage->loadByProperties([
          'entity_type' => 'node',
          'bundle' => $bundle,
          'field_name' => '_workflow_author',
        ]);
        if (empty($existing_field)) {
          $fields['_workflow_author'] = $field_config_storage->create([
            'entity_type' => 'node',
            'field_name' => '_workflow_author',
            'label' => t('Workflow Author'),
            'bundle' => $bundle,
          ])->save();
        }
        else {
          $fields['_workflow_author'] = $existing_field;
        }
      }
      else {
        unset($fields['_workflow_author']);
      }
      Cache::invalidateTags(['entity_field_info']);
    }
  }
}

/**
 * Implements hook_entity_field_storage_info().
 */
function mm_workflow_access_entity_field_storage_info(EntityTypeInterface $entity_type) {
  $fields = [];
  if ($entity_type->id() == 'node') {
    // Create the field storage if it doesn't already exist.
    $field_storage_storage = \Drupal::getContainer()
        ->get('entity_type.manager')
        ->getStorage('field_storage_config');
    $field_storage = $field_storage_storage->load('node._workflow_author');
    if (empty($field_storage)) {
      $field_storage = $field_storage_storage->create([
        'field_name' => '_workflow_author',
        'entity_type' => 'node',
        'type' => 'entity_reference',
        'locked' => TRUE,
        'settings' => [
          'target_type' => 'user',
        ],
      ]);
      $field_storage->save();
    }
    $fields['_workflow_author'] = $field_storage;
  }
  return $fields;
}

/**
 * Implements hook_ENTITY_TYPE_load().
 */
function mm_workflow_access_node_load(array $nodes) {
  /** @var NodeInterface $node */
  foreach ($nodes as $node) {
    if (_mm_workflow_access_node_has_workflow($node)) {
      $node->__set('workflow', workflow_node_current_state($node));
      $node->__set('workflow_author', $node->get('_workflow_author')->getValue()[0]['target_id'] ?? NULL);
      if (!is_null($node->__get('workflow_author')) && ($account = User::load($node->__get('workflow_author')))) {
        $node->__set('workflow_author_name',  $account->getAccountName());
      }
      // @FIXME: set cache tag here
    }
  }
}

/**
 * Implements hook_node_view().
 */
function mm_workflow_access_node_view(array &$build, NodeInterface $node, EntityViewDisplayInterface $display, $view_mode) {
  // Prevent the user from seeing content based on their ability to read at
  // this stage of the workflow
  if (($wf_data = _mm_workflow_access_node_has_workflow($node)) && !_mm_workflow_access_get_user_perm($node, Constants::MM_PERMS_READ)) {
    $node->__set('content', [
      'body' => ['#markup' => Workflow::load($wf_data['id'])->options['mm_workflow_access_no_read'] ?? ''],
    ]);
    $node->__set('mm_workflow_access_read_denied', TRUE);
  }
}

/**
 * Implements hook_link_alter().
 */
function mm_workflow_access_node_links_alter(array &$links, NodeInterface $node) {
  if (!empty($node->mm_workflow_access_read_denied)) {
    unset($links['comment_add']);
  }
}

/**
 * Implements hook_mm_node_access().
 */
function mm_workflow_access_mm_node_access($op, NodeInterface $node, AccountInterface $account) {
  if ($node->id() && _mm_workflow_access_node_has_workflow($node)) {
    if ($op == 'view') {
      $mode = Constants::MM_PERMS_READ;
    }
    elseif ($op == 'update') {
      $mode = Constants::MM_PERMS_WRITE;
    }
    elseif ($op == 'delete') {
      $mode = MM_WORKFLOW_PERMS_DELETE;
    }
    else {
      return;
    }

    if (is_null($node->__get('workflow'))) {
      $node->__set('workflow', workflow_node_current_state($node));
    }
    return _mm_workflow_access_get_user_perm($node, $mode, $account);
  }
}

function _mm_workflow_access_node_has_workflow(NodeInterface $node) {
  static $cache;
  // Return TRUE only if the workflow form is to be displayed and a workflow
  // is assigned to this node type
  $type = $node->getType();
  if (!$type) {
    return FALSE;
  }
  if (!isset($cache[$type])) {
    $cache[$type] = FALSE;
    foreach ($node->getFieldDefinitions() as $definition) {
      if ($definition->getType() == 'workflow') {
        $cache[$type] = ['id' => $definition->getSettings()['workflow_type'], 'field' => $definition->getName()];
      }
    }
  }
  return $cache[$type];
}

function _mm_workflow_access_get_perms(NodeInterface $node, AccountInterface $account = NULL) {
  static $cache;

  if (!isset($account)) {
    $account = \Drupal::currentUser();
  }
  $wf_data = _mm_workflow_access_node_has_workflow($node);
  if (is_null($account->id()) || !$wf_data || is_null($node->__get('workflow_author'))) {
    return [];
  }
  if ($account->hasPermission('administer all menus')) {
    return [Constants::MM_PERMS_READ, Constants::MM_PERMS_WRITE, MM_WORKFLOW_PERMS_DELETE];
  }

  $uid = $account->id();
  if (!isset($cache[$uid][$node->__get('workflow')][$node->__get('workflow_author')])) {
    $cache[$uid][$node->__get('workflow')][$node->__get('workflow_author')] = [];
    // This is somewhat expensive, so only perform if needed, and cache the
    // result for the duration of this function call.
    $user_can_update = function () use ($node, $account, &$user_can_update_result) {
      if (!isset($user_can_update_result)) {
        $user_can_update_result = mm_content_user_can_update_node($node, $account);
      }
      return $user_can_update_result;
    };
    /** @var WorkflowState $state */
    if ($state = WorkflowState::load($node->__get('workflow'))) {
      $access = $state->getThirdPartySetting('mm_workflow_access', 'mm_workflow_access');
      if ($access) {
        foreach ($access as $mode => $gids) {
          foreach ($gids as $gid) {
            if ($gid == MM_WORKFLOW_EVERYONE ||
              $gid == MM_WORKFLOW_AUTHOR_GID && ($node->workflow_author == $uid || $user_can_update()) ||
              $gid > 0 && in_array($uid, mm_content_get_uids_in_group($gid))) {
              $cache[$uid][$node->__get('workflow')][$node->__get('workflow_author')][] = $mode;
            }
          }
        }
      }
    }

    if ($account->hasPermission('view all menus')) {
      $cache[$uid][$node->__get('workflow')][$node->__get('workflow_author')][] = Constants::MM_PERMS_READ;
    }
  }
  return $cache[$uid][$node->__get('workflow')][$node->__get('workflow_author')];
}

function _mm_workflow_access_get_user_perm(NodeInterface $node, $mode, $account = NULL) {
  if (empty($account)) {
    $account = \Drupal::currentUser();
  }
  // If this is a new node, give the owner full access to it.
  if ($node->isNew() && $node->getOwnerId() == $account->id()) {
    return TRUE;
  }
  $list = _mm_workflow_access_get_perms($node, $account);
  // Write also includes read
  if ($mode == Constants::MM_PERMS_READ && in_array(Constants::MM_PERMS_WRITE, $list)) {
    return TRUE;
  }
  return in_array($mode, $list);
}

function _mm_workflow_access_get_current_state(NodeInterface $node) {
  if (!empty($node->__get('workflow')) && ($loaded = WorkflowState::load($node->__get('workflow')))) {
    return $loaded;
  }
  if ($workflow = workflow_get_workflows_by_type($node->getType(), 'node')) {
    return $workflow->getCreationState();
  }
}

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

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