2008/06/22

讓 Drupal 的 Node Import 模組支援 option widget

Node Import 模組是個非常好用的模組,可以把各種類型的 Node 資料先用打好,存成 csv 檔然後匯入。
不過對於自設的 CCK 欄位有個缺憾,就是在匯入之前必須先把 widget 的型態,由 option (select list / checkbox / radio) 改成 text,這樣才能匯入,實在是不太方便。
我修改了一點程式,讓它可以不用轉換 widget 的型態也能匯入,不過有個限制,目前修改的方式,只支援 Allowed values list 為下列形式:
value1
value2
vaule3

不支援 Allowed values list 為下列形式:
key1|value1
key2|value2
key3|value3

這是要注意的地方。

修改方式:
找出 node_import/supported/cck/content.inc 中的 function content_node_import_prepare
在該函數接近結尾的地方找到:

        // Unset the dummy column value.
        unset($node->$dummy_name);


改成:

        $option_type = array('options_select' => true, 'options_onoff' => true, 'options_buttons' => true);
        if (isset($option_type[$field['widget']['type']])) {
            $keys = array();
            foreach($node->$field_name as $value_list) {
               $keys[] = $value_list['value'];
            }
            if ($field['multiple'] || $field['widget']['type'] == 'options_onoff') {
               $node->$field_name += array('keys' => $keys);
            } else {
               $node->$field_name += array('key' => reset($keys));
            }
        }

        // Unset the dummy column value.
        unset($node->$dummy_name);


這樣就可以了~
報錯在這裡

2008/06/14

Drupal 的 Date 模組的一個小問題

目前首上有一個案子,要作一些統計的處理,其中有一向是關於日期欄位的。
我用 Drupal 的 Views 模組搭配 Date 模組來作,於 Argument 指定該日期欄位,這樣就可以顯示出我要的結果。
不過 Date 模組中有個小問題,那就是在生成 SQL 命令時,會產生錯誤,其錯誤的理由是,日期欄位的名稱被設定成 range ,而剛好在 MySQL 中 range 是保留字,必須用 `range` 才能使用該名稱。
所以我修改了 date_views.inc 中的 _date_views_argument_range_handler 函數

找到:
$fieldinfo['fieldname'] = 'range';

改成:
$fieldinfo['fieldname'] = '`range`';

這個問題於 Date 模組 V1 跟 V2 的版本都會發生,報錯在這裡

2008/06/03

擴充 DateTime 類別

今天在處理一個日期比較的問題,發現 DateTime 類別可以進行 1970/01/01 以前的日期比較,不過由於該類別建構式比較嚴格,所以我將他繼承後擴充改寫了一番。
程式碼在下面:

class Zyme_DateTime extends DateTime{
    function __construct($時間 = null, $時區 = null){
        if ($時間 instanceof DateTime) {
            $_時間 = $時間->format('c');
        } else if (is_int($時間)) {
            $_時間 = '@' . $時間;
        } else {
            $_時間 = (string)$時間;
        }
           
        if (is_null($時區)) {
            parent::__construct($_時間);
        } else {
            $_時區 = self::時區($時區);
            parent::__construct($_時間, $_時區);
            $this->setTimezone($_時區);
        }
    }
   
    function parse(){
        return date_parse($this->format('c'));
    }

    function 時戳(){
        return (int)$this->format('U');
    }
   
    function 凌晨(){
        $this->setTime(0,0,0);
        return $this;
    }

    function 午夜(){
        $this->setTime(23,59,59);
        return $this;
    }
   
    static function 時區($時區 = null){
        if ($時區 instanceof DateTimeZone) {
            return $時區;
        } else if (is_null($時區)) {
            $_時區 = date_default_timezone_get();
        } else if (is_numeric($時區)) {
            $_時區 = self::數字時區($時區);
        } else {
            $_時區 = self::字串時區($時區);
        }
        return new DateTimeZone($_時區);
    }

    static protected function 數字時區($時區){
        $_時區 = (Zyme_Filter::數字範圍($時區, -12, 12))
            ? (int)((float)$時區 * 3600)
            : (int)$時區;
        $_表格 = self::時區位移表格();
        $_輸出 = &$_表格[$_時區];
        return (isset($_輸出))
            ? $_輸出
            : 'UTC';
    }
   
