第4章 集成上下文链接

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

其实我一直有一个这样的想法,当我决定为每个页面创建一个面包屑对象的时候,就有了这样的想法,在面包屑这片内容上面输出一个上下文链接,可以方便的添加、配置面包屑:

图片1.png 

 

更前卫一点的想法,是如何与Edit这个模块集成,就是让用户在这里能够在当前页面编辑面包屑。在当前页面编辑面包屑的这个想法,需要等待Spark这个安装包里面的技术成熟稳定以后,才会考虑实现。

我们来看看,上下文链接的形式。在breadcrumb2beta2版里面,已经实现了这个功能,有兴趣的可以直接下载对应的代码,我们这里重新按照我当时的实现步骤,为大家重现一下完整的过程。


Drupal版本:

1 接管theme_breadcrumb函数

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

我们是可以在主题层覆写面包屑的,但是有一个问题,我们这里定义的是自己的模块,我们想在自己的模块里面修改theme_breadcrumb函数。我们知道,我们是不能够直接修改Drupal核心代码的,所以将其接管过来是最佳的办法。这也是我们添加上下文链接的第一步。

我们首先需要知道,theme_breadcrumb是在什么地方定义的。说真的,真心受不了,Google不能用。搜索个theme_breadcrumb都出不来结果。好吧,我们直接登陆http://api.drupal.org/api,在里面搜索theme_breadcrumb。我们看到这个函数是在includes/theme.inc文件里面定义的。函数的代码也很简单:

 

function theme_breadcrumb($variables) {

  $breadcrumb = $variables['breadcrumb'];

 

  if (!empty($breadcrumb)) {

    // Provide a navigational heading to give context for breadcrumb links to

    // screen-reader users. Make the heading invisible with .element-invisible.

    $output = '<h2 class="element-invisible">' . t('You are here') . '</h2>';

 

    $output .= '<div class="breadcrumb">' . implode(' » ', $breadcrumb) . '</div>';

    return $output;

  }

}

很多资料上,讲主题函数的覆写的时候,通常都以这个作为例子。我记得以前的Drupal专业开发指南里面,都拿这个作为例子。

知道了这个函数的位置以后,我们还需要知道,哪个hook_theme钩子函数中,注册了这个主题函数。而theme_breadcrumb这个函数是在drupal_common_theme里面注册的,通过system_theme实现。对应代码如下:

   'breadcrumb' => array(

      'variables' => array('breadcrumb' => NULL),

    ),

想要修改主题的注册表信息,我首先想到的是hook_theme_alter,但是通过Google查找了一下,发现应该使用hook_theme_registry_alter。和我们平时的不一样。我们向breadcrumb2.module追加以下代码:

 

/**

 * Implements hook_theme_registry_alter().

 */

function breadcrumb2_theme_registry_alter(&$theme_registry) {

  if (isset($theme_registry['breadcrumb'])) {

    $path = drupal_get_path('module', 'breadcrumb2');

    $theme_registry['breadcrumb']['path'] = $path;

    $theme_registry['breadcrumb']['template'] = 'breadcrumb';

    $theme_registry['breadcrumb']['function'] = NULL;

  }

}

在这里,我们首先检查,有没有设置$theme_registry['breadcrumb'],如果已经设置了,我们对它进行修改,为它设置$theme_registry['breadcrumb']['template'],同时为它设置$theme_registry['breadcrumb']['path'],最后将$theme_registry['breadcrumb']['function']置为空。这里的'path''template''function'是三个键,如果设置了'template',就应该把'function'置为空,否则默认还是使用'function''function'表示这是一个主题函数,'template'表示这是一个模板文件。'path'用来设置模板所在的目录,我们把它修改为了'breadcrumb2'所在的目录了。

现在清除缓存,我们会得到这样的错误消息:

Warning: include(D:\xampp\htdocs\breadcrumb2/sites/all/modules/breadcrumb2/breadcrumb.tpl.php) [function.include]: failed to open stream: No such file or directory in theme_render_template() (line 1495 of D:\xampp\htdocs\breadcrumb2\includes\theme.inc). 

Warning: include() [function.include]: Failed opening 'D:\xampp\htdocs\breadcrumb2/sites/all/modules/breadcrumb2/breadcrumb.tpl.php' for inclusion (include_path='.;D:\xampp\php\PEAR') in theme_render_template() (line 1495 of D:\xampp\htdocs\breadcrumb2\includes\theme.inc).

意思是说,在breadcrumb2找不到breadcrumb.tpl.php,我们还没有创建嘛,这说明了,我们前面的代码起作用了,而且工作的很好。我们现在就创建breadcrumb.tpl.php文件,里面的代码如下:

<?php

 

