【PHP】2重ポスト、DoS 攻撃対策

PHP Logo

サイトの投稿欄、登録欄などに、form を利用した際、ユーザが入力内容を送信完了した後も、ブラウザにその内容が残ってしまい、もう一度送信をクリックして、2重ポストになってしまうことがあります。
更にこれが悪用されて、DoS 攻撃されてしまう、なんて恐れもあるわけで、対策必要ですね。

前回、PHP Mailer を利用したフォームの作成についてまとめたので、これを引き続き使って、メール送信フォームの場合を考えてみたいと思います。

やりたいこと

3段階でメールを送信するように作ります。

「form.php」 入力画面。「確認」ボタンで「check.php」へ。

↓ ↑

「check.php」 確認画面。「送信」ボタンで「submit.php」へ。「戻る」ボタンで「form.php」へ。

「submit.php」 完了画面。送信の成功、失敗を伝える。

このような流れなのですが、満たしたい条件は以下の通りです。

  1. 「form.php」で入力した内容は、送信完了まで維持。まだ送信完了していない状態なら「check.php」から「form.php」に戻っても、入力画面に初めに入力した内容が表示される。
  2. → 解決方法 セッションを利用

  3. 一度送信したら、入力内容はブラウザから消去させる。2重送信させない。
  4. → 解決方法 ワンタイムチケットを生成

1.は、PHP のセッション機能を利用すればいいわけですが、2.のワンタイムチケットの考え方について、次にまとめます。

ワンタイムチケット

1番目の「form.php」で、ランダムな文字列の $ticket を生成。
それを$_SESSION[‘ticket’] = $ticket でセッション関数に格納。
フォームの中で、<input type=”hidden”> で、$ticket を POST メソッドで送信。

という流れになるのですが、言葉より図の方が分かりやすい気がするので、図を作ってみました。

これを見ていただければ思うのですが、「submit.php」で送信終了後、ブラウザの「戻る」で「check.php」を表示した場合など、
$_POST[‘ticket’] 、 $_SESSION[‘ticket’] が存在しない、またはこの二つの値が一致しない場合、通常のこちらが期待する流れでないことが分かります。そのような状況で、初期化するなどするような仕組みに作ればよいことになります。

2重ポストできないフォーム作成

ページ例 → https://exp.hazu.jp/mail-form/form.php
(submit.php がないので、「送信」をクリックしてもメールは送れませんが、check.php までは見れます。)

form.php (入力画面)

<?php
// セッション開始
session_start();

// キャッシュしない
header( 'Cache-Control: no-store, no-cache, must-revalidate' );
header( 'Cache-Control: post-check=0, pre-check=0', false );
header( 'Pragma: no-cache' );
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");

//文字コードをUTF-8にする
mb_internal_encoding('UTF-8');
header("Content-Type: text/html; charset=UTF-8");

// ワンタイムチケットを生成
$ticket = md5(uniqid(rand(), TRUE));

// ワンタイムチケットをセッション変数に格納
$_SESSION['ticket'] = $ticket;

// HTMLでのエスケープ処理をする関数
function h($string) {
  return htmlspecialchars($string, ENT_QUOTES);
}
?>

<html lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>メール送信フォーム</title>
  </head>
  <body>
    <h1>メール送信フォーム</h1>
    <form action="check.php" method="post">
      <div>氏名: <input type="text" name="name" id="name" value="<?php echo h(@$_SESSION['name']);?>"></div>
      <div>メールアドレス: <input type="text" name="to" id="to" value="<?php echo h(@$_SESSION['to']);?>"></div>
      <div>件名: <input type="text" name="subject" id="subject" value="<?php echo h(@$_SESSION['subject']);?>"></div>
      <div>本文: <br />
        <textarea name="message" id="message" cols="50" rows="10"><?php echo h(@$_SESSION['message']);?></textarea>
      </div>
      <input type="hidden" name="ticket" value="<?php echo h($ticket);?>" />
      <div><input type="button" onclick="submit();" value="入力内容を確認する"></div>
    </form>
  </body>
</html>

ちょっと説明足します。

16行目: ワンタイムチケットを生成しています。
$ticket = md5(uniqid(rand(), TRUE));
rand 関数は、ランダムな数桁の整数を生成します。uniqid 関数は、ランダムな13文字の文字列を生成します。この二つが合体して、同じチケットが生成される可能性はほぼゼロとなります。
さらに、md5 関数で、MD5ハッシュ値を計算して、変数に格納します。

19行目: ワンタイムチケットをセッション変数に格納しています。
このセッション関数は、submit.php でセッションが破棄されるまで、維持されることになります。

35~39行目: フォームの入力欄には、初期の状況では空、check.php から「戻る」ボタンで戻って表示された場合には、最初に入力した内容が表示されるようにします。input 要素ではvalue 属性に、textarea 要素では<textarea> と </textarea> の間に、セッション関数を表示させるようにしますが、初期値が空になるように、関数の前に @ を付けて Null を返せるようにしておきます。

41行目: 画面には表示されない状態で、$ticket を、次の check.php へ POST メソッドで受け渡します。

check.php (確認画面)

<?php
// セッションの開始
session_start();

