第2章 Views 集成

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

一个好的Drupal模块,通常能够充分的利用已有模块的各种功能,而不是重复的发明轮子。当我们打算实现这个面包屑模块的时候,有一个目标就是尽量的简单易用、代码精炼,充分的利用常用的第3方模块,能够帮助我们实现这个目标。我们这里主要集成ViewsRules,顺带集成Field Validation。前面两个都是社区内,排名非常靠前的模块,Field Validation就是老葛写的,集成一下自己写的模块,对于推广使用Field Validation也是很有帮助的。


Drupal版本:

1 Field Validation集成

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

Field validation的集成相对简单一点,而且我本人也非常的熟悉。从Field validation2.1开始,里面自带一个子模块,叫做property validation,我们这里集成的其实是property validation。一个实体通常由两部分组成,字段(Field)和属性(property),注意在Entity API里面将两者都统一为了属性;Field validation负责字段验证,property validation负责属性验证,将两者统一起来,是我的一个目标。我们这里主要想为path这个属性,添加一些验证,比如保证它是唯一的,保证这个路径在内部确实存在,不加这些验证,也能够正常工作。


Drupal版本:

10 在info文件里面注册我们的类

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

清空缓存,向Views里面添加新的字段,此时就会看到我们这里定义的字段了:

2.jpg 

选中,上面的删除、编辑链接,点击添加并配置字段按钮,但是我们却得到了这样的页面:

2.jpg 

我多次清除缓存,都不起作用,包括到Views的高级配置页面,单独的清除views的缓存,仍然无效。我对比了一下model里面的代码,忽然想到了,需要在info文件里面,将这些类注册一下:

files[] = breadcrumb2.admin.inc

files[] = breadcrumb2.info.inc

files[] = breadcrumb2.test

files[] = views/breadcrumb2.views.inc

files[] = views/breadcrumb2_handler_link_field.inc

files[] = views/breadcrumb2_handler_delete_link_field.inc

files[] = views/breadcrumb2_handler_edit_link_field.inc

files[] = views/breadcrumb2_handler_breadcrumb_operations_field.inc

然后,再清空缓存。这下就正常了。很多人想不到,需要在info文件里面注册一下。

将配置好的Views导出到代码里面


Drupal版本:

11 将配置好的Views导出到代码里面

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    由于前面,我们新增了编辑、删除两个链接,所以我们可以将字段“Breadcrumb: Breadcrumb ID (Breadcrumb ID)”“Global: Custom text (Edit)”删除掉了。同时,将每页显示的10条,改为显示30条;设置无结果行为,无结果时显示信息“No breadcrumbs available.”;然后保存视图。

    我们在实际的项目中,也经常需要将配置好的Views导出成代码的形式点击这里的导出链接即可图片1.png

 

系统会为我们自动生成好代码。如下图所示:

图片2.png

 

我们将这里的代码复制下来,然后打开breadcrumb2.views.inc文件,在里面在里面添加钩子函数breadcrumb2_views_default_views,中间的部分,就是我们复制过来的:

/**

 * Implements hook_views_default_views().

 */