/**

 * @file

 * Default theme implementation to display a breadcrumb.

 *

 * Available variables:

 * - $contextual_links: contextual links for breadcrumb.

 * - $breadcrumb: An array of breadcrumb link.

 * @see template_preprocess()

 * @see breadcrumb2_preprocess_breadcrumb()

 * @see template_process()

 *

 * @ingroup themeable

 */

?>

 123456

<?php if (!empty($breadcrumb)): ?>

<div class="breadcrumb-wrapper">

  <h2 class="element-invisible">   <?php print t('You are here'); ?>  </h2>

  <div class="breadcrumb"> <?php print implode(' » ', $breadcrumb); ?> </div>

</div>

<?php endif; ?>

中间的“123456”,是用来测试,是不是这个模板文件起作用,模板覆写的时候,我经常使用这个办法,在模板里面加点东西,用来判断它是否起作用。注释里面有个$contextual_links,这个我们在后面才会添加。

 

我们回到breadcrumb2_theme_registry_alter,在这里面,我们可以加上两行调试代码:

  print debug($theme_registry['breadcrumb']);

  print debug($theme_registry['page']);

   然后,再清除缓存,我们呢可以看到打印出来的消息:

array (

  'variables' => 

  array (

    'breadcrumb' => NULL,

  ),

  'type' => 'module',

  'theme path' => 'modules/system',

  'function' => 'theme_breadcrumb',

  'preprocess functions' => 

  array (

  ),

  'process functions' => 

  array (

  ),

)

array (

  'template' => 'page',

  'path' => 'themes/seven',

  'type' => 'theme_engine',

  'theme path' => 'themes/seven',

  'render element' => 'page',

  'preprocess functions' => 

  array (

    0 => 'template_preprocess',

    1 => 'template_preprocess_page',

    2 => 'contextual_preprocess',

    3 => 'overlay_preprocess_page',

    4 => 'shortcut_preprocess_page',

    5 => 'seven_preprocess_page',

  ),

  'process functions' => 

  array (

    0 => 'template_process',

    1 => 'template_process_page',

    2 => 'rdf_process',

  ),

)

通过将整个数组,打印出来,我们可以看到有多少个键可用。以及每个键的赋值情况。我前面的代码写好了以后,发现了一个问题,就是无法在主题层覆写breadcrumb.tpl.php文件。就是说,我们将breadcrumb.tpl.php复制到themes\bartik\templates下面,并稍微的调整一下里面的内容,比如把刚才的“123456”修改为“123”。清除缓存,发现起作用的还是我们模块里面的模板文件。这是在实际应用当中发现的一个问题。到现在还没有解决。本来打算,一边写作,一边写代码,后来灵感大爆发,直接把模块先写完了。我写到这的时候是20121224日,实际上,1211号就发布了beta4版了,很多功能都已经实现。

不过今天依然没有解决这个无法覆写的问题,不过我想,肯定会解决的。我通过Google搜索过,也有人遇到过同样的问题,但是没有解决。不过今天Google罢工了。我们继续前进,来看一下怎么添加上下文。


Drupal版本:

10 总结

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

    通过本章的学习,我们成功的实现了为breadcrumb.tpl.php文件添加上下文链接的功能,与此同时,我们还学习了contextual模块的机制,以及如何通过预处理函数为模板文件添加变量 。

 



Drupal版本:

2 添加上下文链接

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

我们先来分析一个问题,区块上面的上下文链接,怎么生成的。我们来看一下左边导航区块

图片1.png 

然后使用Firefox打开,使用firebug查看一下,上下文链接的源代码。

图片2.png

 

     在区块的标题和正文之间,插入了一个div

<div class="contextual-links-wrapper contextual-links-processed">

里面包含的就是我么看到的上下文链接。打开区块模块里面的block.tpl.php文件,我们发现标题和正文之间,使用的是这行代码:

<?php print render($title_suffix); ?>

我们把这行注释掉,上下文链接就没有了。我们在api.drupal.org上查找template_preprocess_block,这是区块的预处理函数,里面没有$title_suffix的相关定义。我们直接打开contextual.module文件,这个模块非常小,建议阅读一遍。在这里面,我们找到了contextual_preprocess,在这个预处理函数中,有$title_suffix的对应代码:

function contextual_preprocess(&$variables, $hook) {

  // Nothing to do here if the user is not permitted to access contextual links.

  if (!user_access('access contextual links')) {

    return;

  }

 

  $hooks = theme_get_registry(FALSE);

 

  // Determine the primary theme function argument.

  if (!empty($hooks[$hook]['variables'])) {

    $keys = array_keys($hooks[$hook]['variables']);

    $key = $keys[0];

  }

  elseif (!empty($hooks[$hook]['render element'])) {

    $key = $hooks[$hook]['render element'];

  }

  if (!empty($key) && isset($variables[$key])) {

    $element = $variables[$key];

  }

 

  if (isset($element) && is_array($element) && !empty($element['#contextual_links'])) {

    // Initialize the template variable as a renderable array.

    $variables['title_suffix']['contextual_links'] = array(

      '#type' => 'contextual_links',

      '#contextual_links' => $element['#contextual_links'],

      '#element' => $element,

    );

    // Mark this element as potentially having contextual links attached to it.

    $variables['classes_array'][] = 'contextual-links-region';

  }

}

