Development

views 中只顯示屬於自己角色的頁面

來自社區的一個問題: http://drupalchina.org/node/8514#comment-26113
比如我是 "writer" role, 我想要一個頁面都是 writer role 的用戶的nodes:

views 的 argument 應:
加, User:roles -> Provide default argument
Default argument type: PHP code:

<?php
global $user;
$roles = implode("+",array_keys($user->roles));
return
$roles;
?>

再選 Allow multiple terms per argument.

解說: 先拿到用戶object
再拿出用戶的所在的 role id
因為可以多個role, 需要implode 成 2+5
Allow multiple terms per argument 便會 OR role id 了

2009-09-10 form to email 2分鐘完成

近來因為真的因為太多案子
這邊很久有update 了

又因為真的太多案子
古怪, 快速, 而又欠缺美感的想法, 一直被我無情的實現了

要求:
表單轉成email >Read more

2009-08-08 htmlentities() 和 html_entity_decode()

htmlentities() 和 html_entity_decode()

htmlentities()
http://www.php.net/manual/en/function.htmlentities.php

功能上很簡單, 就是將 < 變作 &lt;
當然, 還有其他的entities, 可以參考: http://www.w3schools.com/tags/ref_entities.asp
主要是給使用者輸入的 entities 會 escape, 輸出的時候便可以直接使用

另一方面, >Read more

2009-07-17 svn tagging and branching

svn 是一種使用之前會討厭,
但使用過以後你已經不能缺少的開發環節 (情況和測試驅動開發(TDD)一樣)

在"使用過" 並愛上的青況之下,
你很容易便會提起興趣學習進階的 branch 和 tag 的功能了
但如果沒有, 直接跳過就好了 >Read more

svn 概念, 初階使用

svn 是一個管理源碼的工具
它提供一個容許多人協作的平台, 幫助一個多人開發的團隊管理代碼
同時提供一個保存多版本的功能 (version-ing)

而我因為多數都自己一個開發, 主要為了 versioning 而使用 svn
但因為有多個開發機器
為了保持代碼在多個機器中同步, 都會使用 svn

我在 windows 機是使用 TortoiseSVN GUI client
ubuntu 上使用 nautilussvn
使用上, 介面上都很類似, 都很好用

先談一下 svn 的概念
svn server 是指在 remote 上的server
大家都將大家的code 上傳 (commit) 到這個中央的server
而開發機則是 client端 (當然, 他們其實可以在同一部機器上)
而 head 是指最新的一組檔案的集合, 統稱 head version
trunk 一般指 server 上的 head.

從日常使用次數最多的功能開始介紹
checkout
從server 上下載源碼(即 server 上己經建立好, 設定好)
在本機(即開發機上)建立一個拷貝
是在開發機上開始建立一個新的 project 的方法
完成便可以開始改動源碼, 開發的工作了
可以從 code.google.com 隨便選一個 project 使可以checkout了 (source tab 便可以找到 svn url, )

commit / add
commit 是指將本機的源碼上載到 svn server 上(要已經checkout 過的檔案)
例如已經checkout 過的 project,
在改動源碼之後, 資料夾上使會有一個紅色的X, 代表需要 commit
而如果你有權力 commit, 便可以上載到 svn server 上了
而每一個commit 都儲存到 server 上, 之後的任何時候都可以調用, 還原等等 (就算commit 再覆寫過都可以)

而 add 則是新增一個沒有 svn 的檔案到 svn server.

commit 的策略是, 每午飯之前, 下班之前都commit, 原因在討論 update 之後再研究

update
如果我 checkout 了一個 project, 但 project code 在svn server 上有其他人 commit 了中
我應該如何更新我的 code 令自己和svn server 同步?
答案是 update.
svn 會幫我們從 server 上下載, 而且萬一需要覆寫一個我們正在修改的檔案(即一個衝突 conflict 發生了),
svn 會嘗試幫我們合併, 確保同事做的修改和我們的修改都正確的保存

如果開發團隊同意了午飯前, 下班前commit 當日的源碼,
update 的策略便是 上班的第一件事, 和午飯後的第一件事了.

change log, diff
每一次的commit 都會留下記錄在 server 上 (change log)
並有一個唯一的 版本(version) id
我們任何時候都可以將任何兩個 commit 比較
並還原, 合併任何兩個檔案

svn 在處理 conflict 的時候, 不一定能自動合併
這時 svn 會要求我們做手動合併, diff 工具都會協助我們分析兩個衝突檔案的內容的

tag / branch
這是一個進階使用者的題目, 請先確保你試過, 用過上面的功能再繼續
head 便是一個等殊tag, 這個tag 是會隨著commit 而移動
而我可以因應需求tag 一個 2009-06-08 tag
標記這些便是 09年 6月 8日 的源碼, 主要是為了方便日後的調用 (稱為日期tag)

而branch 其實和 tag 類似, 但 checkout 了 branch 的話, 你的 commit 會指定為 commit 到這個 branch,
而不是一般的主線
這樣, 你就可以將你的重大更新, refactoring 開一個 branch, 完成再 merge
令自己的重大更新的開發代碼不會干擾其他同事的開發

指令上在 GUI 都好簡單, checkout/update 了以後, 直接在資料夾上右鍵->tag/branch 便可以了

安裝 svn server @ubuntu
首先安裝 SVN
sudo apt-get install subversion

安裝 svn 在 Apache 使用的套件
sudo apt-get install libapache2-svn

進階的權限便自己 google 一下了

Project setup
安裝完便要建立一個 trunk,
而一般的資料夾習慣是:
SVN-ROOT
-PROJECT-NAME1
--trunk
--tags
--branches
-PROJECT-NAME2
--trunk
--tags
--branches

建立 SVN-ROOT 到 /home/svn:
sudo svnadmin create /home/svn

建立 PROJECT-NAME1:
GUI:
在開發資料夾 (例 /var/www) checkout file:///home/svn
checkout 後建立 trunk, branches, tags 三個資料夾
commit 三個資料夾
再 del 三個資料夾, 然後 checkout trunk
便可以開始 add了

conclusion
會使用 svn 工具絕對是其中一個進階程序員的指標 (包括 tag/branch)
工具看似複雜, 但其實真正做起來只是很簡單的概念, 但用文字真的比較難以表達
找一個人教一教, 十分鐘便什麼都懂了.

version control 的工具其實還有 cvs, git 等, 概念有一點不同
但一般的操作反而很類似, 舉一反三就是了

但svn 都不是萬能
sql server 中設定很多的 cms (drupal 便是一個明顯例子) 如何做 version control?
mysqldump + commit?
有高手能解答嗎?

theme 使用theme 自己的 *.tpl.php, build a custom hook_theme() on a theme

一個module 需要輸出html 的話, 會使用 hook_theme()
但如果一個theme 都需要輸出特定的html, 或者需要使用 *.tpl.php的話
因為Drupal 6 新增了 theme registry,
以前 Drupal 5 的theme 內的theme() 函數都不會自動加到theme registry
要在 template.php 內: >Read more

處理複雜的 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 這個模組不太對頭
設定太少, 所以才不用, 要自己動手

[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, 便完成了

將搜尋結果排序

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

思路:
首先是排序
排序首選是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.

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

Drupal6.x 自定form template

今日重看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); 再輸出, 只輸出未使用過的元素, 很方便