CakePHPにCKEditorを導入して記述で画像をアップロードする方法

CakePHPにCKEditorを導入して記述で画像をアップロードする方法

262 回閲覧されました

みなさんこんにちは、jonioです。

今回はCakePHPにCKEditorを導入して記事を投稿する際に画像アップロードをするのを記述でやる方法について解説します。

ネットを調べるとCKFinder・ KCFinderというのを使うみたいですが今回の解説では使いません。

私は最初ネットを探しましたがちゃんと実装できる記事を見かけませんでした。

ですがこの記事を読めば実装できるはずです。

前提条件

CKEditor自体の導入方法は解説しませんので私が書いた記事を読んで下さい。

それでは解説します。

csrfの解除

CakePHP4からはcsrf対策が自動的に入る設定になっているのですが画像をアップロードする時はこれを外します。

この設定を外さないと画像をアップロードする時に投稿のTokenと一致しないみたいなエラーが表示されます。

「CakePHPのプロジェクト > src > Application.php」のコードを修正しますが画像をアップロードするアクション(あとで説明しますがuploadImageアクションです)だけ解除します。

コードを下記にします、必要な部分だけ記載します。

public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
  $middlewareQueue
  // Catch any exceptions in the lower layers,
  // and make an error page/response
  ->add(new ErrorHandlerMiddleware(Configure::read('Error')))

  // Handle plugin/theme assets like CakePHP normally does.
  ->add(new AssetMiddleware([
    'cacheTime' => Configure::read('Asset.cacheTime'),
  ]))

  // Add routing middleware.
  // If you have a large number of routes connected, turning on routes
  // caching in production could improve performance. For that when
  // creating the middleware instance specify the cache config name by
  // using it's second constructor argument:
  // `new RoutingMiddleware($this, '_cake_routes_')`
  ->add(new RoutingMiddleware($this))

  // Parse various types of encoded request bodies so that they are
  // available as array through $request->getData()
  // https://book.cakephp.org/4/en/controllers/middleware.html#body-parser-middleware
  ->add(new BodyParserMiddleware())

  ->add(new RoutingMiddleware($this))
    
  ->add(new AuthenticationMiddleware($this));


  //ここから削除
  // Cross Site Request Forgery (CSRF) Protection Middleware
  // https://book.cakephp.org/4/en/controllers/middleware.html#cross-site-request-forgery-csrf-middleware
  // ->add(new CsrfProtectionMiddleware([
  //     'httponly' => true,
  // ]));
  //ここまで削除


  //ここから追加
  $csrf = new CsrfProtectionMiddleware([
    'httponly' => true,
  ]);    
  
  $csrf->skipCheckCallback(function ($request) {
    if ($request->getParam('controller') == 'Posts' && $request->getParam('action') == 'uploadImage') {
      return true;
    }
  });
  
  $middlewareQueue->add($csrf);
  //ここまで追加
  

  return $middlewareQueue;
}

46行目のPostsがコントローラー名(あとで説明します)でuploadImageがアクション名です。

uploadImageアクション

記事を投稿するコントローラーをPostsコントローラーとします。

addアクションで記事を投稿していましたが画像をアップロードするアクションを「uploadImage」アクションとします。

uploadImageアクションの記述を下記にします。

public function uploadImage()
{
  if ($this->request->is('post')) {

    $upload_file = $this->request->getData('upload');

    $image_name = $upload_file->getClientFilename();
    
    $upload_file->moveTo(WWW_ROOT . 'img/uploads/' . $image_name);

    $image_pass = $this->request->getAttribute('webroot') . 'img/uploads/' . $image_name;

    $this->response = $this->response->withType('application/json')->withStringBody(json_encode(['url' => $image_pass]));

    return $this->response;
  }
}

5行目でCKEditorのフォームに入力した画像を取得しています。

「upload」はname属性ですがCKEditorを使って画像をアップロードする時のname属性はuploadと決まっているみたいです、upload以外にすると画像を取得できませんでした。

7行目でアップロードしたファイルの名前を取得しています。

9行目でアップロードしたファイルをCakePHPのプロジェクトの中に配置しています、「CakePHPのプロジェクト > webroot > img > uploads」の下の階層に配置します。