而且,我们看到区块最外面的divclass里面的'contextual-links-region',就是这里设置的。此外,我们在这个模块里面,还会看到:

/**

 * Implements hook_library().

 */

function contextual_library() {

  $path = drupal_get_path('module', 'contextual');

  $libraries['contextual-links'] = array(

    'title' => 'Contextual links',

    'website' => 'http://drupal.org/node/473268',

    'version' => '1.0',

    'js' => array(

      $path . '/contextual.js' => array(),

    ),

    'css' => array(

      $path . '/contextual.css' => array(),

    ),

  );

  return $libraries;

}

 

/**

 * Implements hook_element_info().

 */

function contextual_element_info() {

  $types['contextual_links'] = array(

    '#pre_render' => array('contextual_pre_render_links'),

    '#theme' => 'links__contextual',

    '#links' => array(),

    '#prefix' => '<div class="contextual-links-wrapper">',

    '#suffix' => '</div>',

    '#attributes' => array(

      'class' => array('contextual-links'),

    ),

    '#attached' => array(

      'library' => array(

        array('contextual', 'contextual-links'),

      ),

    ),

  );

  return $types;

}

我们看到contextual_linksDrupal里面的一个元素,类似于表单元素一样,当呈现这个元素的时候,就会加载对应的JSCSS文件,分别为contextual.jscontextual.css。对应的代码为:

    '#attached' => array(

      'library' => array(

        array('contextual', 'contextual-links'),

      ),

    ),

 

由于我们熟悉表单元素,所以只要我们构建出来contextual_links元素的数组,就能将它呈现出来。这样我当时就决定,不通过title_suffix输出上下文链接了,直接通过输出contextual_links元素的形式,来实现我们的目标。我们直接在breadcrumb.tpl.php里面输入以下代码:

<?php 

   $contextual_links   =    array('#contextual_links' => array(

      //'breadcrumb2' => array('breadcrumb', array('1')),

        //'breadcrumb2' => array('breadcrumb', array('add')),

'menu' => array('admin/structure/menu/manage', array('navigation')),

      )); 

   $contextual_link = array(

      '#type' => 'contextual_links',

      '#contextual_links' => $contextual_links['#contextual_links'],

  '#element' => $contextual_links,

    );

  print drupal_render($contextual_link); 

 ?>

我们这里首先构建了$contextual_links,接着设置了一个类型为上下文链接的元素$contextual_link,最后使用drupal_render将它呈现了出来。这个时候,我们通过Firebug,其实就可以看到我们的输出了,但是显示不出来。我阅读了一下contextual.js的源代码,从中发现了原因:

(function ($) {

 

Drupal.contextualLinks = Drupal.contextualLinks || {};

 

/**

 * Attaches outline behavior for regions associated with contextual links.

 */

Drupal.behaviors.contextualLinks = {

  attach: function (context) {

    $('div.contextual-links-wrapper', context).once('contextual-links', function () {

      var $wrapper = $(this);

      var $region = $wrapper.closest('.contextual-links-region');

      var $links = $wrapper.find('ul.contextual-links');

      var $trigger = $('<a class="contextual-links-trigger" href="#" />').text(Drupal.t('Configure')).click(

        function () {

          $links.stop(true, true).slideToggle(100);

          $wrapper.toggleClass('contextual-links-active');

          return false;

        }

      );

      // Attach hover behavior to trigger and ul.contextual-links.

      $trigger.add($links).hover(

        function () { $region.addClass('contextual-links-region-active'); },

        function () { $region.removeClass('contextual-links-region-active'); }

      );

      // Hide the contextual links when user clicks a link or rolls out of the .contextual-links-region.

      $region.bind('mouseleave click', Drupal.contextualLinks.mouseleave);

      // Prepend the trigger.

      $wrapper.prepend($trigger);

    });

  }

};

 

/**

 * Disables outline for the region contextual links are associated with.

 */

Drupal.contextualLinks.mouseleave = function () {

  $(this)

    .find('.contextual-links-active').removeClass('contextual-links-active')

    .find('ul.contextual-links').hide();

};

 

})(jQuery);

我们看到,外面的'contextual-links-region',是需要的,我们没有加上这个class。我们将:

<div class="breadcrumb-wrapper">

修改为:

<div class="breadcrumb-wrapper contextual-links-region">

并将print drupal_render($contextual_link); 放到这个div的内部。这个时候,面包屑右边的上下文链接显示出来了。

图片3.png 

