2009.09.08
category
comments

サーバータイムを使ったミリ秒対応のカウントダウン

ティザーサイト等で良く使われる「オープンまであと何日」みたいなカウントダウンを作ってみる。Flashだけで作るとローカルタイムに依存するので、ユーザーに時計を進められるとネタばれすることも。たまに見かけるけど。上のデモはアクセスした日から常に1ヶ月先をカウントダウンします。残りゼロになっても、何も起きませんよ。

サーバーから時間を取得した後にFlash側で経過時間を足して、目的の時間から差分を取ることでどのPCから見てもカウントダウンの時刻を同期することができる。

カウンタークラスにしてみた。ドキュメントクラスでは下記のように使います。

package
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import info.five.net.Counter;
	import info.five.events.CountEvent;

	/**
	 * ...
	 * @author 5ive
	 */
	public class Main extends Sprite
	{
		public function Main():void
		{
			var d:Date = new Date();
			var ctr:Counter = new Counter(d.fullYear, d.month + 2, d.date);
			ctr.getServerTime("servertime.php");
			ctr.addEventListener(Counter.CHANGE, onChange);
			ctr.addEventListener(Counter.FINISH, onFinish);
		}

		private function onFinish(e:CountEvent):void
		{
			trace("finish!");
		}

		private function onChange(e:CountEvent):void
		{
			countText.text = e.day + "d  " + e.hour + "h  " + e.minute + "m  " + e.second + "s  " + e.milisecond + "ms";
		}
	}
}

new Counter()の引数でカウントさせたい未来の日付と時間を渡す。getServerTime()メソッドでPHPへのパスを渡す。後はCHANGEとFINISHイベントをリスナーに登録して時間が流れるのを待つだけ。

Counter.as

package info.five.net
{
	import flash.display.Sprite;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLVariables;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.EventDispatcher;
	import flash.utils.getTimer;

	import info.five.events.CountEvent;

	/**
	 * ...
	 * @author 5ive
	 */
	public class Counter extends EventDispatcher
	{
		public static const CHANGE:String = "change";
		public static const FINISH:String = "finish";
		private var _loader:URLLoader;
		private var _serverTime:Number;
		private var _swfTime:Number;
		private var _futureDate:Date;
		private var _sp:Sprite;

		public function Counter(year:uint, month:uint, day:uint, hour:uint = 0, monuites:uint = 0, second:uint = 0, milisecond:uint = 0):void
		{
			_sp = new Sprite();
			_futureDate = new Date(year, month - 1, day, hour, monuites, second, milisecond);
		}

		//------------------------------
		//   サーバーへの問い合わせ
		//------------------------------
		public function getServerTime(url:String):void
		{
			//-----[キャッシュ対策]
			var date:Date = new Date();
			url += "?t=" + date.getTime();

			//-----[サーバーの時間取得]
			_loader = new URLLoader();
			_loader.addEventListener(Event.COMPLETE, onComplete);
			_loader.addEventListener(IOErrorEvent.IO_ERROR, onError);
			_loader.load(new URLRequest(url));
		}

		//------------------------------
		//   IOErrorEvent
		//------------------------------
		private function onError(e:IOErrorEvent):void
		{
			trace("error=" + e.text);
		}

		//------------------------------
		//   サーバーの時間取得
		//------------------------------
		private function onComplete(e:Event):void
		{
			var urlVariables:URLVariables = new URLVariables(_loader.data);
			_serverTime = Number(urlVariables.returnValue);
			_swfTime = getTimer();

			//-----[URLLoaderの削除]
			_loader.removeEventListener(Event.COMPLETE, onComplete);
			_loader.removeEventListener(IOErrorEvent.IO_ERROR, onError);
			_loader.data = null;
			_loader = null;

			//-----[タイマーの開始]
			if (_futureDate.getTime() < _serverTime)
			{
				trace("[error]:カウントする日付が過去を指定しています。");
			}
			else
			{
				if (_sp != null) _sp.addEventListener(Event.ENTER_FRAME, onTicks);
			}
		}

		//------------------------------
		//   タイマーの更新
		//------------------------------
		private function onTicks(e:Event):void
		{
			var passage:Number = _serverTime + (getTimer() - _swfTime);
			var diff:Number = _futureDate.getTime() - passage;
			var day:* = Math.floor(diff / (24 * 60 * 60 * 1000));
			var total:* = Math.floor(diff / (60 * 60 * 1000));
			var hour:* = total - (day * 24);
			var minute:* = Math.floor(diff / (60 * 1000)) - (total * 60);
			var second:* = Math.ceil(diff / 1000) - ((minute * 60) + (total * 60 * 60)) - 1;
			var milisecond:String = diff.toString().substr(diff.toString().length - 3, 3);

			//-----[2桁で表示]
			day = String(day + 100).substr(1, 2);
			hour = String(hour + 100).substr(1, 2);
			minute = String(minute + 100).substr(1, 2);
			second = String(second + 100).substr(1, 2);

			//-----[イベントクラスに値を格納]
			var ce:CountEvent = new CountEvent(CountEvent.CHANGE);

			if (diff > 100)
			{
				ce.day = day;
				ce.hour = hour;
				ce.minute = minute;
				ce.second = second;
				ce.milisecond = milisecond;
				dispatchEvent(ce);
			}
			else
			{
				ce.day = "00";
				ce.hour = "00";
				ce.minute = "00";
				ce.second = "00";
				ce.milisecond = "000";
				dispatchEvent(ce);

				dispatchEvent(new CountEvent(CountEvent.FINISH));
				kill();
			}
		}

		//------------------------------
		//   データの破棄
		//------------------------------
		public function kill():void
		{
			_sp.removeEventListener(Event.ENTER_FRAME, onTicks);
			_sp = null;
		}
	}
}

