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');
    }

}