11行目でアップロードした画像のパス(CakePHPのプロジェクト/webroot/img/uploads/画像名)を取得しています。

13行目はページに返すレスポンスの設定をしています、細かく解説します。

「->withType(‘application/json’)」はMIMEタイプを取得しています。

WEBの世界ではデータの種類を区別するのに拡張子(〜.htmlとか〜.phpとか〜.pngとかの「.」のこと)とMIMEタイプがあり「application/json」の時のMIMEタイプはjsonです。

「->withStringBody(json_encode([‘url’ => $image_pass]))」の「json_encode([‘url’ => $image_pass])」

は「[‘url’ => $image_pass]」をjson形式で表示します。

試しに13行目の下に「dd(json_encode([‘url’ => $image_pass]));」と記述(この記事を読んでいるあなたは今のコードでは動作しないので全部の解説を読み終えてから試して下さい)してエディタに画像を入れて(あとでやり方は説明します)json形式になっているかを確認すると下記になります。

'{"url":"\/img\/uploads\/xxxxx.jpg"}'

urlは「’url‘ => $image_pass」のurlのことで\/img\/uploads\/xxxxx.jpgは$image_passのことです。

「->withStringBody」でページに返す内容(json_encode([‘url’ => $image_pass]))を設定しています。

まとめると13行目はレスポンス(ビュー)に返すデータをjsonタイプを指定して返す内容を画像のパスにしているということになります。

そして15行目でレスポンスにデータを返しています。

add.php

addアクションでCKEditorを使っているのでビューはadd.phpです。

下記の記述をします。

<?php
/**
 * @var \App\View\AppView $this
 * @var \App\Model\Entity\Post $post
 */
?>
<div class="row">
    <aside class="column">
        <div class="side-nav">
            <h4 class="heading"><?= __('Actions') ?></h4>
            <?= $this->Html->link(__('List Posts'), ['action' => 'index'], ['class' => 'side-nav-item']) ?>
        </div>
    </aside>
    <div class="column-responsive column-80">
        <div class="posts form content">
            <?= $this->Form->create($post) ?>
            <fieldset>
                <legend><?= __('Add Post') ?></legend>

                <?php
                    echo $this->Form->control('title');
                    echo $this->Form->control('content',["type"=>"textarea","cols"=>10,"rows"=>2,"label"=>"本文","id"=>"editor"]);
                ?>
            </fieldset>
            <?= $this->Form->button(__('Submit')) ?>
            <?= $this->Form->end() ?>
        </div>
    </div>
</div>


<?= $this->Html->script('//cdn.ckeditor.com/4.7.3/standard/ckeditor.js',['block' => true]) ?>

<script>
  CKEDITOR.replace('editor', {
  
  
    //ここから追加
    language: 'ja',
    filebrowserUploadUrl: '<?= $this->Url->build(['controller' => 'Posts', 'action' => 'uploadImage']) ?>',
    //ここまで追加
    
    
  });
</script>

39行目は言語の設定です、日本語にした方が使いやすいと思うので設定しました。

40行目は画像をアップロードするコントローラーとアクション名を記述します、それ以外の部分はそのまま記述して大丈夫です。

あとで動作確認をするときに分かるのですが40行目がないと画像をアップロードする時のタブが表示されません。

動作確認

記事の投稿画面で下記の赤枠をクリックします。

すると「画像情報」・「リンク」・「アップロード」の3つのタブが表示されるので「アップロード」のタブをクリックします。(add.phpの「filebrowserUploadUrl」の項目がないとアップロードの項目は表示されません)

そしてファイルを選択します。

そして「サーバーに送信」をクリックします。

すると画像のパスが表示されるので「”」で囲まれている中の最初の「\」を取ったパス(/img\/uploads\/demo1.png)をコピーします。

そして「画像情報」のタブをクリックします。

「URL」の項目に先ほどコピーした画像のパスを貼ります。

画像のパスを入力して入力タブ以外の所をクリックするとプレビューの項目にアップロードしようとしている画像が表示されます。

「URL」の項目に入力した画像のパスが間違っている場合は「プレビュー」の項目に「x」が表示されます。

画像がアップロードできたら画面下の「OK」ボタンを押して通常通り記事の投稿をすれば投稿内容が確認できます。

これで実装の完成です。