function breadcrumb2_views_default_views() {

$view = new view();

$view->name = 'breadcrumbs';

$view->description = '';

$view->tag = 'default';

$view->base_table = 'breadcrumb';

$view->human_name = 'Breadcrumbs';

$view->core = 7;

$view->api_version = '3.0';

$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */

 

/* Display: Master */

$handler = $view->new_display('default', 'Master', 'default');

$handler->display->display_options['use_more_always'] = FALSE;

$handler->display->display_options['access']['type'] = 'none';

$handler->display->display_options['cache']['type'] = 'none';

$handler->display->display_options['query']['type'] = 'views_query';

$handler->display->display_options['exposed_form']['type'] = 'basic';

$handler->display->display_options['pager']['type'] = 'full';

$handler->display->display_options['pager']['options']['items_per_page'] = '30';

$handler->display->display_options['pager']['options']['offset'] = '0';

$handler->display->display_options['pager']['options']['id'] = '0';

$handler->display->display_options['pager']['options']['quantity'] = '9';

$handler->display->display_options['style_plugin'] = 'table';

$handler->display->display_options['style_options']['columns'] = array(

  'bid' => 'bid',

);

$handler->display->display_options['style_options']['default'] = '-1';

$handler->display->display_options['style_options']['info'] = array(

  'bid' => array(

'sortable' => 0,

'default_sort_order' => 'asc',

'align' => '',

'separator' => '',

'empty_column' => 0,

  ),

);

/* No results behavior: Global: Text area */

$handler->display->display_options['empty']['area']['id'] = 'area';

$handler->display->display_options['empty']['area']['table'] = 'views';

$handler->display->display_options['empty']['area']['field'] = 'area';

$handler->display->display_options['empty']['area']['empty'] = TRUE;

$handler->display->display_options['empty']['area']['content'] = 'No breadcrumbs available.';

$handler->display->display_options['empty']['area']['format'] = 'filtered_html';

/* Field: Breadcrumb: path */

$handler->display->display_options['fields']['path']['id'] = 'path';

$handler->display->display_options['fields']['path']['table'] = 'breadcrumb';

$handler->display->display_options['fields']['path']['field'] = 'path';

/* Field: Breadcrumb: Breadcrumb Link */

$handler->display->display_options['fields']['link']['id'] = 'link';

$handler->display->display_options['fields']['link']['table'] = 'field_data_link';

$handler->display->display_options['fields']['link']['field'] = 'link';

$handler->display->display_options['fields']['link']['click_sort_column'] = 'url';

$handler->display->display_options['fields']['link']['delta_offset'] = '0';

$handler->display->display_options['fields']['link']['separator'] = ' ? ';

/* Field: Breadcrumb: Delete Link */

$handler->display->display_options['fields']['delete_breadcrumb']['id'] = 'delete_breadcrumb';

$handler->display->display_options['fields']['delete_breadcrumb']['table'] = 'breadcrumb';

$handler->display->display_options['fields']['delete_breadcrumb']['field'] = 'delete_breadcrumb';

$handler->display->display_options['fields']['delete_breadcrumb']['label'] = '';

$handler->display->display_options['fields']['delete_breadcrumb']['element_label_colon'] = FALSE;

/* Field: Breadcrumb: Edit Link */

$handler->display->display_options['fields']['edit_breadcrumb']['id'] = 'edit_breadcrumb';

$handler->display->display_options['fields']['edit_breadcrumb']['table'] = 'breadcrumb';

$handler->display->display_options['fields']['edit_breadcrumb']['field'] = 'edit_breadcrumb';

$handler->display->display_options['fields']['edit_breadcrumb']['label'] = '';

$handler->display->display_options['fields']['edit_breadcrumb']['element_label_colon'] = FALSE;

/* Sort criterion: Breadcrumb: Breadcrumb ID */

$handler->display->display_options['sorts']['bid']['id'] = 'bid';

$handler->display->display_options['sorts']['bid']['table'] = 'breadcrumb';

$handler->display->display_options['sorts']['bid']['field'] = 'bid';

$handler->display->display_options['sorts']['bid']['order'] = 'DESC';

/* Filter criterion: Breadcrumb: path */

$handler->display->display_options['filters']['path']['id'] = 'path';

$handler->display->display_options['filters']['path']['table'] = 'breadcrumb';

$handler->display->display_options['filters']['path']['field'] = 'path';

$handler->display->display_options['filters']['path']['operator'] = 'contains';

$handler->display->display_options['filters']['path']['exposed'] = TRUE;

$handler->display->display_options['filters']['path']['expose']['operator_id'] = 'path_op';

$handler->display->display_options['filters']['path']['expose']['label'] = 'path';

$handler->display->display_options['filters']['path']['expose']['operator'] = 'path_op';

$handler->display->display_options['filters']['path']['expose']['identifier'] = 'path';

$handler->display->display_options['filters']['path']['expose']['remember_roles'] = array(

  2 => '2',

  1 => 0,

  3 => 0,

);


  $views[$view->name] = $view;  

  return $views;

}

    最后的两行代码,是我们人工添加进来的。我们需要返回一个$views数组。我建议大家熟悉一下,这里导出的代码。我们以前在做项目的时候,有时候会遇到这种情况,一下子列出所有的条目,此时带有预览功能,系统会读取所有的条目,而条目数太大,以至于超出了PHP的内存限制,所以此时进入不了视图的编辑页面,此时我们可以将视图导出,然后修改导出后的代码,修改过后,一页显示10条记录,然后再导入这个视图的代码,就解决了问题。

    此外,需要注意的是,Views无法导出特殊字符的,我们在Views里面,配置的时候,使用了“»”这个符号,导出的时候,变成了,因为它是特殊字符。此时,我们可以将文件的编码格式转为UTF-8,然后将分隔符设置为“ » ”

$handler->display->display_options['fields']['link']['separator'] = ' » ';

   不过现在清空缓存,我们的代码无法被识别出来,删除链接还是删除链接,如果我们删除了刚才定义的views,就无法恢复回去。我经过很长时间的测试,调试,最后发现,如果将函数breadcrumb2_views_default_views放到breadcrumb2.module文件中来,就起作用,否则的话,就不起作用,最后,我向breadcrumb2.module文件,直接添加以下代码:

include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'breadcrumb2') . '/views/' . 'breadcrumb2.views.inc';

    就是当加载module文件的时候,自动的加载breadcrumb2.views.inc,现在我们清除缓存,就只能revert(恢复)我们的视图了,无法删除它了。

我检查了model的代码,我的代码和它基本上完全一样的,但是在model里面,就不需要这个include_once。以前导出代码的时候,也从来不用这个的,Views会自动的加载这个文件。我不知道哪里出了问题。这个问题,就留在将来解决吧,反正现在问题不大。

我们这里只需要记住,将导出的代码放到hook_views_default_views的这个钩子函数中,并在函数最后,增加以下两个代码即可:

  $views[$view->name] = $view;  

  return $views;


Drupal版本:

12 导出的views放到breadcrumb2.views_default.inc中

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

前面讲到,我们在module文件中,使用include_once,通过直接加载breadcrumb2.views.inc文件解决的问题,如果你到drupal.org/project/ breadcrumb2上面下载beta4以前的版本时,你会发现include_once这行代码都是存在的。

有句话,讲的很好,兼听则明,偏信则暗,我们写模块的时候,也应该多参照几个模块,我突然想把Rules集成和Views的集成分开来写,同时为两者增加更多一些内容。这个时候,我想到了,介绍一下自己写的Field collection views模块,这个模块主要也是集成Views。我打开本地的Field collection views模块一看,发现导出的views都放在了field_collection_views.views_default.inc这个文件中了。突然想起来,自己以前在Drupal6下面,也是放到这个文件中的。

我将field_collection_views.views_default.inc复制过来,重命名为breadcrumb2.views_default.inc,将文件的编码格式改为UTF-8,将breadcrumb2.views.inc文件中的breadcrumb2_views_default_views函数剪切到breadcrumb2.views_default.inc文件中,同时保存两个文件。将module文件中的include_once这行代码注释掉。

