ホーム > CakePHP

CakePHPのアーカイブ

CakePHPでオレ専用ブログを作ってみる vol.4

認証コンポーネントを使う

CakePHPに用意されているAuthコンポーネントを使用して認証機能を実装する。

Authコンポーネントはデフォルトで「users」という名前のテーブルを参照して認証を行うようになっているので、この名前を使って新しくテーブルを作成する。テーブルに用意するフィールドはid、username、passwordの三つ。

mysql> create table users(
    -> id int not null auto_increment primary key,
    -> username varchar(50) not null,
    -> password varchar(50) not null);

パスワードのフィールド(password)については最低でも40文字以上は保管できるように設定しておくこと。SHA1による暗号化がされるので、これよりも短いと正しく値を保持できなくなる。

ちなみに、この認証のための暗号化に使用されるのが、最初のセットアップ時に文字列を書き換えた「Security.salt」。ユーザ名やパスワードを登録した後でこの値を変更すると認証機能が働かなくなるので注意。最初に変更したら以後はもう変更しないこと。(変更した場合はusersテーブルを新しく作り直す必要がある)

Usersコントローラを作成する

app/controllers/users_controller.php


ビューの作成

Usersコントローラに用意してあるloginとlogoutの二つのアクション用に二つのビューファイルを用意する。

app/views/users/login.ctp

check('Message.auth')) $session->flash('auth');
echo $form->create('User', array('action' => 'login'));
echo $form->input('username');
echo $form->input('password');
echo $form->end('ログイン');

app/views/users/logout.ctp

tag('h3', 'ログアウトしました。') ?>

このlogout.ctpはユーザ側に表示されることはない。ダミーとして置いておくだけのものらしい。

Entriesコントローラーの修正

Entriesコントローラのの全てにAuthによる認証が適用されるように、次の一文をEntriesControllerクラスに追加する。(コントローラ内のいずれかのアクションにではなく、class自体に)

app/controllers/entries_controller.php

public $components = array('Auth');

これで「Usersコントローラ」の全てにおいて認証がかかるようになった。ブラウザで「http://IPアドレス/cblog/entries(エントリ記事一覧ページ)」にアクセスすると、これまでのようには記事一覧を表示せず、ログインフォームのある「login.ctp」へリダイレクトされる。この段階では、ログインユーザでないと新規投稿も記事の閲覧も出来ない状態になった。

ユーザ登録ページのビューを作成する

Usersコントローラで使用するビューなので/views/users/内に作る。

app/views/users/add.ctp

ユーザ登録

'add' echo $form->create('User', array('action' => 'add')); echo $form->input('username'); echo $form->input('password'); echo $form->end('ログイン');

ユーザ登録のためのadd()アクションを定義する

add()アクションはユーザ登録のためのアクションなのでUsersコントローラ(users_controller.php)に定義する。

app/controllers/users_controller.php

    function add() {
        // $this->dataが空でなければ
        if(!empty($this->data)) {
            if($this->data) {
                // ユーザレコードにユーザ情報を保存する
                $this->User->create();
                $this->User->save($this->data);
                // 保存したらLoginページにリダイレクト
                $this->redirect(array('action' => 'login'));
            }
        }
    }

この状態でこの「cblog/users/add」にブラウザでアクセスしても、またしても「cblog/users/login」にリダイレクトされてしまう。それは当然で、Usersコントローラには認証コンポーネントが組み込まれているから、非ログイン状態ではどのページも表示出来ずにログインページにリダイレクトされてしまう。

認証を使用しないページを登録する

認証コンポーネント「Auth」を使わないページとしてユーザ登録ページを登録し、その上であらためてユーザ登録ページへアクセスして登録を行うことにする。非ログインユーザでも、既存の投稿エントリ一覧、エントリ自体は見られるようにしたいので、これも同様に登録することにする。

コントローラに「beforeFilter」というメソッドを用意する。このメソッドはAuthなどの処理が開始される前に実行される。Usersコントローラクラスに追加してみる。

app/controllers/users_controller.php

    function beforeFilter() {
        // Authのallowメソッドを呼び出してadd()とlogout()アクションには認証がかからないようにする
        $this->Auth->allow('add');
        $this->Auth->allow('logout');
    }

これでaddとlogoutについては認証が働かないように(ログインしなくてもアクセス可能)なった。

