處理複雜的 taxonomy 和 breadcrumb 關係

我的一個freelance 之中有一個 Drupal 的普遍問題,
breadcrumb 的作用不太大
Drupal 沒有使用分類輸出一個合適的 breadcrumb

以這次的網站做主軸, 舉個例子(只是個簡化的假設例子):
content type 3個: 經濟, 娛樂, 體育
經濟 type 有一個專屬的 vocab, 有terms: 中國經濟, 美國經濟, 歐洲經濟
娛樂 type 有一個專屬的 vocab, 有terms: 香港娛樂, 日本娛樂
體育 type 有一個專屬的 vocab, 有terms: 足球, 籃球

Primary-links 設定:
為了方便系統管理員, 每一個 vocab 都在 primary-links 內有一個自動更新, 實時的連結
也會顯示該vocab 的terms, 含連結
這樣, 新增了terms 便不需要系統管理員更新 primary-links 了
很容易用 taxonomy_menu 完成

(等一下會講解使用 hierarchy 而不使用 default 的原因)

node/* breadcrumb:
但問題是, node/[nid] 頁面,
不會在breadcrumb 自動算出自己的所屬的 vocab, term
只是一句 Home > [title] 帶過
使用custom_breadcrumb 解決
輸出: Home > [vocab] > [term] > [title]

node/* path:
順便使用pathauto, 建立自動路徑:
設定URL: /admin/build/path/pathauto
Node path settings -> Default path pattern
[type-name]/[term-raw]/[title-raw]
連path 都做成同一個結構, 有利 SEO

taxonomy/term/* breadcrumb:
但 taxonomy/term/[tid] 這種頁面比較麻煩
這頁面來自views, 而views 並沒有對breadcrumb 做優化或者建立輸出選項,
所以要自己用 php 動手了
要在頁面使用php, 要先啟用php filter(Drupal 6 預設關閉)
再在 header 或者 footer 使用php code:

<?php
if ( arg(3)!=null){
 
$term = taxonomy_get_term(arg(3));
 
$vocab = taxonomy_vocabulary_load($term->vid);
 
$breadcrumb[] = l(t('Home'),null);
 
$breadcrumb[] .= l($vocab->name, 'taxonomy/term/'.$vocab->vid);
 
$breadcrumb[] .= l($term->name, 'taxonomy/term/'.$vocab->vid.'/'.arg(3));
 
drupal_set_title($term->name);
}else{
 
$vocab = taxonomy_vocabulary_load(arg(2));
 
$breadcrumb[] = l(t('Home'),null);
 
$breadcrumb[] .= l($vocab->name, 'taxonomy/term/'.$vocab->vid);
 
drupal_set_title($vocab->name);
}
drupal_set_breadcrumb($breadcrumb);
?>

這個修改內建的views 有很多要留意的地方:
1. arguments 不使用多個tid 的方式 (taxonomy/term/[tid1] [tid2] [tid3]) 集合為vocab
而使用 taxonomy/term/vid/tid 表示 term
taxonomy/term/vid 表示vocab
因為: SEO, 和難於使用php 判定這 URL 是 term 還是vocab
例如, argument 傳入 中國經濟 和 足球, 使不能判定 vocab 是 經濟還是體育了
使用上面的方法可以令 views 的 "只容許單一tid" 過濾機制來限制這些可能出現的麻煩
可以得出一定正確的 breadcrumb

2.要小心設定argument (tid 為可選)
所以, URL taxonomy/term 是非法的,
vocab id 為必要, "Hide view / Page not found" 選項
第二個argument 為 tid, 因為 taxonomy/term/[vid] 是合法的, 所以
用 "Display all value" 選項

3.手動設定title
也因為 容許 tid 為可選, title 的設定就變得更複雜
不可以單用 tid 或者 vid 為 title,
所以, 用php 設定 breadcrumb 的時候一次連 title 都設定了

總結:
views 的 breadcrumb 處理的確有不完善的地方
這次在header 做 php code 的方法嚴格來說是一個hack
但這已經是最好的方法
估不到的是, taxonomy_breadcrumb 這個模組不太對頭
設定太少, 所以才不用, 要自己動手

AttachmentSize
Image icon complex-menu-1.gif12.97 KB
Image icon complex-menu-2.gif8 KB

[views 2.0 版] 使用 views 建立tabs, use view to build tabs

tabs 一直是Drupal 中比較少特別提及的功能之一
用戶頁 user/[uid] 便是一個很標準的tab 應用
"view"/"edit" tab 也常見於node 頁

使用views 可以很方便的建立tabs
前文Drupal 5 版: http://www.joetsuihk.com/node/112 中已經介紹過
那是Drupal5, views1 的版本
這次介紹 Drupal6, views2 的版本的設定方法

這次是建立一個tab
顯示用戶建立的node 之中, 留言數目大於10 的頁面, 定義為 "hot"
路徑 user/[uid]/hot
如圖:

我從內建的 tracker 模組的views 開始,
用 clone, 建立一個新的views,
其中, page 的設定:

page settings: 選menu tab
(Default meni tab 是作為預設顯示時才用, 如路徑 user/[uid] )
之後設定頁面的Title, 便完成了

AttachmentSize
Image icon d6-menu-tab-1.gif9.56 KB
Image icon d6-menu-tab-2.gif18.55 KB

將搜尋結果排序

最近做的一件案子中,
有一個比較少見但有時候很實用的功能需求
就是要將搜尋的結果以某條件排序
例如搜尋一些新聞, 時間性很重要
想要將最近相關的新聞排先, 以日期順序
但內建的搜尋是以相關性排序, 最相關的排先

思路:
首先是排序
排序首選是views
但views 的 filter 過濾器並沒有搜尋相關字的設定
但使用views table 排序是最方便的
views 自己就內建

所以我要將搜尋的結果傳給views
自然是使用 arguments 了
找出搜尋結果的nid, 再用逗號分隔, 傳給views
再將views 內嵌到serach result 的頁面

實際解決辦法, 設定:
先新增一個views,
顯示設定為table, 可排序
再到fields 選擇所需的欄位
重點在argument 的欄位,
選Node:nid
Provide default argument
PHP code:

<?php
$results
=node_search('search',arg(2));
 
$size = count($results);

  for(
$i=0;$i<$size;$i++){
    if (
$i+1==$size){
     
$output .= $results[$i]['node']->nid;
    }else{
     
$output .= $results[$i]['node']->nid.",";
    }
  }
return
$output;
?>

Argument type: Node id separated by , or +
選中 Allow multiple terms per argument.

結語:
需然這個方法不是很完美(實際上這是做了兩次一樣的搜尋)
但既然主機的能力不是問題, 開發時間也不多
完成任務還是最重要的

將註冊時連續輸入兩次密碼的表單的字眼改掉

將註冊時連續輸入兩次密碼的表單的字眼改掉的方法:

theme 的 template.php 內:

<?php
function phptemplate_password($element) {
  if(
$element['#id']=='edit-pass-pass1'){
   
$element['#title']=t('joe');
  }
 
  if(
$element['#id']=='edit-pass-pass2'){
   
$element['#title']=t('confirm joe');
  }
 
 
$size = $element['#size'] ? ' size="'. $element['#size'] .'" ' : '';
 
$maxlength = $element['#maxlength'] ? ' maxlength="'. $element['#maxlength'] .'" ' : '';

 
_form_set_class($element, array('form-text'));
 
$output = '<input type="password" name="'. $element['#name'] .'" id="'. $element['#id'] .'" '. $maxlength . $size . drupal_attributes($element['#attributes']) .' />';
  return
theme('form_element', $element, $output);
}
?>

其實其他的form 元素都可以用同一個方法改
甚至改為select 都可以
但亂改一通可能會令數據庫資料丟失
所以, 結構邏輯的修改還是使用 hook_form_alter() 吧

Drupal6.x 自定form template

AttachmentSize
Plain text icon node_form.tpl.php.txt1.08 KB
Plain text icon story-node_form.tpl.php.txt1.39 KB

今日重看form template 的組成 :http://www.joetsuihk.com/form_templates6
混亂得我自己都看不明白, 所以重寫

目的: 重新排位, 令建立新node 的表單簡單點

假設: 要重新排位的 content type 名為 story

  1. 在theme 內建立檔案 node_form.tpl.php
  2. http://api.drupal.org/api/function/theme_node_form 的函數內容貼到 node_form.tpl.php (除去函數開頭結尾), return 改為 print (或直接使用附件)
  3. 打開theme 內的 template.php
  4. 建立函數 function phptemplate_preprocess_node_form()
  5. function phptemplate_preprocess_node_form(&$vars) {
      $vars['template_files'][] = $vars['form']['type']['#value']."-node_form";
    }
  6. 複製node_form.tpl.php 為 story-node_form.tpl.php (theme 內要保留一個可用的node_form.tpl.php)
  7. 修改為:(附件2)
  8.   $output = "\n<div class="node-form">\n";

      $admin = '';
      if (isset($form['author'])) {
        $admin .= "    <div class="authored">\n";
        $admin .= drupal_render($form['author']);
        $admin .= "    </div>\n";
      }
      if (isset($form['options'])) {
        $admin .= "    <div class="options">\n";
        $admin .= drupal_render($form['options']);
        $admin .= "    </div>\n";
      }
      $buttons = drupal_render($form['buttons']);

      $advance = drupal_render($form['menu']);
      $advance .= drupal_render($form['revision_information']);
      $advance .= drupal_render($form['comment_settings']);

      // Everything else gets rendered here, and is displayed before the admin form
      // field and the submit buttons.
      $output .= "  <div class="standard">\n";
      $output .= drupal_render($form);
      $output .= "  </div>\n";

      if (!empty($admin)) {
        $output .= "<div><fieldset class='collapsible collapsed advanced standard'>";
        $output .= "<legend>Advance</legend>";
        $output .= "<div class='fieldset-wrapper'>";
        $output .= $admin.$advance;
        $output .= "</fieldset></div></div>\n";
       
      }
      $output .= "<div>$buttons</div>";
      $output .= "</div>\n";

      print $output;

重點:
第四步, $vars['form']['type']['#value'] 是content type 名, $vars 可以用kprint_r() 或者 theme developer 查看可以變數
第五步, 一定要留一個node_form.tpl.php 在theme 之內, 是Drupal 對自定義template 的要求
第六步, 使用過drupal_render() 的表單元素並不會在drupal_render($form); 再輸出, 只輸出未使用過的元素, 很方便

Pages

Google