其实我一直有一个这样的想法,当我决定为每个页面创建一个面包屑对象的时候,就有了这样的想法,在面包屑这片内容上面输出一个上下文链接,可以方便的添加、配置面包屑:
更前卫一点的想法,是如何与Edit这个模块集成,就是让用户在这里能够在当前页面编辑面包屑。在当前页面编辑面包屑的这个想法,需要等待Spark这个安装包里面的技术成熟稳定以后,才会考虑实现。
我们来看看,上下文链接的形式。在breadcrumb2的beta2版里面,已经实现了这个功能,有兴趣的可以直接下载对应的代码,我们这里重新按照我当时的实现步骤,为大家重现一下完整的过程。
我们是可以在主题层覆写面包屑的,但是有一个问题,我们这里定义的是自己的模块,我们想在自己的模块里面修改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”。清除缓存,发现起作用的还是我们模块里面的模板文件。这是在实际应用当中发现的一个问题。到现在还没有解决。本来打算,一边写作,一边写代码,后来灵感大爆发,直接把模块先写完了。我写到这的时候是2012年12月24日,实际上,12月11号就发布了beta4版了,很多功能都已经实现。
不过今天依然没有解决这个无法覆写的问题,不过我想,肯定会解决的。我通过Google搜索过,也有人遇到过同样的问题,但是没有解决。不过今天Google罢工了。我们继续前进,来看一下怎么添加上下文。
通过本章的学习,我们成功的实现了为breadcrumb.tpl.php文件添加上下文链接的功能,与此同时,我们还学习了contextual模块的机制,以及如何通过预处理函数为模板文件添加变量 。
我们先来分析一个问题,区块上面的上下文链接,怎么生成的。我们来看一下左边导航区块;
然后使用Firefox打开,使用firebug查看一下,上下文链接的源代码。
在区块的标题和正文之间,插入了一个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';
}
}
而且,我们看到区块最外面的div的class里面的'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_links是Drupal里面的一个元素,类似于表单元素一样,当呈现这个元素的时候,就会加载对应的JS、CSS文件,分别为contextual.js,contextual.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的内部。这个时候,面包屑右边的上下文链接显示出来了。
只不过显示的是别处的上下文链接。不过我们已经向前前进了一大步。接着可以尝试使用使用:'breadcrumb2' => array('breadcrumb', array('1')),来构建上下文链接,这次也出来了,只不过这些链接,是bid为1的上下文链接。
可能有读者会问,我怎么知道的,这样构建上下文链接,这个我是参考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,通过实现这个钩子,就能够达到添加链接的目的了。
作者:老葛,北京亚艾元软件有限责任公司,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这个函数,逐层分析,肯定会找到对应的缓存代码的。我们这里清除缓存,这个错误消息就消失了。对于有面包屑对象的,上下文链接是这样的:
如果这个页面没有对应的面包屑实体对象,则显示不出来这个上下文链接。
我们前面讲过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']设置了一个链接。清除缓存,现在,在没有面包屑实体对象的情况下,也有上下文链接了:
我们在这里面,为链接提供了一个"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中追加上面的粗体代码即可。这是现在的效果图:
还不错吧。还有值得改进的地方,实际上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的一个问题。我们这里也顺带的介绍一下,从beta2到beta4里面所解决的问题。以及在实践当中遇到的其它问题。
没有哪个系统是完美的,也没有哪个Drupal模块不存在问题,可是我的心还是有点急,一口气从alpha1发布到beta2,希望别人能够尽快的看到效果。可是后来发现,下载了99次,只安装了一个站点,而这个站点还是我自己的。后来,让身边的朋友反馈意见,发现很多问题。如果你是跟着做过来的,可以尝试一下自己来解决一下这些问题。看看自己能否独立的解决这些问题。这些问题包括:
模块安装不起来,http://drupal.org/node/1863470;
与pathauto模块冲突http://drupal.org/node/1863434;
匿名用户,看到未定义的变量contextual_links,http://drupal.org/node/1863864
在panels窗格里面输出面包屑,输出不出来。
我们现在的这个模块,依赖于Views,Rules,Links,Field 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。
问题地址,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']添加到所有的实体表单上。还让不让人活啊。不过,我们这里的办法仍然适用。
问题的地址: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版。
作者:老葛,北京亚艾元软件有限责任公司,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了。清楚缓存,一切正常工作。前面忘记介绍了一个问题,当系统已经设置了面包屑,并且面包屑里面有多个链接的时候,就与我们这个模块的面包屑设置冲突了,此时,我们只取系统设置的面包屑链接里面的第一个,也就是首页的链接,其它全部去掉。在beta3,beta4里面,都有对应的代码。
还有一个问题,就是说把breadcrumb.tpl.php复制到当前主题目录下,在这里覆写,不起作用的问题,这也是一个大问题。此外,还有两个工作要做,一个就是为breadcrumb2_load_by_path加缓存,就是在同一个页面请求处理期间,如果加载了一次,那么下次使用这个函数时,就会直接返回前面的结果。另外一个工作就是,为面包屑模板加上模板建议,可以按照路径的具体程度进行覆写。还有就是把模板里面的:
<div class="breadcrumb-wrapper contextual-links-region">
把这里的“类”的设置,放到预处理函数中进行。比如contextual-links-region,只有当输出上下文链接的时候,才输出这个类。
由于这些问题,和上下文链接,都快没有直接的关系了,所以就不放到本章介绍了。