これで再度「http://IPアドレス/cblog/users/add」ユーザ登録ページにアクセスし、ユーザ名とパスワードを入力して登録ボタンを押すと、ユーザデータがusersテーブルに保存され、ブラウザの画面はログイン画面「http://IPアドレス/cblog/users/login」にリダイレクトされる。

登録したユーザ情報を入力して送信するとログイン完了し、エントリ一覧ページ(/cblog/entries/index/)にリダイレクトされる。

ログアウトを機能させる

いまのところログアウト用のリンクボタンとかを用意していないので、ログアウトするにはアクションを直接呼び出す必要がある。ブラウザで「http://IPアドレス/cblog/users/logout」をリクエストすると画面に「ログアウトしました。」という文言が表示される。

しかし、この状態(ログアウトした)で再度エントリ一覧ページにアクセスすると、ログイン時と同じように記事一覧を見ることが出来る。Usersコントローラクラスのlogout()アクションを次のように記述して、ログアウト機能が働くようにする。

app/controllers/users_controller.php

    function logout() {
        $this->Auth->logout();
    }                                                                        |    }

再度ブラウザで「http://IPアドレス/cblog/users/logout」をリクエストすると、今度はちゃんとログアウトされるようになった。

エントリ記事一覧とエントリ記事自体を認証から外す

エントリ記事一覧と、エントリ記事自体はログインしていなくとも(一般にも)見えるようにしたいので、前述のbeforeFilterメソッドをさらに追加してみる。

どちらもEntriesコントローラに含まれるメソッド(アクション)なので、Entriesコントローラを開いて、index()アクション(エントリ記事一覧)とview()アクション(エントリ記事自体)を「Authコンポーネントからallowする」ように記述する。

app/controllers/entries_controller.php
beforeFilterメソッドを追加

    function beforeFilter() {
        // エントリ記事一覧は非ログイン状態でも閲覧可能にする
        $this->Auth->allow('index');
        // エントリ記事自体も同じく
        $this->Auth->allow('view');
    }

これで、とりあえずは新規投稿は登録済みのユーザでないと出来ない状態になった。エントリに関しては一覧も記事自体も閲覧可能。

オープンソース徹底活用 CakePHPによるWebアプリケーション開発
掌田 津耶乃
秀和システム
売り上げランキング: 174026
おすすめ度の平均: 5.0

5 初心者向け
5 深く書かれていて便利である

CakePHPでオレ専用ブログを作ってみる vol.3

投稿内容を検証する(サニタイズとヴァリデーション)

まずは投稿されるデータの内容にJavaScriptとかHTMLタグとかがあって、これが機能してしまって不具合というか、よろしくないことが発生してはいけないので、そういう「効かれると好ましくない」タグを無害化(サニタイズ)することにする。

本当はコントローラかモデル側で処理(Sanitize)したかったんだけど、コントローラでやろうとしたら、記事一覧ではSanitizeできてるんだけど、記事本体にとぶとそうなってなかったり(addアクションではやれたのに、viewアクションで同じように出来ず)してうまく処理できなかったので、結局ユーザ側に出力する段階(ビューファイル)のほうで実装した。(エントリ記事一覧ページの/app/views/entries/index.ctpとエントリ本体の/app/views/entries/view.ctp)

とりあえず「clean」を使ったけど、エントリ本体ページのコンテンツ内では、imgタグとaタグは利用できるように変更したいと思うちょります。(オレ個人で利用するので、よく使いそうな外部へのリンクやFlickr等の外部サイトからの画像の読み込みは可能にしておきたい)
clean :: データのサニタイズ(Data Sanitization) :: CakePHPによる作業の定石 :: マニュアル :: 1.2 Collection :: The Cookbook

▼app/views/entries/index.ctp

ブログエントリ一覧

