Development

修改 form 元素的表單 hook_element() #process

需求:
因為上載的圖片太多, 又會分開數次上載/更新圖片, 編輯們想要知道上載某圖片的日期, 以決定刪除/更新某圖片

使用 devel 參考 filefield 的參數, 發覺 filefield 是有將 timestamp 儲存的, 只要在合適的 form 處顯示就可以了

顯示 timestamp 的代碼參考了 imsgefield extended module 的代碼, 使用 hook_element 的方式將 timestamp 加到 form API 之中:

<?php
function ge_filefield_timestamp_elements() {
  return array(
   
'imagefield_widget' => array(
     
'#process' => 'ge_filefield_timestamp_widget_process',
    );
}
?>

以上的代碼在 form API 處理/顯示表單 imagefield widget 的時候會呼叫一次, 而將 timestamp 加到表單的代碼:

<?php
if(isset($element['#value']['timestamp']))
  {
   
$element['data']['timestamp']['body'] = array(
     
'#type' => 'markup',
     
'#title' => 'timestamp',
     
'#default_value' => '123',
     
'#value' => 'Timestamp: ' .date('Y-m-d H:i', $element['#value']['timestamp']),
     
'#prefix' => '<div>',
     
'#suffix' => '</div>',
    );
  }
  return
$element;
?>

就更簡單, 其中, $element['#value']['timestamp'] 可以取得 timestamp, 轉換為編輯們可讀的日期格式就可以了

完整模組的代碼可以見 https://github.com/joetsuihk/filefield_timestamp

multi-site 要如何共用 user 帳號密碼

multi-site 共用同一個 mysql 的話, 例如:
site1:

<?php
//settings.php:
$db_url = 'mysql://root@127.0.0.1/site1';
$db_prefix = array(
       
'default' => '',
       
'users' => 'shared_',
       
'sessions' => 'shared_'
);
?>

site2:

<?php
//settings.php:
$db_url = 'mysql://root@127.0.0.1/site2';
$db_prefix = array(
       
'default' => '',
       
'users' => 'site1.shared_',
       
'sessions' => 'site1.shared_'
);
?>

hook_apachesolr_prepare_query()

Apache solr/PHP solr client 可以使用的 development API 其實還有很多
這次介紹的是其中一個最常用的 hook_apachesolr_prepare_query()

<?php
function MODULE_NAME_apachesolr_prepare_query(&$query, &$params, $caller){}
?>

這個 hook 的用處是可以直接修改傳到 solr 的 query
例如用代碼增加只搜尋某一個 content type (其實 apachesolr_search 已經這一個選項, 見圖)
或者代碼增加 content type=='restanrant' OR taxonomy=='Food' 之類不可以在 views 中達成的複雜 search query

apachesolr 的search filters

prepare_query() 函數的參數 $query 是一個 object, README.txt 的例子中使用 add_filter() 可以新增 AND 的 filter,
也可以使用 add_subquery() 增加 OR 的 filter

$caller 是一組辦悉用的代號, 例如 apachesolr_search.module 的 search 中, $caller 是 apachesolr_search
因為其它模組也可以使用 apachesolr 的 API(上一篇已經提過), 可以區分其他開使用 apachesolr 的搜尋
例如一個自定搜尋的例子:

<?php
  $temp_q
= apachesolr_drupal_query();
 
$temp_q->add_filter("tid", $tid);
 
$cuisine_query->add_subquery($temp_q, 'OR');
?>

例如一個自定模組可以:

<?php
apachesolr_search_execute
($kewords, '', 'score desc', '', 0, 'search_food');
?>

加上 hook_apachesolr_prepare_query, 'search_food' 便是 $caller 的值
可以專為 search_food 自定義合適的 filters

ref:
README.txt
query class implements Drupal_Solr_Query_Interface
interface Drupal_Solr_Query_Interface

分開 apachesolr 和 apachesolr_search

下一個進階的 solr blog 已經寫了大半, 但發覺有必要分開一篇
先談談 apachesolr 和 apachesolr_search 的分別

apachesolr 是一個接口模組, 是連接 Drupal 和 SolrPhpClient 的接口
而 SolrPhpClient 是 PHP 和 solr 的接口
但 solr 是 java base 的, 而且其實有一個易用的 web service query 接口: /solr/CORE0/admin/form.jsp

所以, 嚴格來說, 假如你要自己硬幹, 是可以不使用以上的接口的
但當然無需要就不必重做輪子了

apachesolr 的功能包括提供方便的函數,
以調用正在處理的 query, query 的參數, 它的回傳 response, response 的列數目等等
也會送出 cck 的欄位的資料到 solr

而 apachesolr_search 的工作是提供一個 apachesolr 的實作
提供多個 filtering 的 block, current results 的 block, 取代原生的 search 模組, 可以取代 taxonomy page等等
所以 apachesolr_search 是一個很好的範例, 連同 contrib/apachesolr_nodeaccess 等等
也可以觀察多個 solr 模組實作的時候的分別, 從而參考出自建一個模組的話, 要留意的地方

大家都理解的話, 希望下一篇大家小心分清楚它們的分別就可以了

Feeds module 6.x, supports import UTF-8 CSV files

Feeds 模組的功能是可以使用 CSV 或者 RSS 等格式化的內容匯入到 Drupal 內
可以使用 mapping, 將 CSV 的欄位對到 node 的欄位
但我發覺匯入的欄位的名字有一點問題
一直不能 map 到正確的 node field

我一直挖到反轉了整個處理輸入, 處理 CSV 的核心部份
最後才發覺是因為 uft8 的檔案頭有 BOM 三個字符
導致第一行的欄位名大混亂
而且 Drupal7 的 dev 版本已經修正
我便 port 到 D6 的版本
http://drupal.org/node/1005400#comment-5048076

最後, Feeds 的模組其實已經很完整
你可以將兩個 CSV 的欄位 map 到一個多值的 node field
例如匯入多個 imagefield, 你可以 map CSV 的兩個欄位到同一個 node field
feeds 模組會自動將兩個值都放到正確的 node field 值
而匯入 taxonomy 便應該使用 term name 而不是 term id
如果 vocab 設定了是 tags 的話, 新的 term name 還會自動新增, 很方便

Drush Alias - 進階 Drush 技巧

當一部機器有很多 Drupal installation 的時候,
你便會經常:

cd /var/www/site1
drush en -y views

cd /var/www/site2
drush en -y views

再加上測試環境:
cd /var/www/site1_uat
drush en -y views

cd /var/www/site2_uat
drush en -y views

就變得很難維護

使用了 drush alias 之後, 你可以:

//site 1
drush @site1.uat en -y views
drush @site1.live en -y views
//site 2
drush @site2.uat en -y views
drush @site2.live en -y views

實作:

在你的 drush folder 內有一個 example folder,
內有一個 example.aliases.drushrc.php
將它複製到 drush folder 內, 改成 site1.aliases.drushrc.php
增加內容:

<?php
$aliases
['uat'] = array(
  
'root' => '/var/www/site1_uat',
  
'uri' => 'uat.joetsuihk.com',
);
$aliases['live'] = array(
  
'root' => '/var/www/site1',
  
'uri' => 'uat.joetsuihk.com',
);
?>

site2.aliases.drushrc.php

<?php
$aliases
['uat'] = array(
  
'root' => '/var/www/site2_uat',
  
'uri' => 'uat.drinkingdrupal.com',
);
$aliases['live'] = array(
  
'root' => '/var/www/site2',
  
'uri' => 'uat.drinkingdrupal.com',
);
?>

檔名前端是代號, 使用 domain 很合適 (site1, site2 其實不適合)
$aliases 的 key 則應該使用環境代號, 例如 dev, uat, live 等

Drush 的 example 資料夾內的 example.aliases.drushrc.php 內有更多例子,
例如可以使用 ssh 連線到另一台電腦的 drush,
指定資料庫路徑, 然後用 sql-sync 實現 deployment 等等
我覺得有點 over kill, 便沒有這麼深入的使用了

Development Tips: strip html tags on Drupal like view's doing

Views 的 fields 輸出項目有一個很方便的功能
它可以指定輸出一大段文字中的首 x 個字符
輸出 body 的時候可以大約控制欄位的長闊
實作原來是使用了 Drupal api:
http://api.drupal.org/api/drupal/modules--filter--filter.module/function...

因為 body 會自動插入 <p>
使用一般的 trim() 的話便會有 invalidate HTML 的問題
在 IE 上的版位便會變得更難控制了..

#IEhell

Drupal7 imagefield_extended 的代用品 replacement for imagefield_extended

D6 的 imagefield_extended是我一個常用的模組,
可以將數個文字欄位加到 image 上,
當 imagefield 本身是 unlimited 的時候很有用

但顯然這不是 D7 的做法
D7 的做法是原生的將多個欄位組合, 而不只是 image 和 text
可以 image 和 email, options 等等
http://drupal.org/project/field_collection

而現在的 dev version 有一個 bug,
要支持 imagefield 便要使用以下的 patch
http://drupal.org/node/1187010#comment-4947330

操作上,
先到 structure define "field-collections"
便可以建立一個 "包含數個 field 的 field"
然後像一般的欄位到 content type 的頁面設定便可以
Widget 選 "embedded"

控制 variables table 的值

Variables tables 有很多零碎的設定值
模組需要儲存的簡單資料可以很方便使用 system_settings_form() 的放到 variables

但有些情況之下將 variables 放到資料庫是不方便的
其中一個例子是測試環境和 production 不一樣的時候
deployment 完成之後還要再修改資料庫
步驟多, 又是人手操作, 錯誤便容易發生

例如 gmap 模組需要一個 Google map API key
而該 key 則是 google 根據 domain產生的
例如 uat.joetsuihk.com 和 www.joetsuihk.com 使用的 API key 便不一樣

我的解決方法是在 settings.php 設定 $conf 變數
那資料庫中的 variables 便會被覆寫
例如:

<?php
if($_SERVER['HTTP_HOST']=='uat.joetsuihk.com')
 
$conf['google_map_api'] = 'uat api key';
else
 
$conf['google_map_api'] = 'production api key';
?>

另一個方法是使用 strongarm 模組
配合 ctools 和 features
一樣可以將 variables 收進 version control

Updating views fields changes field alias names

When views is using fields as output,
you can hidden the field, and echo them in other field templates
you will get an alias, but that alias is willing to change.
So you cannot hardcode that in tpl files.

In order to retrieve the alias and its value:

<?php
//within field tpl files
$value = $view->field['field_host_value']->render($row);
?>

http://drupal.org/node/361756#comment-4424090

當使用Views 輸出fields 的時候, 你可以隐藏 (hidden) 一個 field
但這個 field 的別名 (alias) 是會改變的
所以你不可以將這個別名寫到 code 內

要在 code 取得這個別名,

<?php
//within field tpl files
$value = $view->field['field_host_value']->render($row);
?>

http://drupal.org/node/361756#comment-4424090