Counterクラスではサーバータイムを取得した瞬間にENTER_FRAMEを発動。そこからFlash内での経過ミリ秒を加算していき、目的の時間との差分を計算してCountEventクラスに各時間を格納させる。そのイベントをドキュメントクラスに返して時間を取得させる。

CountEvent.as

package info.five.events
{
	import flash.events.Event;

	public class CountEvent extends Event
	{
		public static const CHANGE:String = "change";
		public static const FINISH:String = "finish";
		public var day:String;
		public var hour:String;
		public var minute:String;
		public var second:String;
		public var milisecond:String;

		//------------------------------
		//   コンストラクタ
		//------------------------------
		public function CountEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false):void
		{
			super(type, bubbles, cancelable);
		}

		//------------------------------
		//   クローン
		//------------------------------
		public override function clone():Event
		{
			return new CountEvent(type, bubbles, cancelable);
		}
	}
}

CountEventクラスには「day」、「hour」、「minute」、「second」、「milisecond」プロパティが用意されているので、ドキュメントクラス側で必要に応じて取得して表示させて下さい。

serverttime.php

<?php
 $stamp = microtime();
 $ary = split(" ", $stamp);
 echo "returnValue=".(string)$ary[1].(string)(int)($ary[0] * 1000);
?>

最後にサーバーに置くPHP。Flash側のgetTimer()のミリ秒と、PHP側のmicrotime()のミリ秒で桁が違うので、桁合わせのためにちょっとだけごにょごにょしてます。microtime()でミリ秒を取得すると小数点付きで分割して計算されるので、一度文字列にしてFlash側に合わせるように変換かけてます。

2009.09.01
category
comments

WordPressでfeedを配信しないようにする方法

WordPressでフィードを配信したくない時にコアファイル弄るとすぐ出来るんだけど、アップグレードした時にメンテが面倒になるのでこの方法はできればやりたくない。調べてたら使っているテーマフォルダにfunctions.phpをアップする事で無事解決できた。

functions.php

<?php
	remove_action('wp_head', 'rsd_link');
	remove_action('wp_head', 'feed_links_extra', 3);
?>

WPのヘッダーにwp_head()を入れている場合に、上記の記述でメインのフィードとページ等のコメントフィードへのリンクを削除することができる。

2009.07.21
category
comments

WordPressからProgressionのPRMLを出力してみる

Flashネタをエントリーするのは久々だなー。先日の福岡てら子で発表した内容ですが、WordPressからProgressionで使うPRMLというXMLを出力する方法。PRMLの詳しい説明は本家リファレンスを参照して下さい。これを使うことにより、WordPressからProgressionにダイレクトにシーンを生成出来るようになる。今回はWordPressのカテゴリーをシーンに見立てて出力しているので、親、子、孫、曾孫といった深い階層のシーン構造も簡単に作ることができる。下記のPHPをWPがインストールされているサーバーのテーマフォルダにアップします。次にWPのメニューからページを新規で追加して、タイトルに「prml」と入力し、右サイドバーの属性のテンプレートから「prml」を選択。この状態でページを公開すると、タイトルの下にパーマリンクが表示されるので、このURLにアクセスすると現在のカテゴリー構造を維持したままのPRMLが出力されるようになる。こんな感じ

prml.php