清除缓存,测试,一切正常。只能说model模块写的有问题,但是不知道为什么,它的导出的views就可以这样放,我的就不可以。这个问题,我们就不深究了。


Drupal版本:

13 Field Collection Views代码分析

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们这章,就主要讲Views了。刚才提到,我们受Field collection views模块的启发,我们这里介绍一个这个模块的代码。这个模块的用法,我们在Think in Drupal的第二集,里面已经介绍过了,是对Field Collection模块的一个很好的补充。Field collection views模块的主要功能,就是为Field collection类型的字段,提供一个formatter(格式化器),使用Views来呈现Field collection items

我们来看一下module文件里面的代码:

/**

 * Implements hook_field_formatter_info().

 */

function field_collection_views_field_formatter_info() {

  return array(

    'field_collection_views_view' => array(

      'label' => t('Views field-collection items'),

      'field types' => array('field_collection'),

      'settings' =>  array(

        'name' => 'field_collection_view',

        'display_id' => 'default',

        'add' => t('Add'),

      ),

    ),

  );

}

 

/**

 * Implements hook_field_formatter_view().

 */

function field_collection_views_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {

  $element = array();

  $settings = $display['settings'];

 

  switch ($display['type']) {

    case 'field_collection_views_view':

      //debug($items);

      $args = '';

      $i = 1;

      foreach ($items as $delta => $item) {

        if ($i == 1) {

          $args .= $item['value'];

        }

        else {

          $args .= '+' . $item['value'];

        }

        $i++;

      }

      $view_name = isset($settings['name']) ? $settings['name'] : 'field_collection_view';

      $display_id = isset($settings['display_id']) ? $settings['display_id'] : 'default';

      $content = views_embed_view($view_name, $display_id, $args);

      $element[0] = array(

        '#markup' => $content,

      );

      if (empty($items)) {

        field_collection_field_formatter_links($element, $entity_type, $entity, $field, $instance, $langcode, $items, $display);

      }

      break;

  }

 

  return $element;

}

 

/**

 * Implements hook_field_formatter_settings_form().

 */

function field_collection_views_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {

  $display = $instance['display'][$view_mode];

  $settings = $display['settings'];

 

  $elements['name'] = array(

    '#type' => 'textfield',

    '#title' => t('Name'),

    '#default_value' => $settings['name'],

    '#description' => t('The machine name of the view to embed.'),

  );

  $elements['display_id'] = array(

    '#type' => 'textfield',

    '#title' => t('Display id'),

    '#default_value' => $settings['display_id'],

    '#description' => t('The display id to embed.'),

  );

  $elements['add'] = array(

    '#type' => 'textfield',

    '#title' => t('Add link title'),

    '#default_value' => $settings['add'],

    '#description' => t('Leave the title empty, to hide the link.'),

  );

 

  return $elements;

}

 

/**

 * Implements hook_field_formatter_settings_summary().

 */

function field_collection_views_field_formatter_settings_summary($field, $instance, $view_mode) {

  $display = $instance['display'][$view_mode];

  $settings = $display['settings'];

 

  $links = array_filter(array_intersect_key($settings, array_flip(array('name', 'display_id'))));

  if ($links) {

    return '<em>Embed View:</em> ' . check_plain(implode(', ', $links));

  }

  else {

    return t('Not showing any view.');

  }

 

}

 

/**

 * Implements hook_views_api().

 */

function field_collection_views_views_api() {

  return array(

    'api' => '3.0-alpha1',

    'path' => drupal_get_path('module', 'field_collection_views') . '/views',

  );

}

    我们在Think in Drupal的第1集里面,讲字段API的时候,讲过,如何为已有字段定制格式器,我们这里做的就是这个工作,hook_field_formatter_infohook_field_formatter_viewhook_field_formatter_settings_formhook_field_formatter_settings_summary,这四个钩子函数,是定制格式器时,需要实现的四个钩子函数。

field_collection_views_field_formatter_view里面,注意粗体字部分,这里面的逻辑是,我们获取所有的field collection itemids,然后把它们作为参数传递给对应的视图(view)。views_embed_view,这个函数,我们已经非常熟悉了,注意这里的参数传递方式,我们这里使用了“+”号,来传递多个参数。这是实际当中,经常用的一个技巧。

最后,我们实现了hook_views_api,这里注意的是'api',我们这里用的是'3.0-alpha1',是Views的最初版本,如果把它改为'3.0',也是可以的。


Drupal版本:

14 实现hook_views_data

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

field_collection_views.views.inc,我们实现了hook_views_data这个钩子,为['field_collection_item']追加了几个新的字段,追加这些字段的目的是,为了在Views里面拼凑出来用来编辑、删除、添加的链接。

<?php

 

/**

 * @file

 * Provide extras views data for field_collection.module.

 */

 

/**

 * Implements hook_views_data()

 */

 