    static protected function 字串時區($時區){
        $_表格 = self::時區識別表格();
        $_時區 = (string)$時區;
        return (isset($_表格[$_時區]))
            ? $_時區
            : 'UTC';
    }
   
    static function 時區識別表格(){
        static $_時區 = array();
        if (0 === count($_時區)) {
            $_時區 = array_flip(DateTimeZone::listIdentifiers());
        }
        return $_時區;
    }
         
    static function 時區位移表格(){
        static $_時區 = array();
        if (0 !== count($_時區)){
            return $_時區;
        }
        $_表格 = DateTimeZone::listAbbreviations();
        foreach ($_表格 as $_項目){
            foreach ($_項目 as $_內容) {
                if (false === isset($_時區[$_內容['offset']])) {
                    $_時區[$_內容['offset']] = $_內容['timezone_id'];
                }
            }
        }
        ksort($_時區);
        return $_時區;
    }   

}

2008/05/11

替代 xdebug 的報錯程式


xdebug 的報錯功能非常好用,但由於沒有 nts 的版本,一直以來是我心中的缺憾。
這兩天花了一點時間研究,寫出一個替代用的報錯物件,除了時間和記憶用量無法顯示外,其他都可以做到接近 xdebug 的報錯功能。
我還加上顯示出現錯誤的源碼片段、傳入參數的功能,這個物件還可以繼續改加以完善。

用法:
Zyme_Error::除錯(true);
Zyme_Error::錯誤處理(true);

原始碼在下面:
<?php
final class Zyme_Error {
    static private $除錯 = false;
    static private $詳細 = false;
    static private $間距 = 2;
   
    static function 除錯($除錯 = null){
        $_除錯 = self::$除錯;
        if (false == is_null($除錯)) {
            self::$除錯 = (bool)$除錯;
        }
        return $_除錯;
    }
   
    static function 錯誤處理($設定 = true, $詳細 = false, $間距 = 2){
        static $處理 = false;
        if ($設定) {
            if (false == $處理) {
                set_error_handler(array(__CLASS__, '報錯'));
                $處理 = true;
            }
        } else {
            restore_error_handler();
            $處理 = false;
        }
        self::$詳細 = (bool)$詳細;
        $_間距 = (int)$間距;
        self::$間距 = (0 > $_間距) ? 0 : $_間距;
    }

    static function 報錯($代碼, $訊息, $檔案 = '', $行號 = '', $內容 = array()){
        static $預設 = array('function' => '', 'line' => '', 'file' => '', 'class' => '', 'object' => '', 'type' => '', 'args' => array());
        if (false == self::$除錯 || false == (error_reporting() & $代碼)) {
            return true;
        }
        $除錯 = array_reverse(debug_backtrace());
        array_pop($除錯);

        $輸出 = array();
        $輸出[] = '<table bgcolor="#eeeeec" border="1px" cellspacing="0px" cellpadding="0px">';
        $輸出[] = '<tr><th colspan="3" style="background:#f57900; text-align:left; padding:1ex">' . self::錯誤代碼($代碼) . ' : ' . $訊息 . '</th></tr>';
        $輸出[] = '<tr><td colspan="3" style="padding:1ex">' . $檔案 . '<strong> : </strong>' . $行號 . self::源碼($檔案, $行號) . '</td></tr>';
        $輸出[] = (empty($除錯)) ? '' : '<tr style="background:#e9b96e"><th>#</th><th>Function</th><th>Location</th></tr>';
        foreach ($除錯 as $_索引 => $_內容){
            $輸出[] = self::項目繪製($_索引, $_內容);
        }
        $輸出[] = '</table>';
        echo implode("\n", $輸出), '<br />';
        return true;
    }

    static private function 引用($內容) {
        $_內容 = trim($內容);
        return (empty($_內容)) ? '&nbsp;' : htmlspecialchars($內容, ENT_COMPAT, 'utf-8');
    }