<?php /*
Template Name: prml
*/ ?>
<?php
 header('Content-Type: text/xml; charset='.get_option('blog_charset'), true);

 //-----[PRMLのヘッダー生成]
 $title = get_option('blogname');
 $header .= '<?xml version="1.0" encoding="'.get_option('blog_charset').'"?'.'>';
 $header .= '<prml version="2.0.0" type="text/prml">';
 $header .= '<scene name="index" cls="myproject.IndexScene" title="'.$title.'">';

 //-----[WPから全階層のカテゴリー取得]
 $str = wp_list_categories('orderby=id&echo=0&hide_empty=0&use_desc_for_title=0&title_li=');
 $str = strip_tags($str, '<li>');

 //-----[PRML形式に変換]
 $category = strip_tags($str, '');
 $category = str_replace(array("\r\n","\r","\n"), '', $category);
 $category = preg_replace('/\s+/', ' ', $category);
 $category = ltrim($category);
 $category = split(' ', $category);

 for($i=0; $i<count($category); $i++)
 {
 	$str = str_replace(">".$category[$i]."\n", $i.">", $str);
 }

 $str = preg_replace('/"(.*?)"/', '', $str);
 $str = str_replace('li', 'scene', $str);

 for($i=0; $i<count($category); $i++)
 {
 	$str = str_replace('class='.$i, 'name="'.$category[$i].'" cls="myproject.'.ucfirst($category[$i]).'Scene" title="'.$title." | ".$category[$i].'"', $str);
 }

 //-----[PRMLとして出力]
 echo $header.$str.'</scene></prml>';
?>

このPHPでやっていることはwp_list_categories()で取得したリストタグ付きカテゴリーを力技でPRML形式のXMLに変換してるだけ。ソースを見てもらえば苦笑いできると思います。w

ここではシーン構造のみをPRMLとして出力してるけど、Progressionの各ページ内にWPから画像を読み込んだり、テキストを流し込む場合は個別にXMLを作ったほうが管理しやすいと思う。その場合は以前エントリーしたこの記事が役立つと思います。

今回参考にさせて頂いたのはMotuLogさんのエントリー。ありがとうございます!またflabakaさんは自分とは違ったアプローチでWordPressやMovableTypeとProgressionを連携させる方法をエントリーされてます。

■参考サイト
・MotuLogさん Progression(3.1.52) 動的にシーンを作成する
・flabakaさん ProgressionとWordPressの連携

2009.05.14
category
comments

PHPで画像にシャープをかける

リサイズしてシャープなし

リサイズしてシャープあり

PHPで画像にシャープをかける方法。imageconvolution()関数がPHP5対応なので注意する事。中身は畳み込み配列を使って調整しているだけ。リサイズをプログラムに任せると、ゆるい画像になるのでシャープをかけることで引き締まる。サムネイルを作るときには一手間かけてやること。

sharp.php5

	$img = imagecreatefromjpeg("toco-toucan.jpg");
	$matrix = array(array(-1, -1, -1), array(-1, 16, -1), array(-1, -1, -1));
	imageconvolution($img, $matrix, 8, 0);
	imagejpeg($img, "toco-toucan_sharp.jpg", 100);

	//-----[メモリの解放]
	imagedestroy($img);
2008.12.10
category
tag
comments

PHPで動的生成されたXMLをjQueryで読み込むときの注意

WordPressとかのPHPから動的生成されたxmlをjQueryを使って読み込むときにワナがあるので注意が必要。Flashでは普通に読み込めたので気付かなかったけど、動的生成のxmlをjQueryで読み込むとエラーになる。調べたらPHPから出力されるxmlのヘッダーのContent-Typeをtext/xmlに設定していないことが原因だった。なのでヘッダーを書き換えてやるとうまく読み込めるようになる。

WordPressでxmlを出力する場合は最初にこれを入れること。

header('Content-Type: text/xml; charset='.get_option('blog_charset'), true);
2008.11.28
category
tag
comments

DBのテーブル作成時にauto_incrementとprimary keyを設定する

テーブル作る時にフィールドの設定を細かく行うSQL文の書き方。「auto_increment」は自動的に連番を振ってくれる。「primary key」はテーブル内で重複しないフィールドに設定する。

$sql = "CREATE TABLE tableName".
    "(".
    "key INT(8) auto_increment primary key,".
    "field1 VARCHAR(50),".
    "field2 VARCHAR(50),".
    ")";
mysql_query($sql);
2008.11.21
category
comments

WordPressでFlash用に複数のxmlを出力する方法

デフォルトのRSS配信とは別にページ機能を使ってxmlを出力する事が出来たのね。今まで気付かなかったのでメモ。