function field_collection_views_views_data() {

 

  // hostEntityId

  $data['field_collection_item']['host_entity_id'] = array(

    'title' => t('Host Entity ID'),

    'help' => t('The ID of the Host Entity.'),

    'field' => array(

      'handler' => 'field_collection_views_handler_field_host_entity_id',

    ),

  );

  $data['field_collection_item']['host_entity_path'] = array(

    'title' => t('Host Entity Path'),

    'help' => t('The Path of the Host Entity.'),

    'field' => array(

      'handler' => 'field_collection_views_handler_field_host_entity_path',

    ),

  );

  $data['field_collection_item']['host_entity_type'] = array(

    'title' => t('Host Entity Type'),

    'help' => t('The Type of the Host Entity.'),

    'field' => array(

      'handler' => 'field_collection_views_handler_field_host_entity_type',

    ),

  );

  $data['field_collection_item']['field_path'] = array(

    'title' => t('Field path'),

    'help' => t('The base path of the field-collection field.'),

    'field' => array(

      'handler' => 'field_collection_views_handler_field_field_path',

    ),

  );

  return $data;

}

这里,有一些可以改进的地方,比如这里,我们可以使用hook_views_data_alter,而不是使用hook_views_data。为什么呢?因为我们这里没有定义自己的表,只是为已有的表添加字段而已。

另外的一个改进,可能就是,直接使用:

$data['field_collection_item']['edit_field_collection_item ']

$data['field_collection_item']['delete_field_collection_item ']

而不是去拼凑我们的字段了。


Drupal版本:

15 实现hook_views_default_views

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

在field_collection_views.views_default.inc文件中,我们实现了hook_views_default_views,这里放置我们导出的代码。我把中间的代码,省略掉了:

/**

 * Implementation of hook_views_default_views().

 */

function field_collection_views_views_default_views() {

  $view = new view;

  $view->name = 'field_collection_view';

  $view->description = '';

  $view->tag = 'default';

  $view->base_table = 'field_collection_item';

  $view->human_name = 'field collection view';

  $view->core = 7;

  $view->api_version = '3.0';

  $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */

  

.....

 

  $views[$view->name] = $view;

  return $views;

}



Drupal版本:

16 通过继承views_handler_field,定制自己的视图字段处理器

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们这里给出一个最简单的例子,field_collection_views_handler_field_host_entity_id,这个和我们在前面实现的,简易程度差不多,都非常简单:

<?php

/**

 * @file

 * Contains the host_entity_id field handler.

 */

 

/**

 * Field handler to display the host_entity_id

 */

class field_collection_views_handler_field_host_entity_id extends views_handler_field {

  function query() {

    // Do nothing, as this handler does not need to do anything to the query itself.

  } 

 

  function option_definition() {

    $options = parent::option_definition();

    return $options;

  }

 

  function options_form(&$form, &$form_state) {

    parent::options_form($form, $form_state);

  }

 

  /**

   * Work out the host_entity_id

   */

  function render($values) {

    $host_entity_id = 0;

    //$item_id =  $this->get_value($values, 'item_id');

    $item_id =  $values->item_id;

    //debug($values);

    $field_collection_item = field_collection_item_load($item_id);

    $host_entity_id = $field_collection_item->hostEntityId();

 

    return $host_entity_id;

  }

}

由于field_collection_views_handler_field_host_entity_id是一个类,所以我们需要在info文件中,注册一下这个文件,使用下面的代码:

files[] = views/field_collection_views.views.inc

files[] = views/field_collection_views_handler_field_host_entity_id.inc

files[] = views/field_collection_views_handler_field_host_entity_path.inc

files[] = views/field_collection_views_handler_field_field_path.inc

files[] = views/field_collection_views_handler_field_host_entity_type.inc



Drupal版本:

17 总结

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

    在实际项目中,大部分都已经有了Views的集成,我们需要做的,也就是用的到的,可能就是这里所讲的这些,导出views,在已有的基础上添加一个字段什么的。以后,有机会,我们介绍更多的Views的集成。



Drupal版本:

2 配置property validation验证规则

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

集成property validation,和集成Field validation的过程其实是一样的。首先是我们在后台配置验证规则,然后将其导出成代码,这和后面所讲的将Views的代码导出,是一样的。

启用Property validation模块以后,导航到admin/structure/property_validation

图片1.png 

这是CtoolsExport UI提供的一个默认界面,Property validation模块自带了一个验证规则title_min_words。我们这里添加两个验证规则,具体配置如下:

唯一性验证规则:

Name(机读)

breadcrumb_path_unique

Rule Name(用户可读)

Breadcrumb path unique

Entity type

Breadcrumb

Bundle name

Breadcrumb

Property name

path

Validator 

Unique values

Scope of unique

Entity

Custom error message

Breadcrumb path should be unique.

URL有效性验证规则:

Name(机读)

breadcrumb_path_valid_path

Rule Name(用户可读)

Breadcrumb path valid path

Entity type

Breadcrumb

Bundle name

Breadcrumb

Property name

path

Validator 

URL

Internal path

选中

Custom error message

Breadcrumb path should be a valid path.

现在,我们通过breadcrumb/add,添加面包屑,发现验证规则不起作用,比如第一次将path输入为“node/1”,第二次还是输入“node/1”,唯一性验证不起作用。此外,输入一个不存在的路径,比如“admin1”,还是可以直接保存。但是编辑的时候就会报错。经检查,并不是,property validation模块的问题,而是我们自己这里代码写的有问题。我们对breadcrumb2.admin.inc文件里面的breadcrumb2_form_validate函数,做以下修改:

 

function breadcrumb2_form_validate($form, &$form_state) {

  $breadcrumb = $form_state['breadcrumb'];

  $breadcrumb->path = $form_state['values']['path'];

  

  // Notify field widgets to validate their data.

  field_attach_form_validate('breadcrumb2', $breadcrumb, $form, $form_state);

}

