Drupal 7中每个节点有多个编辑器

One of the things that makes Drupal great is its flexible user permission system. The out of the box permissions grid we are all familiar with covers most uses cases of controlling what users can and cannot do. It is also very easy for module developers to create new permissions and roles that restrict the logic they implement.

使Drupal出色的一件事是其灵活的用户权限系统。 我们都熟悉的开箱即用的权限网格涵盖了控制用户可以做什么和不能做什么的大多数用例。 对于模块开发人员来说,创建新的权限和角色来限制他们实现的逻辑也非常容易。

Drupal logo

Nevertheless, I have encountered a practical use case where the default configuration options are not enough. Namely, if you need to have multiple users with access to edit a particular node of a given type but without them necessarily having access to edit others of the same type. In other words, the next great article should be editable by Laura and Glenn but not by their colleagues. However, out of the box, users of a particular role can be masters either of their own content or of all content of a certain type. So this is not immediately possible.

但是,我遇到了一个实际的用例,其中默认配置选项还不够。 即,如果您需要有多个用户有权编辑给定类型的特定节点,而不必让他们必须有权编辑其他相同类型的节点。 换句话说,下一篇出色的文章应该由Laura和Glenn编辑,但不能由其同事编辑。 但是,具有特定角色的用户可以直接使用其自己的内容或某种类型的所有内容的母版。 因此,这不可能立即实现。

In this article I am going to show you my solution to this problem in the form of a simple custom module called editor_list. Article nodes will have a field where you can select users and only these users (or those who have full access) will be able to edit that particular node. You can find the module already in this git repository and you can install it on your site for a quick start. Do keep in mind that it has a dependency on the Entity Reference module as we will see in a minute.

在本文中,我将以一个名为editor_list的简单自定义模块的形式向您展示我的解决方案。 文章节点将具有一个字段,您可以在其中选择用户,只有这些用户(或具有完全访问权限的用户)才能编辑该特定节点。 您可以在此git存储库中找到该模块,然后将其安装在站点上以快速入门。 请记住,正如稍后所看到的,它依赖于Entity Reference模块。

I will keep the code comments to a minimum to save space but you can find them in the repository if you want. Basic knowledge of Drupal 7 is assumed in the remainder of this tutorial.

我将代码注释减至最少,以节省空间,但是您可以根据需要在存储库中找到它们。 本教程的其余部分将假定Drupal 7的基本知识。

脚手架 (Scaffolding)

We first need the editor_list.info file for our module to get us going:


name = Editor List
description = Module illustrating a custom solution for having multiple editors on a node.
core = 7.x
dependencies[] = entityreference

Next, we need our editor_list.module file where most of our business logic will be located. So go ahead and create it and we will populate it as we go on.

接下来,我们需要我们的editor_list.module文件, editor_list.module文件是我们大多数业务逻辑所在的文件。 因此,继续进行创建,然后继续进行操作。

Finally, though not covered here, we can have an editor_list.install file where we can implement hook_install() and hook_update hooks to create fields and/or deploy configuration. In the repository, you’ll find that I provided an install hook that already creates an entity reference field called field_editors and attaches it to the Article content type. If you are following along but not using the code in the repository, you should go ahead and create the field manually through the UI. It’s a simple field that references User entities and allows for unlimited selections. Nothing major.

最后,尽管这里没有介绍,但是我们可以有一个editor_list.install文件,在其中可以实现hook_install()hook_update挂钩来创建字段和/或部署配置。 在存储库中,您会发现我提供了一个安装钩子,该钩子已经创建了一个名为field_editors的实体引用字段,并将其附加到Article内容类型。 如果您遵循但未使用存储库中的代码,则应继续进行操作,并通过UI手动创建该字段。 这是一个简单的字段,它引用用户实体并允许无限制的选择。 没什么大不了的。

节点访问 (Node access)

Going back to our .module file, it’s time to implement our access logic. First though, to make things as flexible and reusable as possible, let’s have a simple function that returns an array of node types to which we apply our access logic:

