├── .gitignore ├── README.md ├── composer.json ├── src ├── AliOss.php ├── Ftp.php ├── Local.php ├── MyHttp.php ├── MyHttpTrait.php ├── Qiniu.php └── StorageInterface.php └── test ├── ftp.php ├── jquery-file-upload ├── cors │ ├── postmessage.html │ └── result.html ├── css │ ├── demo-ie8.css │ ├── demo.css │ ├── jquery.fileupload-noscript.css │ ├── jquery.fileupload-ui-noscript.css │ ├── jquery.fileupload-ui.css │ ├── jquery.fileupload.css │ └── style.css ├── img │ ├── loading.gif │ └── progressbar.gif └── js │ ├── app.js │ ├── cors │ ├── jquery.postmessage-transport.js │ └── jquery.xdr-transport.js │ ├── jquery.fileupload-angular.js │ ├── jquery.fileupload-audio.js │ ├── jquery.fileupload-image.js │ ├── jquery.fileupload-jquery-ui.js │ ├── jquery.fileupload-process.js │ ├── jquery.fileupload-ui.js │ ├── jquery.fileupload-validate.js │ ├── jquery.fileupload-video.js │ ├── jquery.fileupload.js │ ├── jquery.iframe-transport.js │ ├── main.js │ └── vendor │ └── jquery.ui.widget.js ├── oss.php ├── qiniu-form.php └── qiniu.php /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | .sass-cache 4 | .idea 5 | /vendor 6 | /composer.lock 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 文件存储 2 | 3 | > 安装 4 | 5 | ``` 6 | composer require pfinal/storage 7 | ``` 8 | 9 | * 本地存储 Local 10 | 11 | * 阿里云 AliOss 12 | 13 | 请先 composer require aliyuncs/oss-sdk-php 14 | 15 | * 7牛存诸 Qiniu 16 | 17 | 请先 composer require qiniu/php-sdk:7.1.* 18 | 19 | * Ftp 20 | 21 | 请先 composer require league/flysystem 22 | 23 | 24 | > 提供接口 25 | 26 | ``` 27 | //上传文件 28 | public function put($key, $data); 29 | //获取url 30 | public function url($key, $rule = null); 31 | //重命名 32 | public function rename($key, $newKey); 33 | //删除 34 | public function delete($key); 35 | //获取错误消息 36 | public function error(); 37 | ``` 38 | 39 | > 使用示例 40 | 41 | ```php 42 | 'TOeV-fwwxsssf3s_45tCziKjRD9-bPyXUKjbuX7b', 49 | 'secretKey' => 'pbHrgwwwp_wpClxeeGrYKLNdEhLd02Jrew3t5h', 50 | 'bucketName' => 'test', 51 | 'baseUrl' => 'http://static.pfinal.cn/', 52 | 'separator' => '-', 53 | ); 54 | $qiniu = new \PFinal\Storage\Qiniu($config); 55 | $bool = $qiniu->put('test/1.jpg', file_get_contents('/Users/ethan/Pictures/1.jpg')); 56 | 57 | //原图url 58 | var_dump($qiniu->url('test/1.jpg')); 59 | 60 | //小图url 规则: "m" 61 | var_dump($qiniu->url('test/1.jpg', 'm')); 62 | 63 | 64 | //阿里云OSS 65 | 66 | $config = array( 67 | 'accessKey' => 'your key', 68 | 'secret' => 'your secret', 69 | 'endPoint' => 'oss-cn-shanghai.aliyuncs.com', 70 | 'bucket' => 'your bucket', 71 | ); 72 | $oss = new \PFinal\Storage\AliOss($config); 73 | 74 | $bool = $oss->put('test.jpg', file_get_contents('/Users/ethan/Pictures/1.jpg')); 75 | 76 | //原图url 77 | echo $oss->url('test.jpg'); 78 | 79 | //小图url 规则名称: "s" 80 | echo $oss->url('test.jpg', 's'); 81 | 82 | ``` 83 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pfinal/storage", 3 | "description": "storage", 4 | "license": "MIT", 5 | "homepage": "http://www.pfinal.cn", 6 | "support": { 7 | "source": "https://github.com/pfinal/storage" 8 | }, 9 | "authors": [ 10 | { 11 | "name": "Zou Yiliang" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.3" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "PFinal\\Storage\\": "src/" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/AliOss.php: -------------------------------------------------------------------------------- 1 | $item) { 28 | $this->$key = $item; 29 | } 30 | } 31 | 32 | /** 33 | * 上传文件 34 | * 35 | * @param string $key 36 | * @param mixed $data 数据 例如 $data = file_get_contents('/Users/ethan/Pictures/1.jpg'); 37 | * @return bool 38 | */ 39 | public function put($key, $data) 40 | { 41 | //初始化阿里oss 42 | $ossClient = new OssClient($this->accessKey, $this->secret, $this->endPoint); 43 | 44 | //上传 45 | try { 46 | $ossClient->putObject($this->bucket, $key, $data); 47 | return true; 48 | } catch (OssException $e) { 49 | $this->error = $e->getMessage(); 50 | return false; 51 | } 52 | } 53 | 54 | 55 | /** 56 | * 删除单个文件 57 | * 58 | * @param string $key 59 | * @return bool 60 | */ 61 | public function delete($key) 62 | { 63 | $ossClient = new OssClient($this->accessKey, $this->secret, $this->endPoint); 64 | 65 | try { 66 | $ossClient->deleteObject($this->bucket, $key); 67 | } catch (OssException $e) { 68 | $this->error = $e->getMessage(); 69 | return false; 70 | } 71 | 72 | return true; 73 | } 74 | 75 | /** 76 | * 返回文件外链 77 | * 78 | * @param $key 79 | * @param string|null $rule 处理规则,为null时原样返回 80 | * @return string 81 | */ 82 | public function url($key, $rule = null) 83 | { 84 | $append = ''; 85 | if ($rule != null) { 86 | //http://kushu-test.oss-cn-shanghai.aliyuncs.com/haha.jpg?x-oss-process=style/s 87 | $append = $this->separator . $rule; 88 | } 89 | 90 | //$scheme = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http'; 91 | if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { 92 | $scheme = strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']); 93 | } else { 94 | $scheme = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http'; 95 | } 96 | 97 | if ($this->cdn != null) { 98 | return $scheme . '://' . $this->cdn . '/' . $key . $append; 99 | } 100 | 101 | return $scheme . '://' . $this->bucket . '.' . $this->endPoint . '/' . $key . $append; 102 | } 103 | 104 | /** 105 | * 重命名文件 106 | * 107 | * @param $key 108 | * @param $newKey 109 | * @return bool 110 | */ 111 | public function rename($key, $newKey) 112 | { 113 | $ossClient = new OssClient($this->accessKey, $this->secret, $this->endPoint); 114 | 115 | try { 116 | 117 | //todo 没看到有重命名名接口,暂时先这样处理 118 | 119 | $ossClient->copyObject($this->bucket, $key, $this->bucket, $newKey); 120 | $ossClient->deleteObject($this->bucket, $key); 121 | } catch (OssException $e) { 122 | $this->error = $e->getMessage(); 123 | return false; 124 | } 125 | 126 | return true; 127 | } 128 | 129 | /** 130 | * 复制文件 131 | * 132 | * @param $key 133 | * @param $newKey 134 | * @return bool 135 | */ 136 | public function copy($key, $newKey) 137 | { 138 | $ossClient = new OssClient($this->accessKey, $this->secret, $this->endPoint); 139 | 140 | try { 141 | $ossClient->copyObject($this->bucket, $key, $this->bucket, $newKey); 142 | } catch (OssException $e) { 143 | $this->error = $e->getMessage(); 144 | return false; 145 | } 146 | 147 | return true; 148 | } 149 | 150 | 151 | /** 152 | * 错误消息 153 | * 154 | * @return string 155 | */ 156 | public function error() 157 | { 158 | return $this->error; 159 | } 160 | 161 | 162 | // $callbackUrl测试时,未收到aliyun的回调 163 | // https://help.aliyun.com/document_detail/31988.html 164 | public function getClientToken($dir = 'uploads/', $callbackUrl = '') 165 | { 166 | $id = $this->accessKey; 167 | $key = $this->secret; 168 | // $host的格式为 bucketname.endpoint,请替换为您的真实信息。 169 | $host = '//' . $this->bucket . '.' . $this->endPoint;// 'http://bucket-name.oss-cn-hangzhou.aliyuncs.com'; 170 | // $callbackUrl为上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实URL信息。 171 | // $callbackUrl = 'http://88.88.88.88:8888/aliyun-oss-appserver-php/php/callback.php'; 172 | // $dir = 'user-dir-prefix/'; // 用户上传文件时指定的前缀。 173 | 174 | $callback_param = array('callbackUrl' => $callbackUrl, 175 | 'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}', 176 | 'callbackBodyType' => "application/x-www-form-urlencoded"); 177 | $callback_string = json_encode($callback_param); 178 | 179 | $base64_callback_body = base64_encode($callback_string); 180 | $now = time(); 181 | $expire = 30; //设置该policy超时时间是10s. 即这个policy过了这个有效时间,将不能访问。 182 | $end = $now + $expire; 183 | $expiration = self::gmt_iso8601($end); 184 | 185 | //最大文件大小.用户可以自己设置 186 | $condition = array(0 => 'content-length-range', 1 => 0, 2 => 1048576000); 187 | $conditions[] = $condition; 188 | 189 | // 表示用户上传的数据,必须是以$dir开始,不然上传会失败,这一步不是必须项,只是为了安全起见,防止用户通过policy上传到别人的目录。 190 | $start = array(0 => 'starts-with', 1 => '$key', 2 => $dir); 191 | $conditions[] = $start; 192 | 193 | 194 | $arr = array('expiration' => $expiration, 'conditions' => $conditions); 195 | $policy = json_encode($arr); 196 | $base64_policy = base64_encode($policy); 197 | $string_to_sign = $base64_policy; 198 | $signature = base64_encode(hash_hmac('sha1', $string_to_sign, $key, true)); 199 | 200 | $response = array(); 201 | $response['accessId'] = $id; 202 | $response['host'] = $host; 203 | $response['policy'] = $base64_policy; 204 | $response['signature'] = $signature; 205 | $response['expire'] = $end; 206 | $response['callback'] = $base64_callback_body; 207 | $response['dir'] = $dir; // 这个参数是设置用户上传文件时指定的前缀。 208 | return $response; 209 | } 210 | 211 | public function gmt_iso8601($time) 212 | { 213 | $dtStr = date("c", $time); 214 | $mydatetime = new \DateTime($dtStr); 215 | $expiration = $mydatetime->format(\DateTime::ISO8601); 216 | $pos = strpos($expiration, '+'); 217 | $expiration = substr($expiration, 0, $pos); 218 | return $expiration . "Z"; 219 | } 220 | 221 | } -------------------------------------------------------------------------------- /src/Ftp.php: -------------------------------------------------------------------------------- 1 | $item) { 23 | $this->$key = $item; 24 | } 25 | } 26 | 27 | /** 28 | * 上传 29 | * 30 | * @param string $key 31 | * @param $data 32 | * @return bool 33 | */ 34 | public function put($key, $data) 35 | { 36 | $config = array( 37 | 'host' => $this->host, 38 | 'username' => $this->username, 39 | 'password' => $this->password, 40 | 41 | 'port' => $this->port, 42 | 'passive' => $this->passive, 43 | 'timeout' => $this->timeout, 44 | ); 45 | $adapter = new BaseFtp($config); 46 | $fs = new Filesystem($adapter); 47 | 48 | $bool = @$fs->put($key, $data); 49 | return $bool; 50 | } 51 | 52 | /** 53 | * 返回文件外链 54 | * 55 | * @param $key 56 | * @param string|null $rule 处理规则,为null时原样返回 57 | * @return string 58 | */ 59 | public function url($key, $rule = null) 60 | { 61 | //todo 支持rule 62 | 63 | return $this->baseUrl . $key; 64 | } 65 | 66 | /** 67 | * 移动文件 68 | * 69 | * @param $key 70 | * @param $newKey 71 | * @return bool 72 | */ 73 | public function rename($key, $newKey) 74 | { 75 | $config = array( 76 | 'host' => $this->host, 77 | 'username' => $this->username, 78 | 'password' => $this->password, 79 | 80 | 'port' => $this->port, 81 | 'passive' => $this->passive, 82 | 'timeout' => $this->timeout, 83 | ); 84 | $adapter = new BaseFtp($config); 85 | $fs = new Filesystem($adapter); 86 | 87 | $bool = @$fs->rename($key, $newKey); 88 | return $bool; 89 | } 90 | 91 | /** 92 | * 复制文件 93 | * 94 | * @param $key 95 | * @param $newKey 96 | * @return bool 97 | */ 98 | public function copy($key, $newKey) 99 | { 100 | $config = array( 101 | 'host' => $this->host, 102 | 'username' => $this->username, 103 | 'password' => $this->password, 104 | 105 | 'port' => $this->port, 106 | 'passive' => $this->passive, 107 | 'timeout' => $this->timeout, 108 | ); 109 | $adapter = new BaseFtp($config); 110 | $fs = new Filesystem($adapter); 111 | 112 | return @$fs->copy($key, $newKey); 113 | } 114 | 115 | /** 116 | * 删除文件 117 | * 118 | * @param $key 119 | * @return bool 120 | */ 121 | public function delete($key) 122 | { 123 | $config = array( 124 | 'host' => $this->host, 125 | 'username' => $this->username, 126 | 'password' => $this->password, 127 | 128 | 'port' => $this->port, 129 | 'passive' => $this->passive, 130 | 'timeout' => $this->timeout, 131 | ); 132 | $adapter = new BaseFtp($config); 133 | $fs = new Filesystem($adapter); 134 | 135 | $bool = @$fs->delete($key); 136 | return $bool; 137 | } 138 | 139 | /** 140 | * 错误消息 141 | * 142 | * @return string 143 | */ 144 | public function error() 145 | { 146 | // TODO: Implement error() method. 147 | } 148 | } -------------------------------------------------------------------------------- /src/Local.php: -------------------------------------------------------------------------------- 1 | $item) { 13 | $this->$key = $item; 14 | } 15 | } 16 | 17 | /** 18 | * 保存文件 19 | * 20 | * @param string $key 21 | * @param $data 22 | * @return bool 23 | */ 24 | public function put($key, $data) 25 | { 26 | $filename = $this->getFullName($key); 27 | if (!file_exists(dirname($filename))) { 28 | mkdir(dirname($filename), 0777, true); 29 | } 30 | $size = file_put_contents($filename, $data, LOCK_EX); 31 | return $size !== false; 32 | } 33 | 34 | /** 35 | * 返回文件外链 36 | * @param $key 37 | * @param string|null $rule 38 | * @return string 39 | */ 40 | public function url($key, $rule = null) 41 | { 42 | if ($rule != null) { 43 | $key = $this->getThumbKey($key, $rule); 44 | } 45 | 46 | return $this->baseUrl . $key; 47 | } 48 | 49 | /** 50 | * 重命名 51 | * 52 | * @param $key 53 | * @param $newKey 54 | * @return bool 55 | */ 56 | public function rename($key, $newKey) 57 | { 58 | $newFile = $this->getFullName($newKey); 59 | if (!file_exists(dirname($newFile))) { 60 | mkdir(dirname($newFile), 0777, true); 61 | } 62 | 63 | return rename($this->getFullName($key), $newFile); 64 | } 65 | 66 | /** 67 | * 复制 68 | * 69 | * @param $key 70 | * @param $newKey 71 | * @return bool 72 | */ 73 | public function copy($key, $newKey) 74 | { 75 | $newFile = $this->getFullName($newKey); 76 | if (!file_exists(dirname($newFile))) { 77 | mkdir(dirname($newFile), 0777, true); 78 | } 79 | 80 | return copy($this->getFullName($key), $newFile); 81 | } 82 | 83 | /** 84 | * 完整文件名 85 | * @param $key 86 | * @return string 87 | */ 88 | protected function getFullName($key) 89 | { 90 | //todo 验证不能到basePath的上级 91 | 92 | return rtrim($this->basePath, '/\\') . DIRECTORY_SEPARATOR . $key; 93 | } 94 | 95 | /** 96 | * 删除文件 97 | * 98 | * @param $key 99 | * @return bool 100 | */ 101 | public function delete($key) 102 | { 103 | if (!file_exists($this->getFullName($key))) { 104 | return true; 105 | } 106 | return unlink($this->getFullName($key)); 107 | } 108 | 109 | /** 110 | * 错误消息 111 | * 112 | * @return string 113 | */ 114 | public function error() 115 | { 116 | // TODO: Implement error() method. 117 | } 118 | 119 | /** 120 | * @param string $key "uploads/201809/02/xxx.jpg" 121 | * @param string $rule "s" 122 | * @return string "uploads/thumb/s/201809/02/xxx.jpg" 123 | */ 124 | public function getThumbKey($key, $rule) 125 | { 126 | return preg_replace('/^uploads\//', 'uploads/thumb/' . $rule . '/', $key); 127 | } 128 | 129 | /** 130 | * 生成缩略图文件 131 | * @param string $key 最前面不要加斜线 示例: "uploads/201809/02/xxx.jpg" 132 | * @param string $rule 需要生成的规格 示例: "s" 133 | * @param array $allowRules 允许缩略图规格列表 134 | * @return \Leaf\Response|\Symfony\Component\HttpFoundation\StreamedResponse 135 | * 136 | * //访问时动态生成缩略图 137 | * //http://example.com/uploads/201703/07/2.jpg 138 | * //http://example.com/uploads/thumb/m/201703/07/2.jpg 139 | * Route::any('uploads/thumb/:rule/:month/:day/:file.:ext', function (\Leaf\Application $app, $month, $day, $rule, $file, $ext) { 140 | * $fullFileName = 'uploads/' . $month . '/' . $day . '/' . $file . '.' . $ext; 141 | * return $app['storage']->thumb($fullFileName, $rule); 142 | * }); 143 | */ 144 | public function thumb($key, $rule, $allowRules = null) 145 | { 146 | if (empty($allowRules)) { 147 | $allowRules = array( 148 | 's' => array('w' => 250, 'h' => 250, 'cut' => true), 149 | 'm' => array('w' => 400, 'h' => 400,), 150 | 'l' => array('w' => 800, 'h' => 800,), 151 | ); 152 | } 153 | 154 | if (!array_key_exists($rule, $allowRules)) { 155 | return new \Leaf\Response('404 Not Found', 404); 156 | } 157 | 158 | $fullFileName = $this->getFullName($key); 159 | 160 | $thumbnailKey = $this->getThumbKey($key, $rule); 161 | $newFileName = $this->getFullName($thumbnailKey); 162 | 163 | if (!file_exists($fullFileName)) { 164 | return new \Leaf\Response('404 Not Found', 404); 165 | } 166 | 167 | if (isset($allowRules[$rule]['cut']) && $allowRules[$rule]['cut']) { 168 | \Leaf\Image::thumbCut($fullFileName, $newFileName, $allowRules[$rule]['w'], $allowRules[$rule]['h']); 169 | } else { 170 | \Leaf\Image::resize($fullFileName, $newFileName, $allowRules[$rule]['w'], $allowRules[$rule]['h']); 171 | } 172 | 173 | $ext = ltrim(strtolower(strrchr($thumbnailKey, '.')), '.'); 174 | 175 | //$header = array('Content-type' => 'image/png'); 176 | $header = array('Content-type' => 'image/' . $ext); 177 | 178 | return new \Symfony\Component\HttpFoundation\StreamedResponse(function () use ($newFileName) { 179 | readfile($newFileName); 180 | }, 200, $header); 181 | } 182 | 183 | 184 | } -------------------------------------------------------------------------------- /src/MyHttp.php: -------------------------------------------------------------------------------- 1 | $item) { 26 | $this->$key = $item; 27 | } 28 | } 29 | 30 | public function appendSign($data) 31 | { 32 | $timestamp = (string)time(); 33 | $nonce = (string)rand(1000000, 9999999); 34 | $sign = md5($this->accessKey . $timestamp . $nonce . $this->secretKey); 35 | 36 | return array_merge($data, array( 37 | 'accessKey' => $this->accessKey, 38 | 'timestamp' => $timestamp, 39 | 'nonce' => $nonce, 40 | 'sign' => $sign, 41 | )); 42 | } 43 | 44 | /** 45 | * 上传文件 46 | * 47 | * @param string $key 48 | * @param $data 49 | * @return bool 50 | */ 51 | public function put($key, $data) 52 | { 53 | //$tempFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid(time(), true); 54 | $tempFile = tempnam(sys_get_temp_dir(), uniqid()); 55 | file_put_contents($tempFile, $data); 56 | 57 | $client = new Client(); 58 | $res = $client->file($this->api, 'file', $tempFile, $this->appendSign(array('filename' => $key))); 59 | 60 | unlink($tempFile); 61 | 62 | $this->error = $res->getBody(); 63 | 64 | if ($res->getStatusCode() == 200) { 65 | $arr = @json_decode($res->getBody(), true); 66 | return is_array($arr) && array_key_exists('status', $arr) && $arr['status']; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | /** 73 | * 返回文件外链 74 | * 75 | * @param $key 76 | * @param string|null $rule 处理规则,为null时原样返回 77 | * @return string 78 | */ 79 | public function url($key, $rule = null) 80 | { 81 | if ($rule) { 82 | $key = dirname($key) . '/' . $rule . '/' . basename($key); 83 | } 84 | return $this->baseUrl . $key; 85 | } 86 | 87 | /** 88 | * 移动文件 89 | * 90 | * @param $key 91 | * @param $newKey 92 | * @return bool 93 | */ 94 | public function rename($key, $newKey) 95 | { 96 | $client = new Client(); 97 | 98 | $res = $client->post($this->api, $this->appendSign(array( 99 | 'action' => 'move', 100 | 'key' => $key, 101 | 'newKey' => $newKey, 102 | ))); 103 | 104 | $this->error = $res->getBody(); 105 | 106 | if ($res->getStatusCode() == 200) { 107 | $arr = @json_decode($res->getBody(), true); 108 | return is_array($arr) && array_key_exists('status', $arr) && $arr['status']; 109 | } 110 | 111 | return false; 112 | } 113 | 114 | /** 115 | * 复制文件 116 | * 117 | * @param $key 118 | * @param $newKey 119 | * @return bool 120 | */ 121 | public function copy($key, $newKey) 122 | { 123 | $client = new Client(); 124 | 125 | $res = $client->post($this->api, $this->appendSign(array( 126 | 'action' => 'copy', 127 | 'key' => $key, 128 | 'newKey' => $newKey, 129 | ))); 130 | 131 | $this->error = $res->getBody(); 132 | 133 | if ($res->getStatusCode() == 200) { 134 | $arr = @json_decode($res->getBody(), true); 135 | return is_array($arr) && array_key_exists('status', $arr) && $arr['status']; 136 | } 137 | 138 | return false; 139 | 140 | } 141 | 142 | 143 | /** 144 | * 删除文件 145 | * 146 | * @param $key 147 | * @return bool 148 | */ 149 | public function delete($key) 150 | { 151 | $client = new Client(); 152 | 153 | $res = $client->post($this->api, $this->appendSign(array( 154 | 'action' => 'delete', 155 | 'key' => $key, 156 | ))); 157 | 158 | $this->error = $res->getBody(); 159 | 160 | if ($res->getStatusCode() == 200) { 161 | $arr = @json_decode($res->getBody(), true); 162 | return is_array($arr) && array_key_exists('status', $arr) && $arr['status']; 163 | } 164 | 165 | return false; 166 | } 167 | 168 | /** 169 | * 错误消息 170 | * 171 | * @return string 172 | */ 173 | public function error() 174 | { 175 | return $this->error; 176 | } 177 | 178 | } 179 | 180 | -------------------------------------------------------------------------------- /src/MyHttpTrait.php: -------------------------------------------------------------------------------- 1 | doUpload($request); 15 | //} 16 | 17 | trait MyHttpTrait 18 | { 19 | protected function getBaseDir() 20 | { 21 | return 'temp'; 22 | } 23 | 24 | //子类重写这个方法 25 | protected function checkSign(Request $request) 26 | { 27 | throw new \Exception('sign error'); 28 | // $info = [ 29 | // //$accessKey => $secretKey 30 | // '1001' => '289dff07669d7a23de0ef88d2f7129e7' 31 | // ]; 32 | // 33 | // $accessKey = $request->get('accessKey'); 34 | // $timestamp = $request->get('timestamp'); 35 | // $nonce = $request->get('nonce'); 36 | // 37 | // $sign = $request->get('sign'); 38 | // 39 | // if (!array_key_exists($accessKey, $info)) { 40 | // throw new \Exception('sign error'); 41 | // } 42 | // 43 | // //验证token 44 | // if ($sign !== $this->doSign($accessKey, $timestamp, $nonce, $info[$accessKey])) { 45 | // throw new \Exception('sign error'); 46 | // } 47 | } 48 | 49 | protected function doSign($accessKey, $timestamp, $nonce, $secretKey) 50 | { 51 | //验证token 52 | return md5($accessKey . $timestamp . $nonce . $secretKey); 53 | } 54 | 55 | public function doUpload(Request $request) 56 | { 57 | $this->checkSign($request); 58 | 59 | $action = $request->get('action', 'upload'); 60 | if ($action == 'upload') { 61 | return $this->_doUpload($request); 62 | } 63 | 64 | if ($action == 'move') { 65 | return $this->doMove($request); 66 | } 67 | 68 | if ($action == 'delete') { 69 | return $this->doDelete($request); 70 | } 71 | 72 | if ($action == 'copy') { 73 | return $this->doCopy($request); 74 | } 75 | 76 | } 77 | 78 | private function _doUpload(Request $request) 79 | { 80 | 81 | $filename = $request->get('filename'); 82 | $this->checkFilename($filename); 83 | 84 | //文件存在则报错 85 | $fullName = rtrim($this->getBaseDir(), '/\\') . DIRECTORY_SEPARATOR . $filename; 86 | if (file_exists($fullName)) { 87 | return Json::renderWithFalse('file exists'); 88 | } 89 | 90 | if (!file_exists(dirname($fullName))) { 91 | mkdir(dirname($fullName), 0777, true); 92 | } 93 | 94 | //判断是否在baseDir目录下 95 | if (stripos(realpath(dirname($fullName)), realpath($this->getBaseDir())) !== 0) { 96 | return Json::renderWithFalse('filename error'); 97 | } 98 | 99 | $temp = $_FILES['file']['tmp_name']; 100 | if (move_uploaded_file($temp, $fullName)) { 101 | return Json::renderWithTrue(); 102 | } else { 103 | return Json::renderWithFalse(); 104 | } 105 | } 106 | 107 | private function checkFilename($filename) 108 | { 109 | // 必须限定在baseDir目录内,不能出现 .. 110 | // ([\w\-]+\/)* 目录可有可无 (字母 数字 下划线 中杠) 111 | // [\w\-]+ 文件名必须有 112 | // (\.\w+)* 扩展名可有可无 113 | if (!preg_match('/^([\w\-]+\/)*[\w\-]+(\.\w+)*$/', $filename)) { 114 | throw new \Exception('filename error'); 115 | } 116 | } 117 | 118 | private function doMove(Request $request) 119 | { 120 | $key = $request->get('key'); 121 | $newFile = $request->get('newKey'); 122 | 123 | $this->checkFilename($key); 124 | $this->checkFilename($newFile); 125 | 126 | $fullNameOld = rtrim($this->getBaseDir(), '/\\') . DIRECTORY_SEPARATOR . $key; 127 | if (!file_exists($fullNameOld)) { 128 | return Json::renderWithFalse('file not exists'); 129 | } 130 | 131 | $fullNameNew = rtrim($this->getBaseDir(), '/\\') . DIRECTORY_SEPARATOR . $newFile; 132 | 133 | if (!file_exists(dirname($fullNameNew))) { 134 | mkdir(dirname($fullNameNew), 0777, true); 135 | } 136 | 137 | //判断是否在baseDir目录下 138 | if (stripos(realpath(dirname($fullNameNew)), realpath($this->getBaseDir())) !== 0) { 139 | return Json::renderWithFalse('filename error'); 140 | } 141 | 142 | if (rename($fullNameOld, $fullNameNew)) { 143 | return Json::renderWithTrue(); 144 | } 145 | return Json::renderWithFalse(); 146 | } 147 | 148 | private function doCopy(Request $request) 149 | { 150 | $key = $request->get('key'); 151 | $newFile = $request->get('newKey'); 152 | 153 | $this->checkFilename($key); 154 | $this->checkFilename($newFile); 155 | 156 | $fullNameOld = rtrim($this->getBaseDir(), '/\\') . DIRECTORY_SEPARATOR . $key; 157 | if (!file_exists($fullNameOld)) { 158 | return Json::renderWithFalse('file not exists'); 159 | } 160 | 161 | $fullNameNew = rtrim($this->getBaseDir(), '/\\') . DIRECTORY_SEPARATOR . $newFile; 162 | 163 | if (!file_exists(dirname($fullNameNew))) { 164 | mkdir(dirname($fullNameNew), 0777, true); 165 | } 166 | 167 | //判断是否在baseDir目录下 168 | if (stripos(realpath(dirname($fullNameNew)), realpath($this->getBaseDir())) !== 0) { 169 | return Json::renderWithFalse('filename error'); 170 | } 171 | 172 | if (copy($fullNameOld, $fullNameNew)) { 173 | return Json::renderWithTrue(); 174 | } 175 | return Json::renderWithFalse(); 176 | } 177 | 178 | private function doDelete(Request $request) 179 | { 180 | $key = $request->get('key'); 181 | $this->checkFilename($key); 182 | 183 | $fullName = rtrim($this->getBaseDir(), '/\\') . DIRECTORY_SEPARATOR . $key; 184 | 185 | if (!file_exists($fullName)) { 186 | return Json::renderWithFalse('file not exists'); 187 | } 188 | 189 | //判断是否在baseDir目录下 190 | if (stripos(realpath(dirname($fullName)), realpath($this->getBaseDir())) !== 0) { 191 | return Json::renderWithFalse('filename error'); 192 | } 193 | 194 | unlink($fullName); 195 | } 196 | } -------------------------------------------------------------------------------- /src/Qiniu.php: -------------------------------------------------------------------------------- 1 | $item) { 22 | $this->$key = $item; 23 | } 24 | } 25 | 26 | /** 27 | * 上传文件 28 | * 29 | * @param string $key 30 | * @param $data 31 | * @return bool 32 | */ 33 | public function put($key, $data) 34 | { 35 | $key = $this->basePath . $key; 36 | 37 | $upManager = new UploadManager(); 38 | $auth = new Auth($this->accessKey, $this->secretKey); 39 | $token = $auth->uploadToken($this->bucketName); 40 | list($ret, $error) = $upManager->put($token, $key, $data); 41 | 42 | //失败情况下ret为null 43 | if ($ret == null) { 44 | $this->error = $error->message(); 45 | return false; 46 | } else { 47 | //echo $ret['hash']; //FizFMFnR5n7w8DvaFDQ4__RRXnJV 48 | //echo $ret['key']; //test/bmw.jpeg 49 | return true; 50 | } 51 | } 52 | 53 | /** 54 | * 返回文件外链 55 | * 56 | * @param $key 57 | * @param string|null $rule 处理规则,为null时原样返回 58 | * @return string 59 | */ 60 | public function url($key, $rule = null) 61 | { 62 | $key = $this->basePath . $key; 63 | 64 | if ($rule !== null) { 65 | $key = $key . $this->separator . $rule; 66 | } 67 | return $this->baseUrl . $key; 68 | } 69 | 70 | /** 71 | * 移动文件 72 | * 73 | * @param $key 74 | * @param $newKey 75 | * @return bool 76 | */ 77 | public function rename($key, $newKey) 78 | { 79 | $key = $this->basePath . $key; 80 | $newKey = $this->basePath . $newKey; 81 | 82 | //初始化Auth状态: 83 | $auth = new Auth($this->accessKey, $this->secretKey); 84 | 85 | //初始化BucketManager 86 | $bucketMgr = new BucketManager($auth); 87 | 88 | //确保这个key在你空间中存在 89 | $bucket = $this->bucketName; 90 | 91 | //将文件从文件$key 改成文件名$newKey 可以在不同bucket移动 92 | $err = $bucketMgr->move($bucket, $key, $bucket, $newKey); 93 | 94 | if ($err !== null) { 95 | $this->error = $err->message(); 96 | return false; 97 | } else { 98 | return true; 99 | } 100 | } 101 | 102 | /** 103 | * 复制文件 104 | * 105 | * @param $key 106 | * @param $newKey 107 | * @return bool 108 | */ 109 | public function copy($key, $newKey) 110 | { 111 | $key = $this->basePath . $key; 112 | $newKey = $this->basePath . $newKey; 113 | 114 | //初始化Auth状态: 115 | $auth = new Auth($this->accessKey, $this->secretKey); 116 | 117 | //初始化BucketManager 118 | $bucketMgr = new BucketManager($auth); 119 | 120 | //确保这个key在你空间中存在 121 | $bucket = $this->bucketName; 122 | 123 | $err = $bucketMgr->copy($bucket, $key, $bucket, $newKey); 124 | 125 | if ($err !== null) { 126 | $this->error = $err->message(); 127 | return false; 128 | } else { 129 | return true; 130 | } 131 | } 132 | 133 | 134 | /** 135 | * 删除文件 136 | * 137 | * @param $key 138 | * @return bool 139 | */ 140 | public function delete($key) 141 | { 142 | $key = $this->basePath . $key; 143 | 144 | //初始化Auth状态: 145 | $auth = new Auth($this->accessKey, $this->secretKey); 146 | 147 | //初始化BucketManager 148 | $bucketMgr = new BucketManager($auth); 149 | 150 | //确保这个key在你空间中存在 151 | $bucket = $this->bucketName; 152 | 153 | $err = $bucketMgr->delete($bucket, $key); 154 | 155 | if ($err !== null) { 156 | $this->error = $err->message(); 157 | return false; 158 | } else { 159 | return true; 160 | } 161 | } 162 | 163 | /** 164 | * 错误消息 165 | * 166 | * @return string 167 | */ 168 | public function error() 169 | { 170 | return $this->error; 171 | } 172 | 173 | public function getClientToken($prefix = 'uploads/') 174 | { 175 | $key = $prefix . md5(uniqid('', true) . time() . rand(100000, 999999)); 176 | 177 | $key = $this->basePath . $key; 178 | 179 | $policy = array( 180 | 'returnBody' => 181 | json_encode(array( 182 | "key" => '$(key)', 183 | "name" => '$(fname)', 184 | "size" => '$(fsize)', 185 | "mimeType" => '$(mimeType)', 186 | "etag" => '$(etag)', 187 | "width" => '$(imageInfo.width)', 188 | "height" => '$(imageInfo.height)', 189 | )) 190 | ); 191 | 192 | //初始化Auth状态: 193 | $auth = new Auth($this->accessKey, $this->secretKey); 194 | $token = $auth->uploadToken($this->bucketName, $key, 3600, $policy); 195 | 196 | return array('key' => $key, 'token' => $token); 197 | } 198 | } -------------------------------------------------------------------------------- /src/StorageInterface.php: -------------------------------------------------------------------------------- 1 | 'www.test.com', 7 | 'username' => 'ftpuser', 8 | 'password' => '123456', 9 | 10 | 'port' => 21, 11 | 'passive' => true, 12 | 'timeout' => 30, 13 | 14 | 'baseUrl' => 'http://static.it266.com/', 15 | ]; 16 | 17 | $ftp = new \PFinal\Storage\Ftp($config); 18 | $bool = $ftp->put('test/abc.jpg', file_get_contents('/Users/ethan/Pictures/1.jpg')); 19 | var_dump($bool); 20 | var_dump($ftp->url('test/abc.jpg')); 21 | var_dump($ftp->url('test/abc.jpg', 'm')); 22 | -------------------------------------------------------------------------------- /test/jquery-file-upload/cors/postmessage.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | jQuery File Upload Plugin postMessage API 18 | 19 | 20 | 21 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /test/jquery-file-upload/cors/result.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | jQuery Iframe Transport Plugin Redirect Page 18 | 19 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/jquery-file-upload/css/demo-ie8.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Demo CSS Fixes for IE<9 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .navigation { 14 | list-style: none; 15 | padding: 0; 16 | margin: 1em 0; 17 | } 18 | .navigation li { 19 | display: inline; 20 | margin-right: 10px; 21 | } 22 | -------------------------------------------------------------------------------- /test/jquery-file-upload/css/demo.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Demo CSS 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | body { 14 | max-width: 750px; 15 | margin: 0 auto; 16 | padding: 1em; 17 | font-family: "Lucida Grande", "Lucida Sans Unicode", Arial, sans-serif; 18 | font-size: 1em; 19 | line-height: 1.4em; 20 | background: #222; 21 | color: #fff; 22 | -webkit-text-size-adjust: 100%; 23 | -ms-text-size-adjust: 100%; 24 | } 25 | a { 26 | color: orange; 27 | text-decoration: none; 28 | } 29 | img { 30 | border: 0; 31 | vertical-align: middle; 32 | } 33 | h1 { 34 | line-height: 1em; 35 | } 36 | blockquote { 37 | padding: 0 0 0 15px; 38 | margin: 0 0 20px; 39 | border-left: 5px solid #eee; 40 | } 41 | table { 42 | width: 100%; 43 | margin: 10px 0; 44 | } 45 | 46 | .fileupload-progress { 47 | margin: 10px 0; 48 | } 49 | .fileupload-progress .progress-extended { 50 | margin-top: 5px; 51 | } 52 | .error { 53 | color: red; 54 | } 55 | 56 | @media (min-width: 481px) { 57 | .navigation { 58 | list-style: none; 59 | padding: 0; 60 | } 61 | .navigation li { 62 | display: inline-block; 63 | } 64 | .navigation li:not(:first-child):before { 65 | content: "| "; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/jquery-file-upload/css/jquery.fileupload-noscript.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Plugin NoScript CSS 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileinput-button input { 14 | position: static; 15 | opacity: 1; 16 | filter: none; 17 | font-size: inherit; 18 | direction: inherit; 19 | } 20 | .fileinput-button span { 21 | display: none; 22 | } 23 | -------------------------------------------------------------------------------- /test/jquery-file-upload/css/jquery.fileupload-ui-noscript.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload UI Plugin NoScript CSS 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2012, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileinput-button i, 14 | .fileupload-buttonbar .delete, 15 | .fileupload-buttonbar .toggle { 16 | display: none; 17 | } 18 | -------------------------------------------------------------------------------- /test/jquery-file-upload/css/jquery.fileupload-ui.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload UI Plugin CSS 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2010, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileupload-buttonbar .btn, 14 | .fileupload-buttonbar .toggle { 15 | margin-bottom: 5px; 16 | } 17 | .progress-animated .progress-bar, 18 | .progress-animated .bar { 19 | background: url("../img/progressbar.gif") !important; 20 | filter: none; 21 | } 22 | .fileupload-process { 23 | float: right; 24 | display: none; 25 | } 26 | .fileupload-processing .fileupload-process, 27 | .files .processing .preview { 28 | display: block; 29 | width: 32px; 30 | height: 32px; 31 | background: url("../img/loading.gif") center no-repeat; 32 | background-size: contain; 33 | } 34 | .files audio, 35 | .files video { 36 | max-width: 300px; 37 | } 38 | 39 | @media (max-width: 767px) { 40 | .fileupload-buttonbar .toggle, 41 | .files .toggle, 42 | .files .btn span { 43 | display: none; 44 | } 45 | .files .name { 46 | width: 80px; 47 | word-wrap: break-word; 48 | } 49 | .files audio, 50 | .files video { 51 | max-width: 80px; 52 | } 53 | .files img, 54 | .files canvas { 55 | max-width: 100%; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/jquery-file-upload/css/jquery.fileupload.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Plugin CSS 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | .fileinput-button { 14 | position: relative; 15 | overflow: hidden; 16 | display: inline-block; 17 | } 18 | .fileinput-button input { 19 | position: absolute; 20 | top: 0; 21 | right: 0; 22 | margin: 0; 23 | opacity: 0; 24 | -ms-filter: 'alpha(opacity=0)'; 25 | font-size: 200px; 26 | direction: ltr; 27 | cursor: pointer; 28 | } 29 | 30 | /* Fixes for IE < 8 */ 31 | @media screen\9 { 32 | .fileinput-button input { 33 | filter: alpha(opacity=0); 34 | font-size: 100%; 35 | height: 100%; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/jquery-file-upload/css/style.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* 3 | * jQuery File Upload Plugin CSS Example 4 | * https://github.com/blueimp/jQuery-File-Upload 5 | * 6 | * Copyright 2013, Sebastian Tschan 7 | * https://blueimp.net 8 | * 9 | * Licensed under the MIT license: 10 | * http://www.opensource.org/licenses/MIT 11 | */ 12 | 13 | body { 14 | padding-top: 60px; 15 | } 16 | -------------------------------------------------------------------------------- /test/jquery-file-upload/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfinal/storage/bd5f25bc4551268bc1db641c0fef8669f9b8a190/test/jquery-file-upload/img/loading.gif -------------------------------------------------------------------------------- /test/jquery-file-upload/img/progressbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pfinal/storage/bd5f25bc4551268bc1db641c0fef8669f9b8a190/test/jquery-file-upload/img/progressbar.gif -------------------------------------------------------------------------------- /test/jquery-file-upload/js/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin Angular JS Example 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global window, angular */ 14 | 15 | (function () { 16 | 'use strict'; 17 | 18 | var isOnGitHub = window.location.hostname === 'blueimp.github.io', 19 | url = isOnGitHub ? '//jquery-file-upload.appspot.com/' : 'server/php/'; 20 | 21 | angular.module('demo', [ 22 | 'blueimp.fileupload' 23 | ]) 24 | .config([ 25 | '$httpProvider', 'fileUploadProvider', 26 | function ($httpProvider, fileUploadProvider) { 27 | delete $httpProvider.defaults.headers.common['X-Requested-With']; 28 | fileUploadProvider.defaults.redirect = window.location.href.replace( 29 | /\/[^\/]*$/, 30 | '/cors/result.html?%s' 31 | ); 32 | if (isOnGitHub) { 33 | // Demo settings: 34 | angular.extend(fileUploadProvider.defaults, { 35 | // Enable image resizing, except for Android and Opera, 36 | // which actually support image resizing, but fail to 37 | // send Blob objects via XHR requests: 38 | disableImageResize: /Android(?!.*Chrome)|Opera/ 39 | .test(window.navigator.userAgent), 40 | maxFileSize: 999000, 41 | acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i 42 | }); 43 | } 44 | } 45 | ]) 46 | 47 | .controller('DemoFileUploadController', [ 48 | '$scope', '$http', '$filter', '$window', 49 | function ($scope, $http) { 50 | $scope.options = { 51 | url: url 52 | }; 53 | if (!isOnGitHub) { 54 | $scope.loadingFiles = true; 55 | $http.get(url) 56 | .then( 57 | function (response) { 58 | $scope.loadingFiles = false; 59 | $scope.queue = response.data.files || []; 60 | }, 61 | function () { 62 | $scope.loadingFiles = false; 63 | } 64 | ); 65 | } 66 | } 67 | ]) 68 | 69 | .controller('FileDestroyController', [ 70 | '$scope', '$http', 71 | function ($scope, $http) { 72 | var file = $scope.file, 73 | state; 74 | if (file.url) { 75 | file.$state = function () { 76 | return state; 77 | }; 78 | file.$destroy = function () { 79 | state = 'pending'; 80 | return $http({ 81 | url: file.deleteUrl, 82 | method: file.deleteType 83 | }).then( 84 | function () { 85 | state = 'resolved'; 86 | $scope.clear(file); 87 | }, 88 | function () { 89 | state = 'rejected'; 90 | } 91 | ); 92 | }; 93 | } else if (!file.$cancel && !file._index) { 94 | file.$cancel = function () { 95 | $scope.clear(file); 96 | }; 97 | } 98 | } 99 | ]); 100 | 101 | }()); 102 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/cors/jquery.postmessage-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery postMessage Transport Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global define, require, window, document */ 13 | 14 | (function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | // Register as an anonymous AMD module: 18 | define(['jquery'], factory); 19 | } else if (typeof exports === 'object') { 20 | // Node/CommonJS: 21 | factory(require('jquery')); 22 | } else { 23 | // Browser globals: 24 | factory(window.jQuery); 25 | } 26 | }(function ($) { 27 | 'use strict'; 28 | 29 | var counter = 0, 30 | names = [ 31 | 'accepts', 32 | 'cache', 33 | 'contents', 34 | 'contentType', 35 | 'crossDomain', 36 | 'data', 37 | 'dataType', 38 | 'headers', 39 | 'ifModified', 40 | 'mimeType', 41 | 'password', 42 | 'processData', 43 | 'timeout', 44 | 'traditional', 45 | 'type', 46 | 'url', 47 | 'username' 48 | ], 49 | convert = function (p) { 50 | return p; 51 | }; 52 | 53 | $.ajaxSetup({ 54 | converters: { 55 | 'postmessage text': convert, 56 | 'postmessage json': convert, 57 | 'postmessage html': convert 58 | } 59 | }); 60 | 61 | $.ajaxTransport('postmessage', function (options) { 62 | if (options.postMessage && window.postMessage) { 63 | var iframe, 64 | loc = $('').prop('href', options.postMessage)[0], 65 | target = loc.protocol + '//' + loc.host, 66 | xhrUpload = options.xhr().upload; 67 | return { 68 | send: function (_, completeCallback) { 69 | counter += 1; 70 | var message = { 71 | id: 'postmessage-transport-' + counter 72 | }, 73 | eventName = 'message.' + message.id; 74 | iframe = $( 75 | '' 78 | ).bind('load', function () { 79 | $.each(names, function (i, name) { 80 | message[name] = options[name]; 81 | }); 82 | message.dataType = message.dataType.replace('postmessage ', ''); 83 | $(window).bind(eventName, function (e) { 84 | e = e.originalEvent; 85 | var data = e.data, 86 | ev; 87 | if (e.origin === target && data.id === message.id) { 88 | if (data.type === 'progress') { 89 | ev = document.createEvent('Event'); 90 | ev.initEvent(data.type, false, true); 91 | $.extend(ev, data); 92 | xhrUpload.dispatchEvent(ev); 93 | } else { 94 | completeCallback( 95 | data.status, 96 | data.statusText, 97 | {postmessage: data.result}, 98 | data.headers 99 | ); 100 | iframe.remove(); 101 | $(window).unbind(eventName); 102 | } 103 | } 104 | }); 105 | iframe[0].contentWindow.postMessage( 106 | message, 107 | target 108 | ); 109 | }).appendTo(document.body); 110 | }, 111 | abort: function () { 112 | if (iframe) { 113 | iframe.remove(); 114 | } 115 | } 116 | }; 117 | } 118 | }); 119 | 120 | })); 121 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/cors/jquery.xdr-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery XDomainRequest Transport Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | * 11 | * Based on Julian Aubourg's ajaxHooks xdr.js: 12 | * https://github.com/jaubourg/ajaxHooks/ 13 | */ 14 | 15 | /* global define, require, window, XDomainRequest */ 16 | 17 | (function (factory) { 18 | 'use strict'; 19 | if (typeof define === 'function' && define.amd) { 20 | // Register as an anonymous AMD module: 21 | define(['jquery'], factory); 22 | } else if (typeof exports === 'object') { 23 | // Node/CommonJS: 24 | factory(require('jquery')); 25 | } else { 26 | // Browser globals: 27 | factory(window.jQuery); 28 | } 29 | }(function ($) { 30 | 'use strict'; 31 | if (window.XDomainRequest && !$.support.cors) { 32 | $.ajaxTransport(function (s) { 33 | if (s.crossDomain && s.async) { 34 | if (s.timeout) { 35 | s.xdrTimeout = s.timeout; 36 | delete s.timeout; 37 | } 38 | var xdr; 39 | return { 40 | send: function (headers, completeCallback) { 41 | var addParamChar = /\?/.test(s.url) ? '&' : '?'; 42 | function callback(status, statusText, responses, responseHeaders) { 43 | xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; 44 | xdr = null; 45 | completeCallback(status, statusText, responses, responseHeaders); 46 | } 47 | xdr = new XDomainRequest(); 48 | // XDomainRequest only supports GET and POST: 49 | if (s.type === 'DELETE') { 50 | s.url = s.url + addParamChar + '_method=DELETE'; 51 | s.type = 'POST'; 52 | } else if (s.type === 'PUT') { 53 | s.url = s.url + addParamChar + '_method=PUT'; 54 | s.type = 'POST'; 55 | } else if (s.type === 'PATCH') { 56 | s.url = s.url + addParamChar + '_method=PATCH'; 57 | s.type = 'POST'; 58 | } 59 | xdr.open(s.type, s.url); 60 | xdr.onload = function () { 61 | callback( 62 | 200, 63 | 'OK', 64 | {text: xdr.responseText}, 65 | 'Content-Type: ' + xdr.contentType 66 | ); 67 | }; 68 | xdr.onerror = function () { 69 | callback(404, 'Not Found'); 70 | }; 71 | if (s.xdrTimeout) { 72 | xdr.ontimeout = function () { 73 | callback(0, 'timeout'); 74 | }; 75 | xdr.timeout = s.xdrTimeout; 76 | } 77 | xdr.send((s.hasContent && s.data) || null); 78 | }, 79 | abort: function () { 80 | if (xdr) { 81 | xdr.onerror = $.noop(); 82 | xdr.abort(); 83 | } 84 | } 85 | }; 86 | } 87 | }); 88 | } 89 | })); 90 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/jquery.fileupload-angular.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload AngularJS Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, angular */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'angular', 22 | './jquery.fileupload-image', 23 | './jquery.fileupload-audio', 24 | './jquery.fileupload-video', 25 | './jquery.fileupload-validate' 26 | ], factory); 27 | } else { 28 | factory(); 29 | } 30 | }(function () { 31 | 'use strict'; 32 | 33 | angular.module('blueimp.fileupload', []) 34 | 35 | // The fileUpload service provides configuration options 36 | // for the fileUpload directive and default handlers for 37 | // File Upload events: 38 | .provider('fileUpload', function () { 39 | var scopeEvalAsync = function (expression) { 40 | var scope = angular.element(this) 41 | .fileupload('option', 'scope'); 42 | // Schedule a new $digest cycle if not already inside of one 43 | // and evaluate the given expression: 44 | scope.$evalAsync(expression); 45 | }, 46 | addFileMethods = function (scope, data) { 47 | var files = data.files, 48 | file = files[0]; 49 | angular.forEach(files, function (file, index) { 50 | file._index = index; 51 | file.$state = function () { 52 | return data.state(); 53 | }; 54 | file.$processing = function () { 55 | return data.processing(); 56 | }; 57 | file.$progress = function () { 58 | return data.progress(); 59 | }; 60 | file.$response = function () { 61 | return data.response(); 62 | }; 63 | }); 64 | file.$submit = function () { 65 | if (!file.error) { 66 | return data.submit(); 67 | } 68 | }; 69 | file.$cancel = function () { 70 | return data.abort(); 71 | }; 72 | }, 73 | $config; 74 | $config = this.defaults = { 75 | handleResponse: function (e, data) { 76 | var files = data.result && data.result.files; 77 | if (files) { 78 | data.scope.replace(data.files, files); 79 | } else if (data.errorThrown || 80 | data.textStatus === 'error') { 81 | data.files[0].error = data.errorThrown || 82 | data.textStatus; 83 | } 84 | }, 85 | add: function (e, data) { 86 | if (e.isDefaultPrevented()) { 87 | return false; 88 | } 89 | var scope = data.scope, 90 | filesCopy = []; 91 | angular.forEach(data.files, function (file) { 92 | filesCopy.push(file); 93 | }); 94 | scope.$parent.$applyAsync(function () { 95 | addFileMethods(scope, data); 96 | var method = scope.option('prependFiles') ? 97 | 'unshift' : 'push'; 98 | Array.prototype[method].apply(scope.queue, data.files); 99 | }); 100 | data.process(function () { 101 | return scope.process(data); 102 | }).always(function () { 103 | scope.$parent.$applyAsync(function () { 104 | addFileMethods(scope, data); 105 | scope.replace(filesCopy, data.files); 106 | }); 107 | }).then(function () { 108 | if ((scope.option('autoUpload') || 109 | data.autoUpload) && 110 | data.autoUpload !== false) { 111 | data.submit(); 112 | } 113 | }); 114 | }, 115 | done: function (e, data) { 116 | if (e.isDefaultPrevented()) { 117 | return false; 118 | } 119 | var that = this; 120 | data.scope.$apply(function () { 121 | data.handleResponse.call(that, e, data); 122 | }); 123 | }, 124 | fail: function (e, data) { 125 | if (e.isDefaultPrevented()) { 126 | return false; 127 | } 128 | var that = this, 129 | scope = data.scope; 130 | if (data.errorThrown === 'abort') { 131 | scope.clear(data.files); 132 | return; 133 | } 134 | scope.$apply(function () { 135 | data.handleResponse.call(that, e, data); 136 | }); 137 | }, 138 | stop: scopeEvalAsync, 139 | processstart: scopeEvalAsync, 140 | processstop: scopeEvalAsync, 141 | getNumberOfFiles: function () { 142 | var scope = this.scope; 143 | return scope.queue.length - scope.processing(); 144 | }, 145 | dataType: 'json', 146 | autoUpload: false 147 | }; 148 | this.$get = [ 149 | function () { 150 | return { 151 | defaults: $config 152 | }; 153 | } 154 | ]; 155 | }) 156 | 157 | // Format byte numbers to readable presentations: 158 | .provider('formatFileSizeFilter', function () { 159 | var $config = { 160 | // Byte units following the IEC format 161 | // http://en.wikipedia.org/wiki/Kilobyte 162 | units: [ 163 | {size: 1000000000, suffix: ' GB'}, 164 | {size: 1000000, suffix: ' MB'}, 165 | {size: 1000, suffix: ' KB'} 166 | ] 167 | }; 168 | this.defaults = $config; 169 | this.$get = function () { 170 | return function (bytes) { 171 | if (!angular.isNumber(bytes)) { 172 | return ''; 173 | } 174 | var unit = true, 175 | i = 0, 176 | prefix, 177 | suffix; 178 | while (unit) { 179 | unit = $config.units[i]; 180 | prefix = unit.prefix || ''; 181 | suffix = unit.suffix || ''; 182 | if (i === $config.units.length - 1 || bytes >= unit.size) { 183 | return prefix + (bytes / unit.size).toFixed(2) + suffix; 184 | } 185 | i += 1; 186 | } 187 | }; 188 | }; 189 | }) 190 | 191 | // The FileUploadController initializes the fileupload widget and 192 | // provides scope methods to control the File Upload functionality: 193 | .controller('FileUploadController', [ 194 | '$scope', '$element', '$attrs', '$window', 'fileUpload', 195 | function ($scope, $element, $attrs, $window, fileUpload) { 196 | var uploadMethods = { 197 | progress: function () { 198 | return $element.fileupload('progress'); 199 | }, 200 | active: function () { 201 | return $element.fileupload('active'); 202 | }, 203 | option: function (option, data) { 204 | if (arguments.length === 1) { 205 | return $element.fileupload('option', option); 206 | } 207 | $element.fileupload('option', option, data); 208 | }, 209 | add: function (data) { 210 | return $element.fileupload('add', data); 211 | }, 212 | send: function (data) { 213 | return $element.fileupload('send', data); 214 | }, 215 | process: function (data) { 216 | return $element.fileupload('process', data); 217 | }, 218 | processing: function (data) { 219 | return $element.fileupload('processing', data); 220 | } 221 | }; 222 | $scope.disabled = !$window.jQuery.support.fileInput; 223 | $scope.queue = $scope.queue || []; 224 | $scope.clear = function (files) { 225 | var queue = this.queue, 226 | i = queue.length, 227 | file = files, 228 | length = 1; 229 | if (angular.isArray(files)) { 230 | file = files[0]; 231 | length = files.length; 232 | } 233 | while (i) { 234 | i -= 1; 235 | if (queue[i] === file) { 236 | return queue.splice(i, length); 237 | } 238 | } 239 | }; 240 | $scope.replace = function (oldFiles, newFiles) { 241 | var queue = this.queue, 242 | file = oldFiles[0], 243 | i, 244 | j; 245 | for (i = 0; i < queue.length; i += 1) { 246 | if (queue[i] === file) { 247 | for (j = 0; j < newFiles.length; j += 1) { 248 | queue[i + j] = newFiles[j]; 249 | } 250 | return; 251 | } 252 | } 253 | }; 254 | $scope.applyOnQueue = function (method) { 255 | var list = this.queue.slice(0), 256 | i, 257 | file; 258 | for (i = 0; i < list.length; i += 1) { 259 | file = list[i]; 260 | if (file[method]) { 261 | file[method](); 262 | } 263 | } 264 | }; 265 | $scope.submit = function () { 266 | this.applyOnQueue('$submit'); 267 | }; 268 | $scope.cancel = function () { 269 | this.applyOnQueue('$cancel'); 270 | }; 271 | // Add upload methods to the scope: 272 | angular.extend($scope, uploadMethods); 273 | // The fileupload widget will initialize with 274 | // the options provided via "data-"-parameters, 275 | // as well as those given via options object: 276 | $element.fileupload(angular.extend( 277 | {scope: $scope}, 278 | fileUpload.defaults 279 | )).on('fileuploadadd', function (e, data) { 280 | data.scope = $scope; 281 | }).on('fileuploadfail', function (e, data) { 282 | if (data.errorThrown === 'abort') { 283 | return; 284 | } 285 | if (data.dataType && 286 | data.dataType.indexOf('json') === data.dataType.length - 4) { 287 | try { 288 | data.result = angular.fromJson(data.jqXHR.responseText); 289 | } catch (ignore) {} 290 | } 291 | }).on([ 292 | 'fileuploadadd', 293 | 'fileuploadsubmit', 294 | 'fileuploadsend', 295 | 'fileuploaddone', 296 | 'fileuploadfail', 297 | 'fileuploadalways', 298 | 'fileuploadprogress', 299 | 'fileuploadprogressall', 300 | 'fileuploadstart', 301 | 'fileuploadstop', 302 | 'fileuploadchange', 303 | 'fileuploadpaste', 304 | 'fileuploaddrop', 305 | 'fileuploaddragover', 306 | 'fileuploadchunksend', 307 | 'fileuploadchunkdone', 308 | 'fileuploadchunkfail', 309 | 'fileuploadchunkalways', 310 | 'fileuploadprocessstart', 311 | 'fileuploadprocess', 312 | 'fileuploadprocessdone', 313 | 'fileuploadprocessfail', 314 | 'fileuploadprocessalways', 315 | 'fileuploadprocessstop' 316 | ].join(' '), function (e, data) { 317 | $scope.$parent.$applyAsync(function () { 318 | if ($scope.$emit(e.type, data).defaultPrevented) { 319 | e.preventDefault(); 320 | } 321 | }); 322 | }).on('remove', function () { 323 | // Remove upload methods from the scope, 324 | // when the widget is removed: 325 | var method; 326 | for (method in uploadMethods) { 327 | if (uploadMethods.hasOwnProperty(method)) { 328 | delete $scope[method]; 329 | } 330 | } 331 | }); 332 | // Observe option changes: 333 | $scope.$watch( 334 | $attrs.fileUpload, 335 | function (newOptions) { 336 | if (newOptions) { 337 | $element.fileupload('option', newOptions); 338 | } 339 | } 340 | ); 341 | } 342 | ]) 343 | 344 | // Provide File Upload progress feedback: 345 | .controller('FileUploadProgressController', [ 346 | '$scope', '$attrs', '$parse', 347 | function ($scope, $attrs, $parse) { 348 | var fn = $parse($attrs.fileUploadProgress), 349 | update = function () { 350 | var progress = fn($scope); 351 | if (!progress || !progress.total) { 352 | return; 353 | } 354 | $scope.num = Math.floor( 355 | progress.loaded / progress.total * 100 356 | ); 357 | }; 358 | update(); 359 | $scope.$watch( 360 | $attrs.fileUploadProgress + '.loaded', 361 | function (newValue, oldValue) { 362 | if (newValue !== oldValue) { 363 | update(); 364 | } 365 | } 366 | ); 367 | } 368 | ]) 369 | 370 | // Display File Upload previews: 371 | .controller('FileUploadPreviewController', [ 372 | '$scope', '$element', '$attrs', 373 | function ($scope, $element, $attrs) { 374 | $scope.$watch( 375 | $attrs.fileUploadPreview + '.preview', 376 | function (preview) { 377 | $element.empty(); 378 | if (preview) { 379 | $element.append(preview); 380 | } 381 | } 382 | ); 383 | } 384 | ]) 385 | 386 | .directive('fileUpload', function () { 387 | return { 388 | controller: 'FileUploadController', 389 | scope: true 390 | }; 391 | }) 392 | 393 | .directive('fileUploadProgress', function () { 394 | return { 395 | controller: 'FileUploadProgressController', 396 | scope: true 397 | }; 398 | }) 399 | 400 | .directive('fileUploadPreview', function () { 401 | return { 402 | controller: 'FileUploadPreviewController' 403 | }; 404 | }) 405 | 406 | // Enhance the HTML5 download attribute to 407 | // allow drag&drop of files to the desktop: 408 | .directive('download', function () { 409 | return function (scope, elm) { 410 | elm.on('dragstart', function (e) { 411 | try { 412 | e.originalEvent.dataTransfer.setData( 413 | 'DownloadURL', 414 | [ 415 | 'application/octet-stream', 416 | elm.prop('download'), 417 | elm.prop('href') 418 | ].join(':') 419 | ); 420 | } catch (ignore) {} 421 | }); 422 | }; 423 | }); 424 | 425 | })); 426 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/jquery.fileupload-audio.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Audio Preview Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, require, window, document */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'load-image', 22 | './jquery.fileupload-process' 23 | ], factory); 24 | } else if (typeof exports === 'object') { 25 | // Node/CommonJS: 26 | factory( 27 | require('jquery'), 28 | require('load-image') 29 | ); 30 | } else { 31 | // Browser globals: 32 | factory( 33 | window.jQuery, 34 | window.loadImage 35 | ); 36 | } 37 | }(function ($, loadImage) { 38 | 'use strict'; 39 | 40 | // Prepend to the default processQueue: 41 | $.blueimp.fileupload.prototype.options.processQueue.unshift( 42 | { 43 | action: 'loadAudio', 44 | // Use the action as prefix for the "@" options: 45 | prefix: true, 46 | fileTypes: '@', 47 | maxFileSize: '@', 48 | disabled: '@disableAudioPreview' 49 | }, 50 | { 51 | action: 'setAudio', 52 | name: '@audioPreviewName', 53 | disabled: '@disableAudioPreview' 54 | } 55 | ); 56 | 57 | // The File Upload Audio Preview plugin extends the fileupload widget 58 | // with audio preview functionality: 59 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 60 | 61 | options: { 62 | // The regular expression for the types of audio files to load, 63 | // matched against the file type: 64 | loadAudioFileTypes: /^audio\/.*$/ 65 | }, 66 | 67 | _audioElement: document.createElement('audio'), 68 | 69 | processActions: { 70 | 71 | // Loads the audio file given via data.files and data.index 72 | // as audio element if the browser supports playing it. 73 | // Accepts the options fileTypes (regular expression) 74 | // and maxFileSize (integer) to limit the files to load: 75 | loadAudio: function (data, options) { 76 | if (options.disabled) { 77 | return data; 78 | } 79 | var file = data.files[data.index], 80 | url, 81 | audio; 82 | if (this._audioElement.canPlayType && 83 | this._audioElement.canPlayType(file.type) && 84 | ($.type(options.maxFileSize) !== 'number' || 85 | file.size <= options.maxFileSize) && 86 | (!options.fileTypes || 87 | options.fileTypes.test(file.type))) { 88 | url = loadImage.createObjectURL(file); 89 | if (url) { 90 | audio = this._audioElement.cloneNode(false); 91 | audio.src = url; 92 | audio.controls = true; 93 | data.audio = audio; 94 | return data; 95 | } 96 | } 97 | return data; 98 | }, 99 | 100 | // Sets the audio element as a property of the file object: 101 | setAudio: function (data, options) { 102 | if (data.audio && !options.disabled) { 103 | data.files[data.index][options.name || 'preview'] = data.audio; 104 | } 105 | return data; 106 | } 107 | 108 | } 109 | 110 | }); 111 | 112 | })); 113 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/jquery.fileupload-image.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Image Preview & Resize Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, require, window, Blob */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'load-image', 22 | 'load-image-meta', 23 | 'load-image-exif', 24 | 'load-image-ios', 25 | 'canvas-to-blob', 26 | './jquery.fileupload-process' 27 | ], factory); 28 | } else if (typeof exports === 'object') { 29 | // Node/CommonJS: 30 | factory( 31 | require('jquery'), 32 | require('load-image') 33 | ); 34 | } else { 35 | // Browser globals: 36 | factory( 37 | window.jQuery, 38 | window.loadImage 39 | ); 40 | } 41 | }(function ($, loadImage) { 42 | 'use strict'; 43 | 44 | // Prepend to the default processQueue: 45 | $.blueimp.fileupload.prototype.options.processQueue.unshift( 46 | { 47 | action: 'loadImageMetaData', 48 | disableImageHead: '@', 49 | disableExif: '@', 50 | disableExifThumbnail: '@', 51 | disableExifSub: '@', 52 | disableExifGps: '@', 53 | disabled: '@disableImageMetaDataLoad' 54 | }, 55 | { 56 | action: 'loadImage', 57 | // Use the action as prefix for the "@" options: 58 | prefix: true, 59 | fileTypes: '@', 60 | maxFileSize: '@', 61 | noRevoke: '@', 62 | disabled: '@disableImageLoad' 63 | }, 64 | { 65 | action: 'resizeImage', 66 | // Use "image" as prefix for the "@" options: 67 | prefix: 'image', 68 | maxWidth: '@', 69 | maxHeight: '@', 70 | minWidth: '@', 71 | minHeight: '@', 72 | crop: '@', 73 | orientation: '@', 74 | forceResize: '@', 75 | disabled: '@disableImageResize' 76 | }, 77 | { 78 | action: 'saveImage', 79 | quality: '@imageQuality', 80 | type: '@imageType', 81 | disabled: '@disableImageResize' 82 | }, 83 | { 84 | action: 'saveImageMetaData', 85 | disabled: '@disableImageMetaDataSave' 86 | }, 87 | { 88 | action: 'resizeImage', 89 | // Use "preview" as prefix for the "@" options: 90 | prefix: 'preview', 91 | maxWidth: '@', 92 | maxHeight: '@', 93 | minWidth: '@', 94 | minHeight: '@', 95 | crop: '@', 96 | orientation: '@', 97 | thumbnail: '@', 98 | canvas: '@', 99 | disabled: '@disableImagePreview' 100 | }, 101 | { 102 | action: 'setImage', 103 | name: '@imagePreviewName', 104 | disabled: '@disableImagePreview' 105 | }, 106 | { 107 | action: 'deleteImageReferences', 108 | disabled: '@disableImageReferencesDeletion' 109 | } 110 | ); 111 | 112 | // The File Upload Resize plugin extends the fileupload widget 113 | // with image resize functionality: 114 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 115 | 116 | options: { 117 | // The regular expression for the types of images to load: 118 | // matched against the file type: 119 | loadImageFileTypes: /^image\/(gif|jpeg|png|svg\+xml)$/, 120 | // The maximum file size of images to load: 121 | loadImageMaxFileSize: 10000000, // 10MB 122 | // The maximum width of resized images: 123 | imageMaxWidth: 1920, 124 | // The maximum height of resized images: 125 | imageMaxHeight: 1080, 126 | // Defines the image orientation (1-8) or takes the orientation 127 | // value from Exif data if set to true: 128 | imageOrientation: false, 129 | // Define if resized images should be cropped or only scaled: 130 | imageCrop: false, 131 | // Disable the resize image functionality by default: 132 | disableImageResize: true, 133 | // The maximum width of the preview images: 134 | previewMaxWidth: 80, 135 | // The maximum height of the preview images: 136 | previewMaxHeight: 80, 137 | // Defines the preview orientation (1-8) or takes the orientation 138 | // value from Exif data if set to true: 139 | previewOrientation: true, 140 | // Create the preview using the Exif data thumbnail: 141 | previewThumbnail: true, 142 | // Define if preview images should be cropped or only scaled: 143 | previewCrop: false, 144 | // Define if preview images should be resized as canvas elements: 145 | previewCanvas: true 146 | }, 147 | 148 | processActions: { 149 | 150 | // Loads the image given via data.files and data.index 151 | // as img element, if the browser supports the File API. 152 | // Accepts the options fileTypes (regular expression) 153 | // and maxFileSize (integer) to limit the files to load: 154 | loadImage: function (data, options) { 155 | if (options.disabled) { 156 | return data; 157 | } 158 | var that = this, 159 | file = data.files[data.index], 160 | dfd = $.Deferred(); 161 | if (($.type(options.maxFileSize) === 'number' && 162 | file.size > options.maxFileSize) || 163 | (options.fileTypes && 164 | !options.fileTypes.test(file.type)) || 165 | !loadImage( 166 | file, 167 | function (img) { 168 | if (img.src) { 169 | data.img = img; 170 | } 171 | dfd.resolveWith(that, [data]); 172 | }, 173 | options 174 | )) { 175 | return data; 176 | } 177 | return dfd.promise(); 178 | }, 179 | 180 | // Resizes the image given as data.canvas or data.img 181 | // and updates data.canvas or data.img with the resized image. 182 | // Also stores the resized image as preview property. 183 | // Accepts the options maxWidth, maxHeight, minWidth, 184 | // minHeight, canvas and crop: 185 | resizeImage: function (data, options) { 186 | if (options.disabled || !(data.canvas || data.img)) { 187 | return data; 188 | } 189 | options = $.extend({canvas: true}, options); 190 | var that = this, 191 | dfd = $.Deferred(), 192 | img = (options.canvas && data.canvas) || data.img, 193 | resolve = function (newImg) { 194 | if (newImg && (newImg.width !== img.width || 195 | newImg.height !== img.height || 196 | options.forceResize)) { 197 | data[newImg.getContext ? 'canvas' : 'img'] = newImg; 198 | } 199 | data.preview = newImg; 200 | dfd.resolveWith(that, [data]); 201 | }, 202 | thumbnail; 203 | if (data.exif) { 204 | if (options.orientation === true) { 205 | options.orientation = data.exif.get('Orientation'); 206 | } 207 | if (options.thumbnail) { 208 | thumbnail = data.exif.get('Thumbnail'); 209 | if (thumbnail) { 210 | loadImage(thumbnail, resolve, options); 211 | return dfd.promise(); 212 | } 213 | } 214 | // Prevent orienting the same image twice: 215 | if (data.orientation) { 216 | delete options.orientation; 217 | } else { 218 | data.orientation = options.orientation; 219 | } 220 | } 221 | if (img) { 222 | resolve(loadImage.scale(img, options)); 223 | return dfd.promise(); 224 | } 225 | return data; 226 | }, 227 | 228 | // Saves the processed image given as data.canvas 229 | // inplace at data.index of data.files: 230 | saveImage: function (data, options) { 231 | if (!data.canvas || options.disabled) { 232 | return data; 233 | } 234 | var that = this, 235 | file = data.files[data.index], 236 | dfd = $.Deferred(); 237 | if (data.canvas.toBlob) { 238 | data.canvas.toBlob( 239 | function (blob) { 240 | if (!blob.name) { 241 | if (file.type === blob.type) { 242 | blob.name = file.name; 243 | } else if (file.name) { 244 | blob.name = file.name.replace( 245 | /\.\w+$/, 246 | '.' + blob.type.substr(6) 247 | ); 248 | } 249 | } 250 | // Don't restore invalid meta data: 251 | if (file.type !== blob.type) { 252 | delete data.imageHead; 253 | } 254 | // Store the created blob at the position 255 | // of the original file in the files list: 256 | data.files[data.index] = blob; 257 | dfd.resolveWith(that, [data]); 258 | }, 259 | options.type || file.type, 260 | options.quality 261 | ); 262 | } else { 263 | return data; 264 | } 265 | return dfd.promise(); 266 | }, 267 | 268 | loadImageMetaData: function (data, options) { 269 | if (options.disabled) { 270 | return data; 271 | } 272 | var that = this, 273 | dfd = $.Deferred(); 274 | loadImage.parseMetaData(data.files[data.index], function (result) { 275 | $.extend(data, result); 276 | dfd.resolveWith(that, [data]); 277 | }, options); 278 | return dfd.promise(); 279 | }, 280 | 281 | saveImageMetaData: function (data, options) { 282 | if (!(data.imageHead && data.canvas && 283 | data.canvas.toBlob && !options.disabled)) { 284 | return data; 285 | } 286 | var file = data.files[data.index], 287 | blob = new Blob([ 288 | data.imageHead, 289 | // Resized images always have a head size of 20 bytes, 290 | // including the JPEG marker and a minimal JFIF header: 291 | this._blobSlice.call(file, 20) 292 | ], {type: file.type}); 293 | blob.name = file.name; 294 | data.files[data.index] = blob; 295 | return data; 296 | }, 297 | 298 | // Sets the resized version of the image as a property of the 299 | // file object, must be called after "saveImage": 300 | setImage: function (data, options) { 301 | if (data.preview && !options.disabled) { 302 | data.files[data.index][options.name || 'preview'] = data.preview; 303 | } 304 | return data; 305 | }, 306 | 307 | deleteImageReferences: function (data, options) { 308 | if (!options.disabled) { 309 | delete data.img; 310 | delete data.canvas; 311 | delete data.preview; 312 | delete data.imageHead; 313 | } 314 | return data; 315 | } 316 | 317 | } 318 | 319 | }); 320 | 321 | })); 322 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/jquery.fileupload-jquery-ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload jQuery UI Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, require, window */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define(['jquery', './jquery.fileupload-ui'], factory); 20 | } else if (typeof exports === 'object') { 21 | // Node/CommonJS: 22 | factory(require('jquery')); 23 | } else { 24 | // Browser globals: 25 | factory(window.jQuery); 26 | } 27 | }(function ($) { 28 | 'use strict'; 29 | 30 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 31 | 32 | options: { 33 | processdone: function (e, data) { 34 | data.context.find('.start').button('enable'); 35 | }, 36 | progress: function (e, data) { 37 | if (data.context) { 38 | data.context.find('.progress').progressbar( 39 | 'option', 40 | 'value', 41 | parseInt(data.loaded / data.total * 100, 10) 42 | ); 43 | } 44 | }, 45 | progressall: function (e, data) { 46 | var $this = $(this); 47 | $this.find('.fileupload-progress') 48 | .find('.progress').progressbar( 49 | 'option', 50 | 'value', 51 | parseInt(data.loaded / data.total * 100, 10) 52 | ).end() 53 | .find('.progress-extended').each(function () { 54 | $(this).html( 55 | ($this.data('blueimp-fileupload') || 56 | $this.data('fileupload')) 57 | ._renderExtendedProgress(data) 58 | ); 59 | }); 60 | } 61 | }, 62 | 63 | _renderUpload: function (func, files) { 64 | var node = this._super(func, files), 65 | showIconText = $(window).width() > 480; 66 | node.find('.progress').empty().progressbar(); 67 | node.find('.start').button({ 68 | icons: {primary: 'ui-icon-circle-arrow-e'}, 69 | text: showIconText 70 | }); 71 | node.find('.cancel').button({ 72 | icons: {primary: 'ui-icon-cancel'}, 73 | text: showIconText 74 | }); 75 | if (node.hasClass('fade')) { 76 | node.hide(); 77 | } 78 | return node; 79 | }, 80 | 81 | _renderDownload: function (func, files) { 82 | var node = this._super(func, files), 83 | showIconText = $(window).width() > 480; 84 | node.find('.delete').button({ 85 | icons: {primary: 'ui-icon-trash'}, 86 | text: showIconText 87 | }); 88 | if (node.hasClass('fade')) { 89 | node.hide(); 90 | } 91 | return node; 92 | }, 93 | 94 | _startHandler: function (e) { 95 | $(e.currentTarget).button('disable'); 96 | this._super(e); 97 | }, 98 | 99 | _transition: function (node) { 100 | var deferred = $.Deferred(); 101 | if (node.hasClass('fade')) { 102 | node.fadeToggle( 103 | this.options.transitionDuration, 104 | this.options.transitionEasing, 105 | function () { 106 | deferred.resolveWith(node); 107 | } 108 | ); 109 | } else { 110 | deferred.resolveWith(node); 111 | } 112 | return deferred; 113 | }, 114 | 115 | _create: function () { 116 | this._super(); 117 | this.element 118 | .find('.fileupload-buttonbar') 119 | .find('.fileinput-button').each(function () { 120 | var input = $(this).find('input:file').detach(); 121 | $(this) 122 | .button({icons: {primary: 'ui-icon-plusthick'}}) 123 | .append(input); 124 | }) 125 | .end().find('.start') 126 | .button({icons: {primary: 'ui-icon-circle-arrow-e'}}) 127 | .end().find('.cancel') 128 | .button({icons: {primary: 'ui-icon-cancel'}}) 129 | .end().find('.delete') 130 | .button({icons: {primary: 'ui-icon-trash'}}) 131 | .end().find('.progress').progressbar(); 132 | }, 133 | 134 | _destroy: function () { 135 | this.element 136 | .find('.fileupload-buttonbar') 137 | .find('.fileinput-button').each(function () { 138 | var input = $(this).find('input:file').detach(); 139 | $(this) 140 | .button('destroy') 141 | .append(input); 142 | }) 143 | .end().find('.start') 144 | .button('destroy') 145 | .end().find('.cancel') 146 | .button('destroy') 147 | .end().find('.delete') 148 | .button('destroy') 149 | .end().find('.progress').progressbar('destroy'); 150 | this._super(); 151 | } 152 | 153 | }); 154 | 155 | })); 156 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/jquery.fileupload-process.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Processing Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2012, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, require, window */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | './jquery.fileupload' 22 | ], factory); 23 | } else if (typeof exports === 'object') { 24 | // Node/CommonJS: 25 | factory(require('jquery')); 26 | } else { 27 | // Browser globals: 28 | factory( 29 | window.jQuery 30 | ); 31 | } 32 | }(function ($) { 33 | 'use strict'; 34 | 35 | var originalAdd = $.blueimp.fileupload.prototype.options.add; 36 | 37 | // The File Upload Processing plugin extends the fileupload widget 38 | // with file processing functionality: 39 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 40 | 41 | options: { 42 | // The list of processing actions: 43 | processQueue: [ 44 | /* 45 | { 46 | action: 'log', 47 | type: 'debug' 48 | } 49 | */ 50 | ], 51 | add: function (e, data) { 52 | var $this = $(this); 53 | data.process(function () { 54 | return $this.fileupload('process', data); 55 | }); 56 | originalAdd.call(this, e, data); 57 | } 58 | }, 59 | 60 | processActions: { 61 | /* 62 | log: function (data, options) { 63 | console[options.type]( 64 | 'Processing "' + data.files[data.index].name + '"' 65 | ); 66 | } 67 | */ 68 | }, 69 | 70 | _processFile: function (data, originalData) { 71 | var that = this, 72 | dfd = $.Deferred().resolveWith(that, [data]), 73 | chain = dfd.promise(); 74 | this._trigger('process', null, data); 75 | $.each(data.processQueue, function (i, settings) { 76 | var func = function (data) { 77 | if (originalData.errorThrown) { 78 | return $.Deferred() 79 | .rejectWith(that, [originalData]).promise(); 80 | } 81 | return that.processActions[settings.action].call( 82 | that, 83 | data, 84 | settings 85 | ); 86 | }; 87 | chain = chain.pipe(func, settings.always && func); 88 | }); 89 | chain 90 | .done(function () { 91 | that._trigger('processdone', null, data); 92 | that._trigger('processalways', null, data); 93 | }) 94 | .fail(function () { 95 | that._trigger('processfail', null, data); 96 | that._trigger('processalways', null, data); 97 | }); 98 | return chain; 99 | }, 100 | 101 | // Replaces the settings of each processQueue item that 102 | // are strings starting with an "@", using the remaining 103 | // substring as key for the option map, 104 | // e.g. "@autoUpload" is replaced with options.autoUpload: 105 | _transformProcessQueue: function (options) { 106 | var processQueue = []; 107 | $.each(options.processQueue, function () { 108 | var settings = {}, 109 | action = this.action, 110 | prefix = this.prefix === true ? action : this.prefix; 111 | $.each(this, function (key, value) { 112 | if ($.type(value) === 'string' && 113 | value.charAt(0) === '@') { 114 | settings[key] = options[ 115 | value.slice(1) || (prefix ? prefix + 116 | key.charAt(0).toUpperCase() + key.slice(1) : key) 117 | ]; 118 | } else { 119 | settings[key] = value; 120 | } 121 | 122 | }); 123 | processQueue.push(settings); 124 | }); 125 | options.processQueue = processQueue; 126 | }, 127 | 128 | // Returns the number of files currently in the processsing queue: 129 | processing: function () { 130 | return this._processing; 131 | }, 132 | 133 | // Processes the files given as files property of the data parameter, 134 | // returns a Promise object that allows to bind callbacks: 135 | process: function (data) { 136 | var that = this, 137 | options = $.extend({}, this.options, data); 138 | if (options.processQueue && options.processQueue.length) { 139 | this._transformProcessQueue(options); 140 | if (this._processing === 0) { 141 | this._trigger('processstart'); 142 | } 143 | $.each(data.files, function (index) { 144 | var opts = index ? $.extend({}, options) : options, 145 | func = function () { 146 | if (data.errorThrown) { 147 | return $.Deferred() 148 | .rejectWith(that, [data]).promise(); 149 | } 150 | return that._processFile(opts, data); 151 | }; 152 | opts.index = index; 153 | that._processing += 1; 154 | that._processingQueue = that._processingQueue.pipe(func, func) 155 | .always(function () { 156 | that._processing -= 1; 157 | if (that._processing === 0) { 158 | that._trigger('processstop'); 159 | } 160 | }); 161 | }); 162 | } 163 | return this._processingQueue; 164 | }, 165 | 166 | _create: function () { 167 | this._super(); 168 | this._processing = 0; 169 | this._processingQueue = $.Deferred().resolveWith(this) 170 | .promise(); 171 | } 172 | 173 | }); 174 | 175 | })); 176 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/jquery.fileupload-ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload User Interface Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2010, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, require, window */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'tmpl', 22 | './jquery.fileupload-image', 23 | './jquery.fileupload-audio', 24 | './jquery.fileupload-video', 25 | './jquery.fileupload-validate' 26 | ], factory); 27 | } else if (typeof exports === 'object') { 28 | // Node/CommonJS: 29 | factory( 30 | require('jquery'), 31 | require('tmpl') 32 | ); 33 | } else { 34 | // Browser globals: 35 | factory( 36 | window.jQuery, 37 | window.tmpl 38 | ); 39 | } 40 | }(function ($, tmpl) { 41 | 'use strict'; 42 | 43 | $.blueimp.fileupload.prototype._specialOptions.push( 44 | 'filesContainer', 45 | 'uploadTemplateId', 46 | 'downloadTemplateId' 47 | ); 48 | 49 | // The UI version extends the file upload widget 50 | // and adds complete user interface interaction: 51 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 52 | 53 | options: { 54 | // By default, files added to the widget are uploaded as soon 55 | // as the user clicks on the start buttons. To enable automatic 56 | // uploads, set the following option to true: 57 | autoUpload: false, 58 | // The ID of the upload template: 59 | uploadTemplateId: 'template-upload', 60 | // The ID of the download template: 61 | downloadTemplateId: 'template-download', 62 | // The container for the list of files. If undefined, it is set to 63 | // an element with class "files" inside of the widget element: 64 | filesContainer: undefined, 65 | // By default, files are appended to the files container. 66 | // Set the following option to true, to prepend files instead: 67 | prependFiles: false, 68 | // The expected data type of the upload response, sets the dataType 69 | // option of the $.ajax upload requests: 70 | dataType: 'json', 71 | 72 | // Error and info messages: 73 | messages: { 74 | unknownError: 'Unknown error' 75 | }, 76 | 77 | // Function returning the current number of files, 78 | // used by the maxNumberOfFiles validation: 79 | getNumberOfFiles: function () { 80 | return this.filesContainer.children() 81 | .not('.processing').length; 82 | }, 83 | 84 | // Callback to retrieve the list of files from the server response: 85 | getFilesFromResponse: function (data) { 86 | if (data.result && $.isArray(data.result.files)) { 87 | return data.result.files; 88 | } 89 | return []; 90 | }, 91 | 92 | // The add callback is invoked as soon as files are added to the fileupload 93 | // widget (via file input selection, drag & drop or add API call). 94 | // See the basic file upload widget for more information: 95 | add: function (e, data) { 96 | if (e.isDefaultPrevented()) { 97 | return false; 98 | } 99 | var $this = $(this), 100 | that = $this.data('blueimp-fileupload') || 101 | $this.data('fileupload'), 102 | options = that.options; 103 | data.context = that._renderUpload(data.files) 104 | .data('data', data) 105 | .addClass('processing'); 106 | options.filesContainer[ 107 | options.prependFiles ? 'prepend' : 'append' 108 | ](data.context); 109 | that._forceReflow(data.context); 110 | that._transition(data.context); 111 | data.process(function () { 112 | return $this.fileupload('process', data); 113 | }).always(function () { 114 | data.context.each(function (index) { 115 | $(this).find('.size').text( 116 | that._formatFileSize(data.files[index].size) 117 | ); 118 | }).removeClass('processing'); 119 | that._renderPreviews(data); 120 | }).done(function () { 121 | data.context.find('.start').prop('disabled', false); 122 | if ((that._trigger('added', e, data) !== false) && 123 | (options.autoUpload || data.autoUpload) && 124 | data.autoUpload !== false) { 125 | data.submit(); 126 | } 127 | }).fail(function () { 128 | if (data.files.error) { 129 | data.context.each(function (index) { 130 | var error = data.files[index].error; 131 | if (error) { 132 | $(this).find('.error').text(error); 133 | } 134 | }); 135 | } 136 | }); 137 | }, 138 | // Callback for the start of each file upload request: 139 | send: function (e, data) { 140 | if (e.isDefaultPrevented()) { 141 | return false; 142 | } 143 | var that = $(this).data('blueimp-fileupload') || 144 | $(this).data('fileupload'); 145 | if (data.context && data.dataType && 146 | data.dataType.substr(0, 6) === 'iframe') { 147 | // Iframe Transport does not support progress events. 148 | // In lack of an indeterminate progress bar, we set 149 | // the progress to 100%, showing the full animated bar: 150 | data.context 151 | .find('.progress').addClass( 152 | !$.support.transition && 'progress-animated' 153 | ) 154 | .attr('aria-valuenow', 100) 155 | .children().first().css( 156 | 'width', 157 | '100%' 158 | ); 159 | } 160 | return that._trigger('sent', e, data); 161 | }, 162 | // Callback for successful uploads: 163 | done: function (e, data) { 164 | if (e.isDefaultPrevented()) { 165 | return false; 166 | } 167 | var that = $(this).data('blueimp-fileupload') || 168 | $(this).data('fileupload'), 169 | getFilesFromResponse = data.getFilesFromResponse || 170 | that.options.getFilesFromResponse, 171 | files = getFilesFromResponse(data), 172 | template, 173 | deferred; 174 | if (data.context) { 175 | data.context.each(function (index) { 176 | var file = files[index] || 177 | {error: 'Empty file upload result'}; 178 | deferred = that._addFinishedDeferreds(); 179 | that._transition($(this)).done( 180 | function () { 181 | var node = $(this); 182 | template = that._renderDownload([file]) 183 | .replaceAll(node); 184 | that._forceReflow(template); 185 | that._transition(template).done( 186 | function () { 187 | data.context = $(this); 188 | that._trigger('completed', e, data); 189 | that._trigger('finished', e, data); 190 | deferred.resolve(); 191 | } 192 | ); 193 | } 194 | ); 195 | }); 196 | } else { 197 | template = that._renderDownload(files)[ 198 | that.options.prependFiles ? 'prependTo' : 'appendTo' 199 | ](that.options.filesContainer); 200 | that._forceReflow(template); 201 | deferred = that._addFinishedDeferreds(); 202 | that._transition(template).done( 203 | function () { 204 | data.context = $(this); 205 | that._trigger('completed', e, data); 206 | that._trigger('finished', e, data); 207 | deferred.resolve(); 208 | } 209 | ); 210 | } 211 | }, 212 | // Callback for failed (abort or error) uploads: 213 | fail: function (e, data) { 214 | if (e.isDefaultPrevented()) { 215 | return false; 216 | } 217 | var that = $(this).data('blueimp-fileupload') || 218 | $(this).data('fileupload'), 219 | template, 220 | deferred; 221 | if (data.context) { 222 | data.context.each(function (index) { 223 | if (data.errorThrown !== 'abort') { 224 | var file = data.files[index]; 225 | file.error = file.error || data.errorThrown || 226 | data.i18n('unknownError'); 227 | deferred = that._addFinishedDeferreds(); 228 | that._transition($(this)).done( 229 | function () { 230 | var node = $(this); 231 | template = that._renderDownload([file]) 232 | .replaceAll(node); 233 | that._forceReflow(template); 234 | that._transition(template).done( 235 | function () { 236 | data.context = $(this); 237 | that._trigger('failed', e, data); 238 | that._trigger('finished', e, data); 239 | deferred.resolve(); 240 | } 241 | ); 242 | } 243 | ); 244 | } else { 245 | deferred = that._addFinishedDeferreds(); 246 | that._transition($(this)).done( 247 | function () { 248 | $(this).remove(); 249 | that._trigger('failed', e, data); 250 | that._trigger('finished', e, data); 251 | deferred.resolve(); 252 | } 253 | ); 254 | } 255 | }); 256 | } else if (data.errorThrown !== 'abort') { 257 | data.context = that._renderUpload(data.files)[ 258 | that.options.prependFiles ? 'prependTo' : 'appendTo' 259 | ](that.options.filesContainer) 260 | .data('data', data); 261 | that._forceReflow(data.context); 262 | deferred = that._addFinishedDeferreds(); 263 | that._transition(data.context).done( 264 | function () { 265 | data.context = $(this); 266 | that._trigger('failed', e, data); 267 | that._trigger('finished', e, data); 268 | deferred.resolve(); 269 | } 270 | ); 271 | } else { 272 | that._trigger('failed', e, data); 273 | that._trigger('finished', e, data); 274 | that._addFinishedDeferreds().resolve(); 275 | } 276 | }, 277 | // Callback for upload progress events: 278 | progress: function (e, data) { 279 | if (e.isDefaultPrevented()) { 280 | return false; 281 | } 282 | var progress = Math.floor(data.loaded / data.total * 100); 283 | if (data.context) { 284 | data.context.each(function () { 285 | $(this).find('.progress') 286 | .attr('aria-valuenow', progress) 287 | .children().first().css( 288 | 'width', 289 | progress + '%' 290 | ); 291 | }); 292 | } 293 | }, 294 | // Callback for global upload progress events: 295 | progressall: function (e, data) { 296 | if (e.isDefaultPrevented()) { 297 | return false; 298 | } 299 | var $this = $(this), 300 | progress = Math.floor(data.loaded / data.total * 100), 301 | globalProgressNode = $this.find('.fileupload-progress'), 302 | extendedProgressNode = globalProgressNode 303 | .find('.progress-extended'); 304 | if (extendedProgressNode.length) { 305 | extendedProgressNode.html( 306 | ($this.data('blueimp-fileupload') || $this.data('fileupload')) 307 | ._renderExtendedProgress(data) 308 | ); 309 | } 310 | globalProgressNode 311 | .find('.progress') 312 | .attr('aria-valuenow', progress) 313 | .children().first().css( 314 | 'width', 315 | progress + '%' 316 | ); 317 | }, 318 | // Callback for uploads start, equivalent to the global ajaxStart event: 319 | start: function (e) { 320 | if (e.isDefaultPrevented()) { 321 | return false; 322 | } 323 | var that = $(this).data('blueimp-fileupload') || 324 | $(this).data('fileupload'); 325 | that._resetFinishedDeferreds(); 326 | that._transition($(this).find('.fileupload-progress')).done( 327 | function () { 328 | that._trigger('started', e); 329 | } 330 | ); 331 | }, 332 | // Callback for uploads stop, equivalent to the global ajaxStop event: 333 | stop: function (e) { 334 | if (e.isDefaultPrevented()) { 335 | return false; 336 | } 337 | var that = $(this).data('blueimp-fileupload') || 338 | $(this).data('fileupload'), 339 | deferred = that._addFinishedDeferreds(); 340 | $.when.apply($, that._getFinishedDeferreds()) 341 | .done(function () { 342 | that._trigger('stopped', e); 343 | }); 344 | that._transition($(this).find('.fileupload-progress')).done( 345 | function () { 346 | $(this).find('.progress') 347 | .attr('aria-valuenow', '0') 348 | .children().first().css('width', '0%'); 349 | $(this).find('.progress-extended').html(' '); 350 | deferred.resolve(); 351 | } 352 | ); 353 | }, 354 | processstart: function (e) { 355 | if (e.isDefaultPrevented()) { 356 | return false; 357 | } 358 | $(this).addClass('fileupload-processing'); 359 | }, 360 | processstop: function (e) { 361 | if (e.isDefaultPrevented()) { 362 | return false; 363 | } 364 | $(this).removeClass('fileupload-processing'); 365 | }, 366 | // Callback for file deletion: 367 | destroy: function (e, data) { 368 | if (e.isDefaultPrevented()) { 369 | return false; 370 | } 371 | var that = $(this).data('blueimp-fileupload') || 372 | $(this).data('fileupload'), 373 | removeNode = function () { 374 | that._transition(data.context).done( 375 | function () { 376 | $(this).remove(); 377 | that._trigger('destroyed', e, data); 378 | } 379 | ); 380 | }; 381 | if (data.url) { 382 | data.dataType = data.dataType || that.options.dataType; 383 | $.ajax(data).done(removeNode).fail(function () { 384 | that._trigger('destroyfailed', e, data); 385 | }); 386 | } else { 387 | removeNode(); 388 | } 389 | } 390 | }, 391 | 392 | _resetFinishedDeferreds: function () { 393 | this._finishedUploads = []; 394 | }, 395 | 396 | _addFinishedDeferreds: function (deferred) { 397 | if (!deferred) { 398 | deferred = $.Deferred(); 399 | } 400 | this._finishedUploads.push(deferred); 401 | return deferred; 402 | }, 403 | 404 | _getFinishedDeferreds: function () { 405 | return this._finishedUploads; 406 | }, 407 | 408 | // Link handler, that allows to download files 409 | // by drag & drop of the links to the desktop: 410 | _enableDragToDesktop: function () { 411 | var link = $(this), 412 | url = link.prop('href'), 413 | name = link.prop('download'), 414 | type = 'application/octet-stream'; 415 | link.bind('dragstart', function (e) { 416 | try { 417 | e.originalEvent.dataTransfer.setData( 418 | 'DownloadURL', 419 | [type, name, url].join(':') 420 | ); 421 | } catch (ignore) {} 422 | }); 423 | }, 424 | 425 | _formatFileSize: function (bytes) { 426 | if (typeof bytes !== 'number') { 427 | return ''; 428 | } 429 | if (bytes >= 1000000000) { 430 | return (bytes / 1000000000).toFixed(2) + ' GB'; 431 | } 432 | if (bytes >= 1000000) { 433 | return (bytes / 1000000).toFixed(2) + ' MB'; 434 | } 435 | return (bytes / 1000).toFixed(2) + ' KB'; 436 | }, 437 | 438 | _formatBitrate: function (bits) { 439 | if (typeof bits !== 'number') { 440 | return ''; 441 | } 442 | if (bits >= 1000000000) { 443 | return (bits / 1000000000).toFixed(2) + ' Gbit/s'; 444 | } 445 | if (bits >= 1000000) { 446 | return (bits / 1000000).toFixed(2) + ' Mbit/s'; 447 | } 448 | if (bits >= 1000) { 449 | return (bits / 1000).toFixed(2) + ' kbit/s'; 450 | } 451 | return bits.toFixed(2) + ' bit/s'; 452 | }, 453 | 454 | _formatTime: function (seconds) { 455 | var date = new Date(seconds * 1000), 456 | days = Math.floor(seconds / 86400); 457 | days = days ? days + 'd ' : ''; 458 | return days + 459 | ('0' + date.getUTCHours()).slice(-2) + ':' + 460 | ('0' + date.getUTCMinutes()).slice(-2) + ':' + 461 | ('0' + date.getUTCSeconds()).slice(-2); 462 | }, 463 | 464 | _formatPercentage: function (floatValue) { 465 | return (floatValue * 100).toFixed(2) + ' %'; 466 | }, 467 | 468 | _renderExtendedProgress: function (data) { 469 | return this._formatBitrate(data.bitrate) + ' | ' + 470 | this._formatTime( 471 | (data.total - data.loaded) * 8 / data.bitrate 472 | ) + ' | ' + 473 | this._formatPercentage( 474 | data.loaded / data.total 475 | ) + ' | ' + 476 | this._formatFileSize(data.loaded) + ' / ' + 477 | this._formatFileSize(data.total); 478 | }, 479 | 480 | _renderTemplate: function (func, files) { 481 | if (!func) { 482 | return $(); 483 | } 484 | var result = func({ 485 | files: files, 486 | formatFileSize: this._formatFileSize, 487 | options: this.options 488 | }); 489 | if (result instanceof $) { 490 | return result; 491 | } 492 | return $(this.options.templatesContainer).html(result).children(); 493 | }, 494 | 495 | _renderPreviews: function (data) { 496 | data.context.find('.preview').each(function (index, elm) { 497 | $(elm).append(data.files[index].preview); 498 | }); 499 | }, 500 | 501 | _renderUpload: function (files) { 502 | return this._renderTemplate( 503 | this.options.uploadTemplate, 504 | files 505 | ); 506 | }, 507 | 508 | _renderDownload: function (files) { 509 | return this._renderTemplate( 510 | this.options.downloadTemplate, 511 | files 512 | ).find('a[download]').each(this._enableDragToDesktop).end(); 513 | }, 514 | 515 | _startHandler: function (e) { 516 | e.preventDefault(); 517 | var button = $(e.currentTarget), 518 | template = button.closest('.template-upload'), 519 | data = template.data('data'); 520 | button.prop('disabled', true); 521 | if (data && data.submit) { 522 | data.submit(); 523 | } 524 | }, 525 | 526 | _cancelHandler: function (e) { 527 | e.preventDefault(); 528 | var template = $(e.currentTarget) 529 | .closest('.template-upload,.template-download'), 530 | data = template.data('data') || {}; 531 | data.context = data.context || template; 532 | if (data.abort) { 533 | data.abort(); 534 | } else { 535 | data.errorThrown = 'abort'; 536 | this._trigger('fail', e, data); 537 | } 538 | }, 539 | 540 | _deleteHandler: function (e) { 541 | e.preventDefault(); 542 | var button = $(e.currentTarget); 543 | this._trigger('destroy', e, $.extend({ 544 | context: button.closest('.template-download'), 545 | type: 'DELETE' 546 | }, button.data())); 547 | }, 548 | 549 | _forceReflow: function (node) { 550 | return $.support.transition && node.length && 551 | node[0].offsetWidth; 552 | }, 553 | 554 | _transition: function (node) { 555 | var dfd = $.Deferred(); 556 | if ($.support.transition && node.hasClass('fade') && node.is(':visible')) { 557 | node.bind( 558 | $.support.transition.end, 559 | function (e) { 560 | // Make sure we don't respond to other transitions events 561 | // in the container element, e.g. from button elements: 562 | if (e.target === node[0]) { 563 | node.unbind($.support.transition.end); 564 | dfd.resolveWith(node); 565 | } 566 | } 567 | ).toggleClass('in'); 568 | } else { 569 | node.toggleClass('in'); 570 | dfd.resolveWith(node); 571 | } 572 | return dfd; 573 | }, 574 | 575 | _initButtonBarEventHandlers: function () { 576 | var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), 577 | filesList = this.options.filesContainer; 578 | this._on(fileUploadButtonBar.find('.start'), { 579 | click: function (e) { 580 | e.preventDefault(); 581 | filesList.find('.start').click(); 582 | } 583 | }); 584 | this._on(fileUploadButtonBar.find('.cancel'), { 585 | click: function (e) { 586 | e.preventDefault(); 587 | filesList.find('.cancel').click(); 588 | } 589 | }); 590 | this._on(fileUploadButtonBar.find('.delete'), { 591 | click: function (e) { 592 | e.preventDefault(); 593 | filesList.find('.toggle:checked') 594 | .closest('.template-download') 595 | .find('.delete').click(); 596 | fileUploadButtonBar.find('.toggle') 597 | .prop('checked', false); 598 | } 599 | }); 600 | this._on(fileUploadButtonBar.find('.toggle'), { 601 | change: function (e) { 602 | filesList.find('.toggle').prop( 603 | 'checked', 604 | $(e.currentTarget).is(':checked') 605 | ); 606 | } 607 | }); 608 | }, 609 | 610 | _destroyButtonBarEventHandlers: function () { 611 | this._off( 612 | this.element.find('.fileupload-buttonbar') 613 | .find('.start, .cancel, .delete'), 614 | 'click' 615 | ); 616 | this._off( 617 | this.element.find('.fileupload-buttonbar .toggle'), 618 | 'change.' 619 | ); 620 | }, 621 | 622 | _initEventHandlers: function () { 623 | this._super(); 624 | this._on(this.options.filesContainer, { 625 | 'click .start': this._startHandler, 626 | 'click .cancel': this._cancelHandler, 627 | 'click .delete': this._deleteHandler 628 | }); 629 | this._initButtonBarEventHandlers(); 630 | }, 631 | 632 | _destroyEventHandlers: function () { 633 | this._destroyButtonBarEventHandlers(); 634 | this._off(this.options.filesContainer, 'click'); 635 | this._super(); 636 | }, 637 | 638 | _enableFileInputButton: function () { 639 | this.element.find('.fileinput-button input') 640 | .prop('disabled', false) 641 | .parent().removeClass('disabled'); 642 | }, 643 | 644 | _disableFileInputButton: function () { 645 | this.element.find('.fileinput-button input') 646 | .prop('disabled', true) 647 | .parent().addClass('disabled'); 648 | }, 649 | 650 | _initTemplates: function () { 651 | var options = this.options; 652 | options.templatesContainer = this.document[0].createElement( 653 | options.filesContainer.prop('nodeName') 654 | ); 655 | if (tmpl) { 656 | if (options.uploadTemplateId) { 657 | options.uploadTemplate = tmpl(options.uploadTemplateId); 658 | } 659 | if (options.downloadTemplateId) { 660 | options.downloadTemplate = tmpl(options.downloadTemplateId); 661 | } 662 | } 663 | }, 664 | 665 | _initFilesContainer: function () { 666 | var options = this.options; 667 | if (options.filesContainer === undefined) { 668 | options.filesContainer = this.element.find('.files'); 669 | } else if (!(options.filesContainer instanceof $)) { 670 | options.filesContainer = $(options.filesContainer); 671 | } 672 | }, 673 | 674 | _initSpecialOptions: function () { 675 | this._super(); 676 | this._initFilesContainer(); 677 | this._initTemplates(); 678 | }, 679 | 680 | _create: function () { 681 | this._super(); 682 | this._resetFinishedDeferreds(); 683 | if (!$.support.fileInput) { 684 | this._disableFileInputButton(); 685 | } 686 | }, 687 | 688 | enable: function () { 689 | var wasDisabled = false; 690 | if (this.options.disabled) { 691 | wasDisabled = true; 692 | } 693 | this._super(); 694 | if (wasDisabled) { 695 | this.element.find('input, button').prop('disabled', false); 696 | this._enableFileInputButton(); 697 | } 698 | }, 699 | 700 | disable: function () { 701 | if (!this.options.disabled) { 702 | this.element.find('input, button').prop('disabled', true); 703 | this._disableFileInputButton(); 704 | } 705 | this._super(); 706 | } 707 | 708 | }); 709 | 710 | })); 711 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/jquery.fileupload-validate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Validation Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global define, require, window */ 13 | 14 | (function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | // Register as an anonymous AMD module: 18 | define([ 19 | 'jquery', 20 | './jquery.fileupload-process' 21 | ], factory); 22 | } else if (typeof exports === 'object') { 23 | // Node/CommonJS: 24 | factory(require('jquery')); 25 | } else { 26 | // Browser globals: 27 | factory( 28 | window.jQuery 29 | ); 30 | } 31 | }(function ($) { 32 | 'use strict'; 33 | 34 | // Append to the default processQueue: 35 | $.blueimp.fileupload.prototype.options.processQueue.push( 36 | { 37 | action: 'validate', 38 | // Always trigger this action, 39 | // even if the previous action was rejected: 40 | always: true, 41 | // Options taken from the global options map: 42 | acceptFileTypes: '@', 43 | maxFileSize: '@', 44 | minFileSize: '@', 45 | maxNumberOfFiles: '@', 46 | disabled: '@disableValidation' 47 | } 48 | ); 49 | 50 | // The File Upload Validation plugin extends the fileupload widget 51 | // with file validation functionality: 52 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 53 | 54 | options: { 55 | /* 56 | // The regular expression for allowed file types, matches 57 | // against either file type or file name: 58 | acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, 59 | // The maximum allowed file size in bytes: 60 | maxFileSize: 10000000, // 10 MB 61 | // The minimum allowed file size in bytes: 62 | minFileSize: undefined, // No minimal file size 63 | // The limit of files to be uploaded: 64 | maxNumberOfFiles: 10, 65 | */ 66 | 67 | // Function returning the current number of files, 68 | // has to be overriden for maxNumberOfFiles validation: 69 | getNumberOfFiles: $.noop, 70 | 71 | // Error and info messages: 72 | messages: { 73 | maxNumberOfFiles: 'Maximum number of files exceeded', 74 | acceptFileTypes: 'File type not allowed', 75 | maxFileSize: 'File is too large', 76 | minFileSize: 'File is too small' 77 | } 78 | }, 79 | 80 | processActions: { 81 | 82 | validate: function (data, options) { 83 | if (options.disabled) { 84 | return data; 85 | } 86 | var dfd = $.Deferred(), 87 | settings = this.options, 88 | file = data.files[data.index], 89 | fileSize; 90 | if (options.minFileSize || options.maxFileSize) { 91 | fileSize = file.size; 92 | } 93 | if ($.type(options.maxNumberOfFiles) === 'number' && 94 | (settings.getNumberOfFiles() || 0) + data.files.length > 95 | options.maxNumberOfFiles) { 96 | file.error = settings.i18n('maxNumberOfFiles'); 97 | } else if (options.acceptFileTypes && 98 | !(options.acceptFileTypes.test(file.type) || 99 | options.acceptFileTypes.test(file.name))) { 100 | file.error = settings.i18n('acceptFileTypes'); 101 | } else if (fileSize > options.maxFileSize) { 102 | file.error = settings.i18n('maxFileSize'); 103 | } else if ($.type(fileSize) === 'number' && 104 | fileSize < options.minFileSize) { 105 | file.error = settings.i18n('minFileSize'); 106 | } else { 107 | delete file.error; 108 | } 109 | if (file.error || data.files.error) { 110 | data.files.error = true; 111 | dfd.rejectWith(this, [data]); 112 | } else { 113 | dfd.resolveWith(this, [data]); 114 | } 115 | return dfd.promise(); 116 | } 117 | 118 | } 119 | 120 | }); 121 | 122 | })); 123 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/jquery.fileupload-video.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Video Preview Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* jshint nomen:false */ 13 | /* global define, require, window, document */ 14 | 15 | (function (factory) { 16 | 'use strict'; 17 | if (typeof define === 'function' && define.amd) { 18 | // Register as an anonymous AMD module: 19 | define([ 20 | 'jquery', 21 | 'load-image', 22 | './jquery.fileupload-process' 23 | ], factory); 24 | } else if (typeof exports === 'object') { 25 | // Node/CommonJS: 26 | factory( 27 | require('jquery'), 28 | require('load-image') 29 | ); 30 | } else { 31 | // Browser globals: 32 | factory( 33 | window.jQuery, 34 | window.loadImage 35 | ); 36 | } 37 | }(function ($, loadImage) { 38 | 'use strict'; 39 | 40 | // Prepend to the default processQueue: 41 | $.blueimp.fileupload.prototype.options.processQueue.unshift( 42 | { 43 | action: 'loadVideo', 44 | // Use the action as prefix for the "@" options: 45 | prefix: true, 46 | fileTypes: '@', 47 | maxFileSize: '@', 48 | disabled: '@disableVideoPreview' 49 | }, 50 | { 51 | action: 'setVideo', 52 | name: '@videoPreviewName', 53 | disabled: '@disableVideoPreview' 54 | } 55 | ); 56 | 57 | // The File Upload Video Preview plugin extends the fileupload widget 58 | // with video preview functionality: 59 | $.widget('blueimp.fileupload', $.blueimp.fileupload, { 60 | 61 | options: { 62 | // The regular expression for the types of video files to load, 63 | // matched against the file type: 64 | loadVideoFileTypes: /^video\/.*$/ 65 | }, 66 | 67 | _videoElement: document.createElement('video'), 68 | 69 | processActions: { 70 | 71 | // Loads the video file given via data.files and data.index 72 | // as video element if the browser supports playing it. 73 | // Accepts the options fileTypes (regular expression) 74 | // and maxFileSize (integer) to limit the files to load: 75 | loadVideo: function (data, options) { 76 | if (options.disabled) { 77 | return data; 78 | } 79 | var file = data.files[data.index], 80 | url, 81 | video; 82 | if (this._videoElement.canPlayType && 83 | this._videoElement.canPlayType(file.type) && 84 | ($.type(options.maxFileSize) !== 'number' || 85 | file.size <= options.maxFileSize) && 86 | (!options.fileTypes || 87 | options.fileTypes.test(file.type))) { 88 | url = loadImage.createObjectURL(file); 89 | if (url) { 90 | video = this._videoElement.cloneNode(false); 91 | video.src = url; 92 | video.controls = true; 93 | data.video = video; 94 | return data; 95 | } 96 | } 97 | return data; 98 | }, 99 | 100 | // Sets the video element as a property of the file object: 101 | setVideo: function (data, options) { 102 | if (data.video && !options.disabled) { 103 | data.files[data.index][options.name || 'preview'] = data.video; 104 | } 105 | return data; 106 | } 107 | 108 | } 109 | 110 | }); 111 | 112 | })); 113 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/jquery.iframe-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Iframe Transport Plugin 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2011, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global define, require, window, document */ 13 | 14 | (function (factory) { 15 | 'use strict'; 16 | if (typeof define === 'function' && define.amd) { 17 | // Register as an anonymous AMD module: 18 | define(['jquery'], factory); 19 | } else if (typeof exports === 'object') { 20 | // Node/CommonJS: 21 | factory(require('jquery')); 22 | } else { 23 | // Browser globals: 24 | factory(window.jQuery); 25 | } 26 | }(function ($) { 27 | 'use strict'; 28 | 29 | // Helper variable to create unique names for the transport iframes: 30 | var counter = 0; 31 | 32 | // The iframe transport accepts four additional options: 33 | // options.fileInput: a jQuery collection of file input fields 34 | // options.paramName: the parameter name for the file form data, 35 | // overrides the name property of the file input field(s), 36 | // can be a string or an array of strings. 37 | // options.formData: an array of objects with name and value properties, 38 | // equivalent to the return data of .serializeArray(), e.g.: 39 | // [{name: 'a', value: 1}, {name: 'b', value: 2}] 40 | // options.initialIframeSrc: the URL of the initial iframe src, 41 | // by default set to "javascript:false;" 42 | $.ajaxTransport('iframe', function (options) { 43 | if (options.async) { 44 | // javascript:false as initial iframe src 45 | // prevents warning popups on HTTPS in IE6: 46 | /*jshint scripturl: true */ 47 | var initialIframeSrc = options.initialIframeSrc || 'javascript:false;', 48 | /*jshint scripturl: false */ 49 | form, 50 | iframe, 51 | addParamChar; 52 | return { 53 | send: function (_, completeCallback) { 54 | form = $('
'); 55 | form.attr('accept-charset', options.formAcceptCharset); 56 | addParamChar = /\?/.test(options.url) ? '&' : '?'; 57 | // XDomainRequest only supports GET and POST: 58 | if (options.type === 'DELETE') { 59 | options.url = options.url + addParamChar + '_method=DELETE'; 60 | options.type = 'POST'; 61 | } else if (options.type === 'PUT') { 62 | options.url = options.url + addParamChar + '_method=PUT'; 63 | options.type = 'POST'; 64 | } else if (options.type === 'PATCH') { 65 | options.url = options.url + addParamChar + '_method=PATCH'; 66 | options.type = 'POST'; 67 | } 68 | // IE versions below IE8 cannot set the name property of 69 | // elements that have already been added to the DOM, 70 | // so we set the name along with the iframe HTML markup: 71 | counter += 1; 72 | iframe = $( 73 | '' 75 | ).bind('load', function () { 76 | var fileInputClones, 77 | paramNames = $.isArray(options.paramName) ? 78 | options.paramName : [options.paramName]; 79 | iframe 80 | .unbind('load') 81 | .bind('load', function () { 82 | var response; 83 | // Wrap in a try/catch block to catch exceptions thrown 84 | // when trying to access cross-domain iframe contents: 85 | try { 86 | response = iframe.contents(); 87 | // Google Chrome and Firefox do not throw an 88 | // exception when calling iframe.contents() on 89 | // cross-domain requests, so we unify the response: 90 | if (!response.length || !response[0].firstChild) { 91 | throw new Error(); 92 | } 93 | } catch (e) { 94 | response = undefined; 95 | } 96 | // The complete callback returns the 97 | // iframe content document as response object: 98 | completeCallback( 99 | 200, 100 | 'success', 101 | {'iframe': response} 102 | ); 103 | // Fix for IE endless progress bar activity bug 104 | // (happens on form submits to iframe targets): 105 | $('') 106 | .appendTo(form); 107 | window.setTimeout(function () { 108 | // Removing the form in a setTimeout call 109 | // allows Chrome's developer tools to display 110 | // the response result 111 | form.remove(); 112 | }, 0); 113 | }); 114 | form 115 | .prop('target', iframe.prop('name')) 116 | .prop('action', options.url) 117 | .prop('method', options.type); 118 | if (options.formData) { 119 | $.each(options.formData, function (index, field) { 120 | $('') 121 | .prop('name', field.name) 122 | .val(field.value) 123 | .appendTo(form); 124 | }); 125 | } 126 | if (options.fileInput && options.fileInput.length && 127 | options.type === 'POST') { 128 | fileInputClones = options.fileInput.clone(); 129 | // Insert a clone for each file input field: 130 | options.fileInput.after(function (index) { 131 | return fileInputClones[index]; 132 | }); 133 | if (options.paramName) { 134 | options.fileInput.each(function (index) { 135 | $(this).prop( 136 | 'name', 137 | paramNames[index] || options.paramName 138 | ); 139 | }); 140 | } 141 | // Appending the file input fields to the hidden form 142 | // removes them from their original location: 143 | form 144 | .append(options.fileInput) 145 | .prop('enctype', 'multipart/form-data') 146 | // enctype must be set as encoding for IE: 147 | .prop('encoding', 'multipart/form-data'); 148 | // Remove the HTML5 form attribute from the input(s): 149 | options.fileInput.removeAttr('form'); 150 | } 151 | form.submit(); 152 | // Insert the file input fields at their original location 153 | // by replacing the clones with the originals: 154 | if (fileInputClones && fileInputClones.length) { 155 | options.fileInput.each(function (index, input) { 156 | var clone = $(fileInputClones[index]); 157 | // Restore the original name and form properties: 158 | $(input) 159 | .prop('name', clone.prop('name')) 160 | .attr('form', clone.attr('form')); 161 | clone.replaceWith(input); 162 | }); 163 | } 164 | }); 165 | form.append(iframe).appendTo(document.body); 166 | }, 167 | abort: function () { 168 | if (iframe) { 169 | // javascript:false as iframe src aborts the request 170 | // and prevents warning popups on HTTPS in IE6. 171 | // concat is used to avoid the "Script URL" JSLint error: 172 | iframe 173 | .unbind('load') 174 | .prop('src', initialIframeSrc); 175 | } 176 | if (form) { 177 | form.remove(); 178 | } 179 | } 180 | }; 181 | } 182 | }); 183 | 184 | // The iframe transport returns the iframe content document as response. 185 | // The following adds converters from iframe to text, json, html, xml 186 | // and script. 187 | // Please note that the Content-Type for JSON responses has to be text/plain 188 | // or text/html, if the browser doesn't include application/json in the 189 | // Accept header, else IE will show a download dialog. 190 | // The Content-Type for XML responses on the other hand has to be always 191 | // application/xml or text/xml, so IE properly parses the XML response. 192 | // See also 193 | // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation 194 | $.ajaxSetup({ 195 | converters: { 196 | 'iframe text': function (iframe) { 197 | return iframe && $(iframe[0].body).text(); 198 | }, 199 | 'iframe json': function (iframe) { 200 | return iframe && $.parseJSON($(iframe[0].body).text()); 201 | }, 202 | 'iframe html': function (iframe) { 203 | return iframe && $(iframe[0].body).html(); 204 | }, 205 | 'iframe xml': function (iframe) { 206 | var xmlDoc = iframe && iframe[0]; 207 | return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc : 208 | $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) || 209 | $(xmlDoc.body).html()); 210 | }, 211 | 'iframe script': function (iframe) { 212 | return iframe && $.globalEval($(iframe[0].body).text()); 213 | } 214 | } 215 | }); 216 | 217 | })); 218 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery File Upload Plugin JS Example 3 | * https://github.com/blueimp/jQuery-File-Upload 4 | * 5 | * Copyright 2010, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | */ 11 | 12 | /* global $, window */ 13 | 14 | $(function () { 15 | 'use strict'; 16 | 17 | // Initialize the jQuery File Upload widget: 18 | $('#fileupload').fileupload({ 19 | // Uncomment the following to send cross-domain cookies: 20 | //xhrFields: {withCredentials: true}, 21 | url: 'server/php/' 22 | }); 23 | 24 | // Enable iframe cross-domain access via redirect option: 25 | $('#fileupload').fileupload( 26 | 'option', 27 | 'redirect', 28 | window.location.href.replace( 29 | /\/[^\/]*$/, 30 | '/cors/result.html?%s' 31 | ) 32 | ); 33 | 34 | if (window.location.hostname === 'blueimp.github.io') { 35 | // Demo settings: 36 | $('#fileupload').fileupload('option', { 37 | url: '//jquery-file-upload.appspot.com/', 38 | // Enable image resizing, except for Android and Opera, 39 | // which actually support image resizing, but fail to 40 | // send Blob objects via XHR requests: 41 | disableImageResize: /Android(?!.*Chrome)|Opera/ 42 | .test(window.navigator.userAgent), 43 | maxFileSize: 999000, 44 | acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i 45 | }); 46 | // Upload server status check for browsers with CORS support: 47 | if ($.support.cors) { 48 | $.ajax({ 49 | url: '//jquery-file-upload.appspot.com/', 50 | type: 'HEAD' 51 | }).fail(function () { 52 | $('
') 53 | .text('Upload server currently unavailable - ' + 54 | new Date()) 55 | .appendTo('#fileupload'); 56 | }); 57 | } 58 | } else { 59 | // Load existing files: 60 | $('#fileupload').addClass('fileupload-processing'); 61 | $.ajax({ 62 | // Uncomment the following to send cross-domain cookies: 63 | //xhrFields: {withCredentials: true}, 64 | url: $('#fileupload').fileupload('option', 'url'), 65 | dataType: 'json', 66 | context: $('#fileupload')[0] 67 | }).always(function () { 68 | $(this).removeClass('fileupload-processing'); 69 | }).done(function (result) { 70 | $(this).fileupload('option', 'done') 71 | .call(this, $.Event('done'), {result: result}); 72 | }); 73 | } 74 | 75 | }); 76 | -------------------------------------------------------------------------------- /test/jquery-file-upload/js/vendor/jquery.ui.widget.js: -------------------------------------------------------------------------------- 1 | /*! jQuery UI - v1.11.4+CommonJS - 2015-08-28 2 | * http://jqueryui.com 3 | * Includes: widget.js 4 | * Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */ 5 | 6 | (function( factory ) { 7 | if ( typeof define === "function" && define.amd ) { 8 | 9 | // AMD. Register as an anonymous module. 10 | define([ "jquery" ], factory ); 11 | 12 | } else if ( typeof exports === "object" ) { 13 | 14 | // Node/CommonJS 15 | factory( require( "jquery" ) ); 16 | 17 | } else { 18 | 19 | // Browser globals 20 | factory( jQuery ); 21 | } 22 | }(function( $ ) { 23 | /*! 24 | * jQuery UI Widget 1.11.4 25 | * http://jqueryui.com 26 | * 27 | * Copyright jQuery Foundation and other contributors 28 | * Released under the MIT license. 29 | * http://jquery.org/license 30 | * 31 | * http://api.jqueryui.com/jQuery.widget/ 32 | */ 33 | 34 | 35 | var widget_uuid = 0, 36 | widget_slice = Array.prototype.slice; 37 | 38 | $.cleanData = (function( orig ) { 39 | return function( elems ) { 40 | var events, elem, i; 41 | for ( i = 0; (elem = elems[i]) != null; i++ ) { 42 | try { 43 | 44 | // Only trigger remove when necessary to save time 45 | events = $._data( elem, "events" ); 46 | if ( events && events.remove ) { 47 | $( elem ).triggerHandler( "remove" ); 48 | } 49 | 50 | // http://bugs.jquery.com/ticket/8235 51 | } catch ( e ) {} 52 | } 53 | orig( elems ); 54 | }; 55 | })( $.cleanData ); 56 | 57 | $.widget = function( name, base, prototype ) { 58 | var fullName, existingConstructor, constructor, basePrototype, 59 | // proxiedPrototype allows the provided prototype to remain unmodified 60 | // so that it can be used as a mixin for multiple widgets (#8876) 61 | proxiedPrototype = {}, 62 | namespace = name.split( "." )[ 0 ]; 63 | 64 | name = name.split( "." )[ 1 ]; 65 | fullName = namespace + "-" + name; 66 | 67 | if ( !prototype ) { 68 | prototype = base; 69 | base = $.Widget; 70 | } 71 | 72 | // create selector for plugin 73 | $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { 74 | return !!$.data( elem, fullName ); 75 | }; 76 | 77 | $[ namespace ] = $[ namespace ] || {}; 78 | existingConstructor = $[ namespace ][ name ]; 79 | constructor = $[ namespace ][ name ] = function( options, element ) { 80 | // allow instantiation without "new" keyword 81 | if ( !this._createWidget ) { 82 | return new constructor( options, element ); 83 | } 84 | 85 | // allow instantiation without initializing for simple inheritance 86 | // must use "new" keyword (the code above always passes args) 87 | if ( arguments.length ) { 88 | this._createWidget( options, element ); 89 | } 90 | }; 91 | // extend with the existing constructor to carry over any static properties 92 | $.extend( constructor, existingConstructor, { 93 | version: prototype.version, 94 | // copy the object used to create the prototype in case we need to 95 | // redefine the widget later 96 | _proto: $.extend( {}, prototype ), 97 | // track widgets that inherit from this widget in case this widget is 98 | // redefined after a widget inherits from it 99 | _childConstructors: [] 100 | }); 101 | 102 | basePrototype = new base(); 103 | // we need to make the options hash a property directly on the new instance 104 | // otherwise we'll modify the options hash on the prototype that we're 105 | // inheriting from 106 | basePrototype.options = $.widget.extend( {}, basePrototype.options ); 107 | $.each( prototype, function( prop, value ) { 108 | if ( !$.isFunction( value ) ) { 109 | proxiedPrototype[ prop ] = value; 110 | return; 111 | } 112 | proxiedPrototype[ prop ] = (function() { 113 | var _super = function() { 114 | return base.prototype[ prop ].apply( this, arguments ); 115 | }, 116 | _superApply = function( args ) { 117 | return base.prototype[ prop ].apply( this, args ); 118 | }; 119 | return function() { 120 | var __super = this._super, 121 | __superApply = this._superApply, 122 | returnValue; 123 | 124 | this._super = _super; 125 | this._superApply = _superApply; 126 | 127 | returnValue = value.apply( this, arguments ); 128 | 129 | this._super = __super; 130 | this._superApply = __superApply; 131 | 132 | return returnValue; 133 | }; 134 | })(); 135 | }); 136 | constructor.prototype = $.widget.extend( basePrototype, { 137 | // TODO: remove support for widgetEventPrefix 138 | // always use the name + a colon as the prefix, e.g., draggable:start 139 | // don't prefix for widgets that aren't DOM-based 140 | widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name 141 | }, proxiedPrototype, { 142 | constructor: constructor, 143 | namespace: namespace, 144 | widgetName: name, 145 | widgetFullName: fullName 146 | }); 147 | 148 | // If this widget is being redefined then we need to find all widgets that 149 | // are inheriting from it and redefine all of them so that they inherit from 150 | // the new version of this widget. We're essentially trying to replace one 151 | // level in the prototype chain. 152 | if ( existingConstructor ) { 153 | $.each( existingConstructor._childConstructors, function( i, child ) { 154 | var childPrototype = child.prototype; 155 | 156 | // redefine the child widget using the same prototype that was 157 | // originally used, but inherit from the new version of the base 158 | $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); 159 | }); 160 | // remove the list of existing child constructors from the old constructor 161 | // so the old child constructors can be garbage collected 162 | delete existingConstructor._childConstructors; 163 | } else { 164 | base._childConstructors.push( constructor ); 165 | } 166 | 167 | $.widget.bridge( name, constructor ); 168 | 169 | return constructor; 170 | }; 171 | 172 | $.widget.extend = function( target ) { 173 | var input = widget_slice.call( arguments, 1 ), 174 | inputIndex = 0, 175 | inputLength = input.length, 176 | key, 177 | value; 178 | for ( ; inputIndex < inputLength; inputIndex++ ) { 179 | for ( key in input[ inputIndex ] ) { 180 | value = input[ inputIndex ][ key ]; 181 | if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { 182 | // Clone objects 183 | if ( $.isPlainObject( value ) ) { 184 | target[ key ] = $.isPlainObject( target[ key ] ) ? 185 | $.widget.extend( {}, target[ key ], value ) : 186 | // Don't extend strings, arrays, etc. with objects 187 | $.widget.extend( {}, value ); 188 | // Copy everything else by reference 189 | } else { 190 | target[ key ] = value; 191 | } 192 | } 193 | } 194 | } 195 | return target; 196 | }; 197 | 198 | $.widget.bridge = function( name, object ) { 199 | var fullName = object.prototype.widgetFullName || name; 200 | $.fn[ name ] = function( options ) { 201 | var isMethodCall = typeof options === "string", 202 | args = widget_slice.call( arguments, 1 ), 203 | returnValue = this; 204 | 205 | if ( isMethodCall ) { 206 | this.each(function() { 207 | var methodValue, 208 | instance = $.data( this, fullName ); 209 | if ( options === "instance" ) { 210 | returnValue = instance; 211 | return false; 212 | } 213 | if ( !instance ) { 214 | return $.error( "cannot call methods on " + name + " prior to initialization; " + 215 | "attempted to call method '" + options + "'" ); 216 | } 217 | if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { 218 | return $.error( "no such method '" + options + "' for " + name + " widget instance" ); 219 | } 220 | methodValue = instance[ options ].apply( instance, args ); 221 | if ( methodValue !== instance && methodValue !== undefined ) { 222 | returnValue = methodValue && methodValue.jquery ? 223 | returnValue.pushStack( methodValue.get() ) : 224 | methodValue; 225 | return false; 226 | } 227 | }); 228 | } else { 229 | 230 | // Allow multiple hashes to be passed on init 231 | if ( args.length ) { 232 | options = $.widget.extend.apply( null, [ options ].concat(args) ); 233 | } 234 | 235 | this.each(function() { 236 | var instance = $.data( this, fullName ); 237 | if ( instance ) { 238 | instance.option( options || {} ); 239 | if ( instance._init ) { 240 | instance._init(); 241 | } 242 | } else { 243 | $.data( this, fullName, new object( options, this ) ); 244 | } 245 | }); 246 | } 247 | 248 | return returnValue; 249 | }; 250 | }; 251 | 252 | $.Widget = function( /* options, element */ ) {}; 253 | $.Widget._childConstructors = []; 254 | 255 | $.Widget.prototype = { 256 | widgetName: "widget", 257 | widgetEventPrefix: "", 258 | defaultElement: "
", 259 | options: { 260 | disabled: false, 261 | 262 | // callbacks 263 | create: null 264 | }, 265 | _createWidget: function( options, element ) { 266 | element = $( element || this.defaultElement || this )[ 0 ]; 267 | this.element = $( element ); 268 | this.uuid = widget_uuid++; 269 | this.eventNamespace = "." + this.widgetName + this.uuid; 270 | 271 | this.bindings = $(); 272 | this.hoverable = $(); 273 | this.focusable = $(); 274 | 275 | if ( element !== this ) { 276 | $.data( element, this.widgetFullName, this ); 277 | this._on( true, this.element, { 278 | remove: function( event ) { 279 | if ( event.target === element ) { 280 | this.destroy(); 281 | } 282 | } 283 | }); 284 | this.document = $( element.style ? 285 | // element within the document 286 | element.ownerDocument : 287 | // element is window or document 288 | element.document || element ); 289 | this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); 290 | } 291 | 292 | this.options = $.widget.extend( {}, 293 | this.options, 294 | this._getCreateOptions(), 295 | options ); 296 | 297 | this._create(); 298 | this._trigger( "create", null, this._getCreateEventData() ); 299 | this._init(); 300 | }, 301 | _getCreateOptions: $.noop, 302 | _getCreateEventData: $.noop, 303 | _create: $.noop, 304 | _init: $.noop, 305 | 306 | destroy: function() { 307 | this._destroy(); 308 | // we can probably remove the unbind calls in 2.0 309 | // all event bindings should go through this._on() 310 | this.element 311 | .unbind( this.eventNamespace ) 312 | .removeData( this.widgetFullName ) 313 | // support: jquery <1.6.3 314 | // http://bugs.jquery.com/ticket/9413 315 | .removeData( $.camelCase( this.widgetFullName ) ); 316 | this.widget() 317 | .unbind( this.eventNamespace ) 318 | .removeAttr( "aria-disabled" ) 319 | .removeClass( 320 | this.widgetFullName + "-disabled " + 321 | "ui-state-disabled" ); 322 | 323 | // clean up events and states 324 | this.bindings.unbind( this.eventNamespace ); 325 | this.hoverable.removeClass( "ui-state-hover" ); 326 | this.focusable.removeClass( "ui-state-focus" ); 327 | }, 328 | _destroy: $.noop, 329 | 330 | widget: function() { 331 | return this.element; 332 | }, 333 | 334 | option: function( key, value ) { 335 | var options = key, 336 | parts, 337 | curOption, 338 | i; 339 | 340 | if ( arguments.length === 0 ) { 341 | // don't return a reference to the internal hash 342 | return $.widget.extend( {}, this.options ); 343 | } 344 | 345 | if ( typeof key === "string" ) { 346 | // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } 347 | options = {}; 348 | parts = key.split( "." ); 349 | key = parts.shift(); 350 | if ( parts.length ) { 351 | curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); 352 | for ( i = 0; i < parts.length - 1; i++ ) { 353 | curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; 354 | curOption = curOption[ parts[ i ] ]; 355 | } 356 | key = parts.pop(); 357 | if ( arguments.length === 1 ) { 358 | return curOption[ key ] === undefined ? null : curOption[ key ]; 359 | } 360 | curOption[ key ] = value; 361 | } else { 362 | if ( arguments.length === 1 ) { 363 | return this.options[ key ] === undefined ? null : this.options[ key ]; 364 | } 365 | options[ key ] = value; 366 | } 367 | } 368 | 369 | this._setOptions( options ); 370 | 371 | return this; 372 | }, 373 | _setOptions: function( options ) { 374 | var key; 375 | 376 | for ( key in options ) { 377 | this._setOption( key, options[ key ] ); 378 | } 379 | 380 | return this; 381 | }, 382 | _setOption: function( key, value ) { 383 | this.options[ key ] = value; 384 | 385 | if ( key === "disabled" ) { 386 | this.widget() 387 | .toggleClass( this.widgetFullName + "-disabled", !!value ); 388 | 389 | // If the widget is becoming disabled, then nothing is interactive 390 | if ( value ) { 391 | this.hoverable.removeClass( "ui-state-hover" ); 392 | this.focusable.removeClass( "ui-state-focus" ); 393 | } 394 | } 395 | 396 | return this; 397 | }, 398 | 399 | enable: function() { 400 | return this._setOptions({ disabled: false }); 401 | }, 402 | disable: function() { 403 | return this._setOptions({ disabled: true }); 404 | }, 405 | 406 | _on: function( suppressDisabledCheck, element, handlers ) { 407 | var delegateElement, 408 | instance = this; 409 | 410 | // no suppressDisabledCheck flag, shuffle arguments 411 | if ( typeof suppressDisabledCheck !== "boolean" ) { 412 | handlers = element; 413 | element = suppressDisabledCheck; 414 | suppressDisabledCheck = false; 415 | } 416 | 417 | // no element argument, shuffle and use this.element 418 | if ( !handlers ) { 419 | handlers = element; 420 | element = this.element; 421 | delegateElement = this.widget(); 422 | } else { 423 | element = delegateElement = $( element ); 424 | this.bindings = this.bindings.add( element ); 425 | } 426 | 427 | $.each( handlers, function( event, handler ) { 428 | function handlerProxy() { 429 | // allow widgets to customize the disabled handling 430 | // - disabled as an array instead of boolean 431 | // - disabled class as method for disabling individual parts 432 | if ( !suppressDisabledCheck && 433 | ( instance.options.disabled === true || 434 | $( this ).hasClass( "ui-state-disabled" ) ) ) { 435 | return; 436 | } 437 | return ( typeof handler === "string" ? instance[ handler ] : handler ) 438 | .apply( instance, arguments ); 439 | } 440 | 441 | // copy the guid so direct unbinding works 442 | if ( typeof handler !== "string" ) { 443 | handlerProxy.guid = handler.guid = 444 | handler.guid || handlerProxy.guid || $.guid++; 445 | } 446 | 447 | var match = event.match( /^([\w:-]*)\s*(.*)$/ ), 448 | eventName = match[1] + instance.eventNamespace, 449 | selector = match[2]; 450 | if ( selector ) { 451 | delegateElement.delegate( selector, eventName, handlerProxy ); 452 | } else { 453 | element.bind( eventName, handlerProxy ); 454 | } 455 | }); 456 | }, 457 | 458 | _off: function( element, eventName ) { 459 | eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + 460 | this.eventNamespace; 461 | element.unbind( eventName ).undelegate( eventName ); 462 | 463 | // Clear the stack to avoid memory leaks (#10056) 464 | this.bindings = $( this.bindings.not( element ).get() ); 465 | this.focusable = $( this.focusable.not( element ).get() ); 466 | this.hoverable = $( this.hoverable.not( element ).get() ); 467 | }, 468 | 469 | _delay: function( handler, delay ) { 470 | function handlerProxy() { 471 | return ( typeof handler === "string" ? instance[ handler ] : handler ) 472 | .apply( instance, arguments ); 473 | } 474 | var instance = this; 475 | return setTimeout( handlerProxy, delay || 0 ); 476 | }, 477 | 478 | _hoverable: function( element ) { 479 | this.hoverable = this.hoverable.add( element ); 480 | this._on( element, { 481 | mouseenter: function( event ) { 482 | $( event.currentTarget ).addClass( "ui-state-hover" ); 483 | }, 484 | mouseleave: function( event ) { 485 | $( event.currentTarget ).removeClass( "ui-state-hover" ); 486 | } 487 | }); 488 | }, 489 | 490 | _focusable: function( element ) { 491 | this.focusable = this.focusable.add( element ); 492 | this._on( element, { 493 | focusin: function( event ) { 494 | $( event.currentTarget ).addClass( "ui-state-focus" ); 495 | }, 496 | focusout: function( event ) { 497 | $( event.currentTarget ).removeClass( "ui-state-focus" ); 498 | } 499 | }); 500 | }, 501 | 502 | _trigger: function( type, event, data ) { 503 | var prop, orig, 504 | callback = this.options[ type ]; 505 | 506 | data = data || {}; 507 | event = $.Event( event ); 508 | event.type = ( type === this.widgetEventPrefix ? 509 | type : 510 | this.widgetEventPrefix + type ).toLowerCase(); 511 | // the original event may come from any element 512 | // so we need to reset the target on the new event 513 | event.target = this.element[ 0 ]; 514 | 515 | // copy original event properties over to the new event 516 | orig = event.originalEvent; 517 | if ( orig ) { 518 | for ( prop in orig ) { 519 | if ( !( prop in event ) ) { 520 | event[ prop ] = orig[ prop ]; 521 | } 522 | } 523 | } 524 | 525 | this.element.trigger( event, data ); 526 | return !( $.isFunction( callback ) && 527 | callback.apply( this.element[0], [ event ].concat( data ) ) === false || 528 | event.isDefaultPrevented() ); 529 | } 530 | }; 531 | 532 | $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { 533 | $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { 534 | if ( typeof options === "string" ) { 535 | options = { effect: options }; 536 | } 537 | var hasOptions, 538 | effectName = !options ? 539 | method : 540 | options === true || typeof options === "number" ? 541 | defaultEffect : 542 | options.effect || defaultEffect; 543 | options = options || {}; 544 | if ( typeof options === "number" ) { 545 | options = { duration: options }; 546 | } 547 | hasOptions = !$.isEmptyObject( options ); 548 | options.complete = callback; 549 | if ( options.delay ) { 550 | element.delay( options.delay ); 551 | } 552 | if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { 553 | element[ method ]( options ); 554 | } else if ( effectName !== method && element[ effectName ] ) { 555 | element[ effectName ]( options.duration, options.easing, callback ); 556 | } else { 557 | element.queue(function( next ) { 558 | $( this )[ method ](); 559 | if ( callback ) { 560 | callback.call( element[ 0 ] ); 561 | } 562 | next(); 563 | }); 564 | } 565 | }; 566 | }); 567 | 568 | var widget = $.widget; 569 | 570 | 571 | 572 | })); 573 | -------------------------------------------------------------------------------- /test/oss.php: -------------------------------------------------------------------------------- 1 | 'your key', 10 | 'secret' => 'your secret', 11 | 'endPoint' => 'oss-cn-shanghai.aliyuncs.com', 12 | 'bucket' => 'your bucket', 13 | ]; 14 | $oss = new \PFinal\Storage\AliOss($config); 15 | 16 | $bool = $oss->put('test.jpg', file_get_contents('/Users/ethan/Pictures/1.jpg')); 17 | var_dump($bool); 18 | 19 | //原图 20 | echo $oss->url('test.jpg'); 21 | 22 | echo '
'; 23 | 24 | //图片处理 规则名称: "s" 25 | echo $oss->url('test.jpg', 's'); 26 | 27 | -------------------------------------------------------------------------------- /test/qiniu-form.php: -------------------------------------------------------------------------------- 1 | 'eqk2ITAKUc67_s6RMKoUJgPZgijc_a', 7 | 'secretKey' => 'vvc67_s6ddRMKoUJgUc67_sRMR', 8 | 'bucketName' => 'test', 9 | 'baseUrl' => 'http://oxxx32vf1.bkt.clouddn.com/', 10 | 'separator' => '!', 11 | ); 12 | $qiniu = new \PFinal\Storage\Qiniu($config); 13 | 14 | $baseUrl = $qiniu->url(''); 15 | 16 | $clientToken = $qiniu->getClientToken(); 17 | 18 | ?> 19 | 20 | 21 | 22 | 24 | 25 | demo 26 | 27 | 28 | 29 |

普通表单上传

30 |
31 | 32 | 33 | 34 | 35 | 36 |
37 | 38 |
39 | 40 |

jquery-file-upload

41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 | 51 | 52 |
53 | 54 | 55 | 58 | 59 |
60 | 61 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /test/qiniu.php: -------------------------------------------------------------------------------- 1 | 'TOeV-fwwxsssf3s_45tCziKjRD9-bPyXUKjbuX7b', 7 | 'secretKey' => 'pbHrgwwwp_wpClxeeGrYKLNdEhLd02Jrew3t5h', 8 | 'bucketName' => 'test', 9 | 'baseUrl' => 'http://static.pfinal.cn/', 10 | 'separator' => '!', 11 | ); 12 | $qiniu = new \PFinal\Storage\Qiniu($config); 13 | $bool = $qiniu->put('test/abc.jpg', file_get_contents('/Users/ethan/Pictures/1.jpg')); 14 | var_dump($bool); 15 | var_dump($qiniu->url('test/abc.jpg')); 16 | var_dump($qiniu->url('test/abc.jpg', 'm')); 17 | --------------------------------------------------------------------------------