"; echo "
  • ".Sanitize::clean($arr['Entry']['category'])."
  • "; echo "
  • ".$html->link($arr['Entry']['title'], '/entries/view/' . $arr['Entry']['id']) . "
  • "; echo "
  • ".Sanitize::clean(mb_substr($arr['Entry']['content'], 0, 100)."……")."
  • ";i> echo "
  • " . $arr['Entry']['created'] . "
  • "; echo " "; }

    ▼app/views/entries/view.ctp

    
    

    オレ専用ブログHomeへ

    投稿日時:

    とりあえずこれでいいかなと思ったら、改行コードも強制的に「¥n」に変換されちゃうから、nl2br(改行コードを
    タグに変換する)関数が使えなくなってしまうということに気がついてしまった。

    サニタイズというのも、自分が思うようにやろうとするのはなかなか大変なんだと、こうやって自身で実践してみて初めて実感できるもんだなぁ。

    エントリ本体でのcontentsでは具体的にサニタイズするタグを決めてやったほうがいいのか(これならnl2br使えるだろう)、それともサニタイズ処理の後で「¥n」を改行タグに置き換えるような処理を、例えば正規表現とかでやったほうがいいのかな。

    んー、、今日もそんなに進まなかったけどリアルに色々学んだ気がする。なるほどなるほど。

    オープンソース徹底活用 CakePHPによるWebアプリケーション開発
    掌田 津耶乃
    秀和システム
    売り上げランキング: 174026
    おすすめ度の平均: 5.0

    5 初心者向け
    5 深く書かれていて便利である

    CakePHPでオレ専用ブログを作ってみる vol.2

    ビューアクションを定義する

    エントリの記事本体のデータを取得するためのアクションを定義する。

    既に作成済みの/app/controllers/entries_controller.phpに、view()として追記していく。

    app/controllers/entries_controller.php

        function view($id = null) {
            // Entryモデルからレコードのidを取得して変数$idに代入
            $this->Entry->id = $id;
            // idにひもづく一件だけ取得すればいいのでread()
            $this->set('post', $this->Entry->read());
        }
    

    ビューアクションのためのビューファイルを用意する

    Entriesコントローラのview()アクションが実行された際に必要なビューファイルを用意する。

    app/views/entries/view.ctp

    
    投稿日時:
    
    
    

    これでエントリ自体を表示することが出来た。

    でも、これだとトップページ(本ブログの場合は、エントリ一覧を表示するページ/cblog/entries/がトップということになっている)へのリンクがなくて不便なので、コンテンツ上部にリンクテキストを配置する。(h1タグ部分が追記部分)

    app/views/entries/view.ctp

    オレ専用ブログHomeへ

    投稿日時:

    カテゴリ分け

    次は新規投稿機能の実装だと思ったら、そうだ、カテゴリ分け出来るようにしたいんだったって思い出したので、テーブル「entries」にそれ用の「category」フィールドを追加した。これでいいんだよな?多分。

    mysql> alter table entries add category varchar(255) after content;
    

    新規投稿機能の実装

    Entriesコントローラ(entries_controller.php)に新規投稿機能を実現するための新しいアクション「add()アクション」を追加する。

    app/controllers/entries_controller.php

        function add() {
            if(!empty($this->data)) {
                $this->Entry->save($this->data);
            }
        }
    

    そして、このadd()アクションを実行した際に利用されるビューファイルをapp/views/entries/add.ctpとして用意する。このビューファイルには投稿フォームの内容が表示されることになる。

    app/views/entries/add.ctp

    Home
    

    新規投稿

    create(null, array('type'=>'POST', 'action'=>'')) ?> タイトル:text('Entry.title') ?> 内容:textarea('Entry.content') ?> カテゴリ:text('Entry.category') ?> end("投稿する") ?>

    この新規投稿画面にはブラウザでhttp://IPアドレス/cblog/entries/add/でアクセス出来るようになる。

    テスト投稿してみると無事投稿出来た。でも、投稿後も投稿フォームページに留まっているので、これを投稿後はエントリ一覧ページへ遷移するようにリダイレクトの処理を追加することにする。

    app/controllers/entries_controller.php

        function add() {
            if(!empty($this->data)) {
                $this->Entry->save($this->data);
                $this->redirect('.');
            }
        }
    

    これで投稿後はエントリ一覧ページに遷移するようになった。でも、よく見るとエントリ一覧が「昇順」になっていたので、やっぱり新しい投稿が上に来るのが良いから、これを「降順」に変更する。find(‘all’)にオプションをつけてfind(‘all’, array(‘order’ => ‘Entry.id desc’))にする。

    app/controllers/entries_controller.php

        function index() {
            $data = $this->Entry->find('all', array('order' => 'Entry.id desc'));
            $this->set('data', $data);
        }
    

    でも、これまでに書いてきたコードだとビューファイルにおいてエラーが出るので、内容をけっこう大幅に書き換えた。

    app/views/entries/index.ctp

    ブログエントリ一覧

    "; echo "
  • " . $html->link($arr['Entry']['title'], '/entries/view/' . $arr['Entry']['id']) . "
  • "; echo "
  • " . mb_substr($arr['Entry']['content'], 0, 100) . "… … " . "
  • "; echo "
  • " . $arr['Entry']['created'] . "
  • "; echo " "; }

    これまでは$postからデータを取り出していたのを(これはCakePHPのサイトにあったチュートリアルをまんま真似ていた)$data[$i]に変更して、これを$arrに代入し、出力させたい所で$arr['モデル名']['データフィールド名']で取り出している。

    あ、昨日追加した「カテゴリ」フィールドを表示してなかったので、これも追加して表示するようにする。次の一行を追加。

    app/views/entries/index.ctp

    echo "
    
  • {$arr['Entry']['category']}
  • ";

    ここまでの段階でエントリ一覧ページはこんな感じ。

    エントリ一覧その4

    オープンソース徹底活用 CakePHPによるWebアプリケーション開発
    掌田 津耶乃
    秀和システム
    売り上げランキング: 174026
    おすすめ度の平均: 5.0

    5 初心者向け
    5 深く書かれていて便利である

    CakePHPでオレ専用ブログを作ってみる vol.1

    チュートリアルをいくつかやってみたけど、やっぱり自分が使う、使いたいようなものを作るつもりでやったほうがその過程で経験するものも格段に違うし、より良いアウトプットも出来るのではないかと思って、あらためて自分用の(自分だけが更新できる)ブログサービスをCakePHPで作ってみることにした。

    DBとテーブルの用意

    使用するデータベース名:cake
    mysql> create database cake;

    エントリ用テーブル:entries
    (CakePHPの命名規則に従って、entryの複数形を用いた。これによってモデル名はEntryということになる)

    MySQLモニターでテーブル「entries」を作成する。

    mysql> create table entry(
    -> id int null auto_increment primary key,
    -> title varchar(255) not null,
    -> content longtext not null,
    -> created not null,
    -> modified datetime not null);
    

    テーブルのデータ構造を確認。

    mysql> desc entries;
    +----------+--------------+------+-----+---------+----------------+
    | Field    | Type         | Null | Key | Default | Extra          |
    +----------+--------------+------+-----+---------+----------------+
    | id       | int(11)      | NO   | PRI | NULL    | auto_increment |
    | title    | varchar(255) | NO   |     | NULL    |                |
    | content  | longtext     | NO   |     | NULL    |                |
    | created  | datetime     | NO   |     | NULL    |                |
    | modified | datetime     | NO   |     | NULL    |                |
    +----------+--------------+------+-----+---------+----------------+
    

    CakePHPを新しく展開

    1.2の最新版cake_1.2.4.8284.tar.gzをUbuntu ServerのWebRootにて展開。そして「cblog」としてリネーム。

    新しく作成するブログアプリケーションのディレクトリ
    /var/www/cblog

    以下二つをあらためて確認する。

    1.mod_rewriteは有効になっている。
    (CakePHPではこれを使用するのがデフォになっている)

    2.Apacheの設定で.htaccessが利用可能になっている。

    http://IPアドレス/cblog/にアクセスすると、例によってエラーがいくつか出ているので、これを解消していく。

    参照エントリ:CakePHP 1.2でブログチュートリアル[1]

    エラーが消えてオールグリーンの状態になったところで次の段階へ。

    モデルの作成

    ブログのエントリーを格納するデータテーブル「entries」用に、「Entry」モデルを作成する。(ファイルの名前は単数形)

    app/models/entry.php

    
    

    コントローラの作成

    ブログのエントリーのためのコントローラ「entries_controller.php」を作成する。(ファイルの名前は複数形_controller)

    app/controllers/entries_controller.php

    とりあえずEntriesコントローラのクラスを定義して、名前をつけた。

    
    

    こんだけだとなんにも表示できないんで、エントリ一覧を表示するようにアクションをindexで設定した。(indexアクションはコントローラのデフォルトのアクションで、今回の例だと/cblog/entries/index/でアクセスできる)

    set('entries', $this->Entry->find('all'));
        }
    
    }
    

    コントローラはここまでにして、ここまでやった内容(/cblog/entries/index/にアクセスするとブログのエントリ一覧が全件表示される)をブラウザで表示できるように「ビュー」を次に作成する。

    ビューの作成

    Entriesコントローラ(entries_controller.php)の処理結果をブラウザで表示出来るように「ビュー」ファイルを用意する。命名規則から、このビューファイルは「/app/views/entries/index.ctp」となる。(なので、viewsディレクトリ内にentriesディレクトリを作成する)

    app/views/entries/index.ctp

    ブログエントリ一覧

    • link($post['Entry']['title'], "/entries/view/".$post['Entry']['id']); ?>

    (x)htmlのマークアップはあとであらためてちゃんとやるとして、とりあえず現状ではこんな感じ。(http://IPアドレス/cblog/entries/にアクセスすると)

    エントリ一覧その1

    エントリのタイトルと投稿日時だけが表示されている。エントリタイトルにはエントリ本文へのリンクが張られている。

    エントリ本文も表示させたいのでリスト要素として次のPHPコードを追加。

  • これによってEntryモデル(entriesテーブル)のcontentフィールドの内容も表示されるようになった。

    エントリ一覧その2

    でも、これだとこの時点(エントリ一覧ページを見ている時点)で、どれも全文読めてしまうので、一覧としては意味がない。なので、このエントリ一覧ページではエントリ本文の文字数を制限して表示することにする。

    CakePHPではもっとスマートに記述する方法があるのかもしれないけれど、ちょっと探しただけではなかなか見つからなかったので、普通にPHPのmb_substr()関数を利用することにした。さっき追加したリスト要素のPHPのコード部分を以下のように変更。

  • エントリ本文の冒頭から140文字までを表示することにした。

    エントリ一覧その3

    とりあえずここまで。

    次は、エントリそのものを表示するのに必要なビューファイルを用意して、表示のためのアクションをentries_controller.phpに定義する予定。

    オープンソース徹底活用 CakePHPによるWebアプリケーション開発
    掌田 津耶乃
    秀和システム
    売り上げランキング: 174026
    おすすめ度の平均: 5.0

    5 初心者向け
    5 深く書かれていて便利である

    CakePHPでbakeを利用する

    Ubuntu ServerにセットアップしたCakePHPでbakeを利用する。

    • 先に使用するDBにテーブルを用意しておくこと。
    • 新しくCakePHPアプリを作成するにあたっては、最初のセットアップを忘れないこと。(データベース設定ファイルについてはしなくていい。これからやるbakeでのセットアップでやるので)
      Ubuntu ServerにCakePHPをインストールする

    bakeは1.2でcakeのサブコマンドになっていますので、まずcakeコマンドへのPATHを設定します。(Debian GNU/Linuxでzshの場合)

    CakePHP 1.2の単体テスト作成にはbakeが便利 (前編) より

    ということらしいので、今回自分で試してみようと思っている/var/www/baking/というCakePHPアプリケーションのディレクトリ内で、該当のPATHを以下のように通してみる。

    $ export PATH=$PATH:~/var/www/baking/cake/console/

    (ターミナルを終了しない限りは、このPATHは生きているらしい)

    bakeを起動させる。

    $ cake
    The program 'cake' is currently not installed.  You can install it by typing:
    sudo apt-get install cakephp-scripts
    -bash: cake: command not found
    

    コマンドとしての「cake」を実行するのに、それ自体が足りないと。(cakephp-scriptsというパッケージをインストールしなさいと)

    「cakephp-scripts」をパッケージマネージャで検索してみる。

    $ apt-cache search cakephp-scripts
    cakephp-scripts - MVC rapid application development framework for PHP (scripts)
    

    やはりPHPのフレームワーク(この例で言えばCakePHP)を利用するのに必要なものだということが分かったので、このパッケージをsudoしてapt-get installする。

    $ sudo apt-get install cakephp-scripts

    インストール出来たら再度「cake」コマンドを実行。
    すると、以下のような画面が出現して、またプロンプトに戻る。

    これでbakeを利用する準備が出来た模様。(異なるCakePHPアプリケーションで利用するたびに、冒頭のPATHの設定は必要)

    Welcome to CakePHP v1.2.0.7692 RC3 Console
    ---------------------------------------------------------------
    Current Paths:
     -app: libs
     -working: /var/www/baking/cake/console/libs
     -root: /var/www/baking/cake/console
     -core: /usr/share/php/
    
    Changing Paths:
    your working path should be the same as your application path
    to change your path use the '-app' param.
    Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp
    
    Available Shells:
    
     cake/console/libs/:
    	 bake
    	 acl
    	 i18n
    	 api
    	 testsuite
    	 schema
    	 console
    
    To run a command, type 'cake shell_name [args]'
    To get help on a specific command, type 'cake shell_name help'
    

    bakeの開始(DB設定から)

    今回bakeを利用しようとしているCakePHPアプリケーションのディレクトリ内の/appに移動して、以下コマンドを実行。

    $ cake bake

    すると、ここからbakeとの対話型のセットアップが始まる。英語だけれども、簡単な内容なので詳細は割愛。

    Welcome to CakePHP v1.2.0.7692 RC3 Console
    ---------------------------------------------------------------
    App : app
    Path: /var/www/baking/app
    ---------------------------------------------------------------
    Your database configuration was not found. Take a moment to create one.
    ---------------------------------------------------------------
    Database Configuration:
    ---------------------------------------------------------------
    Name:
    [default] >
    Driver: (db2/firebird/mssql/mysql/mysqli/odbc/oracle/postgres/sqlite/sybase)
    [mysql] >
    Persistent Connection? (y/n)
    [n] > n
    Database Host:
    [localhost] >
    Port?
    [n] >
    User:
    [root] >
    Password:
    > hanage
    Database Name:
    [cake] > db2
    Table Prefix?
    [n] >
    Table encoding?
    [n] > utf8
    
    ---------------------------------------------------------------
    The following database configuration will be created:
    ---------------------------------------------------------------
    Name:         default
    Driver:       mysql
    Persistent:   false
    Host:         localhost
    User:         root
    Pass:         ******
    Database:     db2
    Encoding:     utf8
    ---------------------------------------------------------------
    Look okay? (y/n)
    [y] >
    

    設定の確認画面で確認をして、bakeでのセットアップが完了したら/app/config/の中にdatabase.phpが作成されていることを確認する。これがbakeで自動作成されたDB設定ファイル。

    引き続きbakeを起動させて必要なものを生成していく。再度「$ cake bake」コマンドを実行。

    ここで立ち上がってくるのが、本来のbakeのメニュー画面。前段階での設定メニューは、データベース設定ファイルがないことから強制的に立ち上がってくるデータベース設定ファイルの作成メニュー。

    Welcome to CakePHP v1.2.0.7692 RC3 Console
    ---------------------------------------------------------------
    App : app
    Path: /var/www/baking/app
    ---------------------------------------------------------------
    Interactive Bake Shell
    ---------------------------------------------------------------
    [D]atabase Configuration
    [M]odel
    [V]iew
    
    
    

    ontroller
    [P]roject
    [Q]uit
    What would you like to Bake? (D/M/V/C/P/Q)
    >

    モデルやコントローラやビュー等、必要なものを選んでそれぞれを設定していく。

    とりあえず、ここまで。

    オープンソース徹底活用 CakePHPによるWebアプリケーション開発
    掌田 津耶乃
    秀和システム
    売り上げランキング: 174026
    おすすめ度の平均: 5.0

    5 初心者向け
    5 深く書かれていて便利である

    CakePHP 1.2でブログチュートリアル[5]

    この記事は
    CakePHP 1.2でブログチュートリアル その5
    に引越しました。

    CakePHP 1.2でブログチュートリアル[4]

    この記事は
    CakePHP 1.2でブログチュートリアル その4
    に引っ越しました。

    CakePHP 1.2でブログチュートリアル[3]

    この記事は
    CakePHP 1.2でブログチュートリアル その3
    に引っ越しました。

    CakePHP 1.2でブログチュートリアル[2]

    この記事は
    CakePHP 1.2でブログチュートリアル その2
    に引っ越しました。

    CakePHP 1.2でブログチュートリアル[1]

    この記事は
    CakePHP 1.2でブログチュートリアル その1
    に引っ越しました。

    ホーム > CakePHP

    検索
    フィード

    ページの上部に戻る