// キャッシュしない
header( 'Cache-Control: no-store, no-cache, must-revalidate' );
header( 'Cache-Control: post-check=0, pre-check=0', false );
header( 'Pragma: no-cache' );
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");

//文字コードをUTF-8にする
mb_internal_encoding('UTF-8');
header("Content-Type: text/html; charset=UTF-8");

// チケットを確認
if (isset($_POST['ticket'])  &&  isset($_SESSION['ticket'])) {
  $ticket = $_POST['ticket'];
  if ($ticket != $_SESSION['ticket']) {
   header('Location: form.php');
   exit();
  }
} else {
  header('Location: form.php');
  exit();
}

// HTMLでのエスケープ処理をする関数
function h($string) {
  return htmlspecialchars($string, ENT_QUOTES);
}

// 入力内容の取得・変数に格納
$name    = $_POST['name'];
$to      = $_POST['to'];
$subject = $_POST['subject'];
$message = $_POST['message'];

// 入力値をセッション変数に格納
$_SESSION["name"]    = $name;
$_SESSION["to"]      = $to;
$_SESSION["subject"] = $subject;
$_SESSION["message"] = $message;

?>

<html lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>メール送信フォーム</title>
  </head>
  <body>
    <h1>入力内容確認</h1>
    <form action="submit.php" method="post">
      <div>氏名: <?php echo h($name); ?></div>
      <div>メールアドレス: <?php echo h($to); ?></div>
      <div>件名: <?php echo h($subject); ?></div>
      <div>本文: <br />
        <?php echo h($message); ?></textarea>
      </div>
       <input type="hidden" name="ticket" value="<?php echo h($ticket);?>" />
      <div>
        <input type="button" onClick="history.back()" value="戻る" /> 
        <input type="button" onclick="submit();" value="送信" />
      </div>
    </form>
  </body>
</html>

16~25行目: ここで、POST メソッドで送られてきた ticket と、セッション変数に格納されている ticket を確認します。
$_POST[‘ticket’]、$_SESSION[‘ticket’] 、どちらか一方でもない場合 → 強制的に form.php に転送
$_POST[‘ticket’] と $_SESSION[‘ticket’] の値が一致しない場合 → 強制的に form.php に転送

22~42行目: 入力値は $_POST で取得し、それを更に、$_SESSION でセッション変数に格納し、submit.php にも、form.php に戻った場合にも持ち運べるようにします。

60行目: form.php と同じですが、画面には表示されない状態で、$ticket を、次の form.php へ POST メソッドで受け渡します。

submit.php (完了画面)

メール送信には PHP Mailer を使う前提で書いています。→ PHP Mailer を利用したフォームの作成

<?php
// セッションの開始
session_start();

// キャッシュしない
header( 'Cache-Control: no-store, no-cache, must-revalidate' );
header( 'Cache-Control: post-check=0, pre-check=0', false );
header( 'Pragma: no-cache' );
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");

//文字コードをUTF-8にする
mb_internal_encoding('UTF-8');
header("Content-Type: text/html; charset=UTF-8");

// メール日本語対応
mb_language("japanese");

// チケットを確認
if (isset($_POST['ticket'])  &&  isset($_SESSION['ticket'])) {
  $ticket = $_POST['ticket'];
  if ($ticket !== $_SESSION['ticket']) {
    echo '<html>有効期限切れ <a href="form.php" title="ホーム">入力画面へ戻る</a></html>';
    exit();
  }
} else {
    echo '<html>有効期限切れ <a href="form.php" title="ホーム">入力画面へ戻る</a></html>';
    exit();
}

// HTMLでのエスケープ処理をする関数
function h($string) {
  return htmlspecialchars($string, ENT_QUOTES);
}

// 変数にセッション変数を代入
$name    = $_SESSION['name'];
$to      = $_SESSION['to'];
$subject = $_SESSION['subject'];
$message = $_SESSION['message'];

// PHPMailer クラスをネーム空間にインポート
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

// Composer の autoloader をロード
require 'vendor/autoload.php';

// インスタンス生成
$mail = new PHPMailer(true);

try {
    //Server settings
    $mail->isSMTP();                             // SMTP 利用
    $mail->Host       = 'smtp.gmail.com';        // SMTP サーバー(Gmail の場合これ)
    $mail->SMTPAuth   = true;                    // SMTP認証を有効にする
    $mail->Username   = 'nana.rome@gmail.com';   // ユーザ名 (Gmail ならメールアドレス)
    $mail->Password   = 'gvrvkkcewyspzffl';      // パスワード
    $mail->SMTPSecure = 'tls';                   // 暗号化通信 (Gmail では使えます)
    $mail->Port       = 587;                     // TCP ポート (TLS の場合 587)

    // メール本体
    $mail->setFrom('nana.rome@gmail.com', 'hazuki');  // 送信元メールアドレスと名前
    $mail->addAddress($to, mb_encode_mimeheader($name, 'ISO-2022-JP')); // 送信先メールアドレスと名前
    $mail->Subject = mb_encode_mimeheader($subject, 'ISO-2022-JP');  // 件名
    $mail->Body    = mb_convert_encoding($message, "UTF-8","AUTO");  // 本文

    // 送信
    $mail->send();
    echo '送信済み';

    // セッション変数を破棄
    $_SESSION = array();
    session_destroy();

} catch (Exception $e) {
    echo "送信失敗: {$mail->ErrorInfo}";
}
?>

19~28行目: check.php と同じですが、ここで、POST メソッドで送られてきた ticket と、セッション変数に格納されている ticket を確認します。
$_POST[‘ticket’]、$_SESSION[‘ticket’] 、どちらか一方でもない場合 → form.php へ誘導
$_POST[‘ticket’] と $_SESSION[‘ticket’] の値が一致しない場合 → form.php へ誘導

73, 74行目: メール送信が完了したら、セッションを破棄します。

【PHPライブラリ】PHP Mailer

PHP Logo

PHP でメールを送るには、mail 関数とか、mb_send_mail 関数とかありますが、これで送信すると、スパム扱いされることもあるし、付加機能を付けるのも大変なんですよね。
メール送信用のPHP ライブラリ、「PHP Mailer」は、外部の SMTP サーバーを利用(経由)してメールを送信できるので、このような問題が解決でき、設定も簡単です!
公式ページ → https://github.com/PHPMailer/PHPMailer

PHP Mailer のインストールには、Composer の利用が推奨されています。
PHP のインストールから、Composer の利用の仕方まで、前回まとめました。
PHP 開発ローカル環境構築

Composer を利用、SMTP サーバーは Gmail を利用する仮定でまとめます。

目次
・ Composer で PHP Mailer をインストール
・ 送信フォーム作成
・ Gmail の2段階承認利用時のパスワード

Composer で PHP Mailer をインストール

任意のディレクトリ(どこでも大丈夫です。名前もなんでも)に、「composer.json」という名前のファイルをテキストエディターで作ります。composer.json には、以下のコードを書き込みます。(PHP Mailer の公式マニュアル通りです。)

{
    "require": {
        "phpmailer/phpmailer": "~6.1"
    }
}

コマンドプロンプトを立ち上げ(ここでは、どこに Comproser を置いたかにもよりますが、コマンドプロンプトに「管理者」で入らないと、権限エラーになることもあります。)、Composer のインストールされているディレクトリ(PHP のインストールされているディレクトリ)で、「composer require phpmailer/phpmailer」とコマンドを入力し、実行します。このように表示されます。(ファイルがインストールされるまで、少し時間がかかるかもしれません。)

PHP (XAMPP) のあるディレクトリの中に、「vendor」、「composer.json」、「composer.lock」があるのを確認。

「vendor」をディレクトリごと、PHP Mailer を利用したい PHP ファイルのあるディレクトリの中に移動させます。

送信フォーム作成

まず、簡単なメールフォームを作ってみます。

<mail.html>

<html lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>メール送信フォーム</title>
  </head>
  <body>
    <h1>メール送信フォーム</h1>
    <form action="submit.php" method="post">
      <div>氏名: <input type="text" name="name" id="name"></div>
      <div>メールアドレス: <input type="text" name="to" id="to"></div>
      <div>件名: <input type="text" name="subject" id="subject"></div>
      <div>本文: <br />
        <textarea name="message" id="message" cols="50" rows="10"></textarea>
      </div>
      <div><input type="submit" value="送信" /></div>
    </form>
  </body>
</html>

ページ例 → https://exp.hazu.jp/mail-form/mail.html
(submit.php がないので、「送信」をクリックしてもメールは送れませんが)

次に、PHP Mailer を利用した SMTP 送信の PHP ファイルを作ります。

<submit.php>

<?php
// 入力内容の取得・変数に格納
$name    = $_POST['name'];     // 氏名
$to      = $_POST['to'];       // メールアドレス
$subject = $_POST['subject'];  // 件名
$message = $_POST['message'];  // 本文

// メール日本語対応
mb_language("japanese");
mb_internal_encoding("UTF-8");

// PHPMailer クラスをネーム空間にインポート
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

// Composer の autoloader をロード
require 'vendor/autoload.php';

// インスタンス生成
$mail = new PHPMailer(true);

try {
    // SMTPの設定
    $mail->isSMTP();                       // SMTP 利用
    $mail->Host       = 'smtp.gmail.com';  // SMTP サーバー(Gmail の場合これ)
    $mail->SMTPAuth   = true;              // SMTP認証を有効にする
    $mail->Username   = 'XXXXX@gmail.com'; // ユーザ名 (Gmail ならメールアドレス)
    $mail->Password   = 'xxxxxxxxxx';      // パスワード
    $mail->SMTPSecure = 'tls';             // 暗号化通信 (Gmail では使えます)
    $mail->Port       = 587;               // TCP ポート (TLS の場合 587)

    // メール本体
    $mail->setFrom('XXXXX@gmail.com', 'hazuki');  // 送信元メールアドレスと名前
    $mail->addAddress($to, mb_encode_mimeheader($name, 'ISO-2022-JP'));  // 送信先メールアドレスと名前
    $mail->Subject = mb_encode_mimeheader($subject, 'ISO-2022-JP');  // 件名
    $mail->Body    = mb_convert_encoding($message, "JIS","UTF-8");  // 本文

// 送信
    $mail->send();
    echo '送信済み';
} catch (Exception $e) {
    echo "送信失敗: {$mail->ErrorInfo}";
}
?>

これでいいんですが、ちょっと説明付け足します。

29行目: パスワードは、ここでは、Gmail(Google)の普通のログインパスワードになるんですが、もし2段階認証を利用している場合、PHP Mailer では、普通のパスワードでログインできません。これについて、詳しく後述します。

35、36行目: ヘッダー情報にそのまま日本語を書き込むと文字化けしてしまうことがあります。mb_encode_mimeheader 関数を使って、ヘッダーに使われる「ISO-2022-JP」にエンコーディングします。

37行目: 本文も同じく、文字化けに備えて、mb_convert_encoding 関数を使って、一応 JIS にエンコーディングします。

上手くいかない場合は、SMTP の設定あたりに、「$mailer->SMTPDebug = 2;」(詳細デバッグ)を加えて、デバックを取ってみてください。詳細がコメントアウトされるので、おかしなところが分かるかもしれません。

その他、Cc や Bcc、返信用アドレス、添付ファイル、HTML メールとテキストメールの切り替え等も含めることができますが、公式ページ(https://github.com/PHPMailer/PHPMailer)をご覧ください。

Gmail(Google)の2段階認証利用時のパスワード

Gmail(Google)で2段階認証を利用している場合、PHP Mailer の SMTP 設定では普通のログインパスワードだとログインエラーになります。それ専用のパスワードを生成する必要があります。手順は以下の通りです。

ブラウザで Gmail(Google)を開き、ログインした状態で、右上の自分のサムネイルをクリック。
ドロップダウンメニューの中の「アカウント」をクリック。


左のメニュー項目の「セキュリティ」をクリック。


「Google へのログイン」で、「2段階認証プロセス」が「オン」になっていることを確認の上、「アプリ パスワード」をクリック。


「アプリを選択」をクリック。
「その他(名前を入力)」の欄に、「PHP Mailer」など分かりやすい名前を付ける。
「生成」をクリック。


16桁のパスワードが生成されます。これを PHP Mailer を利用した PHP ファイルの SMTP の設定において利用します。

PHP 開発ローカル環境構築

PHP Logo

PHP Mailer (SMTP 送信のための PHP ライブラリ) についてまとめようと思ったのですが、公開されているサイト https://github.com/PHPMailer/PHPMailer を読んだら、今は Composer の利用が推奨されているんですね。
PHP ライブラリの利用などを書く前に、まずは PHP 開発でのローカル環境構築についてまとめてみます。

目次
・ そもそも PHP とは?
・ XAMPP で PHP をインストール
・ Composer インストールから使い方まで

そもそも PHP とは?

PHP は Web 開発に使われるサーバーサイドプログラミング言語です。Web によく使われるクライアントサイドの Java Scrip と比較するとこんな具合です。

<PHP>
サーバー側 (PHP を実行する):クライアントのリクエストにより、PHP ファイルから HTML ファイルなどを生成し送信
クライアント側 (PHP は関係ない) :サーバーにリクエストし、送られてきた HTML ファイルなどをブラウザに表示

<Java Script>
サーバー側 (Java Script を実行しない):Java Script をそのままユーザーに送信
クライアント側 (Java Script を実行する) :ブラウザで Java Script を実行して表示

そのプログラム言語が実行されるのが、サーバー側でなのか、クライアント側でなのかという違いですね。
Java Script は一般的なブラウザで実行できるので、開発する側はブラウザとテキストエディターさえあれば、ちゃちゃっと書いて確認できますが、PHP は、PHP を動かす環境が必要となります。レンタルサーバーでも、大概のところで PHP は利用できるので、サーバーにアップロードすれば、動作確認はできるのですが、オフラインでローカル(あなたの PC)で動作確認するには、PHP と関連システムをインストールする必要があります。

XAMPP で PHP をインストール

PHP を動かすためには、PHP そのもの以外にも、Apache だの Visual C++ だの、データベース呼び出すなら MySQL だの、色々必要なんですが、こういうのを全部ひっくるめてインストールできるのが「XAMPP」です。Perl も一緒にインストールしてくれます。便利ですね!

ダウンロードページ → XAMPP Apache + MariaDB + PHP + Perl

① ダウンロード後、exe ファイルを実行。アンチウィルスソフトに対する警告が出る場合もありますが、問題なければ「Yes」をクリック。
インストール画面が出だら、「Next」をクリック。
② インストールする項目を選択。「Next」をクリック。
 

③ インストールする場所を選ぶ。「Next」をクリック。
④ Bitnami は今は関係ないので、チェックを外す。「Next」をクリック。
 

⑤ 「Next」をクリックしてインストール開始
⑥ しばらくかかります。
 

⑦ インストール終了後、タスクバーにある XAMPP アイコンをクリック。
(タスクバーにアイコンがなければ、「インストールしたディレクトリ」→「XAMPP」→「xampp-control.exe」をクリック。)
コントロールパネルが開いたら、「Apache」と「MySQL」の Actions「Start」をクリック。
(Apache と MySQL は、時間がたったり、PC を落とすと Stop され、localhost に繋がらなくなることがあるので、その場合はまたコントロールパネルから Start させる必要があります。)

これで準備完了。
実行したい PHP ファイルは、「xampp」(インストールした場所)の中の「htdocs」の中に入れます。

ブラウザで「http://localhost」と入力すると、こう表示されます。

「htdocs」に入れた PHP を実行してブラウザで見るには、「http://localhost/ファイル名.php」と入力して表示させます。

Composer インストールから使い方まで

「Composer」は、PHP のプロジェクトが必要とするライブラリやパッケージを管理する「ライブラリ依存管理ツール」です。
これが便利なのは、例えば、A というライブラリを動かすには、B と C が必要で、C を動かすには D が必要で・・・というように、必要なライブラリが他の幾つもと関わり合っている場合、これを自分であちこちから持ってくるのは大変ですよね。Composer を利用すると、必要なパーツを簡単に集めてくれます。ただ、コマンドプロンプトを利用するので、コマンドプロンプトの基本の基本でもいいので、分かってなくちゃいけません。

ダウンロードページ → Composer – A Dependency Manager for PHP

① ダウンロード後、exe ファイルを実行。アンチウィルスソフトに対する警告が出る場合もありますが、問題なければ「Yes」をクリック。
インストール画面が出だら、「Next」をクリック。
② 利用する PHP を選びます。今回は、先ほど「XAMPP」をインストールしたので、それを選びます。「Next」をクリック。
 

③ プロキシサーバーを利用している場合は、ここで登録します。利用していない場合は、そのまま「Next」をクリック。
④ インストール内容を確認して、「Install」をクリック。
 

⑤ インストールが完了画面が出たら、「Finish」をクリックしてお終い。

インストール完了後、コマンドプロンプトを立ち上げ、インストールされているか確認します。
インストールしたディレクトリ(関連付けた PHP のあるディレクトリ)で、「composer -V」とコマンドを入力して実行。これはバージョンを見るためのコマンドです。

このように表示されれば、インストールされています。

Composer を利用するには、プロジェクトごとに任意のディレクトリ(どこでも大丈夫です。名前もなんでも)を作ります。その中に、「composer.json」という名前のファイルをテキストエディターで作って入れておきます。composer.json には、以下のコードを書き込みます。

{
    "require": {
        "phpmailer/phpmailer": "~6.1"
    }
}

“require” の中は、欲しいライブラリ名(パッケージ名)、及び、バージョン名を記述します。今回は、PHP Mailer が欲しいので、GitHub に公開されている公式のマニュアル通りにこう書きました。

あとは、コマンドプロンプトで実行するだけ。(ここでは、どこに Comproser を置いたかにもよりますが、コマンドプロンプトに「管理者」で入らないと、権限エラーになることもあります。)
Composer のインストールされているディレクトリで、「composer require phpmailer/phpmailer」とコマンドを入力し、実行します。このように表示されます。(ファイルがインストールされるまで、少し時間がかかるかもしれません。)

PHP (XAMPP) のあるディレクトリの中を覗いてみます。

「vendor」というディレクトリと、「composer.json」、「composer.lock」というファイルが出来ていますね。この「vendor」の中に、(今回の場合)PHP Mailer に必要なライブラリーやパッケージが全てインストールされています。

ウェブ用画像の最適化

Cat and Dog

ウェブサイトを魅力的なものにしてくれる写真やロゴは、ウェブデザインの上で欠かせない要素ですが、たくさん利用するとサイトが重くなってしまいますよね。重いサイトは、ユーザがストレスに感じて、待てないよー!って、離脱してしまうかもしれないし、SEO的にも検索エンジンの低評価につながるので、画像の圧縮やリサイズなどの最適化は非常に重要です。

ウェブサイトに画像を利用する際、するべきこと

  1. 魅力的で適切な画像を利用する
  2. 適切なファイルフォーマットを選ぶ
  3. 圧縮、リサイズする
  4. SEO的に有効なファイル名、代替テキスト、キャプションを付ける

順番に見ていきます。

1. 高品質で適切な画像を利用する

ページの趣旨や文章に合った、魅力的な画像を利用することが大切ですね。
画像を自分で用意する、プロに依頼するなどありますが、ここではネット上で画像を提供しているサイトをいくつかまとめてみました。
利用規約がまちまちなので、しっかりと確認してください。

<無料の写真素材>

サイト名 内容
Pixabay.com 世界中のクリエイターによる100万点以上の高品質な画像素材を提供。海外のサイトですが日本語検索可。
PAKTASO 凡庸性の高い高品質な画像素材がストックされていて便利です。
GIRLY DROP 女の子好みのお洒落な画像がたくさん提供されています。
写真AC クリエイター投稿型の写真素材サイト。利用にはユーザ登録が必要。無料で利用できるのは、1日9個まで。
FIND/47 経済産業省のサイトで、日本全国の四季折々の写真が公開されています。見ているだけでも楽しいです。
Unsplash 100万点以上の高品質な写真を提供しているカナダの写真素材サイト。検索は英語で。
BURST ネットショップ作成サービスのShopifyが運営するフリー写真素材サイト。検索は英語で。
food.foto
Foodiesfeed
どちらも料理、食材に特化した写真素材サイトです。

<有料の写真素材>

サイト名 内容
PIXTA 国内最大級。月額プラン、単品で購入も可。
Adobe Stock 世界中の豊富な素材が利用できて、コスパいいです。日本語サイトあり。月額プラン、パッケージプランから選びます。
shutterstock 世界最大級の写真素材サイト。日本語サイトあり。月額プラン、パッケージプランから選びます。

<無料のアイコン>

サイト名 内容
FLAT ICON DESIGN お洒落感高めのアイコンがそろっています。背景色を選べます。
ICOOON MONO モノトーンのアイコン素材集。色味を調整してダウンロードできます。
アイコン配布中 スマホ用にデータが軽減されています。1日12個まで。
イラストAC 写真AC の姉妹サイト。1日9個まで無料。
iconmonstr 非常に豊富なアイコンの揃うドイツのサイトです。
ICONS8 ウェブ用デザインリソースを提供するアメリカのサイト。HTML に画像リンクを埋め込むこともできます。

<無料のイラスト>

サイト名 内容
イラストAC 写真AC、アイコンACの姉妹サイト。1日9個まで。
無料イラスト素材.com パーツとして利用できるイラストが豊富に揃っています。
EC design ネットショップに使い勝手のよさそうなイラストが揃っています。

2. 適切なファイルフォーマットを選ぶ

ウェブサイトの画像には、JPEG、PNG、GIF がよく利用されますね。最近よく利用される SVG も合わせて、特徴をまとめます。
画像の美しさ、ファイルの大きさにかかわってくるので、適切なフォーマットで保存することは大切です。

フォーマット 特徴
JPEG フルカラーで表現できるので、色数の多い画像をきれいに表示できる。
風景・景色・人物像など多くの色を使う写真に利用。
PNG フルカラーで高画質ですが、ファイルサイズが大きめです。透過処理もできる。
色数の多いイラストや、透過処理をする切り抜きの写真などに利用。
GIF 256色で構成されデータ容量が軽い。アニメーションや透過処理ができる。
色数の少ないロゴやイラストなどに利用。
SVG 拡大縮小しても画像が滑らかで、色やサイズの変更が簡単。複雑な形状や写真のように画素数の多いものには不向き。アニメーションや透過処理ができる。
アイコンなど複雑でない画像などに利用。

3. 圧縮、リサイズする

圧縮、リサイズは、Photoshop などの画像エディタツールを利用してできますが、ここでは圧縮、リサイズに特化したものをまとめます。

<ブラウザ上で加工>

サイト名 内容
Optimizilla 圧縮後の画像を見ながら、自分でクオリティを調整できます。日本語対応あり。
対応ファイル形式:JPEG、PNG
TinyPNG 一度に20個、簡単に圧縮ができます。圧縮率は自動で設定され、自分での詳細設定はできません。英語サイト。
対応ファイル形式:JPEG、PNG
Kraken.io DropboxやBox、Googleドライブからファイルをインポートしてアップロードできます。
無料版は圧縮のみですが、有料版はリサイズもできます。英語サイト。
対応ファイル形式:JPEG、PNG、GIF
Bulk Resize リサイズ用サービス。指定したサイズに、複数の画像をまとめてリサイズすることも可能。日本語対応あり。
対応ファイル形式:JPEG、PNG、WEBP

<WordPress プラグイン>
WordPress を利用している場合、自動で圧縮、リサイズを行うプラグインを利用できます。

プラグイン名 内容
Compress JPEG & PNG images ウェブサービスTinyPNG のプラグインバージョンです。
EWWW Image Optimizer 自動で簡単に画像の圧縮ができるプラグインです。
Simple Image Sizes リサイズ用のプラグインです。

4. SEO的に有効なファイル名、代替テキスト、キャプションを付ける

ファイル名: 画像に関連のある英語名。(例: sleeping-cat.jpg)
代替テキスト: 画像を簡潔に表す言葉。視覚障害者の方など、音声読み上げを利用している方には重要。(例: 眠り猫)
キャプション: 画像に関する説明・補足・注釈等の有用な情報。(例: 栃木県日光市の日光東照宮の回廊にある建築装飾彫刻作品)

また、画像の周辺のテキストは、検索エンジンが画像を理解する材料となります。画像と周辺の文字列の関連性も、SEO 的には重要です。

【雑談】どうして Hello, World なの?

hello world C


何かしらのプログラミング言語を勉強したことのある人なら、文字列を表示させるもっともシンプルな構文のテスト文言として “Hello, World” というのを目にしたことがあると思うんですが、どうして “Hello, World” なんでしょう?

ずっと前から疑問に思っていたんですが、まわりの人に聞いても、分からない、とか、特に意味はないんじゃない?、とか、たまたまじゃない?、というような感じなんですよね。

どうしてか、というのははっきりしないけど、いつから使われているのかという起源ならはっきりとしています。テスト文言として “Hello, World” を使う習慣はブライアン・カーニハンとデニス・リッチーによる著書「プログラミング言語C」(1978年)からです。

だったらカーニハンかリッチーに聞けば分かりそうな感じだけど、本人たちがそんなことにあまり言及していなかったみたいですね。たくさんの人が疑問に思ってネットでも質問しています。

でも、どこかしらになにか書かれているだろうと思って、IT系のブログや記事を散々読み漁っていたら、アメリカのITニュース配信サイト「SLATE」で、こんなの見つけました。
Why the first code a programmer writes is “Hello, World.”
NewYork Times にも寄稿しているクライブ・トンプソン氏の記事です。

抜粋です。
「Kernighan had recently seen “some cartoon of a chick coming out of an egg, and it says Hello, World,” as he told me when I called him up to ask about it. It tickled his fancy, so he used that. 」

和訳(変だったらごめんなさい)
「(どうして Hello, World なのか)質問したところ、カーニハンは、最近、こう言っていました。”卵からヒヨコが出てくる漫画があるんだけど、それが Hello, World なんだよ。” それに空想が刺激されて、彼はそれを使うことにしたのです。」

2019年10月16日の記事だったので、最近、このようにおっしゃったのでしょうか。

プログラミングの世界の入り口に立つ者が、これから広がる世界に対して「Hello, World」と1行目を書き込む、広大な世界へ歩き出したばかりのひよっこですものね。
AI だろうが、ロボットのプログラミングだろうが、この一行から始まっているんですよ。

なぜ別の言葉じゃなく、よりによって Hello, World なのか、ようやく納得できて、長年の疑問が解けてうれしくなりました。
明日から自慢げに言いふらしてしまいそうです。(自分の周りにどれぐらいこれを知りたい人がいるのかが疑問ですけど・・・ 笑)

後方参照

後方参照ロゴ

.htaccess では、一部のディレクティブ(RewriteRule、RewriteCond、Macht の含まれるもの)では正規表現が利用でき、基本的な正規表現はこちらにまとめました。⇒ 正規表現の基本

正規表現で条件にマッチングした文字または文字列は、変数として格納して、それより後で利用することができます。

例を見ていきましょう。このような正規表現があったとします。

(blue|green)(\d+)(\.html)

3つにグループ化されていますね。何を意味しているかというとこうなります。

(blue|green) ⇒ “blue” または “green”
(\d+) ⇒ 1個以上の任意の数字の繰り返し
\.html ⇒ “.html”。”.” はメタ文字なので、普通の文字として “.” を扱うために、直前に “\” がつきます。

つまり、”blue1.html”、”blue2.html”、”blue10.html”、”green1.html”、”green2.html”、”green10.html” などがマッチングします。

これらを変数として格納し、 “$n” という表記で、後で利用することができます。(“n” には 0~9 の数字が入ります。)マッチング条件が複数ある場合、左側から、$1、$2、$3 という順番で、変数が格納されていきます。($0 と $1は同じ)

この例の場合はこうなります。

(blue|green) ⇒ $1
(\d+) ⇒ $2
\.html ⇒ $3

実際の.htaccessに使われる例としてはこんなかんじになります。

RedirectMatch /(blue|green)(\d+)(\.html) http://newdomein.com/$1$2$3
#               ↑"$1"に格納 ↑"$2" ↑"$3"              後方参照 ↑
# 転送元にある該当htmlファイルを、newdomein.comにある同じ名前のhtmlファイルに転送

Macht の含まれるディレクティブ、RewriteRule ディレクティブでは、前述したように、後方参照に ” $n ” が使われるのですが、RewriteCond ディレクティブでは、” %n ” が使われます。ご注意ください。

正規表現の基本

正規表現ロゴ

.htaccess では、一部のディレクティブ(RewriteRule、RewriteCond、Macht の含まれるもの)では正規表現が利用できます。

正規表現とは、プログラミングで使用される、いくつかの文字列を1つの形式で表現する記述方法です。何に使われるかといえば、多くの文字列から必要な文字列を検索して、条件を指定したりする場合です。

プログラミングとかしていても、あまり使わないやつとか、忘れちゃうんですよね。
.htaccess のディレクティブをまとめる前に、基本的な正規表現をまとめてみます。

基本的な正規表現

記号 説明
. 任意の1文字にマッチ
例)”の.と” なら、”のーと”、”のうと”、”のりと” などがマッチ。
^ ^ に続く文字列が 行の先頭にある場合にマッチ
例)”^本” なら、”本棚”、”本拠” はマッチ。”台本” はマッチしない。
$ $ の前の文字列が 行の文末にある場合マッチ
例)”花$” なら、”生花”、”紅花” はマッチ。”花束” はマッチしない。
* * の前の文字が 0回以上繰り返す場合にマッチ
例)”はー*い” なら、”はい”、”はーい”、”はーーい” などがマッチ。
+ + の前の文字が 1回以上繰り返す場合にマッチ
例)”はー+い” なら、”はーい”、”はーーい” がマッチ。”はい” はマッチしない。
? ? の前の文字が 0個か1個ある場合にマッチ
例)”はー?い” なら、”はい”、”はーい” がマッチ。”はーーい” はマッチしない。
| ~または~(選択肢の区切り目)
例)”小学生|中学生|高校生” なら、この3つの選択肢のいずれかがマッチした場合。
[] 角括弧内の文字のどれか1つにマッチ。
例)[abc123] なら、”a”、”b”、”c”、”1″、”2″、”3″にマッチ。

角括弧内の文字を “-” を使って範囲指定可能
例)[a-cX-Z0-1] は “a”、”b”、”c”、”X”、”Y”、”Z”、”0″、”1” にマッチ。

[^] 角括弧内の文字が含まれないものにマッチ
例)[^a-c] なら、”a”、”b”、”c” 以外にマッチ。
() グループ化(文字の集合を1つにまとめて扱う)
例)(一時間)+ なら、”一時間” にマッチ。
{} 直前の文字、またはグループの桁数を指定
例)”あ{3}” なら、”あああ” がマッチ。
“あ{2,3}” なら、”ああ”、”あああ” がマッチ。
“あ{2,}” なら、あを2回以上繰り返すものがマッチ。

メタ文字を普通の文字として扱う ” \

正規表現の基本として使用される ” . 、^ 、$ 、* 、+ 、? 、| 、[ 、] 、{ 、} ” は、メタ文字と呼ばれ、上記、「基本的な正規表現」でまとめた通り、文字を形式に沿って表現するための意味が与えられています。これらのメタ文字を、普通の文字として扱うには、直前に “ \ ” を記述することで実現します。

説明
\ エスケープ 直後のメタ文字を普通の文字として扱う
例)”2\+3″ の場合、”2+3″ がマッチ。”+” は、「直前の文字が 1回以上繰り返す場合にマッチ」という意味ではなく、普通の文字として扱われる。