粗体字部分,为我们新增的代码。我们把$breadcrumb对象存储在了$form_state['breadcrumb']里面,但是这里的值,都是旧的,我们这里验证的时候,需要将属性的值重新设置一下。field_attach_form_validate能够自动的提取当前字段的值,对于属性的值,我们需要单独的设置。这就是粗体字代码的含义。

修正这个错误以后,重新测试,现在这两个验证规则,都起作用了。唯一不足的时候,URL支持的内部路径,没有将别名排除出去,不过这个影响不大。


Drupal版本:

3 导出property validation验证规则

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们回到admin/structure/property_validation,对于每个验证规则,它右边都有一组操作链接,默认为编辑,我们展开所有的操作链接,点击导出(Export)链接。下面是唯一性验证导出后的代码:

$rule = new stdClass();

$rule->disabled = FALSE; /* Edit this to true to make a default rule disabled initially */

$rule->api_version = 2;

$rule->rulename = 'Breadcrumb path unique';

$rule->name = 'breadcrumb_path_unique';

$rule->property_name = 'path';

$rule->entity_type = 'breadcrumb2';

$rule->bundle = 'breadcrumb2';

$rule->validator = 'property_validation_unique_validator';

$rule->settings = array(

  'data' => 'entity',

);

$rule->error_message = 'Breadcrumb path should be unique.';

    我们这里的导出,是基于CroolsExport插件功能的。可能读到这里的时候,很多人会问,Crools是怎么智能的导出代码呢?有兴趣的可以阅读以下ctools\includes\export.inc里面的ctools_export_object函数,这个函数负责导出对象。我开始的时候,对这个导出机制也理解的不是很透彻,后来阅读了这个函数的源代码,才知道有些东西是怎么来的,在哪里定义的。

接着,我们在module文件里面,追加以下代码:

/**

 * Implementation of hook_ctools_plugin_api().

 *

 * Tell Ctools that we support the default_property_validation_rules API.

 */

function breadcrumb2_ctools_plugin_api($owner, $api) {

  if ($owner == 'property_validation' && $api == 'default_property_validation_rules') {

    return array('version' => 2);

  }

}

这里我们实现了hook_ctools_plugin_api,通过这个钩子函数,我们告诉Ctools,我们实现了默认的property_validation验证规则,如果你熟悉了钩子函数以后,你发现所有的钩子函数都是很类似的,这里也一样。钩子函数里面的逻辑通常都比较简单。

接下来,我们新建一个breadcrumb2.default_property_validation_rules.inc文件,在里面添加以下代码:

<?php

 

/**

 * @file

 * Provides default property validation rules for breadcrumb path.

 */

 

/**

 * Implementation of hook_default_property_validation_rule().

 * 

 * Provide default validation rules.

 */

function breadcrumb2_default_property_validation_rule() {

  $export = array();

 

  $rule = new stdClass();

  $rule->disabled = FALSE; /* Edit this to true to make a default rule disabled initially */

  $rule->api_version = 2;

  $rule->rulename = 'Breadcrumb path unique';

  $rule->name = 'breadcrumb_path_unique';

  $rule->property_name = 'path';

  $rule->entity_type = 'breadcrumb2';

  $rule->bundle = 'breadcrumb2';

  $rule->validator = 'property_validation_unique_validator';

  $rule->settings = array(

    'data' => 'entity',

  );

  $rule->error_message = 'Breadcrumb path should be unique.';

  $export['breadcrumb_path_unique'] = $rule;

  

  $rule = new stdClass();

  $rule->disabled = FALSE; /* Edit this to true to make a default rule disabled initially */

  $rule->api_version = 2;

  $rule->rulename = 'Breadcrumb path valid path';

  $rule->name = 'breadcrumb_path_valid_path';

  $rule->property_name = 'path';

  $rule->entity_type = 'breadcrumb2';

  $rule->bundle = 'breadcrumb2';

  $rule->validator = 'property_validation_url_validator';

  $rule->settings = array(

    'external' => 0,

    'internal' => 1,

  );

  $rule->error_message = 'Breadcrumb path should be a valid path.';

  $export['breadcrumb_path_valid_path'] = $rule;

  

  return $export;

}

通过钩子hook_default_property_validation_rule可以定义验证规则。这个钩子函数里面,代码都是我们使用Ctools导出来的,将导出的代码,复制过来的时候,我们需要稍微的调整一下代码格式。

现在我们清除缓存,重新回到admin/structure/property_validation,此时右边的操作链接里面,删除delete)链接没有了,换成了恢复(revert):

图片3.png 

     而在存储(Storage)里面,也显示的是“Overridden”了。如果我们恢复(Revert)一下,“Overridden”就变成“Default”了。这里的配置和Views里面是一致的。

Field validation模块,正在日趋流行,但是安装量还没有达到34万的规模,之所以集成它,主要为了大家展示一下Ctools导出插件的具体应用,另外老葛也希望,Breadcrumb2这个模块可以为Field validation模块带来更多的安装量。我们下面来看看Views的集成,这是项目中经常用的。


Drupal版本:

4 Views的集成

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们的面包屑模块,是基于Entity API的,Entity API提供了基本的Views集成,我们这里要做的是,在它的基础之上再添加一些集成,从而充分满足我们的需要。有兴趣的可以阅读一下entity\views下面的源代码。需要说明一下的是,这里的代码非常抽象,我看了以后,也只是大致了解了一下代码的结构。不过这并不影响,我们接下来的工作。


Drupal版本:

5 Entity API默认的Views集成

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们回到admin/structure/breadcrumbs,现在这个页面还是一个空白页面,我们这里想要显示的内容是,面包屑列表,并提供按照路径的查询功能。同时可以编辑面包屑,删除面包屑,还可以直接添加面包屑。

