a-blog cmsでPDFファイルのサムネイル画像を生成してみる【MAMP編】

**ImageMagick** と **Ghostscript** を使って、a-blog cmsでPDFファイルをアップロードするときに、サムネイル画像を生成する方法を実装してみました。

お手本）[【a-blog cms】アップロードしたPDFのサムネイルを作成する](http://atsu666.com/ablogcms/entry-90.html)

基本はお手本サイト通りなのですが、環境設定に苦労しました。黒い画面が苦手なデザイナーが試した長い道のりの備忘録です。

まずはMacのMAMPでテスト。
環境は OS X 10.9.5 (Mavericks) / MAMP 2.2 (PHP 5.4.19) / a-blog cms 2.6.0.1 です。

**【Mac OS / MAMP のバージョンアップについて】**
MAMP 4.0.2（おそらく 3.0 以降？）では、php.iniにエクステンションを追記する（コメントアウトされているのでコメントを外す）ところからで Imagick を有効にできました。それでもエラーが出る場合は、[XQuartz](https://support.apple.com/ja-jp/HT201341) のインストールが必要かもしれません。

## 設定の流れ

1. 作業準備
2. 必要なツール等のインストール
3. a-blog cmsのカスタマイズ
4. アップロード！

## Step1: 作業準備
まずは、 作業をしやすくするためにパッケージマネージャー [Homebrew](http://brew.sh) をインストールします。
ターミナルを起動し、公式サイトのコマンドを実行します。

```
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```

インストールできたか確認してみます。

```
$ brew doctor
```

Warningが出たら書いてある通りに解決してゆきます。
（わたしの場合はXcodeのアップデートが必要でした）

```
Your system is ready to brew.
```

と表示されれば準備完了です。

## Step2: 必要なツール等のインストール
サーバ側に以下の3つを準備します。

- [ImageMagick](http://www.imagemagick.org)
- [Ghostscript](http://www.ghostscript.com) - PostScriptやPDFなどのページ記述言語用インタプリタ（Adobe製）
- [Imagick](http://php.net/manual/ja/book.imagick.php) - ImageMagickを操作するためのPHP拡張モジュール

Homebrewでパッケージをインストールしてゆきます。

### 1. Ghostscript

```
$ brew install ghostscript
```

### 2. ImageMagick
オプションにGhostscriptを指定。

```
$ brew install imagemagick --with-ghostscript
```

インストールのオプションを調べるコマンドはこちら。

```
$ brew options imagemagick
```

### 3. Imagick（PHP拡張モジュール）
peclコマンドを使います。

```
$ pecl install imagick
```

わたしの場合、「 **-bash: pecl: command not found** 」と怒られたので、PEARをインストールしました。

```
$ cd /tmp && mkdir pear && cd pear
$ curl -O http://pear.php.net/go-pear.phar
$ sudo php -d detect_unicode=0 go-pear.phar
```

PEAEのインストール先はこんな感じ（わたしは1と4を変更しました）。

```
 1. Installation base ($prefix)                   : /usr/local/pear
 2. Temporary directory for processing            : /tmp/pear/install
 3. Temporary directory for downloads             : /tmp/pear/install
 4. Binaries directory                            : /usr/local/bin
 5. PHP code directory ($php_dir)                 : /usr/local/pear/share/pear
 6. Documentation directory                       : /usr/local/pear/docs
 7. Data directory                                : /usr/local/pear/data
 8. User-modifiable configuration files directory : /usr/local/pear/cfg
 9. Public Web Files directory                    : /usr/local/pear/www
10. System manual pages directory                 : /usr/local/pear/man
11. Tests directory                               : /usr/local/pear/tests
12. Name of configuration file                    : /private/etc/pear.conf
```

**php.ini** ファイルを作成。

```
$ sudo cp /etc/php.ini.default /etc/php.ini
```

**php.ini** ファイルを開き、インクルードパスを追加。
（ PEARインストール時の **5. PHP code directory ($php_dir)** のパスを追加します）

```
include_path = ".:/php/includes:/usr/local/pear/share/pear"
```

**autoconf** がないと怒られたのでインストール。

```
$ brew install autoconf
```

さらにエラーが…（心折れそう

```
1 error generated.
make: *** [imagick.lo] Error 1
ERROR: `make' failed
```

わたしの環境では **php54-imagick** を使わなきゃいけないみたいなので、以下のコマンドを順に実行。

```
$ brew remove imagemagick
$ brew update && brew upgrade
$ brew install homebrew/php/php54-imagick
```

ここまででインストール作業は完了。のはず。

### 4. MAMPのphp.iniにエクステンションを追記

**/Applications/MAMP/bin/php/php5.4.19/conf/php.ini** に以下の記述を追加しました。

```
extension=imagick.so
```

imagick.soファイルをMAMPのextensionsディレクトリにコピーするのも忘れずに。
（コピー元: **/usr/local/Cellar/php54-imagick/3.3.0_1/imagick.so** もしくは **/usr/local/Cellar/php54/5.4.45_3/lib/php/extensions/no-debug-non-zts-20100525/imagick.so** ）

ここで phpInfo を確認してみると imagick がありません。
ライブラリのパスが通っていないことが原因らしい。

> Comment out the 2 lines in the file /Applications/MAMP/Library/bin/envvars: 
> 
> DYLD_LIBRARY_PATH="/Applications/MAMP/Library/lib:$DYLD_LIBRARY_PATH" 
> export DYLD_LIBRARY_PATH

**/Applications/MAMP/Library/bin/envvars** ファイルを開いて上記2行をコメントアウトすると書いてありました。

わたしの場合、6行くらいあったのですべてコメントアウト。さらに1行追加しました。
最後の1行が抜けていると画像生成できません。

```
# if test "x$DYLD_LIBRARY_PATH" != "x" ; then
#   DYLD_LIBRARY_PATH="/Applications/MAMP/Library/lib:$DYLD_LIBRARY_PATH"
# else
#   DYLD_LIBRARY_PATH="/Applications/MAMP/Library/lib"
# fi
# export DYLD_LIBRARY_PATH
#
export PATH="$PATH:/usr/local/bin"
```

もう一度 phpInfo を見てみると……はい！

![imagick_3.3.0.png](https://qiita-image-store.s3.amazonaws.com/0/111287/30265e2a-2d35-3b03-de90-78ea3aeb3605.png)

これでようやく環境が整いました。
（MAMP PRO 3.0 では ImageMagick が使えるようになっているとか…？）

## Step3: a-blog cmsのカスタマイズ
### 1. カスタムフィールドを作成
**/admin/entry/field.html** にPDFファイルをアップロードするカスタムフィールドを記述します（お好みでincludeファイルに）。

[カスタムフィールドメーカー](http://developer.a-blogcms.jp/tools/) でファイルのカスタムフィールドを作成し、画像ファイルのパス ( /images/fileicon/pdf.gif ) を変数 ( **%{ARCHIVES_DIR}{pdfThumbnail}** ) 変更します。

```html
<table class="entryFormTable acms-admin-table-entry">
	<tr>
		<th>PDFファイル</th>
		<td>
			<!-- BEGIN pdfFile@path:veil -->
			<input type="hidden" name="pdfFile@old" value="{pdfFile@path}" />
			<input type="hidden" name="pdfFile@secret" value="{pdfFile@secret}" />
			<input type="hidden" name="pdfFile@fileSize" value="{pdfFile@fileSize}" />
			<label for="input-checkbox-pdfFile@edit" class="acms-admin-form-checkbox">
				<input type="checkbox" name="pdfFile@edit" value="delete" id="input-checkbox-pdfFile@edit" />
				<i class="acms-admin-ico-checkbox"></i> 削除
			</label>
			<a href="%{ARCHIVES_DIR}{pdfFile@path}"><img src="%{ARCHIVES_DIR}{pdfThumbnail}" width="120" alt="" /></a>
			<!-- END pdfFile@path:veil -->
			<input type="file" name="pdfFile" />
			<input type="hidden" name="field[]" value="pdfFile" />
			<input type="hidden" name="pdfFile@baseName" value="{pdfFile@baseName}" />
			<input type="hidden" name="pdfFile:extension" value="file" />
			<input type="hidden" name="pdfFile@extension" value="pdf" />
		</td>
	</tr>
</table>
```

### 2. Hookを有効に設定
**config.server.php** でフック拡張を有効（0→1）に変更。

```php
define('HOOK_ENABLE', 1);
```

### 3. 独自処理の追加
**/php/ACMS/User/Hook.php** の **beforePostFire** function内に以下のコードを追加（[お手本サイト](http://atsu666.com/ablogcms/entry-90.html)よりコピペ）。

```php
public function beforePostFire($thisModule)
{
    if ( !sessionWithContribution() ) return false;  // 権限チェック

    $class      = get_class($thisModule);  // モジュールのクラス名取得
    $destDir    = 'thumbnail/';            // サムネイルを保存するディレクトリ名
    $fieldName  = 'pdfFile';               // アップロードするPDFのフィールド名
    $thumbnailFieldName = 'pdfThumbnail';  // サムネイル画像の変数名

    // エントリーの作成or更新のみ
    if ( !in_array($class, array(
        'ACMS_POST_Entry_Insert',
        'ACMS_POST_Entry_Update'
    )) ) {
        return false;
    }

    $Post   =& $thisModule->Post;  // POSTデータを取得
    $bid    = $Post->get('bid');
    $cid    = $Post->get('cid');

    if ( !isset($_FILES[$fieldName]['tmp_name']) ) return false;

    $source = $_FILES[$fieldName]['tmp_name'];
    if ( !is_readable($source) ) return false;

    $myurl  = $source.'[0]';  // PDFの1ページを指定

    try {
        $image  = new Imagick();
        $image->setResolution(140, 140);  // 解像度を指定
        $image->readimage($myurl);        // PDFの読み込み
        $image->setImageFormat('png');

        ACMS_POST::setupDir(ARCHIVES_DIR.$destDir, intval(config('permission_dir')));  // 保存先ディレクトリの作成
        $file   = uniqueString().'.png';        // 保存ファイル名
        $dest   = ARCHIVES_DIR.$destDir.$file;  // 保存先

        $image->writeImage($dest);
        $image->destroy();

        // カスタムフィールドに登録する為、POSTデータにサムネイル画像のパスを登録
        $Post->add('field', $thumbnailFieldName);
        $Post->add($thumbnailFieldName, $destDir.$file);
    } catch(Exception $e) {
        echo $e->getMessage();
    }
}
```

【教訓】「 Parse error: syntax error, unexpected T_VARIABLE 」と怒られたら、当該lineの上にセミコロンが抜けていたり全角スペースがある可能性があります。

## Step4: アップロード！
カスタムフィールドでPDFファイルをアップロードしてみます。
作成したエントリーを確認してみると

![customfield.png](https://qiita-image-store.s3.amazonaws.com/0/111287/786b919c-15ce-a759-47b5-7d69e24d59c4.png)

画像が生成されていました！

表示用のテンプレートにも以下のコードを追加してみます。

```html
<!-- BEGIN pdfFile@path:veil -->
<a href="%{ARCHIVES_DIR}{pdfFile@path}" target="_blank"><img src="%{ARCHIVES_DIR}{pdfThumbnail}" width="120" alt="" /></a>
<!-- END pdfFile@path:veil -->
```

![entry.png](https://qiita-image-store.s3.amazonaws.com/0/111287/ad125eac-fcdc-a93a-165d-2a05f1d452ae.png)

表示されました！

サムネイル画像は **/archives/thumbnail** の中に保存されます（Hook.phpで設定）。

## おわりに
現状、問題点がいくつかあります。

- エントリーを編集すると画像が消えてしまう [→ 解決案：追記2へ](#追記2エントリー編集後も画像を保持する)
- PDFのファイル名と生成したサムネイルのファイル名が連動していない [→ 解決案：追記へ](#追記問題点の解決案)
- PDFファイルを差替えても元のサムネイル画像は削除されずに残る

便利に運用するにはもう少しカスタマイズが必要かもしれません。

## 〈追記〉問題点の解決案
PDFのファイル名を元のファイル名でアップロードする方法を見つけたので少しカスタマイズしてみました。

### 1. PDFファイルを元のファイル名でアップロード
**/php/ACMS/User/Hook.php** に次のコードを追加します（自己責任）。

```php
switch($class) {
    case 'ACMS_POST_Entry_Insert':
    case 'ACMS_POST_Entry_Update':

    $thisModule->Post->_aryField['pdfFile@filename'][0] = 'pdf/'.$_FILES['pdfFile']['name'];

    break;
}
```

参考）[カスタムフィールドでアップするファイル名をアップ元のファイル名にする - 気づいたら、ここだった](http://taka4.hateblo.jp/entry/2014/01/24/170207)

ファイル名が元のファイル名になっているのを確認しました。

### 2. サムネイル画像をPDFファイル名と連動
ランダムな文字列だった画像のファイル名を **元のファイル名.png** にします。

```php
$file   = $_FILES['pdfFile']['name'].'.png';
```

元のファイル名には拡張子が含まれるため ***filename.pdf*.png** のような名前になります。
画像のファイル名が固定されたので、アップし直しても画像が増えていくことはなくなりました。

### 3. サムネイル画像の保存先を変更
サムネイル画像が /archives の直下にあるのが気になったので、PDFファイルと同じ階層に変更します。

```php
$destDir    = sprintf('%03d', BID).'/thumbnail/';
```

もとのディレクトリに合わせて数字3桁になるように0で埋めます。
これで、ブログIDが1のときは **/archives/001/thumbnail** にサムネイル画像が保存できるようになりました。

最終的なコードはこんな感じに。

```php
public function beforePostFire($thisModule)
{
    if ( !sessionWithContribution() ) return false;  // 権限チェック

    $class      = get_class($thisModule);              // モジュールのクラス名取得
    $destDir    = sprintf('%03d', BID).'/thumbnail/';  // サムネイルを保存するディレクトリ名
    $fieldName  = 'pdfFile';                           // アップロードするPDFのフィールド名
    $thumbnailFieldName = 'pdfThumbnail';              // サムネイル画像の変数名

    // PDFファイルを元のファイル名に
    switch($class) {
        case 'ACMS_POST_Entry_Insert':
        case 'ACMS_POST_Entry_Update':

        $thisModule->Post->_aryField['pdfFile@filename'][0] = 'pdf/'.$_FILES['pdfFile']['name'];

        break;
    }

    // エントリーの作成or更新のみ
    if ( !in_array($class, array(
        'ACMS_POST_Entry_Insert',
        'ACMS_POST_Entry_Update'
    )) ) {
        return false;
    }

    $Post   =& $thisModule->Post;  // POSTデータを取得
    $bid    = $Post->get('bid');
    $cid    = $Post->get('cid');

    if ( !isset($_FILES[$fieldName]['tmp_name']) ) return false;

    $source = $_FILES[$fieldName]['tmp_name'];
    if ( !is_readable($source) ) return false;

    $myurl  = $source.'[0]';  // PDFの1ページを指定

    try {
        $image  = new Imagick();
        $image->setResolution(140, 140);  // 解像度を指定
        $image->readimage($myurl);        // PDFの読み込み
        $image->setImageFormat('png');

        ACMS_POST::setupDir(ARCHIVES_DIR.$destDir, intval(config('permission_dir')));  // 保存先ディレクトリの作成
        $file   = $_FILES['pdfFile']['name'].'.png';  // 保存ファイル名 (元のファイル名.png)
        $dest   = ARCHIVES_DIR.$destDir.$file;        // 保存先

        $image->writeImage($dest);
        $image->destroy();

        // カスタムフィールドに登録する為、POSTデータにサムネイル画像のパスを登録
        $Post->add('field', $thumbnailFieldName);
        $Post->add($thumbnailFieldName, $destDir.$file);
    } catch(Exception $e) {
        echo $e->getMessage();
    }
}
```

あとは、エントリーを編集すると画像が消えるのを解決できるかどうか。
でも、ここまででわりと満足しました笑

## 〈追記2〉エントリー編集後も画像を保持する
エントリーを編集すると画像が消える問題について、以下の2行をカスタムフィールドに追加するとよいのでは？というアドバイスをいただいたのですが、

```html
<input type="hidden" name="pdfThumbnail" value="{pdfThumbnail}" />
<input type="hidden" name="field[]" value="pdfThumbnail" />
```

わたしがやってもうまくいきませんでした…（未だに変数をカスタムフィールドに格納するのできない）
ただ、生成した画像は残っているので、画像のパスの書き方を変えることで解決したいと思います！

### ファイル名とパスを見てみる
サムネイル画像をPDFファイル名と連動したことで、それぞれのパスは以下のようになります（ブログIDが1のとき）。

PDFファイル: /archives/001/pdf/***filename*.pdf**
サムネイル画像: /archives/001/thumbnail/***filename.pdf*.png**

PDFファイルのパス ( 001/pdf/*filename*.pdf ) はカスタムフィールドの値 **{pdfFile@path}** なので、これに画像の拡張子を追加する ( **{pdfFile@path}.png** ) と画像のファイル名は取得できますね。

生成したサムネイル画像の保存先がPDFファイルと同じ場所 ( /archives/001/pdf ) ならこのままでOKでした。でもわたしはサムネイル画像は個別に管理したいのです。画像のパスを pdf ディレクトリから thumbnail に変更する必要があります。

そこで登場するのが **校正オプションの拡張** です！

### 校正オプションの拡張
まずはファイルを準備します。

> ダウンロードパッケージのomakeフォルダに含まれるファイルを、設置先にコピーします。
> 
> コピー元
> (*ダウンロードパッケージの展開先*)/omake/php/ACMS/User/Corrector.php
> コピー先
> (*a-blog cms設置先*)**/php/ACMS/User/Corrector.php**
> 
> 所定の位置にファイルがコピーされていれば、自動で校正オプションが拡張されるようになります。

参考）[校正オプションの拡張について](https://developer.a-blogcms.jp/document/develop/#eid1818)

**Corrector.php** に以下のコードを追加します。

```php
function pdf2thumb($txt, $args = array())
{
    if ( mb_strpos($txt, '/pdf/') ) {
        $txt = str_replace( '/pdf/', '/thumbnail/', $txt );
    }
    return $txt;
}
```

表示側の画像のパスを **{pdfThumbnail}** から **{pdfFile@path}.png** に変更し、作成した校正オプションを追加します。

```html
<!-- BEGIN pdfFile@path:veil -->
<a href="%{ARCHIVES_DIR}{pdfFile@path}" target="_blank"><img src="%{ARCHIVES_DIR}{pdfFile@path}[pdf2thumb].png" width="120" alt="" /></a>
<!-- END pdfFile@path:veil -->
```

{pdfFile@path}**[pdf2thumb]**.png のように校正オプションを追加することで 001/**pdf**/*filename.pdf*.png が 001/**thumbnail**/*filename.pdf*.png になります。

カスタムフィールドの画像のパスも変更しておきましょう。

```html
<table class="entryFormTable acms-admin-table-entry">
	<tr>
		<th>PDFファイル</th>
		<td>
			<!-- BEGIN pdfFile@path:veil -->
			<input type="hidden" name="pdfFile@old" value="{pdfFile@path}" />
			<input type="hidden" name="pdfFile@secret" value="{pdfFile@secret}" />
			<input type="hidden" name="pdfFile@fileSize" value="{pdfFile@fileSize}" />
			<label for="input-checkbox-pdfFile@edit" class="acms-admin-form-checkbox">
				<input type="checkbox" name="pdfFile@edit" value="delete" id="input-checkbox-pdfFile@edit" />
				<i class="acms-admin-ico-checkbox"></i> 削除
			</label>
			<a href="%{ARCHIVES_DIR}{pdfFile@path}"><img src="%{ARCHIVES_DIR}{pdfFile@path}[pdf2thumb].png" width="120" alt="" /></a>
			<!-- END pdfFile@path:veil -->
			<input type="file" name="pdfFile" />
			<input type="hidden" name="field[]" value="pdfFile" />
			<input type="hidden" name="pdfFile@baseName" value="{pdfFile@baseName}" />
			<input type="hidden" name="pdfFile:extension" value="file" />
			<input type="hidden" name="pdfFile@extension" value="pdf" />
		</td>
	</tr>
</table>
```

これでエントリーを編集しても画像が消えることはなくなりました！

## 参考サイト

トラブルシューティングとして参考にさせていただきました。

- [Installing ImageMagick on Mac OSX for PHP and MAMP · delphian/drupal-convert-file Wiki](https://github.com/delphian/drupal-convert-file/wiki/Installing-ImageMagick-on-Mac-OSX-for-PHP-and-MAMP)
- [Install PEAR and PECL on Mac OS X](http://jason.pureconcepts.net/2012/10/install-pear-pecl-mac-os-x/)
- [Mac OS X LionでPEARをインストールする方法 | Web活メモ帳](http://blog.verygoodtown.com/2012/01/mac-os-x-lion-php-pear/)
- [php54-imagick installation fails on OS X 10.9 (Mavericks) · Issue #852 · Homebrew/homebrew-php](https://github.com/Homebrew/homebrew-php/issues/852)
- [フック処理 | 開発 | ドキュメント | a-blog cms 制作者向け情報](http://developer.a-blogcms.jp/document/develop/user_hook.html)
- [Mac OS X 10.5 - MAMP+ImageMagick - Macが主流になってきました](http://d.hatena.ne.jp/lyco/20090131)
- [MAMP Forum • View topic - MAMP Chart Director Bug](https://forum.mamp.info/viewtopic.php?f=6&t=1933)
- [「Warning: Cannot modify header information…」エラーの解決方法[PHP]](http://kotori-blog.com/php/cmhi_error/)
- [ImageMagick and MAMP - Google Groups](https://groups.google.com/forum/#!topic/omeka-dev/DVER4WHGUjc)