修復了一個 Drupal 安全漏洞

個人第一個 Drupal 安全報告,同時是第一個有份參與 PATCH 的安全問題。

發現過程:

成為 Drupal JS maintainer 後,對一些 PATCH 關心程度大了。在 REVIEW 一個已 COMMIT 的 AJAX Patch 時發現了此漏洞 (以往一定不會太關心),於是向 Drupal Team 報告了。修復過程並不簡單,由於需要兼容 IE6+,做了很多瀏覽器測試 (多逹 32 個 TEST CASES)。 Backend 也需要解決 CACHES 及安全等等問題,討論了很多不同方案。

經過一番戰鬥,終於解決及公諸於世。(由於是安全問題,不多說了 ^_^)

( 最近的更新還包括挺嚴重的安全問題,建議大家更新。Ctools 也需要哦 )

Drupal CKEDITOR 模組 linebreaks 問題

我們的一個網站由於開發時並沒有使用 Ckeditor 模組,最後導致啟動編輯器後引起 LINEBREAK 的錯誤 (右面是期待的正確結果)。從網上及官網資料大概了解到這是一個已知問題, 也沒有解決方法。

想一想,最後決定直接更新文章內容,替代 LINEBREAK 為 HTML <br />。本來較好的方法是透過 Drupal API 更新,可是我們的網站很簡單,寫 API 太浪費時間,所以選用直接 MYSQL REPLACE:

  1. 為安全起見,隨機找了一段文章字詞進行全資料庫搜尋,結果符合預期: 只有一個欄位儲存這些資料 (CACHE TABLES 可以乏略)
  2. MYSQL REPLACE

    
    UPDATE field_data_body SET body_value = REPLACE(body_value, "n", "<br />");
    UPDATE field_revision_body SET body_value = REPLACE(body_value, "n", "<br />");
    
  3. Enjoy It !

如果你是使用 Wysiwyg 模組可以試試: Wysiwyg Linebreaks

效能篇: Field Storage 效能測試

分享一篇很值得一讀的文章: http://posulliv.github.com/2013/01/07/bench-field-storage/

節錄:

  • 透過調整 MYSQL 增加效能,這點一定要做!

innodb_buffer_pool_size=6G
innodb_log_file_size=512M

注意以上兩點需按伺服器性能及網站需求設定,如果設定後不能啟動可以參考這裏修正

  • 使用 MongoDB,提升三倍多效能,需要伺服器支援,也增加維護難度

然而作者的 FOLLOW UP 文章也測試了另一個 Field SQL norevisions 模組。如果你有看過 DRUPAL 數據庫結構,你會發現 DRUPAL 在做很笨的事情: 同時寫入相同的數據到兩個 FIELD 資料表中。而這模組作用就是停止這愚蠢的行為。(Kay.L: 不知有沒有副作用 ^_^)

Drupal: 增加子項目 type 支援到 theme_item_list()

D8 Issue (COMMITED):
http://drupal.org/node/1785310

 

D7 可自已增加到 theme 中:
http://api.drupal.org/api/drupal/includes!theme.inc/function/theme_item_list/7


// .. 省略
      if (count($children) > 0) {
        // Render nested list.
        $data .= theme_item_list(array('items' => $children, 'title' => NULL, 'type' => $type, 'attributes' => $attributes));
      }
// .. 省略

更改為


// .. 省略
      if (count($children) > 0) {
        // Render nested list.
        $data .= theme_item_list(array('items' => $children, 'title' => NULL, 'type' => (isset($item['type']) ? $item['type'] : $type), 'attributes' => $attributes));
      }
// .. 省略

如果不會使用 theme_item_list(),可看看這裏 EXAMPLE

模組推薦:快速新增 MENU 至內容類型 (Content Types)

大家都知道新增選單 (MENU) 後,可以到內容類型管理頁面啟用,增加到 NODE 編輯頁面。

數個步驟,當只有兩個內容類型時,還好。若要增加至十個或更多類型中很要命。以下模組正解決了此問題:

Content types per menu
http://drupal.org/project/ct_per_menu

啟用後可在 MENU 管理頁面進行管理

修改 Drupal Views SQL 沒難度![hook_views_query_alter]

Drupal Views 讓你可透過界面設定所需條件,輕鬆抓取特定資料。不過開發者創意無限,Views 架構沒可能完全滿足您的要求。在此時,可以考慮使用 hook_views_query_alter() 修改/增強 Views 預設不能滿足的條件。

