初心者用CakePHPでブログサイト④記事一覧から詳細ページにアクセスとページネーション

初心者用CakePHPでブログサイト④記事一覧から詳細ページにアクセスとページネーション

161 回閲覧されました

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

今回は記事一覧のページの作り込みと詳細ページへのアクセスとページネーションの実装をします。

詳細ページにアクセスできるようにする前に少しだけ作り込みをします。

scriptタグの無効化(XSS対策)

PostsSeed.phpを下記に修正します。

[
  'title' => '最初の投稿',
  'description' => "<script>alert('javascriptの実行');</script>\n最初の投稿\n改行文章",   //この行を修正
  'body' => '最初の投稿の内容',
  'published' => 1,
  'created' => '2020-05-02 10:00:00',
  'modified' => '2020-05-02 10:00:00',
],


//他は変更がないので省略

これで「http://localhost:8888/cakephp/CakeBlog1/posts/index」にアクセスすると下記の表示になります。

こうなるはカラムのデータにscriptタグが入っているのが原因です、XSS(クロスサイトスクリプティング)といいます。

不特定多数の人が投稿できるサイトだと悪意を持った人がわざとscriptタグが混ざったデータの登録をする可能性があります。

今回はモーダルが表示されるだけですが悪意がある場合は個人情報を抜き取ることもできます。

対策としてはscirptタグをただの文字列に変えればいいです。

その為にはビュー(index.php)を下記にすればいいです。

<div class="content">
    <?php foreach ($posts as $post):?>
        <h3><?= h($post->title) ?></h3>
        <p><?= $post->created ?></p>
        <p><?= h($post->description) ?></p>
        <hr>
    <?php endforeach?>
</div>

3行目と5行目を修正していますがscriptタグを無効化したい部分を「h()」の引数に入れればいいです。(titleカラムにscriptタグはないですが一応h()を書いておきます)

これでモーダルが表示されなくなって下記の表示に変わりscriptタグがただの文字列になっているのが確認できます。

改行が入るようにする

「PostsSeed.php」の3行目ですが改行(「\n」のこと)が入っています。

[
  'title' => '最初の投稿',
  'description' => "<script>alert('javascriptの実行');</script>\n最初の投稿\n改行文章",   //「\n」の部分
  'body' => '最初の投稿の内容',
  'published' => 1,
  'created' => '2020-05-02 10:00:00',
  'modified' => '2020-05-02 10:00:00',
],

今のままでは「最初の投稿」・「改行文章」は改行されていませんが改行する為にindex.phpのコードを下記に変更します。

<div class="content">
    <?php foreach ($posts as $post):?>
        <h3><?= h($post->title) ?></h3>
        <p><?= $post->created ?></p>
        <p><?= $this->Text->autoParagraph(h($post->description)) ?></p>      //この行を修正
        <hr>
    <?php endforeach?>
</div>

「$this->Text->autoParagraph())」の引数に「h($post->description)」を使えばいいです。

これで「http://localhost:8888/cakephp/CakeBlog1/posts/index」にアクセスすると改行の確認ができます。

日付の表示方法の変更(フォーマット)

下記の日付の表示を変えます。

index.phpを下記に変更します。

<div class="content">
    <?php foreach ($posts as $post):?>
        <h3><?= h($post->title) ?></h3>
        <p><?= $post->created->i18nFormat('YYYY年MM月dd日 HH:mm') ?></p>          //この行を修正
        <?= $this->Text->autoParagraph(h($post->description)) ?>      
        <hr>
    <?php endforeach?>
</div>

これで日付の表示が下記に変わります。

5行目の「$this->Text->autoParagraph()」は自動的にpタグになるので元々付けていたpタグは削除しました。

それでは記事の詳細ページにアクセスできるようにします。

記事の詳細ページへのアクセス

index.phpのコードを下記に変更します。

<div class="content">
    <?php foreach ($posts as $post):?>
        <h3><?= h($post->title) ?></h3>
        <p><?= $post->created->i18nFormat('YYYY年MM月dd日 HH:mm') ?></p>          
        <p><?= $this->Text->autoParagraph(h($post->description)) ?></p>      
        <hr>
        <a class="button" href="/cakephp/CakeBlog1/posts/view/<?= $post->id ?>">記事を読む</a>  //この行を追加
    <?php endforeach?>
</div>

これで「http://localhost:8888/cakephp/CakeBlog1/posts/index」にアクセスすると下記の表示になります。