まずはページ用PHPの冒頭でTemplate Nameを設定する。今回の場合は「newsXML」とする。このPHPを、現在使用しているテーマフォルダにアップする。次にWordPressの管理メニューからページをクリック。タイトルを「newsXML」として、下の方にあるページテンプレートを「デフォルトテンプレート」から「newsXML」にする(page-newsXML.phpをアップしていないと、ページテンプレートというオプションが出てこないので注意)。公開ボタンを押下してから再度newsXMLの管理ページを開くと、タイトルの下にパーマリンクのURLが記載されるので、このアドレスにアクセスするとxmlが動的生成されるようになる。

Flashから必要な分だけWordPressでページを作っておいて、個別のxmlを吐き出せば管理が楽になりますよと。ちなみに下のPHPだとカテゴリーが「news」のエントリーの最新5件だけを出力する仕様にしてます。用途に合わせてquery_posts()の引数を変えてやればなんでも出せる。

page-newsXML.php

<?php header('Content-Type: text/xml; charset='.get_option('blog_charset'), true); ?>
<?php /*
Template Name: newsXML
*/ ?>
<?php echo '<?xml version="1.0" encoding="'.get_option('blog_charset').'"?'.'>'; ?>
<root>
<?php query_posts("posts_per_page=5&category_name='news'"); ?>
<?php if (have_posts()) : while (have_posts()) : the_post(); ?>
<item>
<pubdate><?php echo get_post_time('Y-m-d H:i:s', true); ?></pubdate>
<?php the_category_rss(); ?>
<description><![CDATA[<?php the_excerpt_rss(); ?>]]></description>
</item>
<?php endwhile; endif; ?>
</root>
2008.11.04
category
tag
comments

PHPでエクセルのデータを読み込む

まずはSpreadsheet_Excel_Readerというライブラリをダウンロードする。ちょっとだけ修正が必要なので、Excelフォルダ内のreader.phpを開き、31行目を次のようにする。

require_once 'Excel/oleread.inc';

あとはExcelフォルダと下記のPHPファイルと読み込ませたいエクセルファイルを同階層にアップして、アクセスするとセルの中身を取り出せる。

require_once "Excel/reader.php";

$excel = new Spreadsheet_Excel_Reader();
$excel->setUTFEncoder('mb');
$excel->setOutputEncoding('UTF-8');
$excel->read('hoge.xls');

// 指定セルのデータ出力
echo $excel->sheets[シート番号]["cells"][行番号][列番号]."\n";

// 最大行数
echo $excel->sheets[シート番号]["numRows"]."\n";

// 最大列数
echo $excel->sheets[シート番号]["numCols"]."\n";

シート番号は0から始まるみたい。

2008.05.16
category
tag
comments

Zip Libraryで複数ファイルを圧縮してみる

テキストファイルをzip圧縮してサーバーに保存してみる。今回はnochump.comのライブラリを使わせてもらう。ここからダウンロードしてクラスパスを通しておく。

流れとしてはまず、ファイル名を任意に決めてバイトストリームにUTF-8ストリングを書き込む。putNextEntry()でファイルにしたいエントリーをぶち込む。write()メソッドで実際のデータを書き込み、zipOut.closeEntry()で閉じる。複数のテキストファイルをまとめてzip圧縮するには、下のコードのように文字列を書き込んだ後にzipOut.closeEntry()をそれぞれに実行してやればよい。後はZipOutputクラスからbyteArrayを取り出してPHPに送信してやるだけ。送信の際はBase64エンコードをかける。

Main.as

