2014年2月20日木曜日

JQuery File Upload ステップ・バイ・ステップ (JQuery+PHP編)

Demo(公式)
Github:Download
関連記事:使い方(当blog)

GitHubにある右下のDownload Zipからダウンロードして、ローカルホスト上に解凍。

jquery-ui.htmlをローカルホスト上で実行。

適当なjpgファイルをドラッグ・アンド・ドロップし、startを押します。



アップロード自体が成功しているか確認する

server/php/filesを確認します。

この中にファイルがアップロードされていれば、
アップロード自体は成功していることになります。

このアップロードフォルダは、どこで定義されているでしょうか?

それは、server/php/index.phpです。

では、filesフォルダではなく、files2アップロード先を変えてみましょう。

アップロード先を変える(optionsの使い方)

次のファイルが、ファイルのアップロードや取得をするための
サーバーサイドのコアファイルです。

server/php/UploadHandler.php


アップロード先は、このファイルの「options」で規定されています。

    function __construct($options = null, $initialize = true, $error_messages = null) {
        $this->options = array(
            'script_url' => $this->get_full_url().'/',
            'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/files/',
            'upload_url' => $this->get_full_url().'/files/',



ただ、このファイルを直接書き換えるのは、問題があります。

何故なら、バージョンアップに対応できなくなるからです。


そこで、このスクリプトではなく、前段階のindex.phpを書き換えます。

server/php/index.php

index.phpで次のようにオプションを設定します。

$options=array(
  'upload_dir' => 'files2/',
  'upload_url' => 'アップロードするurl/files2'
);

これでもいいですが、UploadHandler.phpに倣って、少し複雑にします。
こうすれば、アップロードURLも自動取得できます。


error_reporting(E_ALL | E_STRICT);
/*=================================
=            myOptions            =
=================================*/
class myOptions{
function get_server_var($id) {
return isset($_SERVER[$id]) ? $_SERVER[$id] : '';
}
function get_full_url() {
$https = !empty($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'on') === 0;
return
($https ? 'https://' : 'http://').
(!empty($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'].'@' : '').
(isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : ($_SERVER['SERVER_NAME'].
($https && $_SERVER['SERVER_PORT'] === 443 ||
$_SERVER['SERVER_PORT'] === 80 ? '' : ':'.$_SERVER['SERVER_PORT']))).
substr($_SERVER['SCRIPT_NAME'],0, strrpos($_SERVER['SCRIPT_NAME'], '/'));
}
function uploadDir(){
return dirname($this->get_server_var('SCRIPT_FILENAME'));
}
function uploadUrl(){
return $this->get_full_url();
}
}
$myOptions=new myOptions();
$upDirName='files2';
$upDir='/'.$upDirName.'/';

$options=array(
'upload_dir' => $myOptions->uploadDir().$upDir,
'upload_url' => $myOptions->uploadUrl().$upDir,
);
/*-----  End of myOptions  ------*/

require('UploadHandler.php');
$upload_handler = new UploadHandler($options);


これで、アップロード先がfiles2に変更されました。

日本語のファイルに対応させる

上記のようにオプションで多くの設定が変更できますが、
日本語ファイル名には対応していません。

ファイルのアップロードファイル名に日本語を利用することは、
あまり良いことではありません。
普通はデータベースを利用して、ファイル名と対応付ける、
エンコードをする、等をします。

ですが、今回は、使い方を学ぶため、単純にアップロードファイル名を日本語対応にします。


日本語のアップロードで問題が発生するのは、アップロード処理でしょうか、
それとも表示処理でしょうか。あるいは両方かもしれません。

試しに、実際にアップロードしたファイルとサムネイルの名前を、
windowsならエクスプローラー、macならファインダー上で変更してみます。

すると、正しく日本語で表示され、サムネイルも見ることが出来ました。

というわけで、アップロード処理に問題があったことが分かりました。

ソースを眺めていくと、455行目でbasenameが使われています。
これは現段階のPHPのバージョンにおいて、日本語処理に問題があります。
そこで、index.phpで次のようにbasenameを修正します。

require('UploadHandler.php');
から
$upload_handler = new UploadHandler($options);
までを、次のように変更します。

require('UploadHandler.php');

class myHandler extends UploadHandler{
    function my_basename($str) {
        $temp = substr(strrchr("/$str",'/'),1);
        return $temp;
    }
    protected function trim_file_name($file_path, $name, $size, $type, $error,
            $index, $content_range) {
        $name = trim($this->my_basename(stripslashes($name)), ".\x00..\x20");
        if (!$name) {
            $name = str_replace('.', '-', microtime(true));
        }
        if (strpos($name, '.') === false &&
                preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) {
            $name .= '.'.$matches[1];
        }
        if (function_exists('exif_imagetype')) {
            switch(@exif_imagetype($file_path)){
                case IMAGETYPE_JPEG:
                    $extensions = array('jpg', 'jpeg');
                    break;
                case IMAGETYPE_PNG:
                    $extensions = array('png');
                    break;
                case IMAGETYPE_GIF:
                    $extensions = array('gif');
                    break;
            }
            if (!empty($extensions)) {
                $parts = explode('.', $name);
                $extIndex = count($parts) - 1;
                $ext = strtolower(@$parts[$extIndex]);
                if (!in_array($ext, $extensions)) {
                    $parts[$extIndex] = $extensions[0];
                    $name = implode('.', $parts);
                }
            }
        }
        return $name;
    }
}
$upload_handler = new myHandler($options);

上記コードは、UploadHandlerを継承させ、
trim_file_nameメソッド内のbasename関数をmy_basenameで置換えています。

そして、

$upload_handler = new Handler($options);



$upload_handler = new myHandler($options);

として変更しています。

これでアップロードに関しては、日本語ファイルに対応できました。

自動アップロードする

デフォルトでは、手動アップロードですが、これを自動アップロードに変更します。
ルートディレクトリにあるjquery-ui.htmlを編集します。

</body>タグの前に、次のコードを追加します。

<script>
   $('#fileupload').fileupload({
        autoUpload: true
    });
</script>

これでファイルをドラッグ・アンド・ドロップしたら自動でアップロードされるようになりました。

他にどのような設定ができるか知るには、Options(公式:英語)を御覧ください。

アップロードしたものを先頭に追加する

さきほどの自動アップロードのコードに、prependFiles:trueを追記します。
<script>
   $('#fileupload').fileupload({
        autoUpload: true,
        prependFiles: true

    });
</script>

これでドラッグ・アンド・ドロップするとファイルが先頭に追加されます。
同じ名前のファイルを複数アップロードしてみてください。
連番ファイルとして追加されていきます。

ですが、ブラウザを更新してみてください。

並び順がファイル名順(1→9、a→Z、あ→ん)に変わってしまいました。

何故、アップロード直後と更新後の並び順が違うのでしょうか。

POSTとGETの処理の違い

アップロード直後は、UploadHandler.phpにおいて、

function post() が実行されます。

それに対して、ブラウザ更新後は、

function get()が実行されます。

これは、161行目のinitialize()メソッドで指定されています。

最終的にはfunction generate_response()メソッドで
json形式に変換され出力される点は同じですが、

post()メソッドは、そのメソッド処理において、
アップロードされたファイル名を直接、配列に入れて、returnします。
(具体的には、$files[]=...以降の部分です。)

一方、get()メソッドはfunction get_file_objects()メソッド内において、
scandir関数を使用し、アップロードされたファイル一覧を取得して、returnします。

そして最終的に、jquery.fileupload-ui.jsにおいて、
返されたjson形式データを、画面上に表示します。

ブラウザ更新後は、なぜ更新日付順にならないのか

返されたjson形式データには、ファイルの更新日時は含まれていません。
また、表示処理においても、更新日付処理はありません。
そのため、更新日付順に表示されません。

PHPスクリプトからどういうデータが返されているか見るには、
http://localhost/jQuery-File-Upload/server/php/index.php
にアクセスしてください。

あるいは、開発コンソールを利用します。
開発コンソールは、ブラウザがchromeの場合、
次のキーを押すと表示されます。

windowsならF12
Macならcommand+option+i


開発コンソールを開き、「Network」→「php/」の部分をクリック→「Preview」をクリックします。
そうすると、server/php/index.phpから返されるjson形式のデータを見ることが出来ます

ブラウザ更新後のファイルの並び順を変更する(実装)

更新日付順に並び替えるには、2つの方法があります。

1つは、返されるjson形式データに、ファイルの更新日時を追加して、
それを元に並び替える方法。

もう一つは、返されるjson形式データの並び自体をソートする方法です。

ここでは、2つ目の方法を取ることにします。
PHPには、filetimeという、ファイルの更新日付を取得する関数があります。

これを、UploadHandler.phpget_file_objectsメソッドに追加します。

先ほど追加したmyHandlerクラスに、次のメソッドを追加してください。
なお、my_scandirは、このサイトにあるコードを使用しました。

function my_scandir($dir) {
    $ignored = array('.', '..', '.svn', '.htaccess');

    $files = array();    
    foreach (scandir($dir) as $file) {
        if (in_array($file, $ignored)) continue;
        $files[$file] = filemtime($dir . '/' . $file);
    }

    arsort($files);
    $files = array_keys($files);

    return ($files) ? $files : false;
}
protected function get_file_objects($iteration_method = 'get_file_object') {
    $upload_dir = $this->get_upload_path();
    if (!is_dir($upload_dir)) {
        return array();
    }
    return array_values(array_filter(array_map(
        array($this, $iteration_method),
        $this->my_scandir($upload_dir)
        )));
}

これで、ファイル名に関係なく、新しく追加したものが上に表示されます。


アップロードしたファイルを削除する

通常、Deleteボタンを押して個別削除をしたり、一括削除をすることが出来ます。
ですが、405エラーで削除できない場合があります。

これは、サーバーが「DELETE」メソッドを処理できないためです。

そのようなサーバーでも削除できるようにするためには、
次のコードをOptionに追加します。

$options=array(
'upload_dir' => $myOptions->uploadDir().$upDir,
'upload_url' => $myOptions->uploadUrl().$upDir,
  'delete_type' => 'POST'
);

ですが、ブラウザーを更新すると、ファイルが削除できていません。
前出のbasenameの日本語バグのためです。

index.phpで、basenameをまとめて変更します。


class myHandler extends UploadHandler{
    function my_basename($str) {
    $temp = substr(strrchr("/$str",'/'),1);
        return $temp;
    }
        protected function get_version_param() {
        return isset($_GET['version']) ? $this->my_basename(stripslashes($_GET['version'])) : null;
    }

    protected function get_file_name_param() {
        $name = $this->get_singular_param_name();
        return isset($_GET[$name]) ? $this->my_basename(stripslashes($_GET[$name])) : null;
    }

    protected function get_file_names_params() {
        $params = isset($_GET[$this->options['param_name']]) ?
            $_GET[$this->options['param_name']] : array();
        foreach ($params as $key => $value) {
            $params[$key] = $this->my_basename(stripslashes($value));
        }
        return $params;
    }
    protected function trim_file_name($file_path, $name, $size, $type, $error,
        $index, $content_range) {
        $name = trim($this->my_basename(stripslashes($name)), ".\x00..\x20");
        if (!$name) {
            $name = str_replace('.', '-', microtime(true));
        }
        if (strpos($name, '.') === false &&
            preg_match('/^image\/(gif|jpe?g|png)/', $type, $matches)) {
            $name .= '.'.$matches[1];
        }
        if (function_exists('exif_imagetype')) {
            switch(@exif_imagetype($file_path)){
                case IMAGETYPE_JPEG:
                $extensions = array('jpg', 'jpeg');
                break;
                case IMAGETYPE_PNG:
                $extensions = array('png');
                break;
                case IMAGETYPE_GIF:
                $extensions = array('gif');
                break;
            }
        if (!empty($extensions)) {
            $parts = explode('.', $name);
            $extIndex = count($parts) - 1;
            $ext = strtolower(@$parts[$extIndex]);
            if (!in_array($ext, $extensions)) {
                $parts[$extIndex] = $extensions[0];
                $name = implode('.', $parts);
            }
        }
    }
    return $name;
    }
    function my_scandir($dir) {
        $ignored = array('.', '..', '.svn', '.htaccess');

        $files = array();  
        foreach (scandir($dir) as $file) {
            if (in_array($file, $ignored)) continue;
            $files[$file] = filemtime($dir . '/' . $file);
        }

        arsort($files);
        $files = array_keys($files);

        return ($files) ? $files : false;
    }
    protected function get_file_objects($iteration_method = 'get_file_object') {
        $upload_dir = $this->get_upload_path();
        if (!is_dir($upload_dir)) {
            return array();
        }
        return array_values(array_filter(array_map(
            array($this, $iteration_method),
            $this->my_scandir($upload_dir)
            )));
    }
}

これで、日本語ファイル名の削除もできるようになりました。


レイアウトを変更する


基本はテーブルレイアウトになっています。
まずはこれを、divに変えてみましょう。

trとtd、table、tbody、すべてをdivに変更します。

tableをdivに変更した次の行に、jqeuryによってデータが流し込まれます。
<!-- The template to display files available for download -->
<div role="presentation"><div class="files"></div></div>

ここを変更して、gridContainerというIDを付加します。
<!-- The table listing the files available for upload/download -->
<div role="presentation"  id="gridContainer"><div class="files"></div></div>

そして、次のコードに
<div class="template-download fade">

gridというクラスを追加します。
<div class="template-download fade grid">

次に、cssを追加します。
head内の適当な箇所に、次のコードを記入します。

<link rel="stylesheet" href="css/grid.css">
そして、grid.cssをcssフォルダに追加し、見た目を作成します。

grid.css

#gridContainer {
  margin: 0 auto 25px;
  position: relative;
  padding-bottom: 10px;
  overflow: hidden;
}

.grid {
  background: #fff;
  color:#000000;
  box-shadow: 0 1px 3px rgba(34, 25, 25, 0.4);
  float: left;
  font-size: 12px;
  margin: 8px;
  min-height: 100px;
  padding: 15px;
  width: 188px;
}
.grid h2 ,.grid h2 a{
  color: #fa3599;
  display: block;
  font-family: "Donegal One", serif;
  font-size: 17px;
  margin: 10px 0;
  padding: 0 0 5px;
}

テンプレート内のタイトルを変更する

拡張子を取り除き、ファイル名だけに変更してみます。
次の行をコメントアウトします
<!-- <a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" {%=file.thumbnailUrl?'data-gallery':''%}>{%=file.name%}</a>
-->

かわりに、次のコードを追加します。

{%=filetitle=file.name.replace(/.[^.]+$/, "")  %}
<a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" {%=file.thumbnailUrl?'data-gallery':''%}>{%=filetitle%}</a>

これで、「名前.jpg」から「名前」に置き換わりました。


続く(気が向いたら)



1 件のコメント:

  1. 始めまして。JQuery File Uploadの使い方について質問させて下さい。
    こちらの記事内に「日本語のファイルに対応させる」という物がありますが、そのままコピーしているのですが
    日本語ファイルが文字化けのままになります。
    自分でも色々見直しては見たものの、一向に解決しません。
    この記事に書かれているprotected function trim_file_name内の記述でどこか間違っている部分があるのでしょうか?

    返信削除