    static private function 參數($參數) {
        return (empty($參數) || false == self::$詳細) ? '' : '<pre style="background:#ffffff; margin:0px; padding:1ex; overflow:auto">' . self::引用(print_r($參數, true)) . '</pre>';
    }

    static private function 源碼($檔案, $行號) {
        static $斷行 = "<br />";
        if (false == is_readable($檔案) || false == self::$詳細) {
            return '';
        }
        $源碼 = explode($斷行, substr(highlight_file($檔案, true), strlen('<code>'), -1 * strlen('</code>')));
        $行數 = count($源碼);
        $_行號 = $行號 - 1;
        $開始 = ($_行號 - self::$間距);
        $結束 = ($_行號 + self::$間距);
        $_開始 = (0 > $開始) ? 0 : $開始;
        $_結束 = ($行數 < $結束) ? $行數 : $結束;
        $格式 = '<div style="color:#000000; display:inline">%0' . ceil(log10($行數)) . 'd<strong> : </strong></div>%s';
        $輸出 = array();
        for ($索引 = $_開始; $索引 <= $_結束; $索引++){
            $_格式 = ($_行號 == $索引) ? '<div style="background:#00ff00; display:inline">' . $格式 . '</div>' : $格式;
            $輸出[] = sprintf($_格式, $索引 + 1, $源碼[$索引]);
        }
        return '<div style="background:#ffffff; padding:1ex; overflow:auto">' . implode($斷行, $輸出) . '</div>';
    }

    static private function 項目繪製($索引, $項目){
        static $預設 = array('function' => '', 'line' => '', 'file' => '', 'class' => '', 'object' => '', 'type' => '', 'args' => array());
        $輸出 = array();
        $_項目 = $項目 + $預設;
        if (empty($_項目['file'])) {
            return '';
        }
        $調用 = (empty($_項目['type'])) ? $_項目['function'] : $_項目['class'] . $_項目['type'] . $_項目['function'];
        $位置 = (empty($_項目['file'])) ? '' : self::引用($_項目['file']) . '<strong> : </strong>' . self::引用($_項目['line']);
       
        $輸出[] = '<tr>';
        $輸出[] = '<td style="padding:1ex; vertical-align:top; text-align:center">' . $索引 . '</td>';
        $輸出[] = '<td style="padding:1ex; vertical-align:top">' . $調用 . self::參數($_項目['args']) . '</td>';
        $輸出[] = '<td style="padding:1ex; vertical-align:top">' . $位置 . self::源碼($_項目['file'], $_項目['line']) . '</td>';
        $輸出[] = '</tr>';
       
        return implode("\n", $輸出);
    }
       
    static private function 錯誤代碼($代碼){
        static $錯誤 = array(
            E_ERROR                            =>    'Error',
            E_WARNING                        =>    'Warning',
            E_PARSE                            =>    'Parse',
            E_NOTICE                        =>    'Notice',
            E_CORE_ERROR                =>    'Core Error',
            E_CORE_WARNING            =>    'Core Warning',
            E_COMPILE_ERROR            =>    'Compile Error',
            E_COMPILE_WARNING        =>    'Compile Warning',
            E_USER_ERROR                =>    'User Error',
            E_USER_WARNING            =>    'User Warning',
            E_USER_NOTICE                =>    'User Notice',
            E_STRICT                        =>    'Strict',
            E_RECOVERABLE_ERROR    =>    'Recoverable Error',
        );
        $輸出 = &$錯誤[$代碼];
        return (isset($輸出)) ? $輸出 : 'Unknown Error';
    }   

}
?>

2008/04/25

SugarCRM 的 射、拔、追

今天我的老闆交代我在 Server 上裝一份新的 SugarCRM ,原以為是個很簡單的事情,結果花了一整天~
原因出在 SugarCRM 在安裝過程中,檢查 MySQL 連線設定那部份一直過不了,可是在同一台機器上,另外一份之前安裝過的 SugarCRM 卻跑的好好的~

我以為是 PHP 版本問題,從 PHP4 升級、換成 PHP5 都不行~
後來以為是 MySQL 的問題,重裝、換了 5.1 的版本也不行~
最後只好使出最終奧義:在 Windows 上裝好後再移過去...