我们首先创建一个Views,导航到admin/structure/views,点击添加视图(Add new view),做以下配置:

图片4.png 

“Show”的下拉选择框里面,已经包含了“Breadcrumbs”了,这是Entity API模块提供的集成。

点击继续并编辑按钮,这里主要添加了三个字段,“Breadcrumb: path (path) ”“Breadcrumb: Breadcrumb Link (Breadcrumb Link)”、 “Global: Custom text (Edit)”;对于“Breadcrumb: Breadcrumb Link (Breadcrumb Link)”字段,由于它是多值的,默认的分隔符为“, ”,我们将它修改为了“ » ”,和Drupal核心保持一致;对于“Global: Custom text (Edit)”,我们覆写了它的输出,将它输出成链接的形式,指定的路径为“breadcrumb/[bid]/edit”;之后将Breadcrumb: Breadcrumb ID (Breadcrumb ID)字段排除显示;将格式“Format”设置为表格的形式;添加过滤器“Breadcrumb: path (exposed)”,并将其暴露出来;添加排序标准“Breadcrumb: Breadcrumb ID (desc)”,按照降序排列,这样新增的面包屑放在前面。这是配置好的样子:

 

图片5.png

Drupal版本:

6 使用views_embed_view嵌入视图

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

接下来,修改breadcrumb2.admin.inc文件里面的breadcrumb2_overview_breadcrumbs函数,这是修改后的样子:

/**

 * Displays the breadcrumb admin overview page.

 */

function breadcrumb2_overview_breadcrumbs(){

  $build['#markup'] = views_embed_view('breadcrumbs','default');

  return $build;

}

 

现在我们回到admin/structure/breadcrumbs,看一下效果,我这里预先定义了很多面包屑,这里给出一个简单的截图:

图片6.png 

下面的分页功能,是正常的,如果对这个页面,做比较多的测试的话,会发现这里面存在一个问题。在上面的搜索框里面,输出“node/1”,点击应用按钮。此时页面直接跳转到了http://localhost/breadcrumb2/?path=node%2F1&=Apply。这个时候,我们会发现views_embed_view这个函数的局限性了。它无法将当前页面的路径传递给暴露出来的表单。为了解决这个问题,我们将代码修改为:

/**

 * Displays the breadcrumb admin overview page.

 */

function breadcrumb2_overview_breadcrumbs(){

  //$build['#markup'] = views_embed_view('breadcrumbs','default');

  $view = views_get_view('breadcrumbs', 'default');

  $view->override_url = $_GET['q'];

  return $view->preview();

  //return $build;

}

这里面,我们首先使用了views_get_view,获取视图,接着使用$view->override_url覆写URL,这里使用的是当前路径,这样就可以将当前路径传递给暴露出来的表单了。最后使用$view->preview()获取内容。通过这种方式,就解决了前面所说的问题。不过还是有一个很小的问题,但是不影响过程,就是搜索的时候,Overlay不起作用了。


Drupal版本:

7 在管理界面添加一个动作链接

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

访问admin/content页面,在最上面,有一个“Add content”链接,我们也想添加一个这样的链接,方法有很多,第一个方法,就是把链接放到breadcrumb2_overview_breadcrumbs函数里面,通过这个函数,加进来。第二种方法,就是通过node里面提供的方法添加。打开node.module文件,它里面有这样的钩子实现:

/**

 * Implements hook_menu_local_tasks_alter().

 */

function node_menu_local_tasks_alter(&$data, $router_item, $root_path) {

  // Add action link to 'node/add' on 'admin/content' page.

  if ($root_path == 'admin/content') {

    $item = menu_get_item('node/add');

    if ($item['access']) {

      $data['actions']['output'][] = array(

        '#theme' => 'menu_local_action',

        '#link' => $item,

      );

    }

  }

}

这就是节点模块里面的实现方法,通过hook_menu_local_tasks_alter这个钩子实现。我开始也不知道这个钩子的含义,是我想到的了这个功能,然后在node.module文件里面逐个函数的查找、浏览,最终才发现的。

我们把它改造一下,改造成我们想要的,向breadcrumb2.module文件中,添加以下代码:

/**

 * Implements hook_menu_local_tasks_alter().

 */

function breadcrumb2_menu_local_tasks_alter(&$data, $router_item, $root_path) {

  // Add action link to 'breadcrumb/add' on 'admin/structure/breadcrumbs' page.

  if ($root_path == 'admin/structure/breadcrumbs') {

    $item = menu_get_item('breadcrumb/add');

    if ($item['access']) {

      $data['actions']['output'][] = array(

        '#theme' => 'menu_local_action',

        '#link' => $item,

      );

    }

  }

}

清除缓存,就可以看到“Add breadcrumb”链接了:

图片1.png 


Drupal版本:

8 在OverLay中添加面包屑

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

说到节点模块,我们还会主要到一个功能,当我们点击node/add这个链接时,它会自动的在覆盖层(Overlay)中打开,我们也希望,当用户点击“Add breadcrumb”,在弹出的覆盖层里面,打开我们的添加表单,而不是直接访问breadcrumb/add页面。

这个功能,节点模块里面就有,通过查找node.module文件里面的函数,我注意到了一个钩子函数的实现:

/**

 * Implements hook_admin_paths().

 */