「記事を読む」をクリックすると記事の詳細ページへアクセスできますが上記のコードの7行目の「/cakephp/CakeBlog1/posts/view/」が直書きでプログラムっぽさがないです。

だから下記のコードに修正します。

<div class="content">
    <?php foreach ($posts as $post):?>
        <h3><?= h($post->title) ?></h3>
        <p><?= $post->created->i18nFormat('YYYY年MM月dd日 HH:mm') ?></p>          
        <p><?= $this->Text->autoParagraph(h($post->description)) ?></p>      
        <hr>
        <?= $this->Html->link('記事を読む', [
            'controller' => 'Posts',        
            'action' => 'view',
            $post->id
        ],['class' => 'button']) ?>
    <?php endforeach?>
</div>

この書き方でも変更前と同じように記事の詳細に入ることができます。

元々あったaタグを7行目〜11行目に変更しています。

リンクにする時は「$this->Html->link()」と書けばいいです。

引数は下記の設定をすればいいです。

  • 1つ目の引数 : aタグで使いたい文言
  • 2つ目の引数の1つ目(’controller’ => ‘Posts’) : リンク先で使うコントローラー名
  • 2つ目の引数の2つ目(’action’ => ‘view’) : リンク先で表示するビュー名
  • 2つ目の引数の3つ目($post->id) : リンクに使うパラメーター
  • 3つ目の引数 : cssのクラス名

今のままでは1つのページに全ての記事を表示しますが記事が大量にあるとページが長すぎてうっとうしくなります。

だから1つのページに表示する記事数を指定する為ページネーションを実装します。

ページネーション

Postsコントローラーに下記の記述をします。


//ここから上の行は変更がない為省略

class PostsController extends AppController
{


    //ここから追加
    public $paginate = [
        'limit' => 2,            
        'order' => [
            'Posts.created' => 'desc'          
        ]
    ];
    //ここまで追加
    

    public function initialize(): void{
        parent::initialize();
        $this->viewBuilder()->setLayout('test');
    }

    /**
     * Index method
     *
     * @return \Cake\Http\Response|null|void Renders view
     */
    public function index()
    {
        $posts = $this->paginate($this->Posts->find('all'));         //この行を修正
     
        $this->set(compact('posts'));
    }


//ここから下の行は変更がない為省略

これで「http://localhost:8888/cakephp/CakeBlog1/posts/index」にアクセスすると下記の表示(記事は2件だけ)になります。

ページネーションを実装するにはまず9行目・14行目を書きます。

10行目の「limit」は1ページで表示する記事の数です、今回は2にしているので2件表示されます。

11行目の「order」は記事の並び順です、新しい順か古い順に並べますがそれを12行目に書きます。

12行目の「Posts.created」は「モデル名s.created」です、今回の解説みたいに使用するモデルが1つの場合は「モデル名s」は書かなくていいです。

そして記事を表示するindexメソッドで「$this->paginate()」の引数に「$this->Posts->find(‘all’)」を入れます。

これでコントローラーでのページネーションの設定はできたので次はビュー(index.php)に記述します。

<div class="content">
    <?php foreach ($posts as $post):?>
        <h3><?= h($post->title) ?></h3>
        <p><?= $post->created->i18nFormat('YYYY年MM月dd日 HH:mm') ?></p>          
        <p><?= $this->Text->autoParagraph(h($post->description)) ?></p>      
        <hr>
        <?= $this->Html->link('記事を読む', [
            'controller' => 'Posts',        
            'action' => 'view',
            $post->id
        ],['class' => 'button']) ?>
    <?php endforeach?>
    
    
    //ここから追加
    <?php if($this->Paginator->total() > 1):?>
        <div class="paginator">
            <ul class="pagination">
                <?= $this->Paginator->first('<< 最初') ?>
                <?= $this->Paginator->prev('< 前へ') ?>
                <?= $this->Paginator->numbers() ?>
                <?= $this->Paginator->next('次へ >') ?>
                <?= $this->Paginator->last('最後 >>') ?>
            </ul>
        </div>
    <?php endif ?>
    //ここまで追加
    
    
</div>

これで「http://localhost:8888/cakephp/CakeBlog1/posts/index」にアクセスすると下記の表示になりページネーションが表示されるのが確認できます。

16行目のif文の条件式は「表示するページ数が1より大きい(2ページ以上ある)場合は」という意味です。

21行目はページ番号(上記画像の「1」・「2」・「3」のこと)を表示しています。

これでページネーションの実装が完成しました。

次回の解説

次回の解説は管理画面・CRUD機能・ログイン機能の作成です。