以前、JavaScriptで15パズル をつくりましたが、今回はユーザーが好きな画像をアップロードできるように15パズルを改良します。
VIDEO
画像をアップロードできるようにする
まず画像をアップロードできるようにしなければなりません。
HTML部分を示します。といってもここではアップロード用のフォームを表示させているだけです。
upload.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
< ! DOCTYPE html >
< html lang ="ja" >
< head >
< meta charset ="UTF-8" >
< title > 画像をアップロードする< /title >
< meta name ="viewport" content ="width=device-width, initial-scale=1.0" >
< /head >
< body >
< form action ="upload.php" enctype ="multipart/form-data" method ="post" >
< input name ="file_upload" type ="file" >
< input type ="submit" value ="アップロード" >
< /form >
< /body >
< /html >
アップロードボタンがクリックされたらupload.phpで処理がおこなわれます。
upload.php
<?php
$upload = './' . $_FILES [ 'file_upload' ] [ 'name' ] ;
//アップロードが正しく完了したかチェック
if ( move_uploaded_file ( $_FILES [ 'file_upload' ] [ 'tmp_name' ] , $upload ) )
echo 'アップロード完了' ;
else
echo 'アップロード失敗' ;
?>
PHPで画像ファイルかどうかを調べるには?
一応、これでアップロードはできるのですが、これだと問題があります。
それは画像以外のファイルでもアップロードできてしまうということです。画像ファイル以外のものをアップロードした場合(とくに有害スクリプト)は削除してしまいたいです。
exif_imagetype関数は危険かも?
exif_imagetype関数を使う方法がよく紹介されています。ただこの方法はあまりよくありません。
<?php
$upload = './' . $_FILES [ 'file_upload' ] [ 'name' ] ;
if ( move_uploaded_file ( $_FILES [ 'file_upload' ] [ 'tmp_name' ] , $upload ) ) {
if ( ! exif_imagetype ( $upload ) ) {
echo 'これは画像ファイルではありません' ;
if ( unlink ( $upload ) )
echo $upload . 'の削除に成功しました。' ;
else
echo $upload . 'の削除に失敗しました。' ;
}
}
else
echo 'アップロード失敗' ;
?>
なぜexif_imagetype関数を使う方法がよくないかというと、この関数は画像の先頭バイトを読んで そのサインを調べているだけだからです。
たとえばテキストファイルをつくって以下を書き込みます。そしてファイル名をGIF画像っぽい名前にしてアップロードしてみると画像ファイルではないのに削除されません。GIFファイルの先頭部分は「GIF89a」なのでここだけ見てGIF画像であると判断しているようです。もしここに有害な作用をするスクリプトが仕込まれていたらゾッとします。
dangerous.gif(悪意があるファイル)
imagecreatefromstring関数で解決
ではどうすればいいのか? imagecreatefromstring関数を使います。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$upload = './' . $_FILES [ 'file_upload' ] [ 'name' ] ;
//アップロードが正しく完了したかチェック
if ( move_uploaded_file ( $_FILES [ 'file_upload' ] [ 'tmp_name' ] , $upload ) ) {
// PHPエラーを非表示にする。画像ファイルではないものをアップロードしてしまうと警告が表示されてしまう
error_reporting ( 0 ) ;
if ( imagecreatefromstring ( file_get_contents ( $upload ) ) ! == false )
echo 'アップロード完了' ;
else {
// 悪意があるユーザーにエラーメッセージを見せる必要はあるのか??
echo 'これは画像ファイルではありません' ;
if ( unlink ( $upload ) )
echo $upload . 'の削除に成功しました。' ;
else
echo $upload . 'の削除に失敗しました。' ;
}
}
else
echo 'アップロード失敗' ;
?>
これだと上記の偽装したGIFファイルや壊れた画像ファイルをアップロードすると削除されます。
ユーザーが画像をアップロードできる15パズルをつくる
ではユーザーが画像をアップできるようにしてみましょう。
同じ名前でファイルをアップロードすると上書きされてしまうので、元ファイル名の先頭にアップロード日時を加えることで名前がバッティングすることを防ぎします。また15パズルの本体があるディレクトリに大量の画像ファイルを置きたくないのでimagesというディレクトリ内に配置します。
upload.php
<?php
// 元ファイル名の先頭にアップロード日時を加える
// 事前にimagesディレクトリを作成しておかないとエラーになるので注意
$upload = './images/' . date ( "Y-md-His" ) . "-" . $_FILES [ 'file_upload' ] [ 'name' ] ;
if ( move_uploaded_file ( $_FILES [ 'file_upload' ] [ 'tmp_name' ] , $upload ) ) {
// PHPエラーを非表示
if ( imagecreatefromstring ( file_get_contents ( $upload ) ) ! == false )
echo "アップロード完了" ;
else
unlink ( $upload ) ;
}
else
echo "アップロード失敗" ;
?>
JavaScript側でアップロードされているファイルを取得できるようにします。そのためにクライアントサイドからディレクトリ内のファイルパスを取得できるように以下のようなphpファイルを用意します。
get-files.php
< ? php
$ result = glob ( './images/*' ) ;
foreach ( $ result as $ value ) {
echo $ value . "\n" ;
}
/* phpの閉じタグがあると余計な改行が入るのでないほうがいいとかしれない */
JavaScript部分の変更箇所
15パズル本体の処理が書かれているindex.js(JavaScriptで15パズルをつくる を参照)の以下の部分を変更します。
ページが読み込まれたときにおこなう処理をまるっと変更します。
get-files.phpにアクセスするとファイルパスが改行区切りで出力されるので、これを改行で分解してファイルのパスを取得します。そしてグローバル変数に格納します。
let files = [ ] ;
window . onload = async ( ) => {
let response = await fetch ( './get-files.php' ) ;
let text = await response . text ( ) ;
let arr = text . split ( '\n' ) ;
for ( let i =0 ; i < arr . length ; i ++) {
if ( arr [ i ] == '' | | arr [ i ] == '\r' )
continue ;
files . push ( arr [ i ] ) ;
}
gameStart ( )
setVolume ( 0.05 ) ;
}
ゲームを開始する処理でランダムで画像を取得してpiecesの配列をクリアします。
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
async function gameStart ( ) {
let index = Math . floor ( Math . random ( ) * files . length ) ;
let image = new Image ( ) ;
let sourceImage = await new Promise ( resolve => {
image . width = colMax * pieceSize ;
image . height = rowMax * pieceSize ;
image . src = files [ index ] ;
image . onload =( ) => {
resolve ( image ) ;
}
} ) ;
can . width = colMax * pieceSize + 4 ;
can . height = rowMax * pieceSize + 4 ;
// 元になる画像がその都度変更されるのでピースもその都度生成し直す
pieces = [ ] ;
for ( let row = 0 ; row < rowMax ; row ++) {
for ( let col = 0 ; col < colMax ; col ++) {
let image = await createImage ( sourceImage , row , col , false ) ;
let outline = await createImage ( sourceImage , row , col , true ) ;
pieces . push ( new Piece ( image , outline , col , row ) ) ;
}
}
// ピースをシャッフルする。これ以降の処理は前回のものと同じ
while ( true ) {
if ( shuffle ( ) % 2 == 0 )
break ;
}
isGameCleared = false ;
drawAll ( ) ;
time = 0 ;
$ time . innerHTML = ` $ { time } 秒` ;
clearInterval ( timer ) ;
timer = setInterval ( ( ) => {
time ++;
$ time . style . color = '#fff' ;
$ time . innerHTML = ` $ { time } 秒` ;
} , 1000 ) ;
}
VIDEO
鳩でも分かるC#管理人からのお願い
できる仕事であれば請け負います。鳩でもわかるC#管理人はクラウドワークスに在宅ワーカーとして登録しています。お仕事の依頼もお待ちしております。
⇒ 仕事を依頼する
コメントについて
コメントで英語などの外国語でコメントをされる方がいますが、管理人は日本語以外はわからないので基本的に内容が理解できず、承認することもありません。それからへんな薬を売っているサイトやリンク先のサイトが存在しないというスパムコメントも多々あります。
Some people make comments in foreign languages such as English, but since the manager does not understand anything other than Japanese, he basically cannot understand the content and does not approve it. Please use Japanese when making comments.
そんななか日本語のコメントもいただけるようになりました。「○○という変数はどこで宣言されているのか?」「××というメソッドはどこにあるのか」「例外が発生する」「いっそのことソース丸ごとくれ」という質問ですが、管理人としては嬉しく思います。「自分が書いた記事は読まれているんだな」と。疑問点には可能な限り答えます。記事に問題があれば修正いたします。
そのうえでお願いがあります。「匿名」という味も素っ気もない名前ではなく、捨てハンでいいのでなにかハンドルネームをつくってほしいと思います。
管理人のモチベーションアップのために
よろしければご支援お願いします。
⇒ 管理人の物乞いリスト