function node_admin_paths() {

  if (variable_get('node_admin_theme')) {

    $paths = array(

      'node/*/edit' => TRUE,

      'node/*/delete' => TRUE,

      'node/*/revisions' => TRUE,

      'node/*/revisions/*/revert' => TRUE,

      'node/*/revisions/*/delete' => TRUE,

      'node/add' => TRUE,

      'node/add/*' => TRUE,

    );

    return $paths;

  }

}

因为我们知道,后台的管理界面,通常都是通过Overlay打开的。钩子函数hook_admin_paths,能够将一些路径设置为管理路径,尽管这些路径没有以admin打头。同样,依葫芦画瓢,改造一下,向我们的module文件追加以下代码:

 

/**

 * Implements hook_admin_paths().

 */

function breadcrumb2_admin_paths() {

  $paths = array(

    'breadcrumb/*' => TRUE,

    'breadcrumb/*/edit' => TRUE,

    'breadcrumb/*/delete' => TRUE,

    'breadcrumb/add' => TRUE,

  );

  return $paths;

}

这里,我们将面包屑相关的操作链接,定义成为了管理路径,清除缓存,重新点击“Add breadcrumb”,在弹出框中显示出来了添加表单。

以前是这个样子的:

 

现在在Overlay中打开了:

图片2.png 

这是一个进步。


Drupal版本:

9 为Views提供更多的可用字段

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们在前面的Views的配置里面,已经看到,现有的功能已经基本能够满足我们的需求了。有时候,我们还需要为Views提供更多地字段,这里的更多,指的是超出了Entity API默认提供的那一部分。比如面包屑的编辑、删除链接,我们想直接提供出来,而不是通过字段覆写的方式实现。

breadcrumb2.module文件追加以下代码:

/**

 * Implements hook_views_api().

 */

function breadcrumb2_views_api() {

  return array(

    'api' => 3,

    'path' => drupal_get_path('module', 'breadcrumb2') . '/views',

  );

}

我们这里实现了钩子hook_views_api,并将我们Views集成的文件,统一放在了breadcrumb2\views目录下面,我们现在就创建这样的一个子目录文件夹。接下来,我们在这个子文件夹下面创建一个文件breadcrumb2.views.inc,并向里面添加以下代码:

<?php

 

/**

 * @file

 * Providing extra integration with views.

 */

 

 

/**

 * Implements hook_views_data_alter ()

 */

function breadcrumb2_views_data_alter(&$data) { 

  $data['breadcrumb']['link_breadcrumb'] = array(

    'field' => array(

      'title' => t('Link'),

      'help' => t('Provide a link to the breadcrumb.'),

      'handler' => 'breadcrumb2_handler_link_field',

    ),

  );

  $data['breadcrumb']['edit_breadcrumb'] = array(

    'field' => array(

      'title' => t('Edit Link'),

      'help' => t('Provide a link to the edit form for the breadcrumb.'),

      'handler' => 'breadcrumb2_handler_edit_link_field',

    ),

  );

  $data['breadcrumb']['delete_breadcrumb'] = array(

    'field' => array(

      'title' => t('Delete Link'),

      'help' => t('Provide a link to delete the breadcrumb.'),

      'handler' => 'breadcrumb2_handler_delete_link_field',

    ),

  );

  // This content of this field are decided based on the menu structure that

  // follows breadcrumb/%breadcrumb2/op

  $data['breadcrumb']['operations'] = array(

    'field' => array(

      'title' => t('Operations links'),

      'help' => t('Display all operations available for this breadcrumb.'),

      'handler' => 'breadcrumb2_handler_breadcrumb_operations_field',

    ),

  );

}

我们通过实现hook_views_data_alter (),向breadcrumb Views中,添加了4个字段,分别为link_breadcrumbedit_breadcrumbdelete_breadcrumboperations。用来方便的编辑、删除、查看、管理面包屑。

这里的$data就是一个大的数组,$data['breadcrumb']是基表,$data['breadcrumb']['edit_breadcrumb']是下面的字段,里面的titlehelp键就不用介绍了,handler用来指定哪个类负责处理这个字段。

对于breadcrumb2_handler_link_field ,我们需要在breadcrumb2\views下面创建一个名为breadcrumb2_handler_link_field.inc的文件,文件创建好以后,里面添加以下代码:

<?php

 

/**

 * @file

 * Contains a Views field handler to take care of displaying links to entities

 * as fields.

 */

 

class breadcrumb2_handler_link_field extends views_handler_field {

  function construct() {

    parent::construct();

 

    $this->additional_fields['bid'] = 'bid';

  }

 

  function option_definition() {

    $options = parent::option_definition();

 

    $options['text'] = array('default' => '', 'translatable' => TRUE);

 

    return $options;

  }

 

  function options_form(&$form, &$form_state) {

    parent::options_form($form, $form_state);

 

    $form['text'] = array(

      '#type' => 'textfield',

      '#title' => t('Text to display'),

      '#default_value' => $this->options['text'],

    );

  }

 

  function query() {

    $this->ensure_my_table();

    $this->add_additional_fields();

  }

 

  function render($values) {

    $text = !empty($this->options['text']) ? $this->options['text'] : t('view');

    $bid = $values->{$this->aliases['bid']};

 

    return l($text, 'breadcrumb/' . $bid);

  }

}

在这个文件里面,我们定义了一个类breadcrumb2_handler_link_field,它继承了类views_handler_field。在定义里面,它实现了构造函数construct,选项定义option_definition,选项表单options_form,查询query(),呈现render($values),四个成员函数。options_form就是在Views里面添加字段时,弹出来的配置表单对话框;render($values)是负责呈现这个字段的;query()里面,可以加点查询什么的,我们这里比较简单。