引用 Drupal Taiwan 論壇上一問題作例子:

做出一個列表,當使用者登入後,可以看到同一組別的成員

對於 Views,這類列表本是很簡單,但要實現時卻發現問題:Views 只能過濾登入使用者的 ID (User: Current)。要以登入使用者的角色或其他因素似乎有點難度。

在主題中,我提出了使用 Contextual 的方案,問題的確能解決,但你的 URL 將變成:my-group-member/{使用者的組別}。也不太完美。(另外,也提及了 Views PHP,估計也能達成效果,但筆者想更深入地看看萬能的方法)

不能確認原發問者需求,為此教程,筆者定義組員列表規格

  • 列表中只能看到同一組別的成員 (此教學中,我們將要列出 K Team 成員)
  • URL:example.com/my-group-member
  • 透過 Profile 增加一個 Team (field_team) 選擇欄位,包括選項:K Team | Drupal Team

 

修改 Views Query

在此略過寫模組的基本需要。我們來建立一個 View,然後如下圖設置。(為了說的更清晰,筆者去除了多餘的設定)
我們增加了 User: Team (= Drupal Team)。這並不是我們想要的,但沒關係,因為即將下來透過程式碼進行修改。

 

此時,如果你有打開 SQL 預覽,可以對比增加 Filter Criteria 前後的 SQL 變化。沒錯,會寫程式的你應看懂 Views 是在做什麼..
([admin/structure/views/settings] -> Live preview settings ->  Show the SQL query)


Views 將一個資料庫表關連了,並設定了 WHERE 過濾條件。所以,我們最終要改變的將是 WHERE 條件中的 IN('Drupal Team') 為當前使用者的 Team

hook_views_query_alter() 真正登場了

*** 下面程式碼包含了詳細註釋,由上至下細看***
(dpm 是 Drupal Devel 模組的 Debug Function)


/**
 * hook_views_query_alter
 * 模組名_views_query_alter
 */
function alterquery_views_query_alter(&$view, &$query) {
  // 看看有什麼東西
  dpm($view, 'Views 的所有資訊');
  dpm($query, 'Views SQL 查詢');

  if (
    // Views 的 Machine Name
    $view->name == 'my_group_member' 
    // Views 頁面 Machine Name (收藏在設定頁面的 Advanced 裏)
    && $view->current_display == 'page' 
  )
  {
    if(!isset($user)) { // $user 是保存當前使用者資訊的變數。在這裏我們檢查有否載入
      $user = user_load($GLOBALS['user']->uid);
    }
    dpm($user, '當前使用者資訊');

    /**
     * $user_team = $user->field_team['und'][0]['value'];
     * 上面這行較直接,但之前談過,由於 Drupal 是支援多語言,這不是最好的方法。
     * 看:https://notabluescreen.com/field_get_items
     */
    $team = field_get_items('user', $user, 'field_team');
    $user_team = $team[0]['value'];
    dpm($team, '使用者組別資訊');


    /**
     * [K-1]我們需要修改 field_data_field_team.field_team_value 這一項
     */
    foreach ($query->where['1']['conditions'] as $key => $value) {
      if ($query->where['1']['conditions'][$key]['field'] == 'field_data_field_team.field_team_value') {
         $query->where['1']['conditions'][$key]['value'][0] = $user_team;
         break;
      }
    }

    // 看看有沒有正確修改
    dpm($query, '修改後的Views SQL 查詢');
  }
}

清除 Drupal Caches,重新整理網頁,應有其效果了!

( 到此為止,要是看不懂,猜想還沒達到 PHP 基本水平。好好再學學 PHP 。 )

 

上面 [K-1] 處的 我們使用了 FOREACH 語句。只要你掌握到所需的資料表,你還可以取消 Filter Criteria: User: Team (= Drupal Team),及將該處程式碼替代為:


// 增加上面關聯的資料表
    $query->add_table('field_data_field_team');
    
    // 增加 WHERE 條件 (不難看出吧!跟上面 [K-1] 的程式碼是對應的)
    $query->add_where(0, 'field_data_field_team.field_team_value', array($user_team), 'in');

 

除了註釋,實際只有十行左右。附上測試模組。

Drupal 小技巧:變更版型的方法

你有沒有參與很多版型開發工作,或是在 CORE Development 作點貢獻? 像是 HTML5 幫助測試。經常變更版型是必須的。在 UI 上轉來轉去好慢哦!

