错误信息

Deprecated function: The each() function is deprecated. This message will be suppressed on further calls 在 menu_set_active_trail() (行 2405/data/itxueku/includes/menu.inc).

Drupal自定义代码实现URL重写

浏览:113
drupal中使用URL别名在SEO以及网站用户体验方面非常重要,通常我们使用如下几个模块,

  1. path(核心模块)




  2. pathauto




  3. path_redirect




  4. global_redirect



一般情况下,给一个URL设置一个别名,全站的所有URL都会更新用这个别名来代替原来的URL。


比如: /user/1 —> /robbin-zhao



这样设置的URL会被保存在url_alias表中。


这里有两个术语:
1. outbound URL 输出URL,或者显示/打印的URL。
2. inbound URL 请求URL,可以理解为进来的URL。


了解了术语之后,我们理解一下Drupal处理URL别名的方式,

1). 输出别名
在输出URL的时候,核心函数是URL





function url($path = NULL, $options = array()) {
  // Merge in defaults.
  $options += array('fragment' => '','query' => '','absolute' => FALSE,'alias' => FALSE,'prefix' => '',
  );  ...   elseif (!empty($path) && !$options['alias']) {$path = drupal_get_path_alias($path, isset($options['language']) ? $options['language']->language : '');
  }   if (function_exists('custom_url_rewrite_outbound')) {// Modules may alter outbound links by reference.custom_url_rewrite_outbound($path, $options, $original_path);
  }

我们重点看下面的两个调用 drupal_get_path_alias 和 custom_url_rewrite_outbound。Drupal通过查询url_alias表,把要显示的URL更新为对应的alias就实现了别名的替换。


2). 处理别名的HTTP请求


Drupal在启动所有模块之前,先初始化URL,调用如下函数:





function drupal_init_path() {
  if (!empty($_GET['q'])) {$_GET['q'] = drupal_get_normal_path(trim($_GET['q'], '/'));
  }
  else {$_GET['q'] = drupal_get_normal_path(variable_get('site_frontpage', 'node'));
  }} function drupal_get_normal_path($path, $path_language = '') {
  $result = $path;
  if ($src = drupal_lookup_path('source', $path, $path_language)) {$result = $src;
  }
  if (function_exists('custom_url_rewrite_inbound')) {// Modules may alter the inbound request path by reference.custom_url_rewrite_inbound($result, $path, $path_language);
  }
  return $result;}

函数 drupal_get_normal_path 主要是查询url_alias表,得到当前URL的实际地址,比如 user/1, 然后把这个URL赋给 $_GET['q']来实现具体的URL重写功能。


在有些情况下,我们需要批量修改一些URL的别名,如果我们用drupal默认的url_alias, 但又有一些问题,首先,更新所有的URL脚本比较繁琐,数据量大的情况需要batch,操作数据不方便。其次,如果用户量大,会产生严重的Drupal性能问题,因此,可以考虑到不用url_alias,举个例子,比如我们希望更新user下面的所有tab url, 如:user/1/info, user/1/blog, user/1/message,user/1/mail … 每个用户有多个URL需要更新,如果有1百万用户,那么就会有上百万、千万的alias数据,对于维护、性能都是很大问题。


自定义函数实现URL重写
通过查看Drupal的URL流程,可以发现,Drupal在处理输出URL的时候,会调用一个自定义函数:custom_url_rewrite_outbound,在处理HTTP请求的URL时,
也会调用一个自定义函数:custom_url_rewrite_inbound,所以我们可以实现这两个函数来实现URL重写。


注意,由于这是单个函数而不是hook,如果每个函数都实现,很容易相互冲突,比如fb模块(facebook),url_alter(用于自定义代码来实现URL重写,主要实现了上面的两个函数)。但是由于这两个函数容易冲突(不是hook),其次,url_alter对inbound URL处理有问题,因为Drupal在调用custom_url_rewrite_inbound这个自定义函数的时候,是在加载所有模块之前,所以把这个函数写在module文件里面,根本掉用不到,这里提供一个目前较为合理的解决方案:



  1. 写一个inc文件,放到(任意)自定义模块下面,比如 my-core/my-core.rewrite.inc




  2. 修改settings文件,include这个文件。比如 include “sites/all/modules/custom/my-core/my-core.rewrite.inc”;




  3. 在该文件中加入inbound和outbound这两个函数。



具体代码如下:





/**
 * Define custom_url_rewrite_inbound()
 * @author robbin
 */if (!function_exists('custom_url_rewrite_inbound')) {
  function custom_url_rewrite_inbound(&$result, $path, $path_language) {{fun_1}_url_inbound_alter($result, $path, $path_language);
  }} /**
 * Define custom_url_rewrite_outbound()
 * @author robbin
 */if (!function_exists('custom_url_rewrite_outbound')) {
  function custom_url_rewrite_outbound(&$path, &$options, $original_path) {{fun_1}_url_outbound_alter($path, $options, $original_path);
  }}

其中 {fun_1}_url_inbound_alter、{fun_1}_url_outbound_alter 表示一组处理inbound/outbound的函数,命名规则最好按照如上方式,因为一些第三方模块以及hook都是这个规则,容易理解。
可以添加多个函数,比如{fun_2}_url_inbound_alter等等,每添加一个,在上面的位置调用函数,以做到每组不通功能的函数分开。如果第三方模块,也需要实现重写,一般情况下,这些模块会实现类似 {module}_url_inbound_alter这样的函数,直接把这个函数加到上面对应的位置来调用即可,比如(facebook模块的fb_url_inbound_alter等)。这里给出函数的简要说明:


 


//修改result的值为最终实际的URL $result是引用传值
hook_url_inbound_alter(&$result, $path, $path_language);
//修改$path的值为想要的别名的URL $path是引用传值
hook_url_outbound_alter(&$path, $options, $original_path);



 


最后,还有一点要注意,自定义inbound函数,有时可能会和global_redirect冲突,(没用这个模块,写了类似的函数,也会冲突),因为redirect模块会检查当前的真是url(从outbound中获取)和当前请求的URL不一样,比如真实url是user/1,而当前的请求是 robbin-zhao,它会自动跳转,导致一个无限循环跳转的bug。
解决办法就是在inbound函数里面设置一个全局变量,阻止继续调转。设置 $_REQUEST['q'] = $result; 的值为最终实际URL的值,而不是别名。


 


示例代码





function my_redirect_url_inbound_alter (&$result, $path, $path_language) {   $arg0 = arg(0); //should be user-name
  $arg1 = arg(1); //should be connections/media/...   if ($arg1) {$user_url = drupal_lookup_path('source', $arg0);if ($user_url != $arg0 && preg_match('{user/(\d+)}i', $user_url, $matches)) {  $user_id = $matches[1];  $result = "user/$user_id/$arg1";       //add this to tell global_redirect not to redirect this url again  $_REQUEST['q'] = $result;}
  }} function my_redirect_url_outbound_alter (&$path, $options, $original_path) {
  //rewrite user's sub tab url to seo-friendly url
  //such as, user/1/media --> robbin-zhao/media
  if (preg_match('{user/(\d+)/(\w+)}i', $path, $matches)) {$uid = $matches[1];$tab = $matches[2]; $alias = drupal_lookup_path('alias', "user/$uid"); if ($alias != $path) {  $path = "$alias/$tab";}//$path = ''
  }}


优化过的代码已经提交到Drupal官方网站,并且已经是一个第三方模块,大家可以下载使用。
模块地址:http://drupal.org/project/rewrite_sub_link



top