package
{
	import flash.display.Sprite;
	import flash.events.ProgressEvent;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLLoader;
	import flash.net.URLVariables;
	import flash.utils.ByteArray;

	import mx.utils.Base64Encoder;
	import nochump.util.zip.ZipOutput;
	import nochump.util.zip.ZipEntry;

	public class Main extends Sprite
	{
		private var url:String = "generateZip.php5";

		//------------------------------
		//   コンストラクタ
		//------------------------------
		public function Main()
		{
			var fileName1:String = "helloworld1.txt";
			var fileName2:String = "helloworld2.txt";
			var fileData1:ByteArray = new ByteArray();
			var fileData2:ByteArray = new ByteArray();
			var ze1:ZipEntry = new ZipEntry(fileName1);
			var ze2:ZipEntry = new ZipEntry(fileName2);
			var zipOut:ZipOutput = new ZipOutput();

			//-----[ファイル1]
			fileData1.writeUTFBytes("ハローワールド1!");
			zipOut.putNextEntry(ze1);
			zipOut.write(fileData1);
			zipOut.closeEntry();

			//-----[ファイル2]
			fileData2.writeUTFBytes("ハローワールド2!");
			zipOut.putNextEntry(ze2);
			zipOut.write(fileData2);
			zipOut.closeEntry();

			zipOut.finish();

			var zipData:ByteArray = zipOut.byteArray;
			sendZip(zipData);
		}

		//------------------------------
		//   zipデータの送信
		//------------------------------
		private function sendZip(byteArr:ByteArray):void
		{
			//-----[Base64エンコード]
			var enc:Base64Encoder = new Base64Encoder();
			enc.encodeBytes(byteArr);

			//-----[送信変数のセット]
			var variables:URLVariables = new URLVariables();
			variables.data = String(enc.flush());
			variables.type = "zip";

			//-----[リクエストの発行]
			var urlRequest:URLRequest = new URLRequest();
			urlRequest.url = url;
			urlRequest.method = URLRequestMethod.POST;
			urlRequest.data = variables;

			//-----[送信]
			var urlLoader:URLLoader = new URLLoader();
			urlLoader.addEventListener(ProgressEvent.PROGRESS, onProress);
			urlLoader.addEventListener(Event.COMPLETE, onComplete);
			urlLoader.load(urlRequest);
		}

		//------------------------------
		//   読み込み途中
		//------------------------------
		private function onProress(e:ProgressEvent):void
		{
			trace(e.bytesLoaded + " / " + e.bytesTotal);
		}

		//------------------------------
		//   読み込み完了
		//------------------------------
		private function onComplete(e:Event):void
		{
			e.target.removeEventListener(ProgressEvent.PROGRESS, onProress);
			e.target.removeEventListener(Event.COMPLETE, onComplete);

			trace("complete");
		}
	}
}

PHPではbase64デコードで復元した後に受け取った拡張子でファイルを生成する。zipファイルはdataフォルダの中に出来上がる。解凍してみるとhelloworld1.txtとhelloworld2.txtの2ファイルが展開され、それぞれの文字列が書き込まれている。

generateZip.php5

$imgdata = base64_decode($_POST['data']);
$type = $_POST['type'];
$fileName = time()."-".round(rand(1,10000)).".".$type;
$fp = fopen("data/".$fileName, 'wb');
fwrite($fp, $imgdata);
fclose($fp);
print $fileName;

最近ちょっとずつバイナリが分かってきて弄れるようになってきた。バイナリ触ってると変に快感があるなー。

2008.05.15
category
comments

PHP経由でBASIC認証を通過する

FlashPlayerのセキュリティー制限でAuthorizationのヘッダーを作成することは禁止されている。swfからBASIC認証を通過するにはいつものごとくPHPとかでproxyするしかないのかな(オーサリング環境からはBASIC認証を通過できる)。以下のコードはswfから「url」、「id」、「pass」をPOST通信でPHPに渡し、BASIC認証を通過する。認証通過後はサーバーから受け取ったデータをechoで出力させてswfに返してやる。これでswfとBASIC認証の先にあるAPIとやり取りできるようになる。

Main.as

package
{
	import flash.display.Sprite;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod
	import flash.net.URLVariables;
	import flash.events.Event;
	import flash.text.TextField;

	public class Main extends Sprite
	{
		private var url:String = "http://***/basicproxy.php";
		private var id:String = "id";
		private var pass:String = "password";

		//------------------------------
		//   コンストラクタ
		//------------------------------
		public function Main()
		{
			//-----[送信するデータを格納]
			var variables:URLVariables = new URLVariables();
			variables.id = id;
			variables.pass = pass;

			//-----[リクエストの発行]
			var request:URLRequest = new URLRequest();
			request.url = url;
			request.method = URLRequestMethod.POST;
			request.data = variables;

			//-----[ローダーの設定]
			var loader:URLLoader = new URLLoader();
			loader.dataFormat = URLLoaderDataFormat.TEXT;
			loader.addEventListener(Event.COMPLETE, onComplete);
			loader.load(request);
		}

		//------------------------------
		//   読み込み完了
		//------------------------------
		private function onComplete(e:Event):void
		{
			var loader:URLLoader = e.target as URLLoader;
			loader.removeEventListener(Event.COMPLETE, onComplete);

			trace(loader.data);
		}
	}
}

basicproxy.php

$url = $_POST["url"];
$id = $_POST["id"];
$pass  = $_POST["pass"];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_USERPWD, "$id:$pass");
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
$data= curl_exec($ch);
curl_close($ch);

echo $data;

curlを使うと簡単に認証を通過できる。あとは受け取ったデータを出力するだけ。

page 1 / 212