PHPコーディング

人のバグに・・・

2012年05月01日
知る人ぞ知る、qmailのバグに振り回されてます。
いえ、振り回されているのは私ではなく、ツールのユーザーさんと言うべきかもしれません。

Eメールには、メール送信形式に事実上の世界標準というのがあります。まぁ当たり前ですが。
ネットビジネス便利ツールでは、この形式でメールを送信しています。まぁこれも当たり前ですが。
ところが、qmailにはせっかく世界標準形式で送信しても、わざわざその形式を崩して送信する、という厄介なバグがあります。
大抵のメーラーの作者は、qmailのバグを吸収するように作ってくださっています。
なのでqmailがおかしな形式のメールを送信しても、受信側のメーラがなんとかしてくれているのです。
ただ、一部のメーラーでは吸収しれくれないので、qmail+吸収しないメーラーの組み合わせになったとき、メールがきちんと表示されない現象に見舞われてしまいます。

しかし、これはメーラー側が悪いわけではなく、そもそも標準形式に従っていない方が悪いのです。

ネットビジネス便利ツールでも、qmailを使っている場合無理やりバグを打ち消すという機能を付けています。使っているMTAがqmailの場合、オンにしてもらうとバグを打ち消してくれます。本当は自動で判別できればよいのですが、以前調iniファイルを調べる以外方法が見つかりませんでしたiniファイルだけでは100%判定できないのです。

本来は他者の作ったバグは、対応する責任も義務もありません。qmailのバグに関して言えば、ベストはqmailの修正、次はパッチを当てるのがいいでしょうか。
でも、そんなことを言っていては世の中回らないこのもままあるので、対応しているわけです。ユーザーさんも困られるのは分かってますし。メーラーの作者も同様でしょう。
これに関して文句を言われる筋合いは全くないわけです。こちらでできる最大限をやっているのですから。
システム関係に明るくない人はなかなか理解しがたく、こちらに文句を言いたくなるのだとは思いますが・・・

この件に関して、ほぼ同時に二人のユーザーさんから連絡がありました。
一人の方はすぐに理解されたようですが、もう一方が・・・
まぁ私も困っておられるだろうと慌ててメールを返信したため、もう一方のメールの内容を見落としてしまったのも悪いのですが(書き方が引用に見えてしまい、スルーしてしまいました。反省。)、まるで理解できていない(しようとしていない)様子です。やれやれ、困った人です。
この方以前にも、自分が良く理解できていないことが原因なのに、ワーワー言って来られて、迷惑をかけられたことがあります。前任者との引継ぎがうまくできていないないことが原因だったのです。あなたの会社の引継ぎなんて、こっちにはなんの関係もないでしょう・・・ まずはきちんと引継ぎしてくださいな。
あちらの会社内部の問題に付き合わされたのに、最後にお礼の一言すらありませんでした。自分が客だから、こちらが何でもすべきとでも思っているのかしら。一体どういう教育している会社なんでしょう???

qmailのバグを打ち消す機能をお教えしたのですが、確認の方法がないとか、自分のお客さんに魅惑がかかっているとか言ってきました。
そうでしょう、迷惑をかけているのでしょう。でも迷惑をかけているのは私ではなく、その方のほうです。
qmailのバグですから、qmailが迷惑をかけているのでしょうけど、そんなことを言い始めるとバグのあるMTAをインストールしユーザーに使わせているサーバー会社、もっと言えばそのサーバーを選んだその方の責任もあるわけですよ。正直言って、こちらも迷惑かけられてます。
確認の方法? もちろんありますが、まぁ自分で調べてくださいな。こういう人には、協力しようという気がおきないですよね。私の責任の範囲でしたら、もちろん嫌でも協力しますが、他者のこしらえたバグですしねぇ。
今回のことを受け、マニュアルをもう少し注意を促す書き方に変更しました。
こちらにできるのはそれくらいです。
qmailを使わない方法もありますが、設定を少しでも簡単にしたいツールに関しては、この方法は見送ってました。導入したほうがいいかなぁ。でも、導入しても注意事項を読まない人は、どんな状況でも読みません。そしてそういう人が、後で困ったことになり、周りに毒を撒き散らすんだよなぁ・・・

いつも思うのですが、自分の責任の範囲を超えてでもこの人には協力しよう!と思える人、淡々と必要なことのみを連絡する人、必要最低限の話もしたくなく早く縁を切りたい人がいますね。実社会だけでなく、メールだけのお付き合いでも全く同じです。私もいい意味で気持ちよく人を巻き込めるようになりたいものです。

そして、ほぼ同時に言ってこられたお二方、よく見るとまず間違いなく同じ会社の方です。同じドメイン、同じ会社名です。隣の席の方が別々に言ってこられたとも考えられます。引継ぎがちゃんとできないのに、外部にワーワー言って迷惑をかけ、お礼も言わないような会社です。ありうる・・・

しかも、少し落ち着いてから調べてみたのですが、どうしても購入履歴が確認できないんです。この人たち、一体・・・? 別人が購入したと考えたいのですが、嫌な考えも浮かんできてしまいます。

今回のことがだけが原因ではないのですが、ネットビジネス便利ツールでは、前々から考えていたライセンスを、順次導入していきます。
正規のユーザーさんとこちらの権利の両方を守るためです。
購入履歴が確認できないお問い合わせに関しては、対応もしません。今後は徹底していきます。まぁ当たり前ですが。

PHPコーディング

クッキーが有効かどうか

2012年04月01日
Webページにアクセスしてきたブラウザで、クッキーが受け入れられたかどうかチェックできないか調べてみました。
PHPより、JavaScriptの方が適切なのかもしれませんが、色々な端末・ブラウザが出てきているので、できればサーバー側で行いたいです。

クッキーをセットするのは、【setcookie】関数ですが、この関数の戻り値では、クッキーが受け入れられたかどうかは分かりません。
調べるには、
 クッキーをセット、
 いずれかのページへ遷移、
 セットしたクッキーがあるかどうかチェックするしかないようです。
クッキーセット後、ページを遷移しなくても確かめられれば楽なんですが。

function chk_cookie(){
 $strMyself = basename($_SERVER[’SCRIPT_NAME’]);
 if(isset($_GET[’mode’]) === false){
  $strGet = "?mode=check";
  foreach($_GET as $strCol => $objColVal){
   $strGet.= "&".$strCol."=".$objColVal;
  }
  @setcookie("DUMMY", "OK");
  header("Location: ".$strMyself.$strGet);
 }
 else{
  if($_COOKIE[’DUMMY’] === "OK"){
   $blnResult = true;
  }
  else{
   $blnResult = false;
  }
  @setcookie("DUMMY", "", time() - 3600);
 }
 return $blnResult;
}

$_GET[’mode’]が存在しなければ、クッキー(キー:DUMMY、値:OK)をセット、
自分自身のURLに【?mode=check】を付加して遷移(他のクエリがある場合、後ろに付加)、
$_GET[’mode’]が存在していれば、クッキーの値をチェック、キー:DUMMY=値:OKなら、クッキーを削除し、trueを返す、
クッキーがセットされていないければ、falseを返す。

PHPコーディング

配列の特定項目削除

2012年01月29日
突如、PHPで配列の特定の項目(フィールド)を削除したくなって、そんな関数はないか調べてみました。

やはりありました。【unset】です。

【unset】は配列の項目を削除するだけでなく、変数の割り当てを破棄する関数です。

変数を破棄する場合は、
 unset(破棄する変数);
でOKです。

配列の特定の項目を破棄する場合は、
 unset(破棄したい項目のある配列[’破棄したい項目’]);
となります。まぁ当たり前ですが。

例えば、配列【$arrHairetsu】の【haishitai】という項目を廃棄したい場合、
 unset($arrHairetsu[’haishitai’]); です。

これで【$arrHairetsu】の【haishitai】以外の項目はそのままに、【haishitai】だけ破棄されます。

PHPコーディング

【.htaccess】の設定を使ってPHPを実行

2011年12月25日
変数の定義で書いたユーザーさんから動いたとの連絡をいただきました。

【php.ini】のパスを指定してcronやコマンドからPHPを動かしてみてください、という内容のメールをお送りしていました。
ですがそのユーザーさんのサーバーでは【php.ini】が使えないので、【.htaccess】にPHPの設定をしておられました。
cronからの実行時に、【.htaccess】の設定を読み込んで実行していなかったため、設定が有効にならず【Notice】が出ていたとのことでした。
【wget】を利用したら、動くようになったとのことでした。

とても詳しいユーザーさんで、私も勉強になりました。

PHPコーディング

変数の定義

2011年12月23日
タイマーメール】のユーザーさんから、動かないとの連絡がありました。
こちらではちゃんと動いていますし、他のユーザーさんからはそのような連絡はないし・・・ おそらく、サーバーの設定を少し変えたら動くのだろうと思います。

で、出ているエラーから、対処法を探してみました。
おそらく原因はこれでしょう。
http://www.sound-uz.jp/php/note/errorsの【Notice: Undefined variable】

対処法は
http://www.sakura-pc.jp/php/02020000.shtmlの【補足】

どうやらPHP5から変数を初期化するようになったようです。
そうかぁ。やっとPHPも変数の初期化を意識するようになったのか。でも型は宣言しない様子。

私のサーバーではエラーは出ない設定になっているけれど、ユーザーさんのサーバーは出る設定になっているようです。
ユーザーさんに対処法をお知らせしました。これで動けばよいのですが。

変数も初期化したほうがセキュリティ上良いようなので、ボチボチ(とても一気にはできない・・・)変更していくことにします。

変数を定義というか初期化することになったのはよいことですが、その変更を知らなかったのは不覚でした!

PHPの基礎体力
ここ面白そうです。じっくり読んでみよう。

PHPコーディング

404エラー

2011年11月22日
PHPで404エラーを起こすのに、
 Header("HTTP/1.0 404 Not Found");
を使っていたのですが、アクセスカウントしてみると、なぜか2回踏んでいることがわかりました。
ちなみに、403エラー
 Header("HTTP/1.0 403 Forbidden");
の場合は1回しか踏まないようです。

調べると、
 Header("HTTP", false, 404);
という書き方もあるようです。
こちらはアクセスカウントはされません。
アクセスしようにもURLが存在しないのでアクセスできない、つまりカウントもされないということのようです。
 Header("HTTP", false, 403);
も同様です。

PHPコーディング

【.forward】から転送されたメールの処理

2011年06月02日
【.forward】から転送されたメールは、
if(($objMail = @fopen("php://stdin", "rb")) == true ){
 while(!feof($objMail)){
  $strLine.= fgets($objMail, 4096);
 }
}
@fclose($objMail);
で読み込むことができます。

上のソースだと、$strLine にはメールヘッダも本文も、全てが取り込まれるので、ここから必要な情報を抜き出していくことになります。

ヘッダと本文は、【¥n¥n】と改行コードが2回続くことで区切られています。
【¥n¥n】より前がヘッダ、後ろが本文です。
ちなみに、ヘッダも本文も、メールの改行コードは【¥n】です。

更にヘッダは
 ・
 ・
To: 送信先メールアドレス
Subject: 件名
From: 差出人名 <差出人メールアドレス>
 ・
 ・
のようになっています。1行ずつチェックして、上の形式から必要な情報を取り出します。

メールの差出人を会員などに登録する場合、【差出人メールアドレス】を抜き出し、登録処理を行えばOKです。

【To: 】と【From: 】以降は、
 名前 <メールアドレス>
の場合もあれば、
 <メールアドレス>
の場合も、
 メールアドレス
の場合もあります。

名前、件名などに、日本語が含まれる場合、エンコードされてますから、【mb_decode_mimeheader】でデコードしてください。

メール受信と同時にCGIを実行と合わせれば、【ビジネス羅針盤 ステップメール】を空メール登録に対応させることができます。

PHPコーディング

ファイルダウンロードでページのhtmlまで出力

2011年04月26日
PayPalオートメール】のユーザーさんからの連絡でもう1点おかしなところが判明しました。

ファイルダウンロードの部分ですが、どうやらダウンロードファイルだけでなく、ダウンロードを行っているWebページのhtmlまで出力されてしまってます。

私のサーバーでは見られない現象。
サーバーによって少し動きが違うのでしょう。
ダウンロードの前に、キャッシュかhttpをクリアしておかなければいけないのでしょう。
自分のサーバーではクリアしなくても大丈夫だったので、気が付かずそのままになっている、ということです。

ネットで調べてみると、これかな、というものが見当たりました。
PHPマニュアルや当時参考にしたサイトには書かれていなかったので、思いもしませんでした。

対処法は簡単。
【ob_start】関数をDL前にコールするだけ。
そしてDL後に【ob_clean】関数をDL前にコール。

つまり、
 ob_start();
 header('Content-Description: File Transfer');
 header('Content-Type: application/octet-stream');
 header('Content-Disposition: attachment; filename='.basename($DLFilePath));
 header('Content-Transfer-Encoding: binary');
 header('Expires: 0');
 header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
 header('Pragma: public');
 header('Content-Length: ' . filesize($DLFilePath));
 ob_clean();
 flush();
 readfile($DLFilePath);
 clearstatcache();
となります。
これ、どこかのサイトから拝借したんですが、出所が分からなくなってしまいました。
ブラウザのお気に入りにうっかり残し忘れたようです。

PHPコーディング

qmailのバグにはまる

2011年04月19日
PayPalオートメール】のユーザーさんから、メールの本文にヘッダが出力される、と連絡がありました。
そんな現象見たことない!
こちらにもテストメールを送っていただきました。

Becky!、Liveメール、Outlook、Outlook Expressで表示してみます。
ちゃんと表示されます。

ちゃんと表示できてますよ、と連絡すると、『そうなんです。友人のメーラでは正しく表示できるのですが、私のOutlook Expressだけおかしいんです。』とのお返事。

では、Outlook Expressのバグ?ということでその線で調べてみることに。
http://support.microsoft.com/kb/973459/ja
『こんなのありました。』とユーザーさんから連絡が。これだと思ったのですが、違っていたようです。

じゃあ、一体何???

私のサーバーから送ったメールは、みんなのところでちゃんと見えている。
でもユーザーさんのサーバーから送ったメールは、ユーザーさんのメーラーでだけ表示がおかしい。
ということは、ユーザーさんのサーバー・・・?
送られたメールが規則に反していて、Outlook Express以外のメーラーはそのバグを吸収してくれている、と解釈すればいいの?

今度はその視点で調べてみます。
もしかして・・・

PHPのmail関数のマニュアルにある
『・・・Unix の MTA の中には、自動的に LF を CRLF に変換してしまう ものがあります・・・』
に当てはまる?

更に調べると、qmailがこれに当たるようです。
qmailにバグが多いのは結構有名な話なんですね。

で、【ini_get('sendmail_path')】で何が出力されるか調べてみました。
すると、【sendmail】の文字が・・・
【qmail】じゃあないの?

でも、一か八かで、改行コードを変更してテストしてもらいました。
ちゃんと表示され始めたとのこと。
となると、隠れ【qmail】か?

ユーザーさんのサーバーのWebサイトを見てみると、『qmailを使用しています』の文字が!
こいつか!! 原因は!!!

で、一件落着。
どうしても【qmail】を利用しているかどうかのチェックの方法が見つからなかったので、どうしてもうまく送信できない場合は、管理画面から改行コードを指定してもらう仕様にちょっと修正。

これにて終了。

mail関数(PHP)のハマりどころ
PHP mail() Header Injection Through Subject and To Parameters

この辺りが参考になります。

PHPコーディング

マルチバイト文字列置換

2011年04月03日
PHPの【str_replace】関数は、マルチバイト文字列に対応していないようです。
今まで気が付かなかった・・・
なんてこった。そういえは、マルチバイトに対応した関数は【mb_】というプレフィックスが付いています。だけと【mb_str_replace】という関数は見当たりません。

ないなら自分で作らなければ、ということで、探してみました。
マルチバイトを考慮したstr_replace関数
が参考になります。

これでちゃんと動くようになりました。

PHPコーディング

MySQLのトランザクション

2011年01月09日
MySQLのトランザクションです。
 pg_query($Connect, 'begin');
 pg_query($Connect, 'commit');
 pg_query($Connect, 'rollback');
が、
 mysql_query("set autocommit = 0", $Connect);
 mysql_query("begin", $Connect);
 mysql_query("commit", $Connect);
 mysql_query("rollback", $Connect);
となります。

PostgreSQLでは【set autocommit = 0】は見たことがありません。
トランザクションを切らずにSQLを発行した場合、自動でコミットされるのをoffにするようです。
なくても、きちんとトランザクションを切れば大丈夫なようです。

PHPコーディング

MySQLのクエリ

2011年01月08日
今日もPostgreSQLのソースとインターネットを参考にMySQLのコーディングです。

クエリの発行は
 $result = pg_query($Connect, $sql);
が、
 $result = mysql_query($sql, $Connect);
となります。

どうやら、概ねPostgreSQLは【pg_xxx】だったのが、MySQLでは【mysql_xxx】になるようです。
ですが、クエリの発行は、SQLとリソース(DBへのリンク)が、PostgreSQLとMySQLでは逆に(引数の順序)なります。
ここをうっかり変更し忘れて、あれ?を何度かやってしまいました。

処理件数の取得は、
 pg_affected_rows($result);
が、
 mysql_num_rows($result);
へ、
リソースの開放は
 pg_free_result($result);
から
 mysql_free_result($result);
になります。

もう一点、気をつける点が。クエリの結果取得です。
PostgreSQLでは、【pg_fetch_assoc】以外に、【pg_fetch_object】も使えました。
 $rsData = pg_fetch_object($result, $cnt);
 $data[$cnt]['id'] = $rsData->id;
とか、
 pg_fetch_result($result, 0, 'MAX');
です。MySQLでは使えないようです。【mysql_fetch_assoc】を使います。
 $rsData = mysql_fetch_assoc($result);
 $data[$cnt] = $rsData;
とやれば、1レコードずつ一気に取得できます。

$rsData['id']でそのレコードのidフィールドが取得できます。

PHPコーディング

PHPからMySQLへ接続

2011年01月07日
今日はPHPからMySQLへ接続、テーブルへの読み書きをしてみました。
PostgreSQLのソースとインターネットを参考にコーディングしていきます。

DBへの接続ですが、PostgreSQLの場合は
 $Connect = pg_connect("host=ホスト名 dbname=DB名 user=ユーザーID password=パスワード");
でしたが、
MySQLでは
 $Connect = @mysql_connect(ホスト名, ユーザーID, パスワード);
でDB接続、
 $blnSelDB = @mysql_select_db(DB名, $Connect);
でDB選択となります。

切断は
 pg_close($Connect);

 mysql_close($Connect);
となります。

PHPコーディング

オブジェクト指向で書いてみる

2010年11月17日
いよいよオブジェクト指向で書いてみました。
PHPのオブジェクト指向入門 | オブジェクト指向PHP.NETとにらめっこしながら、自分のサイトに当てはめてみます。

まずは各ページで共通にある処理を洗い出してみます。
DB接続、DB切断などがありました。

abstract class BaseProcess{
  abstract protected function Main();
  public function strExec(){
    DB接続
    //メイン処理
    $this->strHTML = $this->Main();
    DB切断
  }
}

こんな感じで共通クラスを作ります。
各ページの処理はこのクラスを継承します。

それぞれのページを見ると、いくつかのパターンに分類できます。
・左メニューとメインメニューのある2カラムページ
・左ではなくページの上にメニューがあり、その下にマニュアルのあるマニュアルページ
・商品を売っているセールスページ
などです。

次に、例えば、2カラムページを処理するクラスを作ります。

abstract class TwoColum extends BaseProcess{
  public function Main(){
    DBからデータ取得
    左メニュー作成
    ヘッダ作成
    コンテンツ作成
    htmlタグ作成
    return $html
  }
}

これでBaseProcessが継承されます。
DBからデータを取得する部分は、表ページの表示にも使いますが、管理画面でも使うので、これもクラス化しておきます。

TwoColumを実行したときの実際の処理は、
 1. DB接続
 2. DBからデータ取得
 3. 左メニュー作成
 4. ヘッダ作成
 5. コンテンツ作成
 6. htmlタグ作成
 7. DB切断
となります。

TwoColumを呼び出すPHPでは、

//外部ファイル読み込み
 require_once('・・・');
//インスタンス作成
 $proc = new ContentProcess();
//実行&htmlタグ受取
 $html = $proc->strExec();
//出力
 echo $html;

これだけです。
なんか物足りない。ちょっと不完全燃焼。
でも不完全燃焼ってことは、コーディングが楽ってこと?

しかも、最終呼び出しPHPはどのファイルもほとんど同じになります。
最終PHPでhtml作成をする方法ももちろんあります。

以前にも感じていましたが、オブジェクト指向でコーディングするのって、手続型より設計が大切です。
ちゃんと構造を把握しておかないと、自分でわけがわからなくなりそうです。
だから他人が作ったオブジェクトってわかりにくいんだ・・・

PHPコーディング

オブジェクト指向の実際

2010年11月14日
数日前から始めた、オブジェクト指向の勉強ですが、やっぱり理屈は分かってもなかなかどうコーディングすれば便利がよくなるのかわかりません。

色々やってみて勉強しようと思っていたのですが、やっぱり効率が悪いので、なにかよい参考サイトはないかと探してみました。

すると、さすがはインターネット、ありました。
PHPのオブジェクト指向入門 | オブジェクト指向PHP.NET
理屈だけでなく、実際にどう作ったらよいかがわかります。

まぁ本当に自分のものにするためには、作ってみないといけないことにはかわりませんが、かなり試行錯誤を減らしてくれそうなサイトです。

PHPコーディング

変数の参照

2010年11月12日
PHPでオブジェクト指向を勉強し始めましたが、やっぱり自分でやってみないと実感がわきません。
理屈は書かれていることを読めばわかります。オブジェクト指向の利点がいくつか書かれていますが、実際どうやって使ったら便利がいいのか、どう手間が省けるのかがさっぱり・・・

ということで、とにかく書いて書いて書いてみることにしました。

まずは、htmlのヘッダや左メニューなど、各ページで共通なhtmlを関数化して使っていたのですが、これをいじることに。
今まではオブジェクトのようで不完全なオブジェクトもどきだったので、もうちょっとちゃんとしたオブジェクトにすることに挑戦です。

左メニューはDBに登録されています。
まずはDBから必要なデータを取得して、階層メニューに編集します。

DBからデータを引っ張る部分は今まで手続き型の関数でした。
これをクラスのメソッドに変更。
DBのデータはクラス内の変数(言い方があるのだけど、何だっけ)に格納しておきます。

同じように階層メニューも手続き型関数だったのをメソッドに変更。

手続き型の関数では、変数を引数でやり取りするか、global宣言しないといけませんでしたが、オブジェクト指向でコーディングするとそんな必要はなく、クラス内で変数は保持されています。

これが、手続き型関数では長々と引数を設定しなければいけない地獄だ、ということなんでしょうか。
確かに楽なのかな。
手続き型に慣れている私としては、ちょっと不安に感じてしまいます。
データはどこだ!!?って感じです。

とりあえずコーディングしてみたので、まだまだ便利に組めてないのでしょうけれど、ちょっとやっただけでも違いがわかりました。

PHPコーディング

オブジェクト指向を勉強しよう

2010年11月07日
以前から勉強しようと思っていた、オブジェクト指向。
ソフトのバージョンアップも落ち着いたし、サンプルソースもオブジェクト指向でかかれたものもあるし、SOHOで仕事を探し始めたし、これを機に勉強しようと思い立ちました。

おおよその概念は、何年か前に派遣の仕事でVB.NETを使ったとき理解しています。

しかしその後派遣先の会社が変わり、Webの開発でPHPを使うようになりました。
その会社ではPHP4を使っていたので、中途半端なオブジェクト指向で書かれていたようです。このときから先に進んでいないので、この辺りで新しい知識を入れようと思います。

PHP5は完全オブジェクト指向言語だそうです。
主流がPHP5になった今、勉強しどきです。

手続指向に慣れているとオブジェクト指向は難しいようですが、オブジェクト指向に慣れると手放せなくなるほど便利に感じるという記事を励みに頑張りたいと思います。
とは言っても、概念を思い出しながら、そしてPHP特有の仕様と戦いながらの勉強です。
派遣でVB.NETを使っていたときは、作ってあるクラスを使う側だったので、クラスを作るのは初めてです。

独学PHP はじめよう、PHPでオブジェクト指向
この辺りが初めは参考になると思われます。

まずは自分のサイトのPHPをオブジェクト指向に書き換えて勉強しようと思います。

PHPコーディング

file_get_contents関数は負荷が大きいのか?

2010年09月30日
PHPver.4.3から追加された【file_get_contents】関数。
ファイルの全内容を変数に取得する関数です。

どうやらこの関数のサーバーへの負荷が大きいのか、サーバーによってはファイルの全内容が取得できず、途中切れになっているようなのです。

PayPalオートメール】のユーザーさんから、メール本文のファイルが全て読み込まれていない、との連絡を受けました。

私のサーバーではそんなことは全く起こりません。
ということは、サーバーの問題と思われます。
メール文を短くしたら、全て読み込まれたそうなので、おそらく関数のせいでしょう。

インターネットであちこち調べたのですが、【file_get_contents】がサーバーに大きな負荷を与えるという、はっきりとした記述は見つけられませんでしたが、そうかもしれないね、という記述はありました。

そんなに大きなファイルではないのですが、安いプランの場合、サーバーへの負荷が大きく制限されてい場合もあると思われます。

ユーザーさんには、関数を書き換えてみたので、落ち着いたら試してみていただきたいと連絡しておきました。

ということは、【file_put_contents】関数もかな。

やっぱり【fopen】【fgets】【fwrite】【fclose】などの関数を使った従来の方式の方がいいのでしょうか。

サーバーへの負荷の具合を調べられるはずなので、方法を調べてみようかな。

PHPコーディング

テキストをブラウザに表示

2010年07月23日
PayPalで楽アフィリエイト】をUTF-8のメール送信に対応させました。

それに伴って他の部分のソースをいじったついでに、もっとよいものに書き換えました。

テキストファイルへのリンクを開いたとき、ブラウザへファイルの内容を表示させるソースです。

通常テキストファイルへリンクをはるだけでブラウザにファイル内容が表示されますが、表示する前にちょとプログラムを実行した後、表示させたいので普通にタグでリンクさせたのでは表示されません。

$file_path = 表示したいファイルのパス;
header("Content-type: text/plain");
header("Content-Disposition: inline; filename=".$file_path);
header("Content-length: " . filesize($file_path));
readfile($file_path);
でOKです。

【inline】を【attachment】にすると、ダウンロードダイアログが表示されます。

header("Content-type: text/plain");
header("Content-Disposition: attachment; filename=".$file_path);
header("Content-length: " . filesize($file_path));
readfile($file_path);

PHPコーディング

fgetcsv関数の不思議

2010年06月03日
作成したツールで文字化けが起こっているとの連絡を受けました。
私が契約しているサーバーでは、全く問題なく動作しています。

色々調べると、あるサーバーでその現象が起こっていることがわかりました。
さらに詳しく調べると、【fgetcsv】関数がファイルから文字を読み込む際、文字化けを起こしていることがわかりました。

ネットで調べると、この問題に引っかかっている方もたくさんいらっしゃるようです。
あちこちに書き込みがあります。

そうやら、PHPのバージョンアップの際、関数の仕様が変更になったようです。
こういう基本的な関数の仕様を変更されると、痛いです。

しかし、更に調べると、更にバージョンの高いPHPではこの現象は起こっていません。
ということは、仕様がまた変更になったということのようです。
要は特定のバージョンのPHPでのみ起こる現象、ということです。

csvで囲み文字を使っている場合、問題なく動作するようです。

でも、csvの本来の形式って確か、項目の値を囲まないはずでは???

回避方法としては、ファイルからはシンプルに文字列を読み込んで、あとは自分で処理すればいいようです。

前からいつか書き直そうと思っていた部分なので、ついでに書き直します。

正規表現を使う方法や、ファイル全体をそのまま読み込む方法もありますが、私が取った方法です。

【fgets】関数で、ファイルを1行ずつ読み込み。
改行文字列を削除。
区切り文字列で分割。

です。
今回は囲み文字列はないので、飛ばします。区切り文字列がタブの場合です。

while (($line = fgets($handle)) !== false) {
 $line = str_replace("\n", "", $line);
 $line = str_replace("\r", "", $line);
 $csv_data = explode("\t", $line);
}

これで問題なく動き始めました。

PHPコーディング

【exit】関数でも処理した結果を【fsockopen】で受け取れる

2010年04月12日
昨日の記事処理した結果を【fsockopen】で受け取るでは、【echo】関数を使いましたが、【exit】関数でもOKです。
【exit(("result=false"\n)】
という具合です。

ようは文字を吐き出せばOKのようです。
試してませんが、【print_r】なども大丈夫ということなのでしょう。

PHPコーディング

処理した結果を【fsockopen】で受け取る

2010年04月11日
データをpostして、処理を行い、その結果を取得したい場合、【echo】で返したい文字列を吐き出すと返すことができます。

例えば、【http://sample.com/syori.php】で処理を行うとします。
postする方法は、フォームを介さずPOSTするを参照してください。

【http://sample.com/syori.php】で処理を行い、エラーの場合【result=false】を、成功した場合【result=true】を返すとします。

【syori.php】でエラーの場合、
【echo("result=false"\n);】を実行、
成功の場合、
【echo("result=true"\n);】を実行します。

postした側では、この結果を
while (!feof($sock)) {
  $res = @fgets($sock, 1024);
  if(stristr($res, "result=") !== false){
    $res = str_replace("\n", "", $res);
    $kekka = explode("=", $res);
  }
}
で($kekka)受け取れます。

PHPコーディング

PHPの変数型

2010年02月17日
PHPは変数を使うとき、型を宣言しません。
文字列を代入すれば文字型の変数に、数値を代入すれば数値型の変数になります。
そう、PHPが自動的に型を決めてくれるのです。

同様に、変数の値の比較の際も、自動で型を変換してしまいます。
(文字型と数値型の比較もできてしまうのです。)
しかし、この文字型と数値型の比較で驚くことがおこります。

$test = 0;
if($test == ""){
  通ります。
}

$test = "1a";
if($test == 1){
  通ります。
}

$test = "a1";
if($test == 0){
  通ります。
}

恐ろしいことです。

文字型と数値型を比較するときは、文字型変数の値が数値に(勝手に)変換されてから比較されるので、こんな摩訶不思議なことが起こってしまうのです。

http://www.php.net/manual/ja/language.operators.comparison.php
http://www.php.net/manual/ja/language.types.string.php#language.types.string.conversion

更なる自動変換の数々
http://d.hatena.ne.jp/gallu/20061108/p1
http://techblog.ecstudio.jp/tech-tips/php-string-compare.html

こうした自動変換による不思議な世界は【switch】にも広がっています。


文字列比較のときは、【===】か【strcmp】を使うようにとあります。

$test = 0;
if($test === ""){
  通りません。
}

$test = "1a";
if($test === 1){
  通りません。
}

$test = "a1";
if($test === 0){
  通りません。
}


ここで、【===】と【strcmp】の速度について調べてみました。

for($cnt=0; $cnt<10000000; $cnt++){
  $test1 = "abc";
  $test2 = "def";
  if(strcmp($a, $b) === 0){
  }
}

for($cnt=0; $cnt<10000000; $cnt++){
  $test1 = "abc";
  $test2 = "def";
  if($a === $b){
  }
}

この2つのループにかかる時間を計ったところ、【===】判定のほうがはるかに早かったです(何回か実行しました)。
【===】演算子は型変換を行わないので、処理が早いのだそうです。

しかし、【switch】では【if】のような演算子は使えません。
変数の比較(特に文字列)では、【if】文で【===】と【!==】を用いる、のが安全なようです。

PHPコーディング

ソケットをオープンしてファイル操作をする(put)

2010年02月09日
今回はファイルのアップロード(put)です。

get同様ftpサーバーへログインした接続とは別の接続をオープンして行います。

ローカルファイルの内容を読み込みます
$local = @fopen($localfile, "r");
ファイル内容を全て読み込んでおきます
$write = @fread($local, 100000);

@fclose($local);

ファイル転送モードを指定します。
バイナリならば、【I】、アスキーなら【A】です。
@fputs($sock, "TYPE I\r\n");
$result = @fgets($sock, 512);

パッシブモードをオンにします。
@fputs($sock, "PASV\r\n");
$result = @fgets($sock, 512);

データ用の接続をオープンします。
ポートの計算方法は、ソケットをオープンしてファイル操作をする(get)を参照してください。
$ftp = @fsockopen($ftp_server, $data_port);

リモートにファイルを作成します。
@fputs($sock, "STOR ".$remotefile."\r\n");
$result = @fgets($sock, 512);
成功すれば【150 ・・・】と返ってきます。

リモートにファイルを書き込みます。
$result = @fwrite($ftp, $write);

ファイルサイズを照合をする場合、【fwrite】の戻り値とstrlen($write)をチェックすればOKです。

get同様、バックアップ機能を備えれば安心です。

PHPコーディング

ソケットをオープンしてファイル操作をする(get)

2010年02月08日
今度は、ファイル情報取得やファイル削除よりちょっと複雑なファイルのアップロード(put)やダウンロード(get)などです。

put・getはftpサーバーへログインした接続とは別の接続をオープンして行います。

ローカルへファイルを作成します。
$localfile = "local.txt";
$local = @fopen($localfile, "w");

ファイル転送モードを指定します。
バイナリならば、【I】、アスキーなら【A】です。
@fputs($sock, "TYPE I\r\n");
$result = @fgets($sock, 512);

パッシブモードをオンにします。
@fputs($sock, "PASV\r\n");
$result = @fgets($sock, 512);
成功すれば、【227 Entering Passive Mode (200,0,0,1,100,32)】という具合に、メッセージの後にサーバのIPアドレスとデータ接続用のポート番号が返ってきます。
【200,0,0,1,100,32】がそれです。初めの4つがIPアドレス、後ろの2つがデータ用のポート番号です。
ここから接続用のポート番号を取り出します。

ポート番号は
$data_port = 後ろから2番目の数値 * 256 + 一番後ろの数値
で計算できます。
100 * 256 + 32 = 25632

データ用の接続をオープンします。
$ftp = @fsockopen($ftp_server, $data_port);

リモートのファイルを読み込みます。
$remotefile = "remote.txt";
@fputs($sock, "RETR ".$remotefile."\r\n");

ローカルのファイルへ書き込みます。
$result = @fwrite($local, @fread($ftp, 100000));

念のため、書き込んだファイルサイズを照合したほうがいいでしょう。
ローカルへ書き込んだファイルサイズは、【fwrite】の戻り値でチェックできます。

一方、リモート側のサイズは、
@fputs($sock, "SIZE ".$remotefile."\r\n");
$result = @fgets($sock, 512);
成功すれば、【213】に続き、サイズが戻ってきます。

データ用の接続をクローズします。
@fclose($ftp);

念のため一時バックアップファイルを作成しておき、書き込みに失敗したときに書き戻す機能をつけておけば更に安心でしょう。


ローカルのファイルに追記したい場合、ファイルオープンのとき、モードパラメータで【a】を指定すればOKです。
$local = @fopen($localfile, "a");

PHPコーディング

ソケットをオープンしてファイル操作をする(ファイル削除)

2010年02月07日
ftpサーバーに接続できたら、まずは簡単なファイル削除から。

@fputs($sock, "DELE ".$file."\r\n");
$result = @fgets($sock, 512);

接続に成功していれば、
250 ・・・・・
と返ってきます。

このほかにも、リネーム、ファイルリスト取得、ディレクトリ移動など同じような方法で行えます。

PHPコーディング

ソケットをオープンしてファイル操作をする(ftp接続)

2010年02月06日
PHPでftpを利用するには、ftp関数をインストールすれば一番簡単なのですが、レンタルサーバーの場合、自分でインストールできないことが多々あります。

そういった場合、ソケットをオープンしてファイル操作をしなければいけません。

まずはftpサーバーへの接続です。

$ftp_server = "ftp.domain.xxx";
$ftp_uid = "user_id";
$ftp_pw = "password";
$ftp_port = 21;

$sock = @pfsockopen($ftp_server, $ftp_port);
$result = @fgets($sock, 512);

@fputs($sock, "USER ".$ftp_uid."\r\n");
$result = @fgets($sock, 512);

@fputs($sock, "PASS ".$ftp_pw."\r\n");
$result = @fgets($sock, 512);

接続に成功していれば、
230 ・・・・・
と返ってきます。

PHPコーディング

SMTPソケットをオープンしてメール送信(メールDATA部)

2010年01月27日
前回に続き、今度はDATA部です。いわゆるメールのボディ部です。

ボディ部は、【DATA】コマンドから、【.】のみの行が送信されるまでとなります。


$from_name = mb_encode_mimeheader("差出人名", "EUC", "B");
$from_add = "from@domain.xxx";
$to = "sendto@yyyy.zzz";
$reply-to = "reply@domain.xxx";
$subject = mb_encode_mimeheader("smtpメール送信", "EUC", "B");
$body = "メール本文です。\n";
$body = mb_convert_encoding($body, "ISO-2022-JP", "ASCII,JIS,UTF-8,EUC-JP,SJIS");

$result = @fputs($sock, "DATA\r\n");

$data.= "From: ".$from_name."<".$from_add.">\r\n";
$data.= "To: ".$to."\r\n";
$data.= "Subject: ".$subject."\r\n";
$data.= 'Reply-To:'.$reply-to."\r\n";

$data.= "MIME-version: 1.0\r\n";
$data.= "Content-Type: text/plain; charset=ISO-2022-JP\r\n";
$data.= "Content-Transfer-Encoding: 7bit\r\n";
$data.= "\r\n";
$data.= $body

@fputs($sock, $data."\r\n");
$result = @fgets($sock, 128);

//最後を示す【.】を送信
@fputs($sock, "\r\n.\r\n");
$result = @fgets($sock);

上の例は、内部文字コードは【EUC】、【ISO-2022-JP】のプレーンテキストメールを送信する場合です。

$data.= "MIME-version: 1.0\r\n";
以降を変えることで、もちろんthmlメールや添付ファイル付きのメールを送信することもできます。

この辺りは、【mail】関数で添付なしプレーンテキスト形式メールを送信【mail】関数で添付ありプレーンテキスト形式メールを送信【mail】関数で添付ありhtml形式メールを送信(代替本文なし)【mail】関数で添付なしhtml形式メールを送信(代替本文あり)【mail】関数で添付ありhtml形式メールを送信(代替本文あり)とほぼ同じです。
違うのは、行の最後を表す文字がきっちり【\r\n】になることです。


送信し終えたら、
@fputs($sock, "QUIT\r\n");
@fclose($sock);

PHPコーディング

SMTPソケットをオープンしてメール送信(メールヘッダ)

2010年01月26日
本当は【DIGEST-MD5】もコーディングしたかったのですが、どうしても【DIGEST-MD5】ではどのように認証すればいいのかの記述が見つかりません。英語で書いてある仕様書はあるようなのですが、日本語で書かれているページがみつかりません。

それよりも前に進まないといけないので、とりあえずメール送信に進みます。

$return-path = "return@domain.xxx";
$to = "sendto@yyyy.zzz";
$bcc1 = "bcc1@domain.xxx";
$bcc2 = "bcc2@domain.xxx";

まずはヘッダ部からです。

送信元メールアドレスをサーバーに知らせます。
この送信元はメーラーでは送信者としては表示されません。smtpサーバーによりreturn-pathに設定されます。メーラーに送信者として表示されるのはDATA部のfromです。
@fputs($sock, "MAIL FROM:<".$return-path.">\r\n");

送信先メールアドレスをサーバーに知らせます。
@fputs($sock, "RCPT TO:<".$to.">\r\n");

BCCがある場合はBCC分【RCPT TO】コマンドを送信します。
@fputs($sock, "RCPT TO:<".$bcc1.">\r\n");
@fputs($sock, "RCPT TO:<".$bcc2.">\r\n");

次回はDATA部です。

PHPコーディング

SMTPソケットをオープンしてメール送信(CRAM-MD5認証)

2010年01月24日
今日はsmtp認証の中の【CRAM-MD5】方式です。

まずは、サーバーに【CRAM-MD5】方式で認証を行うことを通知します。
@fputs($sock, "AUTH CRAM-MD5\r\n");

成功すると、サーバーは認証に使うキーを返してきます。
$line = @fgets($sock, 128);
キーは、【334 キー】の形式で返ってくるので、【334 】を取り除きます。
$key = str_replace("334 ", "", $line);

この方式は
Base64エンコード(ユーザ名+空白1文字+サーバーからのキーをパスワードをキーとしてMD5ダイジェストした値)
を認証に用います。

$ninsyo = base64_encode($id.' '.hash_hmac('md5', $key ,$pw));
で認証用文字列が得られます。


次に、得られた文字列で認証です。
@fputs($sock, $ninsyo."\r\n");
$result = @fgets($sock, 128);
成功していたら、【235 ・・・】が返ってきます。


この方式は、IDやパスワードがデコードできなきので、セキュリティが高い方式です。

PHPコーディング

SMTPソケットをオープンしてメール送信(LOGIN認証)

2010年01月22日
今日はsmtp認証の中の【LOGIN】方式です。

まずは、サーバーに【LOGIN】方式で認証を行うことを通知します。
@fputs($sock, "AUTH LOGIN\r\n");

成功すると、サーバーはIDを入力するように求めてきます。
IDをBase64エンコードして送信します。
@fputs($sock, "base64_encode($id)."\r\n");

成功すると今度はパスワードを入力するように求めてきます。
パスワードをBase64エンコードして送信します。
@fputs($sock, "base64_encode($pw)."\r\n");

成功していたら、【235 ・・・】が返ってきます。

この方式は、base64でエンコードしていますが、簡単にIDやパスワードをデコードできるので、セキュリティが高い方式ではありません。

PHPコーディング

SMTPソケットをオープンしてメール送信(PLAIN認証)

2010年01月21日
昨日の続きです。
昨日はソケットオープンまで成功しました。

今日はsmtp認証です。
smtp認証にも色々ありますが、まずは【PLAIN】方式についてです。

この方式は、【PLAIN】方式で認証を行うことをサーバーに通知する文字列に続いて、認証文字列をサーバーに送信します。

認証文字列に使うのは、サーバーによって
$id."\0".$id."\0".$pw

$id."\0".$pw
をbase64でエンコードした文字列です。

$ninsyo = base64_encode($id."\0".$id."\0".$pw);
@fputs($sock, "AUTH PLAIN ".$ninsyo."\r\n");
$result = @fgets($sock, 128);
成功してれば、【235 ・・・】と先頭が【235】の文字列がサーバーから返ってきます。

もしエラーなら、
$ninsyo = base64_encode($id."\0".$pw);
@fputs($sock, "AUTH PLAIN ".$ninsyo."\r\n");
を送ってみます。

この方式は、base64でエンコードしていますが、簡単にIDやパスワードをデコードできるので、セキュリティが高い方式ではありません。

PHPコーディング

SMTPソケットをオープンしてメール送信(ソケットオープン)

2010年01月20日
PHPからメールを送信するには色々な方法があります。

手っ取り早く日本語メールを送信するには【mb_send_mail】があります。
添付ファイルやhtmlメールを送信するには、【mail】を使ってヘッダなど書かけば可能です。
しかし、この【mail】関数、いちいちSMTPソケットを開いたり閉じたりするので、メールマガジン、ステップメールを配信するのには不向きのようです。
その上、sendmailやqmailなどがインストールされていて、PHPから使えないとメールが送信できないそうです。
更に更に、処理が重たいのです。

ソケットのオープン・クローズも制御するには直接ソケットをオープンしてメールを送信しなければいけないようです。

もちろんその辺りの無料のツールも出回ってます。有名どころではPEAR::Mail_Mime

利用してもいいのですが、なにかあったとき自分でちゃっちゃと直せないと困るので、自分で書いてみることにしました。

大まかな流れは、
1. ソケットオープン
2. メール送信
3. ソケットクローズ
です。

メールマガジンやステップメール配信の場合は2を繰り返します。

まずはソケットオープンです。

$smtp = "smtp.domain.xxx";
$port = 587;
メール送信時にも認証が必要になってから、ポート番号は587のことが多くなってます。
$id = "mail_id";
$pw = "smtp_pw";

$sock = @fsockopen($smtp, $port);
でオープンできます。

成功したら、
@fputs($sock, "EHLO ".$port."\r\n");
認証が必要になってからコマンドはEHLO。

ここでサーバーが色々な情報を返してきます。
そのうちの1つが、利用可能な認証方式。
250-AUTH PLAIN LOGIN DIGEST-MD5 CRAM-MD5 NTLM
の形式で返ってきます。

利用できる認証方式は、PLAIN、LOGIN、DIGEST-MD5、CRAM-MD5、NTLM。
それぞれの認証方式は後にして、ここで問題が発生です。

while (!feof($sock)) {
  $result = @fgets($sock, 128);
  $num = preg_match("/^250-AUTH /i", $result);
  if(0 < $num){
    $login_method = str_replace("250-AUTH ", "", $result);
  }
}
として、サーバーから返された文字列を取得しようとしたのですが、どうやら無限ループに陥っているようで、タイムアウトになってしまいます。

調べていくと、【feof($sock)】でfalseが返ってきていないようです。【fsockopen】でオープンしたストリームでも使えるのに・・・なぜ?PHPのバグ??それとも終了しないとeofが返ってこないのか???

仕方ないので、別の方法に切替です。

while ($result = @fgets($sock, 128)) {
  $num = preg_match("/^250-AUTH /i", $result);
  if(0 < $num){
    $login_method = str_replace("250-AUTH ", "", $result);
  }
}
で一応解決です。
でも、ものすごく時間がかかってしまいます。
時間がかかっているのは、【$result = @fgets($sock, 128)】の部分です。
文字列が返ってくる場合は早いのですが、falseが返ってくるのに時間がかかっています。

PHPのマニュアルを読むと、【feof】関数はタイムアウトになったときもfalseを返すとのこと。

そこで、タイムアウトまでの時間を短くし、処理時間の短縮を試みました。

【stream_set_timeout】でタイムアウトまでの時間を設定できます。
タイムアウトになったら【stream_get_meta_data】で値が取得できます。

stream_set_timeout ($sock, 5);    //タイムアウトまでの時間を5秒に設定
while (!feof($sock)) {
  $result = @fgets($sock, 128);
  $info = stream_get_meta_data($sock);
  if($info['timed_out'] == true){  //タイムアウトになったらこの値がtrueになる
    break;
  }
  $num = preg_match("/^250-AUTH /i", $result);
  if(0 < $num){
    $login_method = str_replace("250-AUTH ", "", $result);
  }
}

5秒ではなくもっと小さな値にすればもっと早くなります。

これで解決!のようです。動かしているうちまた不都合が見つかるかもしれませんが。

長くなったので、smtp認証はまた次回。

PHPコーディング

パーミッションの変更

2009年11月21日
PHPからファイルのパーミッションを変更する方法です。

まずは、現在の8進形式のパーミッションの取得から。
$file_path = "パーミッションを取得したいファイルパス";
$now = substr(sprintf('%o', fileperms($file_path)), -4);
これで、【0755】や【0644】などが返ってきます。

次に、パーミッションの設定です。
$permission = 0600;
$file_path = "パーミッションを変更したいファイルパス";
$result = @chmod($file_path, intval($permission, 8));

本当に設定できたか確認。
$after = substr(sprintf('%o', fileperms($file_path)), -4);

しかし、この流れだと問題が起こります。
ファイル情報がキャッシュされるので、パーミッションが無事変更できたとしても、$afterには$nowと同じ値が返ってきます。

そこで、キャッシュ情報をクリアします。関数【clearstatcache】をコールするだけです。

$file_path = "パーミッションを取得したいファイルパス";
$now = substr(sprintf('%o', fileperms($file_path)), -4);

clearstatcache();

$permission = 0600;
$file_path = "パーミッションを変更したいファイルパス";
$result = @chmod($file_path, intval($permission, 8));

$after = substr(sprintf('%o', fileperms($file_path)), -4);

これでOKです。

PHPコーディング

ftp接続でファイルをアップロード&ダウンロード

2009年11月20日
読み込み権限のないファイルをコピーするには、ftpでサーバーに接続し、ダウンロードする必要があります。

それをPHPから行う方法です。cronなど自動実行と組み合わせると、別サーバーにあるファイルと同期をとるなど、色々なことが自動でできるようになります。

//ftp接続
$connection = @ftp_connect(ftpサーバーのアドレス);

//ユーザ名とパスワードでログイン
$loginResult = @ftp_login($connection, ユーザーID, パスワード);

//パッシブモードオン
@ftp_pasv($connection, true);

//ファイルをダウンロードし、サーバー(ローカル)へ保存
@ftp_get($connection, ローカルパス, リモートパス, FTP_BINARY);
//ASCIIモードで転送する場合は、【FTP_BINARY】を【FTP_ASCII】へ

//ファイルをアップロードし、サーバー(リモート)へ保存
@ftp_put($connection, リモートパス, ローカルパス, FTP_ASCII);
//ASCIIモードで転送する場合は、【FTP_ASCII】を【FTP_BINARY】へ

//いらなくなったファイルを削除
@ftp_delete($connection, 削除するファイルのパス);

//接続を閉じる
@ftp_close($connection);

ここで、ftpの転送モード、バイナリモードとASCIIモードについて。
バイナリモードの場合はまったく同じファイルとして転送しますが、テキストのASCIIモード転送の場合はOS側で異なる改行コードを自動的に修正してくれます。
ftpでファイルをアップロード、ダウンロードする場合はどちらかを選べます。

※ サーバーにPHPのftpモジュールが組み込まれていないと使えません。
  私の使っているレンタルサーバーはオプションなので、自分でphp.iniを設定しました。

PHPコーディング

フォームを介さずPOSTする

2009年09月14日
まず、POSTするデータそのものは、
 key1=value1&key2=value2・・・
の形式にします。

$modeと$data['item']をPOSTする場合、
 mode=urlencode(stripslashes($mode)&data[item]=urlencode(stripslashes($data['item'])
となります。
$post_value = "mode=".urlencode(stripslashes($mode)."&data[item]=".urlencode(stripslashes($data['item']);
とします。


//postするURL
$server = "http://sample-site.com/transaction.php";
//URL分解
$url = parse_url($server);
//ポート番号
$port = 80;
//タイムアウトまでの秒数
$timeout = 30;

//ソケット接続オープン
$sock = fsockopen($url['host'], $port, $err_no, $err_msg, $timeout);
//エラーが起こったら、エラーナンバー、エラーメッセージ、$sockにfalseが返される

fputs($sock, "POST " . $url['path'] . " HTTP/1.0\r\n");
fputs($sock, "Host: ".$url['host']."\r\n");
fputs($sock, "Content-Type: application/x-www-form-urlencoded\r\n");
fputs($sock, "Content-length: " .strlen($post_value). "\r\n");
fputs($sock, "User-Agent: request\r\n");
fputs($sock, "\r\n");
fputs($sock, $post_value."\r\n");


postするドメインとpostされるドメインが違う場合はこれで動いていたのですが、ドメインが同じ場合は動きませんでした。
$url['host'] = "localhost";
とすると動きました。

PHPコーディング

文字コードの取得

2009年08月31日
変数に入っている文字のコードを確かめたい場合、【mb_detect_encoding】関数を使うと文字コードが返ってきます。

mb_detect_encoding(コードを確かめたい文字列, 確かめる順番, 検出方法);
で、2番目と3番目の引数は省略可能です。(特に3番目は省略)

なので、
mb_detect_encoding(コードを確かめたい文字列);
として文字コードを取得しようとしたのですが、エラーが出てどうしても取得できません。

そこで、2番目の引数に、順番を指定してやると・・・
あっさり文字コードを返してきました。文字列が短すぎたのか?

【mb_detect_encoding(コードを確かめたい文字列, "EUC-JP, SJIS, JIS, UTF-8, ASCII");】

PHPコーディング

ブラウザからファイルのダウンロード

2009年08月29日
ファイルをサーバーにアップロードするには、htmlのフォームから行えます。
しかし、ダウンロードするのはそうはいきません。

PHPからサーバーへコマンドを出力します。

$dl_filepath = ダウンロードしたいファイル名;
header("Cache-Control: public");
header("Pragma: public");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".basename($dl_filepath)."\"");
header("Content-Length: ".filesize(dl_filepath));
readfile(dl_filepath);

サーバーにあるファイルをダウンロードします。
編集して出力する場合、テンポラリファイルを作るのが簡単です。


こんな例もありました。
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($dl_filepath));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($dl_filepath));
ob_clean();
flush();
readfile($dl_filepath);


しかし、ブラウザによって正常にダウンロードできないこともあるようです。特にIE系がダメなようです。
やっぱり困ったブラウザだ・・・

PHPコーディング

【mail】関数で添付ありhtml形式メールを送信(代替本文あり)

2009年08月27日
だんだん複雑になっていきます。

ヘッダ部
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
From: [宛先メールアドレス]\r\n
Reply-To: [返信メールアドレス]\r\n
Bcc: [メールアドレス]\r\n
MIME-version: 1.0\n
"Content-Type: multipart/mixed; boundary=[バウンダリ文字列]\n
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


ボディ部
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
This is a multi-part message in MIME format.\n\n

--[バウンダリ文字列]\n
Content-Type: multipart/alternative; boundary=[バウンダリ文字列_1]\n

//ボディ代替
--[バウンダリ文字列_1]\n
Content-type: text/plain; charset=ISO-2022-JP\r\n
Content-Transfer-Encoding: 7bit\n\n

[JISにエンコードした代替本文]
\n

//htmlボディ
--[バウンダリ文字列_1]\n
Content-type: text/html; charset=ISO-2022-JP\r\n
Content-Transfer-Encoding: 7bit\n\n
[JISにエンコードした本文]
\n

--[バウンダリ文字列_1]\n

//添付ファイル
\n\n
--[バウンダリ文字列]\n
Content-Type: [MIMEファイルタイプ]\n
Content-Transfer-Encoding: base64\n
Content-Disposition: attachment; filename="[添付ファイルにつける名前]"\n\n
chunk_split(base64_encode([base64でエンコードした添付ファイルの内容]))\n
//添付ファイル

--[バウンダリ文字列]--
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

ボディ本体、代替本文のバウンダリ文字列を別に定義していることがポイントです。

※ []には送信するメールの内容を設定します。

PHPコーディング

【mail】関数で添付なしhtml形式メールを送信(代替本文あり)

2009年08月26日
ヘッダ部
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
From: [宛先メールアドレス]\r\n
Reply-To: [返信メールアドレス]\r\n
Bcc: [メールアドレス]\r\n
MIME-version: 1.0\n
Content-Type: multipart/alternative; boundary=[バウンダリ文字列]\n
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
【multipart/alternative】で、html本体と、代替本文があることを宣言しています。


ボディ部
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
This is a multi-part message in MIME format\n\n

--[バウンダリ文字列]\n
Content-Type: text/plain; charset=ISO-2022-JP\n
Content-Transfer-Encoding: 7bit\n\n

[JISにエンコードした代替本文]
\n

--[バウンダリ文字列]\n
Content-type: text/html; charset=ISO-2022-JP\r\n
Content-Transfer-Encoding: 7bit\n\n
[JISにエンコードした本文]
\n

--[バウンダリ文字列]--
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

(html形式の【Content-Transfer-Encoding】に【quoted-printable】が指定してある例もあり)

※ []には送信するメールの内容を設定します。

PHPコーディング

【mail】関数で添付ありhtml形式メールを送信(代替本文なし)

2009年08月25日
添付ありプレーンテキスト形式メールを送信する場合とほとんど同じです。

違うのは、ボディ部の【Content-type】で、【text/plain】から【text/html】にします。

ヘッダ部は
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
From: [宛先メールアドレス]\r\n
Reply-To: [返信メールアドレス]\r\n
Bcc: [メールアドレス]\r\n
MIME-version: 1.0\n
Content-Type: multipart/mixed; boundary=[バウンダリ文字列]\n
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


ボディ部は
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
This is a multi-part message in MIME format\n\n
--[バウンダリ文字列]\n
Content-type: text/html; charset=ISO-2022-JP\r\n
Content-Transfer-Encoding: 7bit\n\n

[JISにエンコードした本文]

//添付ファイル
\n\n
--[バウンダリ文字列]\n
Content-Type: [MIMEファイルタイプ]\n
Content-Transfer-Encoding: base64\n
Content-Disposition: attachment; filename="[添付ファイルにつける名前]"\n\n
chunk_split(base64_encode([base64でエンコードした添付ファイルの内容]))\n
//添付ファイル

--[バウンダリ文字列]--
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


※ []には送信するメールの内容を設定します。

PHPコーディング

【mail】関数で添付なしhtml形式メールを送信(代替本文なし)

2009年08月24日
本文がhtml形式のメールを送る場合、メーラーが対応していないことを考慮して、代替本文も付けて送ることがありますが、まずは代替本文のないメールを送ります。

この場合、プレーンテキストメールを送信する場合とほとんど同じです。

違うのは、ヘッダの【Content-type】で、【text/plain】から【text/html】にします。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
From: [宛先メールアドレス]\r\n
Reply-To: [返信メールアドレス]\r\n
Bcc: [メールアドレス]\r\n
MIME-version: 1.0\n
Content-type: text/html; charset=ISO-2022-JP\r\n
Content-Transfer-Encoding: 7bit
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


本文には、JISでエンコードした文字列を引き渡します。

※ []には送信するメールの内容を設定します。

PHPコーディング

【mail】関数で添付ありプレーンテキスト形式メールを送信

2009年08月23日
次はプレーンテキスト形式のメールに、添付ファイルを付けて送ってみます。


ヘッダ部は
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
From: [宛先メールアドレス]\r\n
Reply-To: [返信メールアドレス]\r\n
Bcc: [メールアドレス]\r\n
MIME-version: 1.0\n
Content-Type: multipart/mixed; boundary=[バウンダリ文字列]\n
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

バウンダリ文字列は区切りをあらわす文字列で、メールごとに定義します。
md5(uniqid(rand()));
などでランダムに作成します。

--[バウンダリ文字列]\n
で区切りを表し、

--[バウンダリ文字列]--
で終了を表します。



ボディ部は
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
This is a multi-part message in MIME format\n\n
--[バウンダリ文字列]\n
Content-Type: text/plain; charset=ISO-2022-JP\n
Content-Transfer-Encoding: 7bit\n\n

[JISにエンコードした本文]

//添付ファイル
\n\n
--[バウンダリ文字列]\n
Content-Type: [MIMEファイルタイプ]\n
Content-Transfer-Encoding: base64\n
Content-Disposition: attachment; filename="[添付ファイルにつける名前]"\n\n
chunk_split(base64_encode([base64でエンコードした添付ファイルの内容]))\n
//添付ファイル

--[バウンダリ文字列]--
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


添付ファイルを複数つける場合は、【//添付ファイル】を繰り返します。


プレーンテキストやhtmlファイルなど、テキスト形式のファイルを添付する場合、MIMEタイプは【text/plain】や【text/html】ではなく、【application/octet-stream】にします。


他にも書き方がありますが、これが私には一番分かりやすくしっくりきました。

※ []には送信するメールの内容を設定します。

PHPコーディング

【mail】関数で添付なしプレーンテキスト形式メールを送信

2009年08月22日
まずは一番簡単な、添付ファイルのないプレーンテキスト形式のメールを、【mail】関数で送ってみます。


【mail】関数の仕様は
$result = mail($to, $subject, $body, $headers, $parameters);

【$to】は宛先メールアドレス、
【$subject】は件名、
【$body】はメール本文、
【$headers】はヘッダ、
【$parameters】は拡張パラメータです。



添付ファイルのない、プレーンテキスト形式のメールを送るには、MIME形式にする必要はないのですが、送られてくるメールを調べると、MIME形式で送られてくるので、私もMIME形式で送ることにします。

メールの文字コードはJISが一般的なので、メール本文はJISで送信します。


まずはどんなメールにも共通の宛先とサブジェクトです。

宛先メールアドレスへは、メールアドレスを引き渡してやればOKです。

サブジェクトにもし日本語が含まれる場合は、エンコードしてから引渡します。
mb_encode_mimeheader($subject, "EUC", "B");
(EUCへは$subjectの文字コードを引き渡す)


さて、問題のヘッダです。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
From: [宛先メールアドレス]\r\n
Reply-To: [返信メールアドレス]\r\n
Bcc: [メールアドレス]\r\n
MIME-version: 1.0\n
Content-Type: text/plain; charset=ISO-2022-JP\n
Content-Transfer-Encoding: 7bit
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

ここで、from句に日本語宛先名を加える場合ですが、日本語名をサブジェクトと同様にエンコードします。
From: [エンコードした日本語宛先名]<[宛先メールアドレス]>\r\n

もちろんbccしない場合はbccの行はなし、ccする場合は同じように加えます。


メール本文は、JISにエンコードしてから引き渡します。
mb_convert_encoding([メール本文], "JIS", "ASCII,JIS,UTF-8,EUC-JP,SJIS");
本文の最後に【\n】を付加して引き渡します。


拡張パラメータには、リターンパスを設定することにしました。
-fリターンパスメールアドレス


※ []には送信するメールの内容を設定します。

PHPコーディング

ファイル更新日時の取得

2009年06月21日
PayPalは決済完了に自動で独自メールを送信できません。
なので、送信できるツール(PayPalオートメール)を配っているのですが、そのツールが動かないと連絡がありました。

自分のサーバーでは動いているので、全然気が付きませんでした。
サーバーによってエラーを吐いて止まるものと、無視して実行するものとあり、自分のところで動いているからといって、すべてのサーバーで動くとは限らないのが難しいところです。

で、その部分を直したら、今度は別のところで止まってしまった、という連絡が・・・

【get_headers】という関数を使って、ネットワーク上のファイルの更新日時を取得している部分があります。エラーメッセージを見てみると、この【get_headers】が認識できていないようなのです。

【get_headers】はPHPバージョン5以上でサポートしています。連絡を下さった方はバージョン5以上なのですが、インストールされていないようなのです。

困ったもんだ。

ロリポップを使っておられるのですが、以前私がロリポップでテストしたときは動いていたんだけどな・・・ なぜ動かない???
摩訶不思議。

もう一つファイルの更新日時を取得する関数に【filectime】がありますが、この関数は同じサーバー上のファイルにしか使えません。

で、結局【get_headers】は使わないようにソースを変更しました。

PHPコーディング

実行ファイルのフルパスを取得

2009年04月21日
ビジネス羅針盤 ステップメール】を動かしていて、ファイルのフルパスを取得する必要に迫られました。

cronから実行する場合、ファイルのパスと実行パスが変わるので、実行ファイルの絶対パスが必要になります。

【__FILE__】で取得できます。

$fullpath = __FILE__;
$base = basename(__FILE__);

【__DIR__】はファイルが存在するディレクトリです。

これらの定数は自動的に定義されるようです。
他にも色々あります。

自動的に定義される定数

PHPコーディング

不達メールの取り出し

2009年03月02日
送ったメールアドレスが無効な場合、エラーメールが返ってきます。

PHPからエラーメールをメールボックスから取り出します。

メールボックスにあるメールの全てのヘッダとボディを見ていると、不達で返ってきたリターンメールにはどうやら、

Final-Recipient: rfc822; メールアドレス
Action: failed

が含まれているようです。


不達メールを調べるには、【Final-Recipient】と【Action: failed】を探し、メールアドレスを抽出するのがよさそうです。


はじめはボディに含まれている【User Unknown】と【Host Unknown】を調べていたのですが、Yahoo!メールはアカウントがないという意味のエラーメールを返しているようなのです。Yahoo!メールからのリターンメールには【User Unknown】も【Host Unknown】も含まれていません。


他に共通するものがないかと調べてみると、全てに共通するのが、
Final-Recipient: rfc822; メールアドレス
Action: failed
のようなのです。

エラーナンバーからの調査も考えたのですが、他のものもひっかかりそうなので、断念しました。



メールボックスのメールを読むの各々のメールを取得の部分を少し書き換えればエラーメールのみ抽出できます。

for($id=1; $id<=$mail_num; $id++){
  @fputs($fp, "RETR ".$id."\r\n");
  $line = @fgets($fp);
  while(!eregi("^\.\r?\n", $line)){
    $mail = fgets($fp, 512);
  }
}

$mailの中に【stristr】関数を使って【Final-Recipient】と【Action: failed】が含まれるかチェックすればわかります。

PHPコーディング

メールボックスのメールを読む

2009年03月01日
PHPからメールボックスへ接続して、ボックス内のメールを取得する方法です。

まずはログインです。

接続
$fp = @fsockopen("メールサーバー", "ポートナンバー");
$line = @fgets($fp, 512);

ユーザー認証
@fputs($fp, "USER "."ユーザーID"."\r\n");
$line = @fgets($fp, 512);
@fputs($fp, "PASS "."パスワード"."\r\n");
$line = @fgets($fp, 512);

うまく接続できれば、最後の$lineに【OK】の文字が含まれています。これでログインした状態です。


ボックス内の情報を取得します。
@fputs($fp, "STAT\r\n");
$line = @fgets($fp, 512);

$lineにはステータス、メール数、サイズが半角スペースで区切られて返されます。
list($stat, $mail_num, $size) = explode(' ', $line);
でそれぞれの値が取り出せます。


各々のメールを取得するには、
for($id=1; $id<=$mail_num; $id++){
  @fputs($fp, "RETR ".$id."\r\n");
  $line = @fgets($fp);
  while(!eregi("^\.\r?\n", $line)){
    $mail.= fgets($fp, 512);
  }
}

$mailにはヘッダもボディも含まれます。


メールをサーバーから削除するには、
@fputs($fp, "DELE "."削除するメールのid"."\r\n");
です。


終了は
@fputs($fp, "QUIT\r\n");
@fclose($fp);
です。

PHPコーディング

エラーメールの受け取りアドレスを指定する

2009年01月09日
宛先のメールアドレスがない、などの理由で返信されるエラーメール。このエラーメールを受け取るアドレスを指定できれば便利です。

まず思い浮かぶのが【Return-Path】です。が、実はこの【Return-Path】、メールを送るときこちらが指定しても、経由するサーバーによって勝手に書き換えられてしまいます。

書き換えられるアドレスは、【envelope FROM】というものだそうです。

PHPからメールを送信するとき、この【envelope FROM】を指定できればエラーメールを受け取るアドレスを指定できるのだそうです。

【mail】関数や【mb_send_mail】関数の第5引数を使います。

mb_send_mail("送信先アドレス", "件名", "ボディ", "Fromなどのヘッダ", "-fエラーを受け取るアドレス");
となります。
【エラーを受け取るアドレス】が【err@domain.xxx】だとすると、第5引数には【-ferr@domain.xxx】を渡してやればいいのです。

【-f】オプションは【sendmail】のオプションです。

PHPコーディング

【mb_send_mail】関数でcc、bccが送信できない!?

2008年12月05日
改正迷惑メール防止法施行に伴い、ステップメールで登録があったとき、管理者アドレスへも登録メールを送信するようにソースを変更してみました。これでログになるはずです。

実際の送信は、bccで管理者へ送信します。


ところが・・・
送信できない。

なぜだ???

bccはヘッダへ書き込みますが、間違っているのか??
色々調べまわったのですが、合っている。


色々変えてみてもやっぱり送信できていない・・・


考えていて思い出しました。Webに置いている連絡フォームはbccが送信できていました。同じソースを利用しているので、ソースが違っているわけではないようです。


そこで、送信先のメールアドレスを変更してみました。

なんと!! ちゃんと来るではありませんか。

同じドメインが含まれているとサーバーが弾いているのかと思い、色々アドレスを変更してみました。私が持っているドメインで送れないようです。

レンタルサーバーの同じIDに契約しているドメインがダメなようです。
なんだぁ。あっさり解決です。


日本語メールを送るソースです。

//言語設定
mb_language("Japanese");
//内部エンコーディング
mb_internal_encoding("EUC") ;

$to = "aaa@bb.xxx";
$subject = "サブジェクト";
$body = "本文";
$from_name = "Aさん";

$from_name = mb_encode_mimeheader($from_name,"EUC","B");
$headers = "From: ".$from_name."<".$from.">\r\n";
$headers.= 'Reply-To:'.$Reply."\r\n";
$headers.= 'BCC:'.$bcc."\r\n";

//送信
$send_result = mb_send_mail($to, $subject, $body, $headers);


【mb_send_mail】関数は、サブジェクト、本文はエンコーディングしてくれますが、ヘッダはエンコーディングしてくれません。なので、【Form】に日本語を含める場合は事前にエンコーディングしておきます。

【to】、【bcc】、【cc】には日本語は含められません。
2012年05月
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
先月
来月
カテゴリ
検索
関連サイト
プロフィール
【ネットネーム】
みゅー

キャラクター by ツカエルサイト


【趣味】
・プログラミング
・写真
・楽器演奏
・手芸
・考えること
・自然科学
・猫と戯れる
・ジグソーパズル

【仕事】
・派遣社員でSE・PGやってます。やっと派遣期間が終了し、ビジネスへ注力しようかと思っています。
・経験言語:VB6、VB.NET、PHP、Perl、JAVA、Oracle、SQLServer、PostgreSQL

RSSフィールド