回到我们的.module文件,是时候实现我们的访问逻辑了。 不过,首先,为了使事情变得尽可能灵活和可重用,让我们有一个简单的函数,该函数返回一个节点类型数组,我们将对其应用访问逻辑:

function editor_list_node_types() {
  return array('article');

Since we are only targeting articles, this will suffice. But we will use this function in multiple places so in case we need to target other types as well, we just have to update this array.

由于我们仅针对文章,因此就足够了。 但是我们将在多个地方使用此函数,因此如果我们还需要定位其他类型,则只需更新此数组即可。

Next, let’s write another helpful function that returns all the user IDs set in the editors field of a given node. We will also use this in multiple places:

接下来,让我们编写另一个有用的函数,该函数返回在给定节点的editors字段中设置的所有用户ID。 我们还将在多个地方使用它:

function editor_list_uids_from_list($node) {
  $users = field_get_items('node', $node, 'field_editors');

  $allowed_uids = array();
  if ($users) {
    $allowed_uids = array_map(function($user) {
      return $user['target_id'];
    }, $users);

  return $allowed_uids;

I believe the function is quite self explanatory so I won’t go into details here. Instead, we can turn to our hook_node_access() implementation that gets called by Drupal whenever a user tries to do something with a node (view, edit or delete):

我相信该功能是很容易解释的,因此在此我将不做详细介绍。 取而代之的是,我们可以转向我们的hook_node_access()实现,只要用户尝试对节点执行某项操作 (查看,编辑或删除),Drupal就会调用该实现:

 * Implements hook_node_access().
function editor_list_node_access($node, $op, $account) {
  $node_types = editor_list_node_types();

  if ( ! is_object($node) || ! in_array($node->type, $node_types) || $op !== 'update') {

  $allowed_uids = editor_list_uids_from_list($node);

  if (empty($allowed_uids)) {

  if (in_array($account->uid, $allowed_uids)) {

So what’s happening here?


First, we use our previously declared helper function to get the list of node types we want to target, and we basically ignore the situation and return if the node type of the currently accessed node is not within our list or if the operation the user is attempting is not of the type “update”. Then we use our other helper function to check if there are any users in the editor list for this node and again ignore the situation if there aren’t. However, if there are, and our accessing user is among them, we return the NODE_ACCESS_ALLOW constant which basically gives the user access to perform the attempted operation. And that’s it.

首先,我们使用先前声明的helper函数来获取要定位的节点类型的列表,并且如果当前访问的节点的节点类型不在我们的列表中或者用户的操作是否在列表中,则我们基本上会忽略这种情况并返回尝试的类型不是“更新”。 然后,我们使用其他帮助器功能来检查该节点的编辑器列表中是否有任何用户,如果没有,则再次忽略该情况。 但是,如果有,并且正在访问的用户在其中,我们将返回NODE_ACCESS_ALLOW常量,该常量基本上使用户能够执行尝试的操作。 就是这样。

You can check out the documentation for more information about how this hook works.


Let’s say you have admin users who can create and edit any type of content and regular authenticated users who cannot edit articles (apart from maybe the ones they created themselves). Adding one of these latter users to a node’s editor list would give them access to that particular node. And another great thing is that since this is all nicely integrated, contextual filters and tabs also take these dynamic permissions into account.

假设您有可以创建和编辑任何类型的内容的管理员用户,以及不能编辑文章(可能是他们自己创建的文章)的常规身份验证用户。 将这些用户之一添加到节点的编辑器列表中,将使他们能够访问该特定节点。 另一个很棒的事情是,由于这一切都很好地集成在一起,因此上下文过滤器和选项卡也将这些动态权限考虑在内。

现场访问 (Field access)

We now have a working module that does what I initially set out for it to do. But let’s say that your admin users are the only ones responsible for adding users to the editor lists. In other words, you are afraid that if your editors can edit their nodes and remove themselves from the list, they’ll get locked out of the node they are supposed to work on.

现在,我们有一个工作模块,该模块可以完成我最初打算要做的事情。 但是,假设您的管理员用户是唯一负责将用户添加到编辑器列表的用户。 换句话说,您担心如果您的编辑者可以编辑其节点并将其从列表中删除,他们将被锁定在他们应该处理的节点之外。

To account for this situation, we need to implement a field access check and remove the possibility that editors tamper with that field. Implementing hook_field_access should do the trick nicely. And if you are wondering, this hook is similar to hook_node_access() but is responsible for individual fields rather than the entire node (+ a couple of other small differences).

为了解决这种情况,我们需要实施字段访问检查,并消除编辑者篡改该字段的可能性。 实现hook_field_access应该很好。 而且,如果您想知道,该钩子类似于hook_node_access()但它负责单个字段而不是整个节点(加上一些其他小的区别)。

 * Implements hook_field_access().
function editor_list_field_access($op, $field, $entity_type, $entity, $account) {
  $node_types = editor_list_node_types();
  if ($entity_type === 'node' && is_object($entity) && in_array($entity->type, $node_types)) {
    return editor_list_control_field_access($op, $field, $entity_type, $entity, $account);

And here we have it. There are a few more parameters because this hook gets called for all entities, not just nodes. But again, we check if the currently accessed node is one of those we defined earlier (and that the entity is in fact a node) and this time delegate to another function to keep things tidier:

在这里,我们有它。 还有更多参数,因为该钩子被所有实体(不仅仅是节点)调用。 但是再次,我们检查当前访问的节点是否是我们先前定义的节点之一(并且该实体实际上是一个节点),这次我们委托给另一个函数以保持整洁:

function editor_list_control_field_access($op, $field, $entity_type, $entity, $account) {
  if ($op !== 'edit') {

  $uids = editor_list_uids_from_list($entity);
  if (!in_array($account->uid, $uids)) {

  $deny = array('field_editors');
  if (in_array($field['field_name'], $deny)) {
    return false;

Since we only care if the user is trying to update a particular field, we return nothing if this is not the case. Keep in mind that the op string here is edit and not update as it was in the other hook. This is just one of those Drupal quirks of inconsistency we all came to love so much. And like before, we ignore the situation if the current user is not part of the editor list.

由于我们只关心用户是否尝试更新特定字段,因此如果不是这种情况,我们将不返回任何内容。 请记住,此处的操作字符串是可edit ,不会像在另一个挂钩中那样进行update 。 这只是我们所有人都非常喜欢的那些Drupal不一致的怪癖之一。 和以前一样,如果当前用户不属于编辑器列表,我们将忽略这种情况。

Then, we define an array of field names we want to deny access to (in our case only one but we can add to it depending on the use case). Finally, we return false if the currently accessed field is part of our $deny array. Yet another difference here in that we have to return a boolean instead of a constant like we did before.

然后,我们定义了一个我们想拒绝访问的字段名数组(在我们的情况下,只有一个,但是我们可以根据用例添加到其中)。 最后,如果当前访问的字段是$deny数组的一部分,则返回false。 此处的另一个区别在于,我们必须返回布尔值而不是像以前一样返回常量。

Now the editors in the list of a given node cannot remove themselves or add anybody else to the list. But then again, in some cases you may want this functionality and in others not. It’s up to you.

现在,给定节点列表中的编辑器无法删除自己或将其他任何人添加到列表中。 但话又说回来,在某些情况下,您可能需要此功能,而在其他情况下,则可能不需要。 由你决定。

整理 (Tidying up)

The last thing I am going to show you here relates to organization and maybe a bit of user experience. With our current implementation, the editor list field on the Article nodes is present somewhere on the form (wherever you dragged-and-dropped it when editing the field settings). However, wouldn’t it be nice if it were automatically part of the Authoring information group at the bottom of the page? Something like this:

在这里我要向您展示的最后一件事与组织有关,也许与用户体验有关。 在我们当前的实现中,“文章”节点上的“编辑器列表”字段显示在表单上的某个位置 (在编辑字段设置时将其拖放到的任何位置)。 但是,如果它自动成为页面底部的Authoring information组的一部分,那会不会很好? 像这样:

Drupal 7 multiple editors per node

I think so. Let’s see how we can do that.

我认同。 让我们看看如何做到这一点。

First, we need to implement hook_form_alter or one of its variations. I prefer the most targeted one to avoid unnecessary calls to it and a bunch of conditional checks:

首先,我们需要实现hook_form_alter或其变体之一。 我更喜欢针对性最强的代码,以避免不必要的调用和一堆条件检查:

 * Implements hook_form_BASE_FORM_ID_alter().
function editor_list_form_article_node_form_alter(&$form, &$form_state, $form_id) {
  $form['#after_build'][] = 'editor_list_node_form_after_build';

We went with the BASE_FORM_ID of the article nodes here so if we extend our application to other types we would do the same for those as well. Inside, we just define an #after_build function to be triggered when the form has finished building. This is to ensure all the form alterations have been already done by contributed modules. All that is left to be done is to write the function responsible for making changes to the form:

我们在这里使用了BASE_FORM_ID节点的BASE_FORM_ID ,因此,如果将应用程序扩展到其他类型,我们也将对它们进行同样的处理。 在内部,我们仅定义一个#after_build函数,当表单完成构建时将触发该函数。 这是为了确保所有表单更改都已由贡献模块完成。 剩下要做的就是编写负责更改表单的函数:

function editor_list_node_form_after_build($form, &$form_state) {
  $field = field_info_field('field_editors');
  if ( ! field_access('edit', $field, 'node', $form['#entity'])) {
    return $form;

  if ($form['author']['#access'] === 0) {
    return $form;

  $field_editors = $form['field_editors'];
  $field_editors['#weight'] = 0;
  $form['author']['additional_authors'] = $field_editors;
  $form['field_editors'] = array();

  return $form;

This looks complicated but it really isn’t. We begin by loading the field definition of our editor list field. This is so that we can run the field_access check on it and just return the form array unchanged if the current user doesn’t have access to the field. Next, we do the same if the current user does not have access to the author group on the form (this is the Authoring information group we want to place the field into). And lastly, we make a copy of the field definition, change its weight and place it into the group, followed by unsetting the original definition to avoid having duplicates.

这看起来很复杂,但实际上并非如此。 我们首先加载编辑器列表字段的字段定义。 这样一来,我们就可以对其进行field_access检查,如果当前用户无权访问该字段,则只需返回表单数组即可。 接下来,如果当前用户无权访问表单上的author组(这是我们要将该字段放入的Authoring information组),则执行相同的操作。 最后,我们复制字段定义,更改其权重并将其放入组中,然后取消设置原始定义以避免重复。

And that is pretty much it. Now the editors list field should be tucked in with the rest of the information related to authorship.

就是这样。 现在,应该将编辑者列表字段与其余有关作者身份的信息一起放入。

结论 (Conclusion)

In this article, we created a solution to a content editing problem that Drupal 7 could not fix out of the box. However, it did provide us with the development tools necessary to make this an easy task inside of a custom module.

在本文中,我们为Drupal 7无法立即解决的内容编辑问题创建了解决方案。 但是,它确实为我们提供了必要的开发工具,以使其在自定义模块中轻松完成。

We now have an editor list field on the article node form by which we can specify exactly which users have access to that particular node. Though do keep in mind that in order for this to be of any use, the users you add to these lists must not have a role that allows them to edit all article nodes. Otherwise you won’t see much of a difference.

现在,我们在文章节点表单上有了一个编辑器列表字段,通过它可以确切指定哪些用户可以访问该特定节点。 尽管要记住,为了使此功能有用,添加到这些列表的用户不得具有允许他们编辑所有文章节点的角色。 否则,您不会看到太大的区别。

翻译自: https://www.sitepoint.com/multiple-editors-per-node-drupal-7/

  • 0
  • 0
    收藏 更改收藏夹
  • 0


  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助




当前余额3.43前往充值 >
领取后你会自动成为博主和红包主的粉丝 规则
钱包余额 0