只不过显示的是别处的上下文链接。不过我们已经向前前进了一大步。接着可以尝试使用使用:'breadcrumb2' => array('breadcrumb', array('1')),来构建上下文链接,这次也出来了,只不过这些链接,是bid1的上下文链接。

可能有读者会问,我怎么知道的,这样构建上下文链接,这个我是参考block.module里面的代码:

$build[$key]['#contextual_links']['block'] = array('admin/structure/block/manage', array($block->module, $block->delta));

以及menu.module里面的代码:

$data['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($delta));

我们通过前台知道,就这两个模块有上下文链接。走到这里的时候,需要弄明白这个数组里面的两个参数的含义。这个时候,我还是阅读的源代码:

function contextual_pre_render_links($element) {

  // Retrieve contextual menu links.

  $items = array();

  foreach ($element['#contextual_links'] as $module => $args) {

    $items += menu_contextual_links($module, $args[0], $args[1]);

  }

 

  // Transform contextual links into parameters suitable for theme_link().

  $links = array();

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

    $class = drupal_html_class($class);

    $links[$class] = array(

      'title' => $item['title'],

      'href' => $item['href'],

    );

    // @todo theme_links() should *really* use the same parameters as l().

    $item['localized_options'] += array('query' => array());

    $item['localized_options']['query'] += drupal_get_destination();

    $links[$class] += $item['localized_options'];

  }

  $element['#links'] = $links;

 

  // Allow modules to alter the renderable contextual links element.

  drupal_alter('contextual_links_view', $element, $items);

 

  // If there are no links, tell drupal_render() to abort rendering.

  if (empty($element['#links'])) {

    $element['#printed'] = TRUE;

  }

 

  return $element;

}

通过上面的代码,我们可以看到,上下文链接,是通过函数menu_contextual_links生成的,通过查看这个函数的API文档,我们可以弄明白传递给它的三个参数的含义。也就对应于我们代码里面的:

      //'breadcrumb2' => array('breadcrumb', array('1')),

       //'breadcrumb2' => array('breadcrumb', array('add')),

  'menu' => array('admin/structure/menu/manage', array('navigation')),

然后,通过阅读contextual_pre_render_links,我还弄明白了一个问题,那就是上下文链接,本质上还是链接。这不是废话么。理解到这一点,是一个进步,当我们的面包屑对象不存在时,是无法构建出来上下文链接的。中间的这段代码:

'breadcrumb2' => array('breadcrumb', array('add')),

是不工作的。我们理解了,它本质上是一组链接,最后还是通过$element['#links']输出的,所以我们在构建不出来上下文链接的时候,可以直接构建出来链接,放进来。而这里面,还提供了hook_contextual_links_view_alter,通过实现这个钩子,就能够达到添加链接的目的了。


Drupal版本:

3实现预处理函数

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

我们前面的工作都是在模板里面直接进行的,我们把它转移到预处理函数里面。主题函数的预处理函数是有限制的,而我们这里的breadcrumb是一个模板文件了,所以可以为它在模块里面实现预处理函数。我们的实现如下。

/**

 * Implements MODULE_preprocess_HOOK().

 */

function breadcrumb2_preprocess_breadcrumb(&$variables) {

  if (user_access('administer breadcrumbs') && user_access('access contextual links')) {

    $current_path = current_path();

    $breadcrumb = breadcrumb2_load_by_path($current_path);

    if (!empty($breadcrumb)) {

      $contextual_links_element   =    array(

        '#contextual_links' => array(

          'breadcrumb2' => array('breadcrumb', array($breadcrumb->bid)),

        )

      ); 

}

else{

      $contextual_links_element   =    array(

        '#contextual_links' => array(

          'breadcrumb2' => array('breadcrumb', array('0')),

        )

      ); 

}

 

    $contextual_links = array(

      '#type' => 'contextual_links',

      '#contextual_links' => $contextual_links_element['#contextual_links'],

      '#element' => $contextual_links_element,

    );

    $variables['contextual_links'] = drupal_render($contextual_links);

  }

}

这是我最初的实现,这里对模板里面的代码,做了相应的调整。在预处理函数里面,我们添加了一个新的变量,这样在模板文件里面就可以直接输出这个变量的。逻辑代码通常放到预处理函数、处理函数里面,这样可以让模板文件更干净一些。如果是实际当中的项目,就另当别论了。我们这里最终要贡献到drupal.org的,所以应该把逻辑代码独立出来。

现在,我们需要把模板里面的输出,也做相应的调整,实际上这里,只需要把前面的:

<?php print drupal_render($contextual_link); ?>

修改为:

<?php print $contextual_links; ?>

即可,或者直接注释掉原来的,添加上这里的最新代码也可以。另外,模板里面的代码可以删除、或者注释掉了。我们这里是注释掉了。