定義済みの文字クラス

記号 説明
\d [0-9] 全ての数字
\D [^0-9] 数字以外の全ての文字
\s [\t\f\r\n] 垂直タブ以外のすべての空白文字
\S [^\t\f\r\n] 空白文字以外の全ての文字
\w [a-zA-Z_0-9] アルファベット、アンダーバー、数字
\W [^a-zA-Z_0-9] アルファベット、アンダーバー、数字以外の全ての文字

後方参照

正規表現で条件にマッチングした文字または文字列は、変数として格納して、それより後で利用することができます。
後方参照について詳しくはこちら⇒後方参照

ドロップダウンメニュー(レスポンシブ対応)jQuery編

jquery logo

WordPressの最近のテーマは大概、メニューなどレスポンシブ対応で、自動でドロップダウン式に切り替わるようになっていますが、古いテーマを利用するとか、手書きで書く場合など、使えるかなと思って、レシピ的にメモしておきます。
ドロップダウンメニューはCSSだけでも実現できますが、今回はjQueryです。

ddmenu.js

jQuery(document).ready(function($){

	var agent = navigator.userAgent;
	var list = jQuery("div.menu-container");

	function menuslide(){
	//メニュー表示非表示
	jQuery("p.navtitle").click(function(){
	if(jQuery(list).css("display")=="none"){
		jQuery(list).slideDown("normal");
	}else{
		jQuery(list).slideUp("normal");
	}
    });
    }

    if(agent.search(/iPhone/) != -1 || agent.search(/iPod/) != -1){
        menuslide();

    }else if(agent.search(/Android/) != -1){
        menuslide();

	}else{
		var replaceWidth = 700;
		function widthSize(){
			var windowWidth = parseInt(jQuery(window).width());
			if(windowWidth >= replaceWidth) {
				jQuery(list).css("display","block");
			} else if(windowWidth < replaceWidth) {
				jQuery(list).css("display","none");
			}
		}
		jQuery(window).resize(function(){widthSize();});
			widthSize();
			menuslide();
  }
});

