2013/09/04

修正 record feature 的一個 bug

已提出到 https://drupal.org/node/2081333


先說明一下這個問題的原因:

由於 record feature 會使用 array_diff_assoc 函數來比較 start / stop 之間的差異,但是該函數在比較時無法處理非 scalar 的變數,所以會造成 error 使得在 stop record 時程式中斷。

另外在 view diff 表格時,render cell 時也是無法處理非 scalar 的變數。

我提出的 Patch 主要是在修正這兩個 Bug,Patch 檔在上面的連結裡面可以下載~

2013/08/31

SOAP 在 Windows 與 Linux 上的不同行為

前些日子做了一個專案,過程中有用到 SOAP 去呼叫遠端的 Web Service。
當我把程式寫完並在自己的機器上測試通過後,佈署到客戶租用的虛擬主機上時,卻發現程式不能正常執行。
後來追蹤後發現 PHP 的 SOAP 模組,在不同平台上的行為會不一樣~

其差異點如下:
  • 在 Windows 下,執行 SoapClient::__soapCall 時,$arguments 要多包一層 array
    • 也就是說,如果在 Linux 下,$arguments 是 $data 的話;在 Windows 下要改為 array(&$data)

  • 在 Windows 下,執行 SoapClient::__soapCall 的返回值是 array 結構;而在 Linux 下則是 object 結構
    • 這部分的差異,可以參考這裡,用 objectToArray 函數來統一轉成 array 結構

2013/08/23

在 Migrate node 時匯入 body 中的圖檔

前兩天在試著用 MigrateDestinationMedia 來匯入在 Body 中的圖檔,不過一直無法匯入多個檔案。

後來問了 Google 大神,請教先進,追了程式碼之後,還是不曉得該如何直接利用 MigrateDestinationMedia 來直接匯入多個檔案。

所以今天我研究了一下,寫了一個 FileMigration 類別來幹這件事。

有一些事要注意:
  • 需要 simplehtmldom API 來做 html 碼的解析,以取出 img 標籤做進一步的處理
  • 匯入的檔案的目錄結構,第一層必須是以 Drupal 的 language code 命名
  • 可以在 prepareRow 或是 prepare 中呼叫 parseContentImage 來處理需要處理的欄位
  • 需要在 prepare 中自行呼叫 MigrateDestinationMedia::rewriteImgTags 來改寫img 標籤

類別原始碼:



class FileMigration extends Migration {

    const FAILURE = 0;
    const SUCCESS = 1;

    /**
     * @var array
     */
    protected $import_file_number = array(self::FAILURE => 0, self::SUCCESS => 0);

    /**
     * @param string $content
     * @param string $language
     * @return string
     */
    protected function parseContentImage($content, $language) {
        if (empty($content)) {
            return '';
        }

        $html = str_get_html($content);

        foreach ($html->find('img') as $image) {
            $source = $image->attr['src'];

            if (valid_url($source, TRUE)) {
                continue;
            }

            $format_source = $this->formatImageSource($source, $language);
            $file = $this->defaultFile();
            $file->value = $format_source;
            $file->destination_file = $format_source;
            $file->field_file_image_alt_text = (empty($image->attr['alt'])) ? '' : $image->attr['alt'];
            $file->field_file_image_title_text = (empty($image->attr['title'])) ? '' : $image->attr['title'];

            try {
                $this->processFile('image', $file);
                $image->attr['src'] = $this->formatUrl($file->destination_dir . '/' . $format_source);
            } catch (Exception $exception) {
                $this->import_file_number[self::FAILUE]++;
                $this->saveMessage($exception->getMessage());
            }
        }

        return $html->save();
    }

    protected function formatUrl($uri) {
        $scheme = file_uri_scheme($uri);

        if (FALSE === $scheme) {
            return ('/' == drupal_substr($uri, 0, 1)) ? $uri : '/' . drupal_encode_path($uri);
        }

        if ($scheme == 'http' || $scheme == 'https') {
            return $uri;
        }

        $wrapper = file_stream_wrapper_get_instance_by_uri($uri);

        if (FALSE == $wrapper) {
            return '';
        }

        if (FALSE == is_subclass_of($wrapper, 'DrupalLocalStreamWrapper')) {
            return $wrapper->getExternalUrl();
        }

        return '/' . $wrapper->getDirectoryPath() . '/' . file_uri_target($uri);
    }

    /**
     * @param string $source
     * @param string $language
     * @return string
     */
    protected function formatImageSource($source, $language) {
        $part = explode('/', $source);
        $und = FALSE;

        while (true) {
            $first = trim(reset($part));

            if (empty($first) || ('..' == $first)) {
                array_shift($part);
                $und = TRUE;
            } else {
                if ($und) {
                    array_unshift($part, LANGUAGE_NONE);
                } else {
                    array_unshift($part, $language);
                }

                break;
            }
        }

        return implode('/', $part);
    }

    /**
     * @param string $type
     * @param stdClass $file
     * @return array
     */
    protected function processFile($type, stdClass $file) {
        if (empty($file->type)) {
            $file->type = $type;
        }

        if (FALSE == isset($file->uid)) {
            $file->uid = 1;
        }

        if (isset($file->timestamp)) {
            $timestamp = MigrationBase::timestamp($file->timestamp);
        }

        $file->preserve_files = FALSE;
        $source = new MigrateFileUri((array) $file, $file);
        $file = $source->processFile($file->value, $file->uid);

        if (is_object($file) && isset($file->fid)) {
            if (isset($timestamp)) {
                db_update('file_managed')
                    ->fields(array('timestamp' => $timestamp))
                    ->condition('fid', $file->fid)
                    ->execute();
                $file->timestamp = $timestamp;
            }

            $this->import_file_number[self::SUCCESS]++;
            $return = array($file->fid);
        } else {
            $this->import_file_number[self::FAILURE]++;
            $return = FALSE;
        }

        return $return;
    }

