ラベル 正規表現 の投稿を表示しています。 すべての投稿を表示
ラベル 正規表現 の投稿を表示しています。 すべての投稿を表示

2016年9月3日

再入荷お知らせメールの話3


前回からの引き続きで、再入荷お知らせメールの話3。
今回は入力フォームのサンプルを作ってみました。

「初めてのPHP5(オライリー本)」のコードを参照しました(解説はオライリー本参照)。
PHP入門書には、この手の入力フォームのコードが載っていますので、読み解きやすいものを選ばれると良いでしょう。

入力画面→入力項目のチェック→登録
↑     ↓エラー
↑←←←←←↓


私が行ったテストはソースコードレビューのみで、他はほとんど行っていません。
本番環境に実装する場合には、ご自身の責任でしっかりとテストを行ってください(ソフトウェアテストについて理解しておく必要があります)。


1. コーディング(人柱版)

<?php

if (isset($_POST['_submit_check']) && $_POST['_submit_check']) {
  if ($form_errors = validate_form()) {
    show_form($form_errors);
  } else {
    process_form();
  }
} else {
  if(! isset($_GET['pid'])) {
    $_POST['pid'] = '';
  } else {
    $_POST['pid'] = $_GET['pid'];
  }
  if(! isset($_POST['mail'])) {
    $_POST['mail'] = '';
  }
  show_form();//最初の画面表示
}


function show_form($errors = '') {
  if ($errors) {
    $error_text = implode('<br/>',$errors);
  } else {
    $error_text ='';
  }

?>

<!--入力画面-->
<form method="POST" action="<?php print $_SERVER['SCRIPT_NAME']; ?>">
  <input type="text" name="mail" value="<?php print htmlentities($_POST['mail']) ?>" />
  <input type="hidden" name="pid" value="<?php print htmlentities($_POST['pid']) ?>" />
  <input type="hidden" name="_submit_check" value="1" />
  <input type="submit" value="登録する" />
</form>

<?php

  print $error_text;
}


function validate_form() {
  $errors = array();
  if (! strlen(trim($_POST['mail']))) {
    $errors[] = 'メールアドレス未入力エラー';
  } elseif (! preg_match('/^[^@\s]+@([-a-z0-9]+\.)+[a-z]{2,}$/i', $_POST['mail'])) {
    $errors[] = 'メールアドレス構文エラー';
  }
  if (! strlen(trim($_POST['pid']))) {
    //正規のリンクから呼び出されていないのでトップに飛ばすなりする
    $errors[] = '商品ID未入力エラー';
  } elseif(! is_numeric($_POST['pid'])) {
    //正規のリンクから呼び出されていないのでトップに飛ばすなりする
    $errors[] = '商品ID数字エラー';
  }
  //商品IDがカラーミーショップに存在しているか
  //pidとmailの組み合わせが、既に登録されていないか
  //在庫はゼロかなどをチェック

  return $errors;
}


function process_form() {
  //データベース登録
  $dsn = 'mysql:dbname=mydb;host=localhost';
  $user = 'root';
  $password = '';

  try{
    $pdo = new PDO($dsn, $user, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

    $sql = 'INSERT INTO restock SET pid=?, mail=?';
    $stmt = $pdo->prepare($sql);
    $stmt->execute(array($_POST['pid'], $_POST['mail']));
  } catch (PDOException $e){
    //print('Error:'.$e->getMessage());
    die();
  }

  //登録完了画面
print <<<_HTML_
登録ありがとうございました。<br/>
_HTML_;



  exit();
}
?>

2. 簡単な説明


ウィンドウ(or モーダルウィンドウ)で入力フォームを開くと良さげ、閉じるボタンも付けて。その他、デザイン的な体裁を整える必要があります。

表示させたい項目があれば、カラーミーショップAPIで取ってくる。

isset()がないと、最初の画面表示でNoticeが出るので。

メールアドレス構文チェックの正規表現も「初めてのPHP5(オライリー本)」から。検索すると、たくさん例文が見つかりますが、どれがよいのやら。

pidのエラーは明らかにオカシイので、入力画面に戻さず、トップページに飛ばすなりする。

htmlentities箇所は定番の、htmlspecialchars($str, ENT_QUOTES, 'UTF-8')でよいかと。

CSRF対策をしていません(IPAの「安全なウェブサイトの作り方」参照)。
ログイン不要の入力フォームの場合はもともといたずらし放題なので、トークンのやりとりをしないアプリケーションも見かけます(焼け石に水という考え方だと思います)。
今回サンプルとして作ったコードも、他人のメールアドレスを入力して登録できる仕組みなので、考え方も弱点も似たようなものです。
ただ、本番環境で動かすなら、わずかな問題でも対応するのがよい開発者。検索すると、トークンのやりとりで対策するサンプルが見つかります。

いたずら対策として、カラーミーショップのアカウントにログインするよう促して、そこからメールアドレスを取ってくるか(アカウント登録が必須になる)。
あと、CAPTCHAつけたり。

登録メール宛に内容を送信し、リンクをクリックしたのちに登録するような仕組みにするか(登録に手間がかかって嫌かもしれないが)。認証コードを送信して、入力してもらうか。


3. おわりに 


省略箇所もありますので、のこりを自分で仕上げられる人用です。

この手のウェブアプリケーションでもっとも気を遣う点は、セキュリティだと思います(知識をインプットしておかないと、なにをどこまでがわからない)。
IPAの「安全なウェブサイトの作り方」が読みやすくて、入門編として勉強になりました(同ページ内の「安全なSQLの呼び出し方」もチェック)。
引き続き、ウェブや書籍でインプットしていきたいと思います。

【追記】
時折見かけますが、クエリパラメータをディレクトリ風にして渡すこともできます。興味あれば、検索してみてください。

【追記2】
徳丸浩さん著の「体系的に学ぶ安全なWebアプリケーションの作り方」を読みましたが、大変分かりやすかったです。これは手元に置いておきたい。

2010年6月2日

Smartyサンプル 1

【仕事サイトにSmartyの関連記事を書きました】
カラーミーショップのカスタマイズに便利なSmartyを学ぶ (1/2) 
カラーミーショップのカスタマイズに便利なSmartyを学ぶ (2/2)

 
カラーミーショップでは、Smartyと呼ばれるテンプレートエンジンが使用されています。

Smartyでやれること
・独自タグでデータ出力しページ上に表示させる (商品名や価格など。基本中の基本)
・デザインにより近いところでの、条件分岐やループ(JavaScriptでも条件分岐やループはできるがデザインが大変)
・正規表現と置換(regex_replace)が強力
たとえば、商品名を表示させる独自タグ(<{$product.name}>)には、「商品名への付加画像設定」で画像(New画像など)を指定した場合には、HTMLのimgタグがもれなくついてきます。
商品名のみが必要な場合は、
<{$product.name|regex_replace:'/\\<\\/?[^\\>]*\\>/':''}>
とすると、不要なタグ部分をのぞいて取り出せます(商品名によっては必要な部分までとりのぞいてしまいますが)。
そのほか、特定の文字列以降をごっそり消してしまうこともできます(例の場合は■以降を空の文字列に置換している)。
カラーミーには大・小2カテゴリーしかありませんので、商品名にカテゴリー要素を組み込んでおき、正規表現で上手に取り出すという使い方もできます。
<{$product.name|regex_replace:'/■.+/':''}>


Smartyの、あくまでサンプル
・$smarty リクエスト変数(たとえば、URLの”cid=”のあとの文字)などを参照する。例の場合は、商品検索時のカテゴリidを表示します。
<{$smarty.get.cid}>
・assign テンプレート変数を割り当てる。
<{assign var="name" value=$product.name|regex_replace:'/\\<\\/?[^\\>]*\\>/':''}>
<{$name|regex_replace:'/■/':'☆'}>
これでも同じこと(参考)
<{assign var="name" value=$product.name|regex_replace:'/\\<\\/?[^\\>]*\\>/':''|regex_replace:'/■/':'☆'}>
<{$name}>
・fetch コンテンツを読む。ページを読んで、必要な情報を切り出すことも可能です。
<{fetch file="http://~/?mode=srh&cid=&keyword=hoge" assign="res"}>
<{$res|regex_replace:'/\\<\\/?[^\\>]*\\>/':''}>
・strip_tags 変数の修飾子。あとから気づいたんですが、HTMLタグを取り除く機能は、わざわざ正規表現を使用して置換せずとも、はじめからついています(どう考えてもこっちのほうが簡単)。
例の場合は、商品名からHTMLタグを取り除いています。商品登録時に、「商品名への付加画像設定」をしていると、imgタグがつきます(たとえば、New画像など)。
<{$product.name|strip_tags}>
上記の場合はHTMLタグをひとつの空白に置き換えています。
さらに、別の文字列や空白なしにすることもできます(:以降で設定、下例では空白なし)。
<{$product.name|strip_tags:''}>

【参照リンク】
・Smarty

【関連記事】
・Smartyサンプル 2
・ツィートする 2

2010年5月26日

カートに入れるを二ヶ所につける 3

「カートに入れるを二ヶ所につける 2」のつづき。

今回はJavaScriptのサンプルです。ベースになるソースは「カートに入れるを二ヶ所につける 1」です。
JavaScriptですが、外部ファイルを読み込む場合は、ロリポップ!のようなレンタルサーバーに置き、「共通」か「商品詳細」の先頭で読み込みます( 汎用性のある関数は「共通」で)。<body>部でOK。
<script src="http://~/sample.js" type="text/javascript"></script>
ポイントは前回説明済みですので、今回は補足事項のみ。
・カートの二つめはまるまるコピーで、橙色部分を修正。
・置換処理では関数名しか差し替えていない。

1.テンプレート「商品詳細」の購入数からカートに入れるまで抜粋。橙色部分が修正箇所。
<form name="product_form" method="post" action="<{$cart_url}>">
  (中略)
  <{if $product.soldout_flg == 0 && $shop_stop_flg == false && $product.login_sale_flg == false }>
    <tr>
      <th>購入数</th>
      <td style="padding: 0px 5px;">
        <table border="0" cellpadding="0" cellspacing="0">
          <tr>
            <td style="padding: 0px;">
              <input type="text" name="product_num" value="<{$product.init_num}>" style="width: 50px;" onKeyUp="change_key_num()"/>
            </td>
            <td style="padding: 0px;">
              <div style="width: 15px; margin: 0px;">
                <a href="<{$num_up_url|regex_replace:'/f_change_num2/':'change_button_num'}>"><img src="<{$num_up_img_url}>"></a>
                <a href="<{$num_dw_url|regex_replace:'/f_change_num2/':'change_button_num'}>"><img src="<{$num_dw_img_url}>"></a>
              </div>
            </td>
            <td style="padding: 0px;"><{$product.unit}></td>
          </tr>
       </table>
     </td>
   </tr>
  <{/if}>
  (中略)
  <{if $product.soldout_flg == 0 && $shop_stop_flg == false && $product.login_sale_flg == false }>
    <tr>
      <th>購入数</th>
      <td style="padding: 0px 5px;">
        <table border="0" cellpadding="0" cellspacing="0">
          <tr>
            <td style="padding: 0px;">
              <input type="text" name="product_num2" value="<{$product.init_num}>" style="width: 50px;" onKeyUp="change_key_num2()" />
            </td>
            <td style="padding: 0px;">
              <div style="width: 15px; margin: 0px;">
                <a href="<{$num_up_url|regex_replace:'/f_change_num2\(document.product_form.product_num/':'change_button_num(document.product_form.product_num2'}>"><img src="<{$num_up_img_url}>"></a>
                <a href="<{$num_dw_url|regex_replace:'/f_change_num2\(document.product_form.product_num/':'change_button_num(document.product_form.product_num2'}>"><img src="<{$num_dw_img_url}>"></a>
              </div>
            </td>
            <td style="padding: 0px;"><{$product.unit}></td>
          </tr>
       </table>
     </td>
   </tr>
  <{/if}>
  (中略)
</form>

2.JavaScript
function change_key_num(){
  document.product_form.product_num2.value = document.product_form.product_num.value;
}

function change_key_num2(){
  document.product_form.product_num.value = document.product_form.product_num2.value;
}

//***********************************
//個数UP・DOWNボタン処理(差し替え版)
//***********************************
function change_button_num(select, pType, pMinNum, pStockNum) {
  if( select.value.match(/[^0-9]/)){ select.value = pMinNum; return; }
  wNum = parseInt(select.value);
  if (pType == "0" && wNum > pMinNum) {
    document.product_form.product_num.value = String(wNum-1);
    document.product_form.product_num2.value = String(wNum-1);
  }
  if (pType == "1") {
    if (pStockNum) {
      if (wNum >= pStockNum) return;
    }
    document.product_form.product_num.value = String(wNum+1);
    document.product_form.product_num2.value = String(wNum+1);
  }
}

テストした感じだと問題なさそうですが、いかがでしょうか。
以前に仕事でつくったソースより、ずいぶんシンプルになりました。


「カートに入れるを二ヶ所につける - オプション編」に続く。

【追記】
間違い部分を訂正しました。ご指摘ありがとうございました。