CSS

@media screen and (min-width: 701px) {
    p.navtitle {
        display: none;
    }
}

@media (max-width: 700px) {
    /* @media (max-width: 700px) Reduce font-sizes for better readability on smaller devices */
p.navtitle {
        color: #ffffff;
        dispaly: block;
        cursor:pointer;
        line-height:1.4;
        font-size: 14px;
        font-weight:bold;
        margin: 1.2em 0 1.2em 5px;
    }
    #access div {
        margin: 0;
    }
    #access ul {
        margin: 0;
    }
    div.menu-menu-1-container {
        height: auto;
        width: 100%;
        display: none;
    }
    div.menu-container ul {
        width: 100%;
    }
    div.menu-container ul li {
        float: none;
        background: #222;
        width: 100%;
        padding: 0;
        text-align: left;
        font-size: 14px;
    }
    div.menu-container ul li a {
        border-bottom: solid 1px #666;
    }
    
   .nav-btn-ico {
      display: inline-block;
      position:relative;
      margin: 0 10px 3px 0;
      padding: 0;
      background: #ffffff;
    }
    .nav-btn-ico, .nav-btn-ico:before, .nav-btn-ico:after {
      width: 14px;
      height: 3px;
    }
    .nav-btn-ico:before, .nav-btn-ico:after {
      display: block;
      content: "";
      position:absolute;
      top: 50%;
      left: 0;
      background: #ffffff;
    }
    .nav-btn-ico:before {
      margin-top: -8px;
    }
    .nav-btn-ico:after {
      margin-top: 4px;
    }  
}