接下来在breadcrumb2\views下面创建一个名为breadcrumb2_handler_edit_link_field.inc的文件,文件创建好以后,里面添加以下代码:

<?php

 

/**

 * @file

 * Contains a Views field handler to take care of displaying edit links

 * as fields

 */

 

 

class breadcrumb2_handler_edit_link_field extends breadcrumb2_handler_link_field {

  function construct() {

    parent::construct();

  }

 

 

  function render($values) {

    // Check access.

    if (!user_access('administer breadcrumbs')) {

      return;

    }

    

    $text = !empty($this->options['text']) ? $this->options['text'] : t('edit');

    $bid = $values->{$this->aliases['bid']};

    

    return l($text, 'breadcrumb/' . $bid . '/edit');

  }

}

breadcrumb2_handler_edit_link_field的定义就比较简单,这里归功于面向对象的继承,在呈现函数里面我们构建了一个编辑链接。

然后再创建breadcrumb2_handler_delete_link_field.inc文件,里面的代码如下:

<?php

 

/**

 * @file

 * Contains a Views field handler to take care of displaying deletes links

 * as fields

 */

 

 

class breadcrumb2_handler_delete_link_field extends breadcrumb2_handler_link_field {

  function construct() {

    parent::construct();

  }

 

 

  function render($values) {

    // Check access.

    if (!user_access('administer breadcrumbs')) {

      return;

    }

    

    $text = !empty($this->options['text']) ? $this->options['text'] : t('delete');

    $bid = $values->{$this->aliases['bid']};

    

    return l($text, 'breadcrumb/' . $bid . '/delete');

  }

}

最后创建breadcrumb2_handler_breadcrumb_operations_field.inc文件,并添加以下代码:

<?php

 

/**

 * This field handler aggregates operations that can be done on a breadcrumb

 * under a single field providing a more flexible way to present them in a view

 */

class breadcrumb2_handler_breadcrumb_operations_field extends views_handler_field {

  function construct() {

    parent::construct();

 

    $this->additional_fields['bid'] = 'bid';

  }

 

  function query() {

    $this->ensure_my_table();

    $this->add_additional_fields();

  }

 

  function render($values) {

 

    $links = menu_contextual_links('breadcrumb2', 'breadcrumb', array($this->get_value($values, 'bid')));

    

    if (!empty($links)) {

      return theme('links', array('links' => $links, 'attributes' => array('class' => array('links', 'inline', 'operations'))));

    }

  }

}

breadcrumb2_handler_breadcrumb_operations_field里面,我们使用了menu_contextual_links来返回对应的链接数组,并使用theme('links'来呈现这些链接。

如果对这里的代码,不理解的话,可以参看model里面的定义,和那里的定义是一样的。我只是修改了一下。有关Views集成的更多代码,可以直接参看ViewsDrupal核心模块的支持,位于目录views\modules目录下面。在Think in Drupal的第三集里面,我曾经向Ubercart提交了一个补丁,这个补丁就是有关Views集成的。我补丁里面的代码如下,粗体部分就是我添加的:

/**

 * Implements hook_views_data().

 */

function uc_order_views_data() {

   ...

  // Ordered products.

  $data['uc_order_products']['table']['group'] = t('Ordered product');

  $data['uc_order_products']['table']['base'] = array(

    'field' => 'order_product_id',

    'title' => t('Ordered products'),

    'help' => t('Products that have been ordered in your Ubercart store.'),

  );

  

  // Expose nodes to ordered products as a relationship.

  $data['uc_order_products']['nid'] = array(

    'title' => t('Nid'),

    'help' => t('The nid of the ordered product. If you need more fields than the nid: Node relationship'),

    'relationship' => array(

      'title' => t('Node'),

      'help' => t('Relate product to node.'),

      'handler' => 'views_handler_relationship',

      'base' => 'node',

      'field' => 'nid',

      'label' => t('node'),

    ),

    'filter' => array(

      'handler' => 'views_handler_filter_numeric',

    ),

    'argument' => array(

      'handler' => 'views_handler_argument_node_nid',

    ),

    'field' => array(

      'handler' => 'views_handler_field_node',

    ),

  );

 

  // Expose orders to ordered products as a relationship.

  $data['uc_order_products']['order_id'] = array(

    'title' => t('Order ID'),

    'help' => t('The order ID of the ordered product. If you need more fields than the order ID: Order relationship'),

    'relationship' => array(

      'title' => t('Order'),

      'help' => t('Relate product to order.'),

      'handler' => 'views_handler_relationship',

      'base' => 'uc_orders',

      'field' => 'order_id',

      'label' => t('order'),

    ),

    'filter' => array(

      'handler' => 'views_handler_filter_numeric',

    ),

    'argument' => array(

      'handler' => 'views_handler_argument_numeric',

    ),

    'field' => array(

      'handler' => 'uc_order_handler_field_order_id',

    ),

  );

 

  // Pull in node fields directly.

  $data['node']['table']['join']['uc_order_products'] = array(

    'left_field' => 'nid',

    'field' => 'nid',

  );

 

  // Pull in product fields directly.

  $data['uc_products']['table']['join']['uc_order_products'] = array(

    'left_field' => 'nid',

    'field' => 'nid',

  );

 

  ....

 

  return $data;

}

我们看到,将我们的数据库表,集成到Views里面并不复杂,只需要实现hook_views_data即可;如果要修改其它模块的hook_views_data,则可以使用hook_views_data_alter


Drupal版本: