今見てるページを書き換える

これは使えそう!と思ったけど、実際にはほとんど使い道がない気がしてきた。
なにがしたかったんだろうか。。。
今見てるページのurlをサーバに送って、サーバ側で該当する文字を置き換えてクライアントに返すというプログラム。

<?php

class LoadRe
{
  public $replacecomp;
  public $url;
  public $lines;
  public $loadok = false;

  public function seturl()
  {
    $this->url = $_GET['url'];
  }

  public function line($str)
  {
    $lines = array();
    foreach($str as $line => $val){
      $lines[$line] = rtrim($val);
    }
    $this->lines =  $lines;
  }

  public function getline($n)
  {
    return $this->lines[$n];
  }

  public function replacetxt($tgt,$rep)
  {
     $n = array_search($tgt,$this->lines);
     if($n){
       $this->lines[$n] = $rep;
       $this->replacecomp = true;
     }
  }

  public function getContent()
  {
    $content = file($this->url);
    if($content){
      $this->line($content);
      $this->loadok = true;
    }
    return $this->loadok;
  }

  public function tostr()
  {
    $list = array_values($this->lines);
    return implode("\n",$list);
  }

}

使い方はこんな感じ。以下のように書き換えたい部分を書くと、書き換え後の文字列を返してくれる。仮にt.phpとして保存しておく。

<?php
require_once './LoadRe.php';

$k = new LoadRe();
$k->seturl();
$loadok = $k->getContent();

if($loadok){
  $k->replacetxt('<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">','<meta name="viewport" content="width=device-width">');
  $k->replacetxt('pon','<h1>'.$k->url.'</h1>');
  $k->replacetxt('load();','');//これを忘れると永久にリクエストするので注意
  $k->tostr();
}

さっきのt.phpXMLHttpRequestオブジェクトを用意してリクエストする。リクエストを送って、返ってきた内容をdocument.writeで書き出す。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<script src="http://code.jquery.com/jquery-1.9.0.min.js"></script>
</head>
<body>

<h1>hi</h1>
pon
<script>
function load(){
  $.get('t.php',{url:location.href},function(data){
    document.open();
    document.write(data);
    document.close();
  });
}

load();

</script>

</body>
</html>

テストも書いた!

<?php

require_once 'PHPUnit/Autoload.php';
require_once './LoadRe.php';

class Unagi extends PHPUnit_Framework_TestCase
{

  protected $lr;

  protected function setup()
  {
     $this->lr = new LoadRe();
  }

  public function testUrl()
  {
   $this->lr->seturl();
   $this->assertEquals('http://localhost/test/k.php',$this->lr->url);
  }

  public function testLoadurl()
  {
      $this->lr->seturl();
      if($this->lr->getContent()){
        $this->assertTrue(true);
      }
  }

  public function testExist()
  {
    $this->lr->seturl();
    $this->lr->getContent();
    $line = $this->lr->getline(9);
    $this->assertEquals('pon',$line);
  }

  public function testRep()
  {
    if($this->lr->replacecomp){
      $this->assertTrue(true);
    }
  }
}

スパムメールの対策

フォームからの問い合わせを受け付けているとスパムが多く届く。数が多いと削除する手間と時間がけっこうかかるので何かしらの対応をしようと思い、対策を調べたのでメモ。リンク先にmailtoというのではなく、フォームからの受付を対象にした対策。

まずは直接的な対策から。スパム自体を送信できなくする、サーバー側ではじく方法を紹介。

hidden要素を追加する
<input type="hidden" name="mail" value="あいうえお">
<input type="hidden" name="address" value=""> 

フォームの内容とは直接関係ないinputタグを入れておき、botによる改変があったかを調べるやり方。mailにあいうえお以外が入力されていたらスパムと判定する。また、addressに変な文字が入っていればそれもスパムと判定。
※上記は例なのでname、value属性の値は適宜変更

◎メリット
サーバー問わず対策できる
閲覧者に負担がない

◎デメリット
hiddenのinput要素に変更を加えないbotの送信は防止できない

スパム対策第2弾

画像認証をする

画像を生成して、そこに表示された文字を入力してもらう。いろんなサービスで取り入れられてるのでよく見る。でも文字が判別しづらくて困ることがあるからあまり親切じゃないかも。

◎メリット
目視での確認が必要なのでスパム対策に効果あり

◎デメリット
表示される文字が読みにくい、入力作業が手間
サーバーに画像を扱うライブラリが入っていないと使えない

Securimage PHP Captcha

postされたデータをもとに判定する

サーバに送られてきたデータを見て判定する。スパムっぽいかどうかの判断が難しいので、特定の文字が入ってたら通すというようにホワイトリストで対応するのがよさそう。

◎メリット
サーバー問わず対策できる
閲覧者に負担がない

◎デメリット
ブラックリストで運用するとスパムの種類が多いのでどこを基準にスパムと判定するかが難しい(URLあり/なし、英字のみ、スパムっぽいメルアド)

メルアドを画像にして表示する

メルアドを画像にして表示。ユーザーに手入力してもらう(フォームなのに送信先を入力させるというのはどうかと思うけど)

◎メリット
目視での確認が必要なのでスパム対策に効果あり
サーバー問わず対策できる

◎デメリット
打ち間違え、入力作業が手間

フォームをjsで表示する

フォームに関する要素をjsで表示する。jsを実行できなければ送信できないので普通にブラウザからアクセスしてるなら問題なく動く。

document.write('<form action="http://zapanet.info/blog/?page='+'message" method="POST">');
var web="1";
document.write('<input type="hidden" name="web" value="'+web+'"> ');

◎メリット
サーバー問わず対策できる
閲覧者に負担がない

◎デメリット
jsを実行できないと送信できない。
jsを実行するボットには効果がない

メッセージフォーム設置&スパム対策第3弾

以下は間接的な対策。必ずしもスパムの防止効果があるわけじゃないのでやってもいいけどやらなくてもいいかなという感じ。

検索エンジンにクロールさせない

検索結果の一覧に問い合わせのページを表示させないようにしてスパムに減らす。フォームのあるページがクロールされないようにする。

User-agent: *
Disallow: /bbs/postForm
  • METAタグのrobots
<meta name="robots" content="noindex" />

そのほか、検索エンジンがインデックスしたページの削除を依頼する、フォームへのリンクはjsで文字列として生成するという対応で検索エンジン経由の流入を制限する。

積極的なスパム投稿対策

メーラーで振り分ける

スパム判定をメーラーに任せて受信時に振り分ける。使ってるメーラーの種類によるけど設定画面から振り分け設定をする。

facebookのユーザーの投稿を取得する

facebookapiが変更になって、Graph APIを使ってユーザーの情報にアクセスする方法が変わった。offline_accessというパーミッションが指定できなくなり、常時ユーザー情報にアクセスすることができなくなった。これまではそのパーミッションを指定することで、一度アクセストークンを取得したらあとはいつでもユーザーにアクセスできたけど、数時間の有効期限が設定されて困った。

この有効期限ありのトークンは以下の状態で使用不可になる。

・有効期限を過ぎた
・ユーザーがアプリを削除した
・ユーザーがログアウトした
・ユーザーがパスワードを変更した

有効期限が1〜2時間て短すぎるし、ログアウトしたら使えなくなるなんてつらい。ではどうするかというとトークンの有効期限を60日に変更する。アプリの作成から有効期限を変更したトークンの取得について説明する。例として、ユーザーの投稿内容を取得してサイトに表示するというのをやってみる。

offline_accessパーミッション削除に対応する
Facebook Graph API での投稿(Post)にハマった件
Facebookアクセストークンを取得してそれを60日間使えるようにする
Authentication : Facebook開発者向けドキュメントの日本語訳とTips

facebookの開発者登録

Facebook開発者のページからアプリの作成をする。アプリ名やリダイレクト先のサイトURLなどを入力してアプリを作成。

有効期限が60日に変更されたアクセストークンを取得

続いて作成したアプリのAPP_IDとAPP_SECRETを差し替え。アクセストークンデバッガーを開いてDebugをクリック、表示されたUser TokenをコピーしてEXISTING_ACCESS_TOKENと差し替え。
以下の該当個所をそれぞれ変更する。

https://graph.facebook.com/oauth/access_token?             
    client_id=APP_ID&
    client_secret=APP_SECRET&
    grant_type=fb_exchange_token&
    fb_exchange_token=EXISTING_ACCESS_TOKEN 

あとはこのURLにリクエスト(見やすく改行と空白を入れてるのでそれは消すこと!)すると有効期限が60日のトークンが表示される。実際はもっとトークン部分が長いけどこんな感じのレスポンスが返ってくる。

access_token=AAAD***&expires=5183896

access_token=と&expires=5183896の間に挟まれた部分が60日間使用できるトークン。めでたくユーザー情報にアクセスできる期間が延長された。

ユーザーの投稿を取得

アプリがデフォルトで取得できるユーザー情報はプロフィールとか基本的なものだけなので、そのままだと投稿内容やアップロードした写真などが取得できない。なので、アプリに必要な権限を与えるためにユーザーに認証してもらう。以下のAPP_IDを先ほどのアプリのものに差し替え、YOUR_URLをアプリ作成で入力したURLに差し替える。scopeには許可して欲しい項目を列挙する。取得可能な情報とscope一覧はこちらを参照。

https://www.facebook.com/dialog/oauth?
     client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&scope=read_stream

必要なところを変更して↑を開くと認証用の確認画面が表示される。ここでユーザーがアプリを認証してくれればユーザー情報にアクセスできるようになる。これで準備はできたので、有効期限の延びたトークンを使って自分のfeedを取得する(↓は架空のトークンだから動かないよ)。

https://graph.facebook.com/me/feed?access_token=AAABsckKHaY4BAA8QZDZD

今は自分のフィードを取得したけど、URLのmeのところをアプリを認証してくれたユーザーのIDに変更すればそのユーザーの情報にアクセスできる。ここまで進めて思ったんだけど、このアクセストークンはユーザーごとに作るんではなくて、アプリの管理者が取得したものだからすべてのユーザーで共通のものを使うってことになるよね。それって何かよくない気がするんだけどどうなんだろう。ちなみにユーザーの情報を取得するには上記の手順になるけど、facebookページの内容を取得する場合はアプリのトークンがあればいいので、ユーザーの時のようなアプリの認証はしなくてもいい。
また、facebookページの場合はトークンの有効期限はないので一度の取得でずっと使える。


Facebookのアクセストークンの有効期限について調べてみた
FacebookページIDの確認方法

firefoxではdisplay:boxを指定すると、widthの相対値が無視される

firefoxのバージョンは17.0.1なので他のバージョンは違うかも。こんなページがあって。

<div id="wrap">
  <div class="shop">
    <section class="photo">
    <img src="./imgs/photo.jpg" width="150" height="150" />
    </section>
    <section class="shop_txt">
      <article class="info">
        <h2>ラーメン屋</h2>
        <p>おいしいラーメンがあります。</p>
      </article>
    </section>
  </div>
</div>

要素を横に並べるのにcss3から追加されたdisplay:boxを使う。これまでfloatで横に並べてclearfixを使ったりしてたけどこれのおかげで少し便利になった。とのことで実際に使ってみる。cssはこんな感じ。

img{
width:100%;
}

#wrap{
width:100%;
margin:0 auto;
padding:20px 0;
}

.shop{
width:80%;
margin:0 auto;
display:-moz-box;
display:-webkit-box;
box-orient:horizontal;
}

.photo{
width:20%;
}

.shop_txt{
width:80%;
padding:0 0 0 20px;
}

これで要素は横並びで、かつ画面サイズに合わせて横幅が自動で変わるはず。ところがfirefoxでは相対値で指定したwidthが無視されて、画面サイズを変えても横幅が変化しない。pxで指定しちゃうと、もちろん幅が変わらないからスマホ対応にあまり意味がない。メディアクエリでわけるならいいんだけど、PCやスマホcssをわけたくないみたいなときに困る。まぁ、手抜きせずにわけろよって話しなんですけどね。残念だけど、この場合はfloatを使った通常の横並びが必要。

AS2でシーンを移動して再生するがうまく動かない

ActionScript2でシーンをスクリプトで移動して再生したいときにはgotoAndPlayを使うらしい。
でも実際はここここに書いてあるやり方ではエラーがでてだめ。
ここにはフレームの番号かフレームラベルを引数にすると書いてあるけど、型がobjectになってるので数値や文字列を渡すとエラーで動かない。
↓のように移動先のフレーム番号(シーン1のフレーム数とシーン2の目的のフレーム番号までを足したもの)を渡したけどだめだった。

gotoAndPlay(522);

この場合のオブジェクトは何で、どうやって取得するのかわからないので↓を書いてみたけどまったく動かず。

var myObj:Object = new Object();
myObj = 522;
gotoAndPlay(myObj);

そりゃそうだ。値の型が違うわ。
調べたけどやり方がわからない。前任者が書いた似たような処理のコードはなぜかこうなってた。

var goto:int = 2;
setTimeout(gotoAndPlay, 7500, goto);

7500ミリ秒あとにgotoAndPlayに引数gotoを渡してる。けど、なぜこんなことになるんだろう。
日本語の情報がなくて説明がわかりやすかったのがこちら
たぶんこれかなと思う。
swfをコンパイルしたりロードしたりするときに全てのフレームは読み込まれるけど、
再生時にはフレームがスタックになってるわけではないので、全てのフレームが読み込まれるのを待ってからgotoAndPlayを実行しなきゃだめということ。
移動先のフレームが再生できるまで待つってことね。
なるほど!だからsetTimeoutを使うわけか。
これまたASの人には常識っぽいなー。

スマホサイトで画面が拡大されるときの対処法

スマホ用サイトでは画面のサイズがいくつもあるので、
viewportを指定して自動で端末の画面サイズに合わせた表示になるようにする。
viewportはPCでいうところのウインドウサイズとは違い、コンテンツを表示する領域。詳しくは
appleの解説をどうぞ。それをmetaタグに記述することでスマホで表示するときのサイズを調節できる。
こちらにサイズの算出について書かれているので参考にするといいよ!

スマホサイトの作り方でよく紹介されるのは↓の指定。

<meta name="viewport" content="width=device-width, initial-scale=1.0,minimum-scale=1.0, maximum-scale=1.0,user-scalable=no">

それぞれ以下の意味になる。

プロパティ名 説明
width 端末の幅
initial-scale 初期表示の拡大率
minimum-scale ピンチイン(縮小時の操作)の最小値
maximum-scale ピンチアウト(拡大時の操作)の最大値
user-scalable ユーザーによる拡大/縮小操作の可否

1.0だと100%ということなので、minimum-scale・maximum-scaleが共に1.0(サイトによっては1としてる場合もあり)だと、現在の表示状態からの拡大・縮小はしないということになる。
だけど上記の指定だとコーディングの仕方によっては、iPhoneやgalaxy nexusで640pxの画像を使ったサイトを見たときに画面が拡大される。拡大されているので画像が半分だけ表示され、残りは画面をはみ出した状態になってしまう。理由は、

・divやimg要素のwidthをpxで指定している
・ウインドウの横幅をはみ出すサイズの画像を表示している

から。
試しにjavascriptでウインドウの横幅を取得すると↓のようになる。

■iPhone4 ※retinaディスプレイだと解像度は倍になる(iPhone3と比べて画面サイズは3.5インチで変わらないが解像度だけ高くなっている)
縦持ち:320px 
横持ち:480px

■galaxy nexus
縦持ち:360px
横持ち:598px

width=device-widthとすると、それぞれ上記のpxが指定され、なおかつ初期表示の拡大率が1.0なので、640pxの画像はウインドウの横幅をはみ出してしまう。
これを回避する方法は2つある。

1・viewportの指定を変える

<meta name="viewport" content="width=device-width,initial-scale=0.5,minimum-scale=0.5" />

スマートフォン(iphone、Xperia、GALAXY等)のviewportの最適な指定方法とは?
で書かれているけど、あらかじめ初期表示の拡大率を半分にして、縮小時の最小値も半分にする。
これで最初からピンチインで縮小したのと同じ状態で表示される。
また、ユーザーが拡大して見れるようにmaximum-scaleは指定してない。

2・要素の幅を相対値で指定する

divやimg要素のwidthを%で指定する。これで要素の幅はウインドウの横幅に合わせて調整されるのでwidth=device-widthの値に沿った幅で要素が表示される。
この方法の場合、最初にあげた↓の指定でも、

<meta name="viewport" content="width=device-width, initial-scale=1.0,minimum-scale=1.0, maximum-scale=1.0,user-scalable=no">

画像はウインドウの横幅に収まるようになる。

viewportはもともとiPhoneで対応が始まったもので、androidはそれに遅れる形でviewportのサポートを始めた。viewportには仕様というものがなく、他のブラウザはiOS Safariの挙動を参考にしてviewportのサポートをしているそうだ。ブラウザの独自実装ほどあとで問題が(ブラウザごとの差異)起きるものはないので、通常は要素の幅を相対値で指定するやり方がいいと思う。
スマホサイトについては情報が多くても実際に制作してみないとわからないことがあるので、
今後も情報収集を続けることが大事!

画像をCSSで本のようにする

これは新鮮!ただの画像をCSSで回転させて立体感をだして本のように表示する手法。
発想って大事だなと思った。
やり方はリンク先のhtmlとcssをそのままコピペするだけだけど、Modernizrというjavascriptのライブラリを読み込まないといけない。
Modernizrは使用しているブラウザがhtml5、css3に対応しているかを検出するライブラリ。
ここから検出したい項目を選択して、自分用にカスタマイズしたものをダウンロード。
あとはタグの前で読み込む。
Modernizrがcsstransforms3dというクラスを追加するので、対応してるブラウザとそうじゃないブラウザとにわけてcssが書ける。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<style>
.book {
  display: inline-block;
  box-shadow: 5px 5px 20px #333;
  margin: 10px;
}

.book img { vertical-align: middle; }

.csstransforms3d  .books {
  -moz-perspective: 100px;
  -moz-transform-style: preserve-3d;
  -webkit-transform-style: preserve-3d;
}

.csstransforms3d  .book {
  position: relative;
  -moz-perspective: 100px;
  -moz-transform: rotateY(-3deg);
  -webkit-transform: perspective(100) rotateY(-3deg);
  outline: 1px solid transparent; /* Helps smooth jagged edges in Firefox */
  box-shadow: none;
  margin: 0;
}

.csstransforms3d  .book img {
  position: relative;
  max-width: 100%;
}

.csstransforms3d  .book:before,
.csstransforms3d  .book:after {
  position: absolute;
  top: 2%;
  height: 96%;
  content: ' ';
  z-index: -1;
}

.csstransforms3d  .book:before {
  width: 100%;
  left: 7.5%;
  background-color: #5a2d18;
  box-shadow: 5px 5px 20px #333;
}

.csstransforms3d  .book:after {
  width: 5%;
  left: 100%;
  background-color: #EFEFEF;
  box-shadow: inset 0px 0px 5px #aaa;
  -moz-transform: rotateY(20deg);
  -webkit-transform: perspective(100) rotateY(20deg);
}

</style>
</head>
<body>


<div class="books">
  <div class="book">
   <img src="./book.jpg" />
  </div>
</div>

<script src="modernizr.custom.17973.js"></script>
</body>
</html>

3Dを扱うプロパティは使う機会がなくてよくわからないけど、
以下のプロパティで画像を回転させている。

プロパティ名 説明
-moz-perspective 配置された要素に視点を与えるためにz平面とユーザー間の距離を指定
-moz-transform-style、-webkit-transform-style 子要素が3D空間に配置されてるか要素の平面に平らにされているかを指定
-moz-transform、-webkit-transform 配置された要素を変形

さらに擬似要素のbefore、afterで要素の前と後にcontentで空白を表示して、それをbox-shadowやbackground-colorで装飾して本の厚みを表現してる。
WebGLほど本格的なことはできないと思うけど、ハードウェアが対応してなくても表示できるのは嬉しい。
奥行きをそれっぽく見せるくらいの簡単な目的なら十分そう。
もっと勉強しないとなー。