這下終於可以動了,不過我還是不曉得 SugarCRM 的安裝程式出了什麼問題。

另外,我在 Windows 下安裝程序最後會發生 500 錯誤,只要把 SugarCRM 目錄下的 .htaccess 這個檔案刪除即可正常執行~

2008/04/15

重構插件類別

算是在實做過程中發現一些問題,然後重構這個強力工具!

我知道我目前碰到一個危險的境界,由於這個插件的類別非常強力,我經常拿他來替代繼承,我知道有點過頭了,但它真的很好用!
~我是說你可以維持一個物件變數,然後把需要功能一直掛入(只要你繼承這個抽象的插件類別,寫出你需要的功能)~

這幾天一直為了unset前後記憶用量沒有改變的問題無法釋懷,後來問了 kiang,他表示那是 Zend 引擎還沒到記憶體回收的週期,所以不會釋放~
這個類別的 __更新() 方法有一個地方我一直無法瞭解為何會有這個效果,知道的網友請開示吧~

另外一提,ADODB Lite 目前仍然是記憶用量最小的 DB Layer ,我自己寫的這個 DB Layer 跟它比較,兩邊都不掛任何多餘的模組,硬是比它大了約 50 K...
我該檢討了~


<?php
abstract class Zyme_Class_Plugin{
    private $_插件 = array();
    private $_方法 = array();
    private $_反映 = array();

    function __construct($宿主 = null){
        static $類別 = __CLASS__;
        if ($宿主 instanceof $類別) {
            $this->__插件($宿主);
        }       
    }
   
    final function __call($方法, $參數){
        return call_user_func_array($this->_方法[$方法], $參數);
    }

//    清除插件方法
    final protected function __清除(){
        $this->_插件 = array();
        $this->_方法 = array();
        $this->_反映 = array();
    }   

//    測試某個插件是否已安裝
    final protected function __已裝($類別){
        return isset($this->_插件[$類別]);
    }
   
//    將插件的方法加入$this的方法中
    final protected function __插件(Zyme_Class_Plugin $插件){
        $類別 = get_class($插件);
        if (false == $this->__已裝($類別)){
            $this->_插件[$類別] = $插件;
            $this->_方法 = $插件->__反映() + $this->_方法;
        }
    }

//    取得插件的可呼叫方法列表
    final function __方法(){
        return $this->_方法;
    }

//    取得插件本身的方法列表(除了方法名稱開頭是'__'的方法,例如一些魔術方法,或是方法本身屬於私有方法)
    final private function __反映(){
        $輸出 = &$this->_反映;
        if (false == empty($輸出)) {
            return $輸出;
        }
        $反映 = new ReflectionObject($this);
        $方法 = $反映->getMethods();
        foreach ($方法 as $項目){
            $名稱 = $項目->getName();
            if (0 !== strpos($名稱, '__') && false == $項目->isPrivate()) {
                $輸出[$名稱] = array($this, $名稱);
            }
        }
        return $輸出; 
    }

//    匯入插件的可呼叫方法表
    final function __匯入(Zyme_Class_Plugin $宿主){
        $this->_方法 = $宿主->__方法() + $this->_方法;
    }

//    更新所有插件的方法,通常在載入所有插件後調用
    final protected function __更新(){
        foreach ($this->_插件 as $插件){           
            $插件->_方法 = $this->_方法 + $插件->_方法;    //    照理說,這行應該會出現錯誤(存取私有成員),但不知為何可以執行
//            $插件->__匯入($this);    //    萬一未來版本上面那行無法執行時,可以改用這行
        }
    }
   
}

?>


2008/04/07

開發平台暫時維持在 PHP 5.2.5

由於名稱空間的諸多限制,以及 PHP 5.3 的 call_user_func_array 傳遞參照參數陣列 與 &new 的 Deprecated 警告問題,個人決定還是先將開發平台維持在 PHP 5.2.5 上面。
名稱空間的限制還好,但後面那兩個會造成許多現有的函式庫噴出一堆警告訊息,我沒時間一個一個去修,所以只好暫時放棄,等年底 PHP 5.3 出來一陣子後再看看。