    /**
     * @return stdClass
     */
    protected function defaultFile() {
        $file = new stdClass();
        $file->destination_dir = 'public://';
        $file->urlencode = FALSE;
        return $file;
    }

    protected function postImport() {
        parent::postImport();
        $total = array_sum($this->import_file_number);

        if (0 >= $total) {
            return;
        }

        $message = t("Imported !numitems files (!succeed succeed, !failed failed) - done with '!name'", array(
            '!numitems' => $total,
            '!succeed' => $this->import_file_number[self::SUCCESS],
            '!failed' => $this->import_file_number[self::FAILURE],
            '!name' => $this->machineName,
        ));

        self::displayMessage($message, 'completed');
    }

}



2013/08/14

使用 Demonstration site 取代 Backup and Migrate

在建立 Drupal 網站的時候,我們會花很多在測試模組上,這時候我們會需要一個能夠快速備份還原資料庫的功能~

在 Drupal 模組中,有兩個模組可以做這件事,一個是 Backup and Migrate 模組,另外一個是 Demonstration site 模組。

然而 Backup and Migrate 有個致命的缺點:那就是在還原資料庫時,他不會先 Drop 掉所有的資料表再還原。

這個缺點會有什麼影響呢?試想下列情況:
  1. 用 Backup and Migrate 備份 =>Test Backup
  2. enable 某個會建立資料表的模組 (例如: Commerce Cart) => Test Module
  3. 用 Backup and Migrate 還原 Test Backup
  4. enable Test Module
在第二次 enable Test Module 時,會發生資料表已存在的錯誤
通常我們在使用還原功能時,並不會有習慣去 disable & uninstall 模組,所以這個缺點還蠻要命的~

另外 Backup and Migrate 還有個問題就是只支援 MySQL / MariaDB,其他的資料庫並不支援;不過這個問題我個人是覺得還好,畢竟比較常用的也是 MySQL。

最後要說一下 Demonstration site 模組的問題:
  • 目前 7.x-1.0 這個版本有問題,不能正常還原,要用 7.x-1.x-dev 的版本才行
  • Demonstration site 的更新沒有 Backup and Migrate 勤快
  • 設定也比較簡單,沒有備份檔案或是其他的進階功能
  • 在 bug report 那邊也是有 bug 擺了半年以上沒人處裡

2013/08/12

配合 Drupal Media 的 Player

直接講結論:還是 JW Player 最好~

我測試的結果,播放器有以下的問題 (至少有一點):
  • 沒有支援 Flash fallback (純 HTML 5 的播放器都沒有)
  • 沒辦法支援 Embedded Media Field
  • 明明把長寬尺寸設定好,大小還是不對
目前測到沒問題的就只有 JW Player~

下載:

2013/08/07

Drupal Media 的檔案管理

在 Drupal 中,檔案的管理始終是一個麻煩的問題。

以前我使用 File Field 來做檔案管理,可是這種方式,在碰到使用編輯器插入圖片或其他檔案時,沒辦法使用 File Field Paths 來變更目錄與檔名;必須禁用編輯器的插入功能,先把檔案上傳到另外的 File Field 欄位中,再配合 Insert 模組來插入,實在很不方便。

跟 File Field 最大不同是,Media 的檔案管理方式,是以檔案的種類為基礎作集中式的管理。
Media 預設的檔案種類有四類:Image、Video、Audio、Document
在使用上配合 File Entity Paths,可以對 Body 欄位插入的檔案做管理。

目前我使用 3.6.X 的 CKEditor 編輯器,不用 4.X 的版本是因為 MediaBrowser 的 Plugin 會有問題。

使用 MediaBrowser 來插入圖片時,可以重複利用之前上傳的檔案,插入時也可以選擇不同的 Display 方式,彈性很大。

不過預設的 Default 顯示方式並沒有設定好,可以在

admin/structure/file-types/manage/image/file-display

找到設定介面,勾選 Enabled displays 裡面的 Image,儲存後再插入圖片時就會顯示出來。



2013/07/21

一顆硬碟,一個分割區

在過去常有朋友問我,要怎麼分割硬碟,每個分割區多少空間比較好?
其實在每個階段,我給出的答案都不一樣。
時至今日,如果你問我,我自己又是如何做的呢?

目前我的電腦上的硬碟是這樣配置的:
  • 一顆 SSD (64G):用來放系統
  • 一顆 WD 黑標 (500G):用來放一般 HTTP、FTP 下載回來的東西 (通常檔案較小)、免安裝軟體、其他我自己或程式產生的資料、音樂、圖檔等等大雜燴
  • 一顆 WD 紅標 (2T):用來放 BT、eMule、迅雷等等下載回來的檔案倉庫
對了,我還有一個 NAS 用來放那個檔案倉庫裝不下的東西~
我個人算是有輕微的收集癖好,所以要用到 NAS 來放,如果你時間很多且勤於整理的話,不用 NAS 也是可以~

在這裡還有一個重點,那就是這三顆硬碟都是 MBR / One Primary Partition,即標題所說的:一顆硬碟,一個分割區。

現今硬碟已經很便宜,主機板上的晶片組支援的 SATA3 也很多,不需要折磨自己只用一顆硬碟切來切去。

分割硬碟不如做好目錄管理比較重要。