Command Line 愛好者:Drush 是你最好的朋友

版型主要由兩個變數控制:一是 theme_default;二是 admin_theme。

可以輕鬆通過 drush vset theme_default themeName 來轉換,預設的 CORE 版型分別有 "seven", "bartik", "stark"。

例:"drush vset theme_default stark"

既然變數可以改變,不用 DRUSH 的你大概也想到可以通過 settings.php 裏的 $conf 設置。

模組達人: Switchtheme

此模組提供了三種方法:

  • BLOCK
    輕鬆打開 swichtheme BLOCK 就可以轉來轉去
  • 透過傳遞參數
    在 URL 加上 "?theme=themeName"
  • 通過 Browscap 模組自動匹配

論速度:直接修改 DB

在 USERS TABLE 中的 THEME 欄位存儲了版型資訊。

 

Okay,我知道你會告訴我:"開兩個視窗….不就好了….."… 嗯.. 🙂

增減 Drupal HTML HEAD 的資訊 (Meta Tags & LINKs)

Drupal 頁面的 HTML <head> 有一堆 META TAGs 及 LINKs。這都是經 html.tpl.php 的 $head 輸出 ( $head = drupal_get_html_head() )。您可經由 hook_html_head_alter 修改其內容,比如要刪除 Drupal 的版權及版本宣告:


// 以 Bartik Theme 為例
function bartik_html_head_alter(&$head_elements){
  // dpm($head_elements); // 看看內裏有什麼怪東西

  // 找到了 'system_meta_generator' 這個不想要的傢伙
  unset($head_elements['system_meta_generator']);

  // 想增加 X-UA-Compatible,令 IE 永遠用最新的 Rendering Engine 或 Chrome Frame
  $head_elements['x_ua_Compatible'] = array(
    '#type' => 'html_tag', // 這不是必要的
    '#tag' => 'meta',
    '#attributes' => array(
      'http-equiv' => "X-UA-Compatible",
      'content' => "IE=edge,chrome=1",
    ),
  );
}

**
如果沒有效果:

一、你見鬼了;
二、忘記在 template.php 增減 FUNCTIONS 後重建 CACHES

特殊情況: 要是真的見鬼了,你可以手動修改 html.tpl.php,也不要在此留言帶給我衰氣 🙂
**

此外,也可以在 theme_preprocess_html() 或模組中利用 drupal_add_html_headdrupal_add_html_head_link 增加你想要的東西。

Drupal: 殺掉討厭的權重下拉表單

如果你像我一樣很討厭這種長長的權重下拉表單 (Weight Options),Drupal 7.11 及以後的版本能很簡單殺死它。

只要將 Drupal 中的 Variable: drupal_weight_select_max 改變就可以。(@see form_process_weight)

比如在 settings.php 中加入:$conf['drupal_weight_select_max'] = 0

或 Drush:drush vset drupal_weight_select_max 0

 

相反,如果你真的超喜歡下拉,要跟你的伺服器及瀏覽器效能作對,可以改為:$conf['drupal_weight_select_max'] = 99999999

 

Drupal Devel 模組: 你可能不知的事…

大家經常使用 dpm() 嗎?

你知不知雙擊相關的列會顯示出 PHP Array / Object ..

dd() 你用過嗎 ?

dd($node) 可以將 $node Object 寫到系統暫存資料夾 (admin/config/media/file-system) 的 drupal_debug.txt 檔案中。不要再那麼傻,自己寫 file_put_contents… 哦…
相信 Linux 下的 tail -f /tmp/drupal_debug.txt 你也會用了 🙂

ddebug_backtrace() 又如何 ?

PHP 中的 debug_backtrace 用慣了,不如試試 Drupal 下的 ddebug_backtrace(),如:


ddebug_backtrace(user_load(1));

在 Devel 的設定中,還可以更改預設的錯誤處方式哦:
[admin/config/development/devel]

看不懂 Drupal 的 DBTNG Query object ??

像這堆東西:


$query = db_select('users', 'u');
$query
  ->condition('u.uid', 0, '<>')
  ->fields('u', array('uid', 'name', 'status'))
  ->range(0, 50);

試試 dpq($query);

是不是明白多了 🙂

不用 Devel,你也可以這樣:


echo (string) $query;

是還有一些的….

待你自己發現及跟我分享啦,或者試試 Devel Demo 吧!