预处理函数,也是钩子函数,所以实现了这个函数以后,特别是第一次实现一个钩子函数,我们都需要清除一下缓存。Drupal7能够缓存哪些模块实现了某个钩子。这与Drupal6相比,就是一个小小的进步。而我们需要做的就是,当新建一个钩子函数的时候,需要清除缓存,否则就会不工作。我们此时会看到这样的错误消息:

Notice: Undefined variable: contextual_links in include() (line 35 of D:\xampp\htdocs\breadcrumb2\sites\all\modules\breadcrumb2\breadcrumb.tpl.php).

查找钩子函数的实现,是一个耗费资源的操作,将其缓存起来,可以提升性能。有兴趣的读者,可以顺着module_invoke_all这个函数,逐层分析,肯定会找到对应的缓存代码的。我们这里清除缓存,这个错误消息就消失了。对于有面包屑对象的,上下文链接是这样的:

图片1.png 

如果这个页面没有对应的面包屑实体对象,则显示不出来这个上下文链接。


Drupal版本:

4 通过hook_contextual_links_view_alter修改上下文链接

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

我们前面讲过contextual_pre_render_links里面,提供了hook_contextual_links_view_alter这个钩子函数。在上下文,链接呈现前,通过这个钩子函数,可以修改上下文链接,也就是说,可以伪造模拟出来一个链接。可能很多人不明白,为什么。在前面,我们看到这样的代码:

$types['contextual_links'] = array(

    '#pre_render' => array('contextual_pre_render_links'),

….

这里为contextual_links元素,指定了一个预呈现回调函数contextual_pre_render_links,就是说,在呈现上下文链接的时候,会调用这个函数,而在contextual_pre_render_links里面,通过“drupal_alter('contextual_links_view', $element, $items);”,就为我们提供了一个钩子函数,来修改上下文链接。

好了,我们来看一下,具体的实现代码:

/**

 * Implements hook_contextual_links_view_alter().

 */

function  breadcrumb2_contextual_links_view_alter(&$element, &$items) {

  if (isset($element['#contextual_links']['breadcrumb2']) && empty($items)) {

    $current_path = current_path();

    $element['#links'] = array(

      array('title' => t('Add breadcrumb'), 'href' => 'breadcrumb/add', "query" => array('path' => $current_path, drupal_get_destination())),

    );

  }

}

    在这里,我们直接为$element['#links']设置了一个链接。清除缓存,现在,在没有面包屑实体对象的情况下,也有上下文链接了:

图片1.png 

我们在这里面,为链接提供了一个"query"参数,它是这样设置的; 

"query" => array('path' => $current_path, drupal_get_destination())

这里面,drupal_get_destination()返回的本身就是一个数组。除了它以外,我们还提供了'path',这里的目的是,通过URL传递参数。这样,用户在添加面包屑对象的时候,就不需要输入刚才的路径了。修改文件里面的:

function breadcrumb2_add(){

  $breadcrumb = entity_get_controller('breadcrumb2')->create();

  //drupal_set_title(t('Create breadcrumb'));

  if (isset($_GET['path'])) {

    $breadcrumb->path = $_GET['path'];

  }

  $output = drupal_get_form('breadcrumb2_form', $breadcrumb);

 

  return $output;

}

粗体为我们新增的代码。这里我们直接尝试从URL获取path参数,并将它赋值给$breadcrumb->path。这样用户就不用输入路径了。

当然,还有一点不足,那就是用户对于这个路径还是可以编辑的,实际上,我们应该不允许用户编辑这个路径。当路径已经存在的情况下,就不允许编辑了。因为路径是唯一的。

function breadcrumb2_form($form, &$form_state, $breadcrumb) {

……

  $form['path'] = array(

    '#type' => 'textfield',

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

    '#maxlength' => 60,

    '#default_value' => !empty($breadcrumb->path) ? $breadcrumb->path : '',

    '#weight' => -10,

  );

  if (!empty($breadcrumb->path)) {

    $form['path']['#disabled'] = TRUE;

  }

  ……

breadcrumb2_form中追加上面的粗体代码即可。这是现在的效果图:

图片2.png 

    还不错吧。还有值得改进的地方,实际上Drupal本身的面包屑,总是存在的。所以我们这里的上下文链接,不应该叫做添加面包屑。此外,删除面包屑,对我们来说也没有什么用,所以删除链接,也可以不要。最终,我们将编辑、添加面包屑,统一成为配置面包屑。下面是修改后的代码。

/**

 * Implements MODULE_preprocess_HOOK().

 */

function breadcrumb2_preprocess_breadcrumb(&$variables) {

  if (user_access('administer breadcrumbs') && user_access('access contextual links')) {

    $contextual_links_element   =    array(

      '#contextual_links' => array(

        'breadcrumb2' => array('breadcrumb', array('0')),

      )

    ); 

 

    $contextual_links = array(

      '#type' => 'contextual_links',

      '#contextual_links' => $contextual_links_element['#contextual_links'],

      '#element' => $contextual_links_element,

    );

    $variables['contextual_links'] = drupal_render($contextual_links);

  }

}

 

 

/**

 * Implements hook_contextual_links_view_alter().

 */

function  breadcrumb2_contextual_links_view_alter(&$element, &$items) {

  if (isset($element['#contextual_links']['breadcrumb2']) && empty($items)) {

    $current_path = current_path();

    $breadcrumb = breadcrumb2_load_by_path($current_path);

    if (!empty($breadcrumb)) {

      $contextual_links_element['#contextual_links']['breadcrumb2'] = array('breadcrumb', array($breadcrumb->bid));

      $element['#links'] = array(

        array('title' => t('Config breadcrumb'), 'href' => 'breadcrumb/' . $breadcrumb->bid . '/edit', "query" => drupal_get_destination()),

      );

    }

    else {

      $element['#links'] = array(

        array('title' => t('Config breadcrumb'), 'href' => 'breadcrumb/add', "query" => array('path' => $current_path, drupal_get_destination())),

      );

    }

  }

}

我们这里只是构建了一个不存在的上下文链接,相当于占位符吧,然后在hook_contextual_links_view_alter里面,模拟出来上下文链接。

还有一个很小的问题,在IE8里面,鼠标常常放不到这个链接上,快移上去时,链接就会消失。后来我想到了一个办法,外面又加了层div,并为div设置了高度。这是修改后的代码:

<?php if (!empty($breadcrumb)): ?>

<div class="breadcrumb-wrapper contextual-links-region">

  <h2 class="element-invisible">   <?php print t('You are here'); ?>  </h2>

   <?php if (!empty($breadcrumb)): ?> 

    <div style='height:10px'> <?php print $contextual_links; ?> </div>

  <?php endif; ?>

  <?php //print drupal_render($contextual_link); ?>

  <div class="breadcrumb"> <?php print implode(' » ', $breadcrumb); ?> </div>

</div>

<?php endif; ?>

这里面可是有个bug哦,细心的朋友一眼可能就看出来了。到目前为止,我们已经完成了breadcrumb2模块项目的beta2版里面的所有代码了。同时还解决了Views的一个问题。我们这里也顺带的介绍一下,从beta2beta4里面所解决的问题。以及在实践当中遇到的其它问题。


Drupal版本:

5 Breadcrumb2已有问题的修正

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

没有哪个系统是完美的,也没有哪个Drupal模块不存在问题,可是我的心还是有点急,一口气从alpha1发布到beta2,希望别人能够尽快的看到效果。可是后来发现,下载了99次,只安装了一个站点,而这个站点还是我自己的。后来,让身边的朋友反馈意见,发现很多问题。如果你是跟着做过来的,可以尝试一下自己来解决一下这些问题。看看自己能否独立的解决这些问题。这些问题包括:   

模块安装不起来,http://drupal.org/node/1863470

pathauto模块冲突http://drupal.org/node/1863434

匿名用户,看到未定义的变量contextual_linkshttp://drupal.org/node/1863864

panels窗格里面输出面包屑,输出不出来。


Drupal版本:

6 模块安装不了

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

我们现在的这个模块,依赖于ViewsRulesLinksField validation,模块依赖的太多,安装的时候就会存在问题。模块直接安装不起来。我信心满满的,发布了beta2,别人却装不上。

我在本地,一个新建的站点,把所有的模块都下载好,一起安装,报了这样的错误消息:

FieldException: Attempt to create a field of unknown type link_field. in field_create_field() (line 110 of D:\xampp\htdocs\breadcrumb3\modules\field\field.crud.inc).

我把这个问题发到了官网上,http://drupal.org/node/1863470。我后来查了相关的资料,发现,这个和Drupal核心也有点关系,其实应该算是Drupal核心的一个bug。不过,解决办法还是有的:

function breadcrumb2_install() {

   // Add or remove the link field, as needed.

  $field = field_info_field('link');

  if (empty($field)) {

    $field = array(

      'cardinality' => '-1',

      'entity_types' => array('breadcrumb2'),

      'field_name' => 'link',

      'module' => 'link',

      'type' => 'link_field',

    );

    $field = field_create_field($field);

  }

 

  // If a field type we know should exist isn't found, clear the Field cache.

  if (!field_info_field_types('link_field')) {

    cache_clear_all('field_info_types:', 'cache_field', TRUE);

  }

粗体部分,为新增的,我们通过代码创建了link类型的字段以后,如果后面还要添加该类型的字段实例,此时就需要清除字段缓存。不然就会找不到对应的类型。而我们在安装函数的下面,就有field_create_instance($instance)这样的代码。

这段代码是从Commerce模块里面复制过来的,在此做出说明。我调试这个问题的时候,还发现了另外的一个问题,就是我把breadcrumb2模块卸载了,还是无法禁用link模块,这个也是因为缓存的原因。卸载了breadcrumb2模块以后,手动的清除缓存,就可以禁用、卸载link模块了。当然,我们也可以在代码里面清除缓存。这个也被列为了Drupal核心的bug

 


Drupal版本:

7 与Pathauto的冲突问题

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

问题地址,http://drupal.org/node/1863434。当我在一个实际的生产站点,添加面包屑的时候,报错了,错误信息如下:

Notice: Undefined index: alias in path_form_element_validate() (line 156 in /var/www/eplus.cn/modules/path/path.module).

 Recoverable fatal error: Argument 2 passed to drupal_array_set_nested_value() must be an array, null given, called in /var/www/eplus.cn/includes/form.inc on line 2518 and defined in drupal_array_set_nested_value() (line 6510 in /var/www/eplus.cn/includes/common.inc).

这是path模块报的错,我当时怀疑,是不是系统环境的问题,因为在我本地,一切都是好好的,但是到了线上,就出了问题。

我把线上的代码,下载到本地,然后使用NotePad++的强大的搜索功能,搜索都有哪个模块调用了path_form_element_validate函数,问题的原因一下子就找出来了,原来罪魁祸首是pathauto模块。pathauto.module里面有这样的一段代码:

/**

 * Implements hook_field_attach_form().

 *

 * Add the automatic alias form elements to an existing path form fieldset.

 */

function pathauto_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {

  list($id, , $bundle) = entity_extract_ids($entity_type, $entity);

 

  if (!isset($form['path'])) {

    // This entity must be supported by core's path.module first.

    // @todo Investigate removing this and supporting all fieldable entities.

    return;

  }

  else {

    // Taxonomy terms do not have an actual fieldset for path settings.

    // Merge in the defaults.

    $form['path'] += array(

      '#type' => 'fieldset',

      '#title' => t('URL path settings'),

      '#collapsible' => TRUE,

      '#collapsed' => empty($form['path']['alias']),

      '#group' => 'additional_settings',

      '#attributes' => array(

        'class' => array('path-form'),

      ),

      '#access' => user_access('create url aliases') || user_access('administer url aliases'),

      '#weight' => 30,

      '#tree' => TRUE,

      '#element_validate' => array('path_form_element_validate'),

    );

  }

我的眼睛一下子亮了,我们定义了同样的表单元素’path’, pathauto模块为它追加了验证规则path_form_element_validate。这是我休整后的样子:

function breadcrumb2_form($form, &$form_state, $breadcrumb) {

  // Save the breadcrumb for later, in case we need it.

  $form['#breadcrumb'] = $breadcrumb;

  $form_state['breadcrumb'] = $breadcrumb;

  

  $form['bid'] = array(

    '#type' => 'value',

    '#value' => isset($breadcrumb->bid) ? $breadcrumb->bid : NULL,

  );

   

  // Add the field related form elements.

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

  

  $form['path'] = array(

    '#type' => 'textfield',

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

    '#maxlength' => 60,

    '#default_value' => !empty($breadcrumb->path) ? $breadcrumb->path : '',

    '#weight' => -10,

  );

  if (!empty($breadcrumb->path)) {

    $form['path']['#disabled'] = TRUE;

  }

 

  $form['actions'] = array('#type' => 'actions');

  $form['actions']['submit'] = array(

    '#type' => 'submit',

    '#value' => t('Save'),

    '#weight' => 40,

  );

  if (!empty($breadcrumb->bid)) {

    $form['actions']['delete'] = array(

      '#type' => 'submit',

      '#value' => t('Delete breadcrumb'),

      '#weight' => 45,

      '#limit_validation_errors' => array(),

      '#submit' => array('breadcrumb2_form_submit_delete')

    );

  }

  $form['#validate'][] = 'breadcrumb2_form_validate';

  $form['#submit'][] = 'breadcrumb2_form_submit';

  return $form;

}

就是将field_attach_form放到了$form['path']的前面,两者调整了一下位置而已,这样pathauto模块就找不到$form['path']这个元素了。pathauto模块里面的注释让我害怕,它要把它的$form['path']添加到所有的实体表单上。还让不让人活啊。不过,我们这里的办法仍然适用。


Drupal版本:

8 未定义的变量

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

问题的地址:http://drupal.org/node/1863864。当使用普通用户,或者匿名用户访问时,总是报这样的错误信息:

Notice: Undefined variable: contextual_links in include() (line 34 of D:\xampp\htdocs\breadcrumb3\sites\all\modules\breadcrumb2\breadcrumb.tpl.php

我检查了一下breadcrumb.tpl.php,原来是一个笔误。不过此时我已经发布了beta3版了。真是汗颜啊。都beta3了,还存在这样的问题。这是修正后的样子:

<?php if (!empty($breadcrumb)): ?>

<div class="breadcrumb-wrapper contextual-links-region">

  <h2 class="element-invisible">   <?php print t('You are here'); ?>  </h2>

   <?php if (!empty($contextual_links)): ?> 

    <div style='height:10px'> <?php print $contextual_links; ?> </div>

  <?php endif; ?>

  <?php //print drupal_render($contextual_link); ?>

  <div class="breadcrumb"> <?php print implode(' » ', $breadcrumb); ?> </div>

</div>

<?php endif; ?>

你开始的时候,看出来哪里出错了吗?还是读到这里才看出来这个错误。这就是beta4版。


Drupal版本:

9 与Panels的兼容问题

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

在一个实际的项目中,面包屑是添加到Panels的窗格里面去的。如果节点页面不使用Panels的话,就正常,使用了Panels,把面包屑放到Panels的窗格里面输出的话,就不正常了。你可以通过这个步骤重现这个问题,在panels的内容页面,为某一个区域添加内容,在弹出的对话框里面,左边选择页面元素(page elements),此时右边就会有面包屑可以添加,添加后,就会出现我们这里所说的问题。

 

我是这样解决这个问题的,首先,找到这个面包屑元素在Panels里面是怎么定义的,先通过前台界面,鼠标移上去,看看左下路径的变化,从路径里面可找到 page_breadcrumb,根据这个使用NotePad++的查找功能,就能找到对应的代码。代码位于ctools\plugins\content_types\page下面的page_breadcrumb.inc文件中:

$plugin = array(

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

  'single' => TRUE,

  'icon' => 'icon_page.png',

  'description' => t('Add the breadcrumb trail as content.'),

  'category' => t('Page elements'),

  'render last' => TRUE,

);

 

/**

 * Output function for the 'page_breadcrumb' content type.

 *

 * Outputs the breadcrumb for the current page.

 */

function ctools_page_breadcrumb_content_type_render($subtype, $conf, $panel_args) {

  $block = new stdClass();

  $block->content = theme('breadcrumb', array('breadcrumb' => drupal_get_breadcrumb()));

 

  return $block;

}

我们看到,Panels里面的面包屑,根本没有走页面,所以通过hook_page_alter是修改不了这里的面包屑的。可能这样解释更正确一点,就是Panels生成面包屑之前,还没有调用hook_page_alter呢,就是说Panels先生成的面包屑,后面才调用了我们的hook_page_alter

解决办法很简单,就是将breadcrumb2_page_alter里面的代码搬到面包屑的预处理函数breadcrumb2_preprocess_breadcrumb里面。这是添加后的代码:

 

function breadcrumb2_preprocess_breadcrumb(&$variables) {

  $current_path = current_path();

  $breadcrumb2 = breadcrumb2_load_by_path($current_path);

  if(!empty($breadcrumb2)){

    $breadcrumbs = array();

      // Only keep the first one. 

    if (!empty($variables['breadcrumb'][0])) {

      $breadcrumbs[] = $variables['breadcrumb'][0];

    }

    $wrapper = entity_metadata_wrapper('breadcrumb2', $breadcrumb2);

    $breadcrumb_links = $wrapper->link->value();

    foreach($breadcrumb_links as $breadcrumb_link){

      $breadcrumbs[]= l($breadcrumb_link['title'], $breadcrumb_link['url']);

    }

$variables['breadcrumb'] = $breadcrumbs;

  }

 

  if (user_access('administer breadcrumbs') && user_access('access contextual links')) {

 

粗体部分为新增的。现在我们就可以注释掉breadcrumb2_page_alter了。清楚缓存,一切正常工作。前面忘记介绍了一个问题,当系统已经设置了面包屑,并且面包屑里面有多个链接的时候,就与我们这个模块的面包屑设置冲突了,此时,我们只取系统设置的面包屑链接里面的第一个,也就是首页的链接,其它全部去掉。在beta3beta4里面,都有对应的代码。

还有一个问题,就是说把breadcrumb.tpl.php复制到当前主题目录下,在这里覆写,不起作用的问题,这也是一个大问题。此外,还有两个工作要做,一个就是为breadcrumb2_load_by_path加缓存,就是在同一个页面请求处理期间,如果加载了一次,那么下次使用这个函数时,就会直接返回前面的结果。另外一个工作就是,为面包屑模板加上模板建议,可以按照路径的具体程度进行覆写。还有就是把模板里面的:

<div class="breadcrumb-wrapper contextual-links-region">

把这里的的设置,放到预处理函数中进行。比如contextual-links-region,只有当输出上下文链接的时候,才输出这个类。

由于这些问题,和上下文链接,都快没有直接的关系了,所以就不放到本章介绍了。


Drupal版本: