ユーザー登録の確認で、入力されたメールアドレスに有効期限つきのURLが送信されて
そのリンクをクリックしてページを表示して初めて登録が完了するというのがある。
それについてGoogle先生に聞いても具体的に教えてもらえなかったので、想像だけで組んでみた。
send:
- ユーザーから送信された登録情報をチェック
- エラーがなければトークン作成
- トークンを名前にしたテキストファイルをトークンディレクトリに作成
- 作成されたトークンファイルに有効期限のタイムスタンプ保存
- トークンを追加したURLを本文に書いたメール送信
- 送信しましたメッセージ表示
access:
- メール送信したURLにアクセスがある
- GETでトークン取得
- トークンディレクトリから同名のファイルを検索
- ファイル作成日+期限と現在時刻の比較
- 期限内であればtrue ファイル削除
- 期限外であればfalse ファイル削除
フォルダをテーブルにしてファイルをレコードにすればデータベースでも…
サンプルなので色々省いてます。
$tokendir = dirname( __FILE__ ). DIRECTORY_SEPARATOR ."token" . DIRECTORY_SEPARATOR; if($_POST["mail"]==""){ print "メールアドレスを入力してください"; }elseif(mb_strlen($_POST["mail"]) > 0 && !preg_match("/^([a-z0-9_]|\-|\.|\+)+@(([a-z0-9_]|\-)+\.)+[a-z]{2,6}$/i",$_POST["mail"])){ print "メールアドレスの書式に誤りがあります。"; }else{ print "確認メールを送信しました"; mail_to_token($_POST["mail"]); } if(isset($_GET["key"])){ if(delete_old_token($_GET["key"])) print "登録完了しました。"; else print 'もう一度初めからやり直してください。'; } function mail_to_token($address) { global $tokendir; $limit = (time()+3600); $token= rand(0,100).uniqid();//トークン touch($tokendir.$token.".log");//トークンファイル作成 $url = $_SERVER["HTTP_REFERER"]."?key=".$token; file_put_contents($tokendir.$token.".log", $limit, LOCK_EX);//期限保存 delete_old_token($tokendir);//古いトークン削除 //本文スタイル $message="登録を完了するには、以下のアドレスを開いてください。\n60分以内にアクセスが無かった場合は無効となります。\n"; $message.=$url."\n\n"; my_send_mail($address,'登録確認',$message); } function delete_old_token($token = NULL) { global $tokendir; if (is_dir($tokendir)) { if ($dh = opendir($tokendir)) { while (($file = readdir($dh)) !== false) { if(is_file($tokendir.$file) && is_null($token)){ $data = file_get_contents($tokendir.$file); if(time() > $data) unlink($tokendir.$file); }else if(is_file($tokendir.$file) && !is_null($token)){ if(time() < (filemtime($tokendir.$token.".log")+3600) ){ @unlink($tokendir.$token.".log"); return true; }else{ @unlink($tokendir.$token.".log"); return false; } } } closedir($dh); } } } function my_send_mail($mailto, $subject, $message) { $message = mb_convert_encoding($message, "JIS", "UTF-8"); $subject = mb_convert_encoding($subject, "JIS", "UTF-8"); $header ="From: WebTecNote <info@example.com>\n"; mb_send_mail($mailto, $subject, $message, $header); }
トークン作るときついでに古いトークンも消す。
消す時にファイルに保存されているタイムスタンプを利用する。
アクセスされたトークンは$_GET[“key”]で取得。
多分こんな感じなんじゃないかと思うんだ。
ワンタイムURLクリック後にメール送信処理とか
実際に使う時は管理者にメール送ったり登録者に登録内容送ったりする手順が入ると思う。
トークン作ってメール送るまでは$_POSTの情報使えばいいが、
ワンタイムURLをクリックした後にメールを送信するならメアドを一時的に保存しなければならない。
それならトークン作った時に期限と一緒にメアド保存しておけば後であれこれ処理出来るよね、っていうサンプルです。
- ユーザーから送信された登録情報をチェック
- エラーがなければトークン作成
- トークンを名前にしたテキストファイルをトークンディレクトリに作成
- 作成されたトークンファイルに有効期限のタイムスタンプとメールアドレス保存
- トークンを追加したURLを本文に書いたメール送信
- 送信しましたメッセージ表示
- メール送信したURLにユーザーがアクセスする
- GETでトークン取得
- トークンディレクトリから同名のファイルを検索
- ファイル作成日+期限と現在時刻の比較
- ファイル内容からメールアドレス取得
- 期限内であればtrue ファイル削除
- 期限外であればfalse ファイル削除
- ユーザーに登録完了メール送信
- 管理者に登録完了メール送信
$tokendir = dirname( __FILE__ ). DIRECTORY_SEPARATOR ."token" . DIRECTORY_SEPARATOR; $email = ""; $success_page ="http://example.com/"; if($_POST["mail"]==""){ print "メールアドレスを入力してください"; }elseif(mb_strlen($_POST["mail"]) > 0 && !preg_match("/^([a-z0-9_]|\-|\.|\+)+@(([a-z0-9_]|\-)+\.)+[a-z]{2,6}$/i",$_POST["mail"])){ print "メールアドレスの書式に誤りがあります。"; }else{ print "確認メールを送信しました"; mail_to_token($_POST["mail"]); } if(isset($_GET["key"])){ if(delete_old_token($_GET["key"])){ mail_to_master($email);//管理者にメール mail_to_success($email);//ユーザーにメール print "登録完了メールを送信しました。<a href=\"{$success_page}\">ログインページへ</a>"; }else{ print 'もう一度初めからやり直してください。'; } } function mail_to_token($address) { global $tokendir; $limit = (time()+3600); $token= rand(0,100).uniqid();//トークン touch($tokendir.$token.".log");//トークンファイル作成 $url = $_SERVER["HTTP_REFERER"]."?key=".$token; file_put_contents($tokendir.$token.".log", $limit, LOCK_EX);//期限保存 delete_old_token($tokendir);//古いトークン削除 $message="登録を完了するには、以下のアドレスを開いてください。\n60分以内にアクセスが無かった場合は無効となります。\n"; $message.= $url."\n\n"; my_send_mail($address,'登録確認',$message); } function mail_to_success($mailto) { $message="登録が完了しました。\n\nログインパスワード:password\n\n"; $message.="こちらでログインできます:".$success_page."\n\n"; my_send_mail($mailto, '['.SITENAME.']登録完了', $message); } function mail_to_master($mail) { $message ="新しく登録されたユーザーの情報は次の通りです。\n"; $message.="────────────────────────────────────\n"; $message.= $mail."\n\n"; $message.="DATE: ".date("Y/m/d - H:i:s")."\n"; $message.="IP: ".$_SERVER['REMOTE_ADDR']."\n"; $message.="HOST: ".@gethostbyaddr($_SERVER['REMOTE_ADDR'])."\n"; $message.="USER AGENT: ".$_SERVER['HTTP_USER_AGENT']."\n"; $message.="────────────────────────────────────\n"; my_send_mail(MAILTO_MASTER, 'ユーザー登録通知', $message); } function delete_old_token($token = NULL) { global $tokendir,$email; if (is_dir($tokendir)) { if ($dh = opendir($tokendir)) { while (($file = readdir($dh)) !== false) { if(is_file($tokendir.$file) && is_null($token)){ $log = file_get_contents($tokendir.$file); list($data,$mail) = split("<>",$log); $email = $mail; if(time() > $data) @unlink($tokendir.$file); }else if(basename($file,".log")==$token && !is_null($token)){ if(time() < (filemtime($tokendir.$token.".log")+180) ){ $log = file_get_contents($tokendir.$token.".log"); list($data,$mail) = split("<>",$log); $email = $mail; @unlink($tokendir.$token.".log"); return true; }else{ @unlink($tokendir.$token.".log"); return false; } } } closedir($dh); } } } function my_send_mail($mailto, $subject, $message) { $message = mb_convert_encoding($message, "JIS", "UTF-8"); $subject = mb_convert_encoding($subject, "JIS", "UTF-8"); $header ="From: WebTecNote <info@example.com>\n"; mb_send_mail($mailto, $subject, $message, $header); }
JISにしないと文字化けするの忘れてた。
トライしてみましたが、サーバによって機能したり、しなかったり。
ファイルアップローダーも付けたいが。
お願いするにはどうすれば。
トーマス さん>
想像だけで組んだソースなのであんまり過信はしないでください(笑
file_put_contentsがPHP5の関数なので4のサーバーだと動かないです。
ファイルアップロードはmove_uploaded_fileのマニュアルにサンプルがありますよ。
Tenderfeel さん
ワンタイムURLをググッてやってきました。わっかりやすく記載されていたので、これはもう、トライするしかないな、と。。。
大変たすかります。
早速アップローダーもサイトに組み込んでみました。
ログも同様、アップローダーもなのですが、ローカル上ではログは生成され、ファイルもフォルダにアップされました。
しかし、サーバーでは、どちらも生成されず、アップもされないのです。これは、サーバーの仕様なのかな、と考えております。
ロケットサーバー(レンサバ)に聞いてみます(汗)。
ローカル上の話ですが、ログとHTML生成しました。当然生成されました。
touch($tokendir.$token.”.html”);//html作成
実は、私のやりたいことは、アップローダーのファイルを、「作成HTML」にリンクさせ、ワンタイムURLを「作成HTML」にすることで完成させたいのですが・・・
[senduit.com]というサイトがあり、これを創ろうという壮大(?)すぎる計画に、自分でまいっています。
Tenderfeel さん
ヘテムルサーバーで試しました。
成功です!
ログも、HTML、ファイルのアップでき、メールも来まして、アクセスすると登録完了の表示がでました。
光が見えてきました。
トーマス さん >
メールとファイルアップロードならどちらのロジックも使っているzeromailが参考になるかもしれません。
http://code.google.com/p/zeromail/
動作しないということは何かしらエラーが発生して処理が途中で止まっているということですから
ページ上にエラーが表示されていなくても、大抵のレンタルサーバーではサーバーのログに残ります。
どんなエラーが出ているのか教えていただければアドバイスも出来るかと思います。
Tenderfeel さん
zeromailありがとうございます。参考に拝見させて頂きます。
ロケットサーバー(レンサバ)の件は、ログエラーを問合わせてみます。
他サーバー(ヘテムル)でトライアル申し込み、稼働したものをアップしました。
http://tampfile.heteml.jp/view.php5 (PHP5とすることで PHPバージョン5になるという・・・)
試して頂けると幸いなのですが、filemtime エラーがでてしまいます。
コードの53行目ということで以下部分になります。期限が設定できていないという事だと思うのですが。
> if(time() <(filemtime($tokendir.$token.”.log”)+3600) ){
メールが送られてくるアドレスが、生成されたHTMLに変えると、変えたらワンタイムURLにならないので・・・ちょっと現段階、思考停止です。時間が過ぎると、URLを削除すれば良いのですが、キー=生成URLだと分かりすいな、レベルです。
良かったらサイト、のぞいてみてください。
トーマスさん>
ロケットと、このヘテルムのエラーは違うものが出てるのでしょうか?
エラーメッセージに出てくるパスがダブってるみたいなんですが、うっかり余計な文字列連結とかしてませんか?
(うpろだみたいな)ダウンロードボタンの表示されるページをワンタイムにしたいってことですよね。
鯖に保存されたファイルとその情報、生成されたトークンを関連付けて保持しておけば、
その情報を元にページを動的に生成することが出来るのではないかと。(大規模サイトならデータベース使うかな?)
先にやりたい流れをフローチャートに書いてみると流れが分かりやすくなりますよ。私も混乱したとき書いてますw
Tenderfeelさん
filetime()エラーはサーバーによらずでます。ロケット鯖は画像アップもできないです。
ロケ鯖は問合わせしましたので、しばらくヘテムルで行おうと思います。
ZEROmail見ました。
contact.html → zeromail.php →completion.html
completion.html に記載される名前をランダムに変えれば完成ですね。なのですが・・・
[ファイル],[情報],[生成されたトークン] の関連付け ですね。
DBテーブル1 フィールド3 作成して・・・これは後でしないとわからないです。
まずzeromail.phpでトークンを作成して、関連づけしてみます。
tampfile.heteml.jp/index.html にアップしていきます。
トーマスさん >
製作経験とか現在状況とかが分からないのでなんともコメントが返しづらいです。
filetimeも画像アップロードもパスが間違ってると出来ないです。その辺はエラーの内容見れば分かると思うんですがいかがでしょう
尋ねられている内容からPHPのプログラミングにあまり慣れていない印象を受けます。
ページに書いてあるコメントを見ると、使われている変数の中身とか、関数の処理内容などを理解出来ていないように見えました。
勘で切り張りしているだけではエラーとバグまみれになっていつまでたっても完成しませんよ~~~
どう組み立てればいいのか筋道が思い浮かばないのなら、いきなり複数のことを同時に片付けようとせずに
STARTになる部分から1つずつロジックを作っていく方が近道だったりします。
ロケット鯖のエラーですが、アップローダーを一旦削除して、メールアドレスのみにしたところ、ログが生成されました。
エラー表示もでませんでした。アップローダーが原因かもしれません。ただ、ヘテムルの方は、ログは生成されるものの、引き続き、以下の警告が表示されます。
確認メールを送信しました
Warning: filemtime() [function.filemtime]: stat failed for /home/sites/heteml/users131/0/1/t/01tampfile/web/token//home/sites/heteml/users131/0/1/t/01tampfile/web/token/.log in /home/sites/heteml/users131/0/1/t/01tampfile/web/view.php5 on line 43
Tenderfeel さんのご指摘どおりプログラミングは初心者です。今DBの入り口も勉強始めるところなのです。
勉強会に参加したことがあり、seduit.comは時間的にどれぐらいでできるものが質問したところ、慣れた人で、2-3時間と回答がありました。まさに「エーッ」。。。
スタート地点を決めなければ。。近道として、zeromailを、必要なものだけそぎ落として、理解しやすくしようと思います。
まずはそこからです、ね。
トーマスさん >
前にも書きましたがエラーに出てくるパスが不自然ですよね?
homeから始まるルートパスは1つだけでいいのに2つ重なっている…ということは何処かでファイルパスが狂っているっていう事になります。
エラー発生箇所がdelete_old_token内なので、実行より前にあるトークン作成とメール送信は問題なく行われます。
delete_old_tokenにあるis_dirとかは通過していることから、
・opendirの後にパスが重なるようなミスをしている
・web/token/.log ←$tokenで渡されるはずの値が行方不明
ということが見えてきます。この2つの謎が解ければエラーは消えると思います。
慣れてる人なら2~3時間は確かにそれくらいかもしれません~。必要なものをざっと書き出すと
ファイルアップロード+データ保存+ファイル作成+ユニークな文字列の生成+メール送信+ページ出力
なので、個々はそんなに難しいものではないです。調べればいくらでもソースが出てくるものばかりですから。
手を付けるなら起点であるファイルアップロード、という事になりますね。
まだプログラムを書いた経験が余りないのなら、必要な機能を1つずつ別個に完成させることを目標にしてみては如何でしょう。
ピラミッドとかレゴみたいに小さいパーツを後で組み合わせるイメージで。
GoogleCodeのダウンロードにZeroMailの昔のバージョンが置いてあるのですが、
必要なパーツをちまちま組み込んでいったので最初の頃のソースはかなりさっぱりしてますよ。
データベースはファイル関数マスターしてからでも遅くはないと思います。
「あなたがプログラムを理解出来ない10の理由」を読んでみるといいかもです。いくつか当てはまってそう(笑)
http://builder.japan.zdnet.com/sp/10-reasons-programming-2008/
Tenderfeel さん
コメントに感謝です。
以下に変更したところ、パスの2重は回避でき、ローカル上はエラーはきえました。
if(time() < (filemtime($token.”.log”)+3600) ){
・・・ただ、
Warning: filemtime() [function.filemtime]: stat failed for /home/sites/heteml/users131/0/1/t/01tampfile/web/token/.log in /home/sites/heteml/users131/0/1/t/01tampfile/web/view.php5 on line 64
パスはweb/以下あっているとおもうのですが。
ヘテムルサーバーのみでます。
Tenderfeel さんにこれ以上、甘えるわけにもいかないので何か考えてみます。
ありがとうございます。
トーマス さん >
拡張子.logと最後のスラッシュの間に入るべきトークンが入ってませんよね。
変数$tokenにトークンが渡されていたら
/home/sites/heteml/users131/0/1/t/01tampfile/web/token/yu37u8FR.log みたいになります。
つまりまだ私が挙げた二つ目のバグ 「web/token/.log ←$tokenで渡されるはずの値が行方不明」 があるはずです。
根気よくチェックしてみてください。
何か分からないことがあればコメントやメールを送っていただければお答えしますよ。
質問や相談は私自身も勉強になるのでお気軽にどうぞ 😀
Tenderfeel さん
コメントに大変感謝です。。
なぜ if(time() <(filemtime($tokendir.$token.”.log”)+3600) ){
トークンが呼び出せないかわからず、.$tokenを .$file に変えたところ
web/token/ (トークン.html)/.logが呼び出せました。
そこでトークンhtmlをコメントアウトして、以下に変えました。
if(time() < (filemtime($tokendir.$file)+3600) ){
これで、読み込め、エラーがきえました。
結局、トークンのみを呼び出す謎は、残りますが、以下のファイルを読みに行った時に、
拡張子まで読み込んでhtmlを優先されたということでしょうか…
トークンhtmlを生かしたままで、以下でもエラーはきえますがhtmlの方を読みに行っているからです。
if(time() < (filemtime($tokendir.$file)+3600) ){
こんなんでどうでしょ?
トーマスさん>
NOTICEエラーを表示したら多分いっぱいエラーが出ますよ(笑
ファイル名が一致しないっていう現象の原因は乱数発生ロジックを同じ変数に対して複数回実行してるからです。
変数は常に上書きされます。ところてんみたく新しい値を棒(イコール)で押し込んだら古い値は押し出されてしまうので、実行した時には直近に代入された値しか残りません。
http://nyx.pu1.net/reference/variable/variable.html
同じ値ならずっと同じ値が上書きされ続けるけど、違う値であれば値は変異し続けます。
人が書いたソースを弄るなら「何でこのif文がここにあるのか」とか「この変数の大本は何処だ」なんて事をまず先に把握しないと
完成していた形を崩すばかりでいつまでたっても纏まりません。
私も最初のころは1行1行の文末に全部コメントを付けるって事をよくしてましたよ。
Tenderfeel さん
サボったわけではないのですが理解するため以下記載させて頂きます。
($tokendir.$file)がおかしいのかなと思い、以下にしましたが、keyの受け取りでkeyが確認できません。
$fileはトークンdirを開いて、読み込むとの意味なので、$tokendirと$fileを2回読むと2重になりエラーになるのかなと思いました。
もしくは、加えて、Eメールで送られたURLの中から、キーであることが$tokenと確認できないのかとも考えられるが一度URLを分解して?key=がトークンとする定義が必要なのかも知れないです.
現在はここで止まっています。
while (($file = readdir($dh)) !== false) {//$file =オープンしたトークンDirを読む。違っていたらエラー
if(is_file($file) && is_null($token)){//$file =オープンしたトークンDirの変数トークンについて
$data = file_get_contents($file.”.log”);//データ=コンテンツ(トークンdir読み込み)
if(time()> $data) unlink($file.”.log”);//時刻>コンテンツの数字より多き時、削除
}else if(is_file($file) && !is_null($token)){//$file =オープンしたトークンDirの変数トークンについて
if(time() <(filemtime($file.$token.”.log”)+3600) ){//時刻<ディレクトリからトークンログ+3600)
@unlink($file.$token.”.log”);//オープンしたトークンDirの変数トークンにログを削除
return true;
}else{
@unlink($file.$token.”.log”);オープンしたトークンDirの変数トークンにログを削除
return false;
}
}
}
トーマスさん>
かなり間違ってますね。関数をマニュアルで調べてますか?正しくはこうです
[pre]
//オープンしたディレクトリの中身をreaddirで読み込む
//($fileはディレクトリにあるファイルやディレクトリ名が入る)
//失敗してfalseが返るまで繰り返す
while (($file = readdir($dh)) !== false){
//●$fileというファイルが存在し、かつ$tokenがNULLの場合
if(is_file($file) && is_null($token)){
//$filesというファイルの中身を読み込む
$data = file_get_contents($file.”.log”);
//ファイルに保存されていた$dataが現在のタイムスタンプよりも小さかった場合、ファイルを削除
if(time()> $data) unlink($file.”.log”);
//$fileというファイルが存在せず、$tokenがNULLでない場合
}else if(is_file($file) && !is_null($token)){
//☆$tokenファイルの更新時刻を取得して猶予時間を加え、現在時刻のタイムスタンプと比較
if(time() <(filemtime($file.$token.”.log”)+3600) ){
//猶予時間内ならファイル削除してTRUEを返す
@unlink($file.$token.”.log”);
return true;
}else{
//猶予時間外ならファイル削除してFALSEを返す
@unlink($file.$token.”.log”);
return false;
}//☆
}//●
}//while
[/pre]
readdirの戻り値が入る$fileにはディレクトリにあるファイルや子ディレクトリ名が入ります。
なのでこれに拡張子とか$tokenとかを連結するのはおかしい。というのが分かるでしょうか?
ちなみにこのwhileを使ってディレクトリの中身を得るやり方はマニュアルに載っている例そのまんまです。
また、$tokendirは私が書いたサンプルソースそのままだとすれば、
__FILE__定数で得られるファイルパスをdirnameでディレクトリ名までにカットして
トークンを保存するディレクトリであるtokendir/という文字列をくっ付けたものが入っています。
DIRECTORY_SEPARATORは名前の通りセパレート文字列の定数で、Linuxだと/でwindowsだと\が入ります。
この部分だけでも凄い食い違い方してるので、他にもまだ勘違いしたままやってる所があるのではないでしょうか。
Tenderfeel さん
道は果てしなく遠いようです。
今日は勉強会なので基礎を教わってきたいと思います。
一般サラリーマンのためのWEBを作ろうという意志はあるのですが
ファイルバンクもとっても遠いです・・・
ロケット鯖へ問合わせると、PHPのアップロードは2MBまでです、と回答がありました。
これは自鯖か?と思ってしまいます。
トーマスさん>
私は自作を始める前にWordpressだとかフリーのスクリプトだとかでPHPに触れた土壌があって
例えるなら英語の「喋れないけど何となく読める」っていう状態によく似ていたので、
PHPマニュアルと初心者向けサイトの例を参考にしながら、自分が欲しいものを作りつつ覚える事が出来ました。
今2年目なのですが、マイペースにやっているので遅い方だと思います。
勉強会とか講習とかに参加するのならもっと早く習得出来るんじゃないかな?
私は基本引き篭もりなので(笑)完全に独学でやってますが、教えてくれる人を得ると確実に近道になりますよ。
ただ、Webに関すること(HTML, JavaScript, CSS, Perl, etc…)に全く経験がなければ、
PHPはそれらと密接に絡むので道のりは長くなりそうです。
サーバーはどの程度の規模でサービスを提供するかによると思いますが、
アップローダーを真面目にやるなら専用鯖が必須でしょうね…。
共用鯖でやんちゃなことするとアカウント停止になってしまいますから。
コメントに感謝です。。。
コメント欄を埋めまくってすいません。。。
メールを送らせて頂きます。
トーマスさん>
お気軽にどうぞ 😀
Tenderfeel さん
gmailの方へ送らせて頂きました。
.comでよかったですよね?
間違ったかな。
初心者なのですが、具体的にどのようにすれば良いのでしょうか?
この記事の文章やサンプルソースを見てやっていることが理解できないほどの初心者なら
危ないので手を出さない方がいいです