├── composer.json ├── assets └── FileUploadAsset.php ├── views └── index.php ├── README.md ├── statics ├── js │ ├── upload-input.js │ ├── upload.js │ └── jquery.form.js └── css │ └── upload.css ├── FileUpload.php ├── config.php ├── UploadAction.php └── Uploader.php /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyiifu/yii2-file-upload", 3 | "description": "The ueditor extension for the Yii framework", 4 | "keywords": ["yii2", "file-upload", "yii-china"], 5 | "homepage": "https://github.com/org-yii-china/yii2-file-upload.git", 6 | "type": "yii2-extension", 7 | "license": "BSD-3-Clause", 8 | "authors": [ 9 | { 10 | "name": "Xianan Huang", 11 | "email": "xianan_huang@163.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.3.0" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "yii-china\\file-upload\\": "" 20 | } 21 | }, 22 | "extra": { 23 | "branch-alias": { 24 | "dev-master": "1.0.x-dev" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /assets/FileUploadAsset.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class FileUploadAsset extends AssetBundle 16 | { 17 | public $css = [ 18 | 'css/upload.css', 19 | 20 | ]; 21 | 22 | public $js = [ 23 | 'js/jquery.form.js', 24 | 'js/upload.js', 25 | 'js/upload-input.js', 26 | ]; 27 | 28 | public $depends = [ 29 | 'yii\web\YiiAsset', 30 | ]; 31 | 32 | /** 33 | * 初始化:sourcePath赋值 34 | * @see \yii\web\AssetBundle::init() 35 | */ 36 | public function init() 37 | { 38 | $this->sourcePath = dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR . 'statics'; 39 | } 40 | } -------------------------------------------------------------------------------- /views/index.php: -------------------------------------------------------------------------------- 1 | 5 | * 图片上传组件 6 | * 如何配置请到官网(Yii中文网)查看相关文章 7 | */ 8 | 9 | use yii\helpers\Html; 10 | ?> 11 |
12 |
':''?>
13 |
图片上传
14 |
15 |

选择图片

16 |

仅支持文件格式为jpg、jpeg、png以及gif
大小在1MB以下的文件

17 |
18 | ' value="" filetype="img" /> 19 |
-------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yii2 插件整合-图片上传(file-upload) 2 | 3 | 实例教程:http://www.yii-china.com/post/detail/15.html 4 | 5 | 安装扩展: 6 | 7 | 1.点击上面扩展下载下载扩展 8 | 9 | 然后重命名为file_upload放在/common/widgets文件夹中 10 | 11 | 2.在使用图片上传控件的控制器(controller)中,加入以下代码 12 | 13 | public function actions() 14 | { 15 | return [ 16 | 'upload'=>[ 17 | 'class' => 'common\widgets\file_upload\UploadAction', //这里扩展地址别写错 18 | 'config' => [ 19 | 'imagePathFormat' => "/image/{yyyy}{mm}{dd}/{time}{rand:6}", 20 | ] 21 | ] 22 | ]; 23 | } 24 | 25 | 3.views渲染图片上传界面有两种方式: 26 | 27 | 第一种:不带model 28 | 29 |
30 | use common\widgets\file_upload\FileUpload;   //引入扩展
31 | echo FileUpload::widget();
32 | echo FileUpload::widget(['value'=>$url]);    //如果编辑时要带默认图,$url为图片路径
33 | 
34 | 35 | 第二种:带model 36 | 37 |
38 | $form = ActiveForm::begin(); 
39 |     echo $form->field($model, 'label_img')->widget('common\widgets\file_upload\FileUpload',[
40 |         'config'=>[
41 |             '图片上传的一些配置,不写调用默认配置'
42 |         ]
43 |     ]);
44 | ActiveForm::end();
45 | 
46 | 47 | -------------------------------------------------------------------------------- /statics/js/upload-input.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | var form_temp = []; 3 | var toAppend = [ 4 | '
', 5 | '', 6 | '
' 7 | ].join(''); 8 | 9 | form_temp.push(toAppend); 10 | 11 | if($('.form_temp').length < 1){ 12 | $('body').append('
'); 13 | } 14 | $('.form_temp').append(form_temp.join('')); 15 | $('.choose_btn').click(function(){ 16 | var input= $('#upfile'); 17 | input.attr('from', $(this).attr('id')); 18 | input.click(); 19 | }); 20 | $('.file_upload').on('change',function(){ 21 | var from=$('#upfile').attr('from'); 22 | var thisUrl = $('.'+from).parent().attr('data-url'); 23 | var thisType = $(this).attr('filetype'), 24 | maxSize = 1*1024*1024; 25 | $(this).upload({ 26 | url : thisUrl, 27 | maxSize : maxSize, 28 | img : { 29 | img : ['jpg','JPG','jpeg','JPEG','gif','GIF','png','PNG'] 30 | }, 31 | sucFn : function(json){ 32 | var from=$('#upfile').attr('from'); 33 | var domainUrl = $('.'+from).attr('domain-url'); 34 | json = $.parseJSON(json); 35 | if(json.state == 'SUCCESS'){ 36 | $('input[up-id='+ from+']').val(json.url); 37 | $('.'+ from).html($('').attr('src', domainUrl+json.url)); 38 | }else{ 39 | alert(json.state); 40 | } 41 | } 42 | }); 43 | }); 44 | 45 | })(jQuery) -------------------------------------------------------------------------------- /statics/css/upload.css: -------------------------------------------------------------------------------- 1 | .per_upload_con { 2 | width: 462px; 3 | height: 148px; 4 | position: relative; 5 | } 6 | .per_upload_img { 7 | width: 218px; 8 | height: 146px; 9 | background: url(../images/bg.jpg) no-repeat center center; 10 | border: 1px solid #e4e4e4; 11 | cursor: pointer; 12 | float: left; 13 | margin-right: 12px; 14 | color: #999999; 15 | line-height: 146px; 16 | text-align: center; 17 | } 18 | .per_upload_img.empty { 19 | background: url(../images/white.png) repeat center center; 20 | } 21 | .per_real_img { 22 | position: absolute; 23 | width: 218px; 24 | height: 146px; 25 | top: 1px; 26 | left: 1px; 27 | overflow: hidden; 28 | cursor: pointer; 29 | } 30 | .per_real_img img{ 31 | width: 100%; 32 | height:100%; 33 | } 34 | .per_upload_img:hover { 35 | border-color: #f3a948; 36 | color: #ff7624; 37 | } 38 | .per_upload_img:hover .plus, .per_upload_img:hover .plus_text { 39 | color: #ff7624; 40 | } 41 | .per_upload_text { 42 | width: 216px; 43 | float: left; 44 | padding-top: 12px; 45 | } 46 | .per_upload_con .red { 47 | color: #ff6702; 48 | } 49 | .per_upload_con .upname { 50 | color: #1f1d1c; 51 | line-height: 12px; 52 | } 53 | .per_upload_con .rule { 54 | color: #999999; 55 | line-height: 22px; 56 | } 57 | .per_upload_con .plus { 58 | font-size: 100px; 59 | color: #b8b8b8; 60 | line-height: 100px; 61 | } 62 | .per_upload_con .plus_text { 63 | color: #999999; 64 | line-height: 22px; 65 | } 66 | .green{ 67 | background-color: #35aa47; 68 | color: white; 69 | text-shadow: none; 70 | } -------------------------------------------------------------------------------- /statics/js/upload.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | var settings = { 4 | fileType : { 5 | img : ['jpg','JPG','jpeg','JPEG','gif','GIF','PNG','png'], 6 | zip : ['zip','ZIP','rar','RAR'] 7 | }, 8 | maxSize : 30*1024*1024, 9 | url : '/default/certificate', 10 | sucFn : function(){console.log('success')} 11 | }; 12 | 13 | $.fn.extend({ 14 | 15 | upload:function(options){ 16 | var that = this; 17 | settings = $.extend(settings,options); 18 | 19 | //验证文件后缀 20 | function checkType(){ 21 | var filepath = that.val(), 22 | filetype = that.attr('filetype'), 23 | thisType = filepath.split('.').pop(); 24 | if(filepath){ 25 | var pass = false; 26 | $.each(settings.fileType[filetype],function(key,val){ 27 | if(thisType == val){ 28 | pass = true; 29 | return pass; 30 | } 31 | }); 32 | return pass; 33 | } 34 | } 35 | 36 | //验证文件格式 37 | if(!checkType()){ 38 | alert('文件格式不正确'); 39 | return false; 40 | } 41 | 42 | //验证文件大小 43 | var fileSize = that[0].files[0].size; 44 | if(fileSize > settings.maxSize){ 45 | alert('文件超出规定大小'); 46 | return false; 47 | } 48 | 49 | //上传相关 50 | var thisId = that.attr('id'); 51 | var options={ 52 | url:settings.url, 53 | type:"post", 54 | success:settings.sucFn 55 | }; 56 | if(!that.hasClass('inited')){ 57 | that.addClass('inited') 58 | $("#form_"+thisId).submit(function() { 59 | $(this).ajaxSubmit(options); 60 | return false; 61 | }); 62 | } 63 | $("#form_"+thisId).submit(); 64 | 65 | } 66 | 67 | }); 68 | })(jQuery); -------------------------------------------------------------------------------- /FileUpload.php: -------------------------------------------------------------------------------- 1 | 5 | * 图片上传组件 6 | * 如何配置请到官网(Yii中文网)查看相关文章 7 | */ 8 | namespace common\widgets\file_upload; 9 | 10 | use Yii; 11 | use yii\widgets\InputWidget; 12 | use yii\helpers\Html; 13 | use yii\web\View; 14 | use common\widgets\file_upload\assets\FileUploadAsset; 15 | use yii\helpers\ArrayHelper; 16 | use yii\helpers\Url; 17 | 18 | class FileUpload extends InputWidget 19 | { 20 | public $config = []; 21 | 22 | public $value = ''; 23 | 24 | public function init() 25 | { 26 | $_config = [ 27 | 'serverUrl' => Url::to(['upload','action'=>'uploadimage']), //上传服务器地址 28 | 'fileName' => 'upfile', //提交的图片表单名称 29 | 'domain_url' => '', //图片域名 不填为当前域名 30 | ]; 31 | $this->config = ArrayHelper::merge($_config, $this->config); 32 | } 33 | 34 | public function run() 35 | { 36 | $this->registerClientScript(); 37 | if ($this->hasModel()) { 38 | $inputName = Html::getInputName($this->model, $this->attribute); 39 | $inputValue = Html::getAttributeValue($this->model, $this->attribute); 40 | return $this->render('index',[ 41 | 'config'=>$this->config, 42 | 'inputName' => $inputName, 43 | 'inputValue' => $inputValue, 44 | 'attribute' => $this->attribute, 45 | ]); 46 | } else { 47 | return $this->render('index',[ 48 | 'config'=>$this->config, 49 | 'inputName' => 'file-upload', 50 | 'inputValue'=> $this->value 51 | ]); 52 | } 53 | } 54 | 55 | public function registerClientScript() 56 | { 57 | FileUploadAsset::register($this->view); 58 | //$script = "FormFileUpload.init();"; 59 | //$this->view->registerJs($script, View::POS_READY); 60 | } 61 | } -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | 5 | * 图片上传组件 6 | * 如何配置请到官网(Yii中文网)查看相关文章 7 | */ 8 | return [ 9 | /* 上传图片配置项 */ 10 | "imageActionName" => "uploadimage", /* 执行上传图片的action名称 */ 11 | "imageFieldName" => "upfile", /* 提交的图片表单名称 */ 12 | "imageMaxSize" => 2048000, /* 上传大小限制,单位B */ 13 | "imageAllowFiles"=> [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 上传图片格式显示 */ 14 | "imageCompressEnable"=> true, /* 是否压缩图片,默认是true */ 15 | "imageCompressBorder"=> 1600, /* 图片压缩最长边限制 */ 16 | "imageInsertAlign"=> "none", /* 插入的图片浮动方式 */ 17 | "imageUrlPrefix"=> "", /* 图片访问路径前缀 */ 18 | "imagePathFormat"=> "/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */ 19 | "uploadFilePath" => "", /* 文件保存绝对路径 */ 20 | /* {filename} 会替换成原文件名,配置这项需要注意中文乱码问题 */ 21 | /* {rand:6} 会替换成随机数,后面的数字是随机数的位数 */ 22 | /* {time} 会替换成时间戳 */ 23 | /* {yyyy} 会替换成四位年份 */ 24 | /* {yy} 会替换成两位年份 */ 25 | /* {mm} 会替换成两位月份 */ 26 | /* {dd} 会替换成两位日期 */ 27 | /* {hh} 会替换成两位小时 */ 28 | /* {ii} 会替换成两位分钟 */ 29 | /* {ss} 会替换成两位秒 */ 30 | /* 非法字符 \ : * ? " < > | */ 31 | /* 具请体看线上文档: fex.baidu.com/ueditor/#use-format_upload_filename */ 32 | 33 | 34 | /* 上传文件配置 */ 35 | "fileActionName"=> "uploadfile", /* controller里,执行上传视频的action名称 */ 36 | "fileFieldName"=> "upfile", /* 提交的文件表单名称 */ 37 | "filePathFormat"=> "/ueditor/php/upload/file/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */ 38 | "fileUrlPrefix"=> "", /* 文件访问路径前缀 */ 39 | "fileMaxSize"=> 51200000, /* 上传大小限制,单位B,默认50MB */ 40 | "fileAllowFiles"=> [ 41 | ".png", ".jpg", ".jpeg", ".gif", ".bmp", 42 | ".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg", 43 | ".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid", 44 | ".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso", 45 | ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml" 46 | ], /* 上传文件格式显示 */ 47 | 48 | ]; -------------------------------------------------------------------------------- /UploadAction.php: -------------------------------------------------------------------------------- 1 | 6 | * 图片上传组件 7 | * 如何配置请到官网(Yii中文网)查看相关文章 8 | */ 9 | 10 | use Yii; 11 | use yii\base\Action; 12 | use yii\helpers\ArrayHelper; 13 | use common\widgets\file_upload\Uploader; 14 | 15 | class UploadAction extends Action 16 | { 17 | /** 18 | * 配置文件 19 | * @var array 20 | */ 21 | public $config = []; 22 | 23 | public function init() 24 | { 25 | //close csrf 26 | Yii::$app->request->enableCsrfValidation = false; 27 | //默认设置 28 | $_config = require(__DIR__ . '/config.php'); 29 | //load config file 30 | $this->config = ArrayHelper::merge($_config, $this->config); 31 | parent::init(); 32 | } 33 | 34 | public function run() 35 | { 36 | $action = Yii::$app->request->get('action'); 37 | switch ($action) { 38 | /* 上传图片 */ 39 | case 'uploadimage': 40 | /* 上传文件 */ 41 | case 'uploadfile': 42 | $result = $this->ActUpload(); 43 | break; 44 | default: 45 | $result = json_encode(array( 46 | 'state' => '请求地址出错' 47 | )); 48 | break; 49 | } 50 | echo $result; 51 | } 52 | 53 | /** 54 | * 上传 55 | * @return string 56 | */ 57 | protected function ActUpload() 58 | { 59 | $base64 = "upload"; 60 | switch (htmlspecialchars($_GET['action'])) { 61 | 62 | case 'uploadimage': 63 | $config = array( 64 | "pathFormat" => $this->config['imagePathFormat'], 65 | "maxSize" => $this->config['imageMaxSize'], 66 | "allowFiles" => $this->config['imageAllowFiles'], 67 | ); 68 | $fieldName = $this->config['imageFieldName']; 69 | break; 70 | 71 | case 'uploadfile': 72 | default: 73 | $config = array( 74 | "pathFormat" => $this->config['filePathFormat'], 75 | "maxSize" => $this->config['fileMaxSize'], 76 | "allowFiles" => $this->config['fileAllowFiles'] 77 | ); 78 | $fieldName = $this->config['fileFieldName']; 79 | break; 80 | } 81 | $config['uploadFilePath'] = isset($this->config['uploadFilePath'])?$this->config['uploadFilePath']:''; 82 | /* 生成上传实例对象并完成上传 */ 83 | $up = new Uploader($fieldName, $config, $base64); 84 | /** 85 | * 得到上传文件所对应的各个参数,数组结构 86 | * array( 87 | * "state" => "", //上传状态,上传成功时必须返回"SUCCESS" 88 | * "url" => "", //返回的地址 89 | * "title" => "", //新文件名 90 | * "original" => "", //原始文件名 91 | * "type" => "" //文件类型 92 | * "size" => "", //文件大小 93 | * ) 94 | */ 95 | /* 返回数据 */ 96 | return json_encode($up->getFileInfo()); 97 | } 98 | } -------------------------------------------------------------------------------- /Uploader.php: -------------------------------------------------------------------------------- 1 | 6 | * 图片上传组件 7 | * 如何配置请到官网(Yii中文网)查看相关文章 8 | */ 9 | 10 | class Uploader 11 | { 12 | private $fileField; //文件域名 13 | private $file; //文件上传对象 14 | private $base64; //文件上传对象 15 | private $config; //配置信息 16 | private $oriName; //原始文件名 17 | private $fileName; //新文件名 18 | private $fullName; //完整文件名,即从当前配置目录开始的URL 19 | private $filePath; //完整文件名,即从当前配置目录开始的URL 20 | private $fileSize; //文件大小 21 | private $fileType; //文件类型 22 | private $stateInfo; //上传状态信息, 23 | private $stateMap = array( //上传状态映射表,国际化用户需考虑此处数据的国际化 24 | "SUCCESS", //上传成功标记,在UEditor中内不可改变,否则flash判断会出错 25 | "文件大小超出 upload_max_filesize 限制", 26 | "文件大小超出 MAX_FILE_SIZE 限制", 27 | "文件未被完整上传", 28 | "没有文件被上传", 29 | "上传文件为空", 30 | "ERROR_TMP_FILE" => "临时文件错误", 31 | "ERROR_TMP_FILE_NOT_FOUND" => "找不到临时文件", 32 | "ERROR_SIZE_EXCEED" => "文件大小超出网站限制", 33 | "ERROR_TYPE_NOT_ALLOWED" => "文件类型不允许", 34 | "ERROR_CREATE_DIR" => "目录创建失败", 35 | "ERROR_DIR_NOT_WRITEABLE" => "目录没有写权限", 36 | "ERROR_FILE_MOVE" => "文件保存时出错", 37 | "ERROR_FILE_NOT_FOUND" => "找不到上传文件", 38 | "ERROR_WRITE_CONTENT" => "写入文件内容错误", 39 | "ERROR_UNKNOWN" => "未知错误", 40 | "ERROR_DEAD_LINK" => "链接不可用", 41 | "ERROR_HTTP_LINK" => "链接不是http链接", 42 | "ERROR_HTTP_CONTENTTYPE" => "链接contentType不正确" 43 | ); 44 | /** 45 | * 构造函数 46 | * @param $fileField 表单名称 47 | * @param $config 配置项 48 | * @param string $type 是否解析base64编码,可省略。若开启,则$fileField代表的是base64编码的字符串表单名 49 | */ 50 | public function __construct($fileField, $config, $type = "upload") 51 | { 52 | $this->fileField = $fileField; 53 | $this->config = $config; 54 | $this->type = $type; 55 | if ($type == "remote") { 56 | $this->saveRemote(); 57 | } else if ($type == "base64") { 58 | $this->upBase64(); 59 | } else { 60 | $this->upFile(); 61 | } 62 | // $this->stateMap['ERROR_TYPE_NOT_ALLOWED'] = iconv('unicode', 'utf-8', $this->stateMap['ERROR_TYPE_NOT_ALLOWED']); 63 | } 64 | /** 65 | * 上传文件的主处理方法 66 | * @return mixed 67 | */ 68 | private function upFile() 69 | { 70 | $file = $this->file = $_FILES[$this->fileField]; 71 | if (!$file) { 72 | $this->stateInfo = $this->getStateInfo("ERROR_FILE_NOT_FOUND"); 73 | return; 74 | } 75 | if ($this->file['error']) { 76 | $this->stateInfo = $this->getStateInfo($file['error']); 77 | return; 78 | } else if (!file_exists($file['tmp_name'])) { 79 | $this->stateInfo = $this->getStateInfo("ERROR_TMP_FILE_NOT_FOUND"); 80 | return; 81 | } else if (!is_uploaded_file($file['tmp_name'])) { 82 | $this->stateInfo = $this->getStateInfo("ERROR_TMPFILE"); 83 | return; 84 | } 85 | $this->oriName = $file['name']; 86 | $this->fileSize = $file['size']; 87 | $this->fileType = $this->getFileExt(); 88 | $this->fullName = $this->getFullName(); 89 | $this->filePath = $this->getFilePath(); 90 | $this->fileName = $this->getFileName(); 91 | $dirname = dirname($this->filePath); 92 | //检查文件大小是否超出限制 93 | if (!$this->checkSize()) { 94 | $this->stateInfo = $this->getStateInfo("ERROR_SIZE_EXCEED"); 95 | return; 96 | } 97 | //检查是否不允许的文件格式 98 | if (!$this->checkType()) { 99 | $this->stateInfo = $this->getStateInfo("ERROR_TYPE_NOT_ALLOWED"); 100 | return; 101 | } 102 | //创建目录失败 103 | if (!file_exists($dirname) && !mkdir($dirname, 0777, true)) { 104 | $this->stateInfo = $this->getStateInfo("ERROR_CREATE_DIR"); 105 | return; 106 | } else if (!is_writeable($dirname)) { 107 | $this->stateInfo = $this->getStateInfo("ERROR_DIR_NOT_WRITEABLE"); 108 | return; 109 | } 110 | //移动文件 111 | if (!(move_uploaded_file($file["tmp_name"], $this->filePath) && file_exists($this->filePath))) { //移动失败 112 | $this->stateInfo = $this->getStateInfo("ERROR_FILE_MOVE"); 113 | } else { //移动成功 114 | $this->stateInfo = $this->stateMap[0]; 115 | } 116 | } 117 | /** 118 | * 处理base64编码的图片上传 119 | * @return mixed 120 | */ 121 | private function upBase64() 122 | { 123 | $base64Data = $_POST[$this->fileField]; 124 | $img = base64_decode($base64Data); 125 | $this->oriName = $this->config['oriName']; 126 | $this->fileSize = strlen($img); 127 | $this->fileType = $this->getFileExt(); 128 | $this->fullName = $this->getFullName(); 129 | $this->filePath = $this->getFilePath(); 130 | $this->fileName = $this->getFileName(); 131 | $dirname = dirname($this->filePath); 132 | //检查文件大小是否超出限制 133 | if (!$this->checkSize()) { 134 | $this->stateInfo = $this->getStateInfo("ERROR_SIZE_EXCEED"); 135 | return; 136 | } 137 | //创建目录失败 138 | if (!file_exists($dirname) && !mkdir($dirname, 0777, true)) { 139 | $this->stateInfo = $this->getStateInfo("ERROR_CREATE_DIR"); 140 | return; 141 | } else if (!is_writeable($dirname)) { 142 | $this->stateInfo = $this->getStateInfo("ERROR_DIR_NOT_WRITEABLE"); 143 | return; 144 | } 145 | //移动文件 146 | if (!(file_put_contents($this->filePath, $img) && file_exists($this->filePath))) { //移动失败 147 | $this->stateInfo = $this->getStateInfo("ERROR_WRITE_CONTENT"); 148 | } else { //移动成功 149 | $this->stateInfo = $this->stateMap[0]; 150 | } 151 | } 152 | /** 153 | * 拉取远程图片 154 | * @return mixed 155 | */ 156 | private function saveRemote() 157 | { 158 | $imgUrl = htmlspecialchars($this->fileField); 159 | $imgUrl = str_replace("&", "&", $imgUrl); 160 | //http开头验证 161 | if (strpos($imgUrl, "http") !== 0) { 162 | $this->stateInfo = $this->getStateInfo("ERROR_HTTP_LINK"); 163 | return; 164 | } 165 | //获取请求头并检测死链 166 | $heads = get_headers($imgUrl); 167 | if (!(stristr($heads[0], "200") && stristr($heads[0], "OK"))) { 168 | $this->stateInfo = $this->getStateInfo("ERROR_DEAD_LINK"); 169 | return; 170 | } 171 | //格式验证(扩展名验证和Content-Type验证) 172 | $fileType = strtolower(strrchr($imgUrl, '.')); 173 | if (!in_array($fileType, $this->config['allowFiles']) || stristr($heads['Content-Type'], "image")) { 174 | $this->stateInfo = $this->getStateInfo("ERROR_HTTP_CONTENTTYPE"); 175 | return; 176 | } 177 | //打开输出缓冲区并获取远程图片 178 | ob_start(); 179 | $context = stream_context_create( 180 | array('http' => array( 181 | 'follow_location' => false // don't follow redirects 182 | )) 183 | ); 184 | readfile($imgUrl, false, $context); 185 | $img = ob_get_contents(); 186 | ob_end_clean(); 187 | preg_match("/[\/]([^\/]*)[\.]?[^\.\/]*$/", $imgUrl, $m); 188 | $this->oriName = $m ? $m[1] : ""; 189 | $this->fileSize = strlen($img); 190 | $this->fileType = $this->getFileExt(); 191 | $this->fullName = $this->getFullName(); 192 | $this->filePath = $this->getFilePath(); 193 | $this->fileName = $this->getFileName(); 194 | $dirname = dirname($this->filePath); 195 | //检查文件大小是否超出限制 196 | if (!$this->checkSize()) { 197 | $this->stateInfo = $this->getStateInfo("ERROR_SIZE_EXCEED"); 198 | return; 199 | } 200 | //创建目录失败 201 | if (!file_exists($dirname) && !mkdir($dirname, 0777, true)) { 202 | $this->stateInfo = $this->getStateInfo("ERROR_CREATE_DIR"); 203 | return; 204 | } else if (!is_writeable($dirname)) { 205 | $this->stateInfo = $this->getStateInfo("ERROR_DIR_NOT_WRITEABLE"); 206 | return; 207 | } 208 | //移动文件 209 | if (!(file_put_contents($this->filePath, $img) && file_exists($this->filePath))) { //移动失败 210 | $this->stateInfo = $this->getStateInfo("ERROR_WRITE_CONTENT"); 211 | } else { //移动成功 212 | $this->stateInfo = $this->stateMap[0]; 213 | } 214 | } 215 | /** 216 | * 上传错误检查 217 | * @param $errCode 218 | * @return string 219 | */ 220 | private function getStateInfo($errCode) 221 | { 222 | return !$this->stateMap[$errCode] ? $this->stateMap["ERROR_UNKNOWN"] : $this->stateMap[$errCode]; 223 | } 224 | /** 225 | * 获取文件扩展名 226 | * @return string 227 | */ 228 | private function getFileExt() 229 | { 230 | return strtolower(strrchr($this->oriName, '.')); 231 | } 232 | /** 233 | * 重命名文件 234 | * @return string 235 | */ 236 | private function getFullName() 237 | { 238 | //替换日期事件 239 | $t = time(); 240 | $d = explode('-', date("Y-y-m-d-H-i-s")); 241 | $format = $this->config["pathFormat"]; 242 | $format = str_replace("{yyyy}", $d[0], $format); 243 | $format = str_replace("{yy}", $d[1], $format); 244 | $format = str_replace("{mm}", $d[2], $format); 245 | $format = str_replace("{dd}", $d[3], $format); 246 | $format = str_replace("{hh}", $d[4], $format); 247 | $format = str_replace("{ii}", $d[5], $format); 248 | $format = str_replace("{ss}", $d[6], $format); 249 | $format = str_replace("{time}", $t, $format); 250 | //过滤文件名的非法自负,并替换文件名 251 | $oriName = substr($this->oriName, 0, strrpos($this->oriName, '.')); 252 | $oriName = preg_replace("/[\|\?\"\<\>\/\*\\\\]+/", '', $oriName); 253 | $format = str_replace("{filename}", $oriName, $format); 254 | //替换随机字符串 255 | $randNum = rand(1, 10000000000) . rand(1, 10000000000); 256 | if (preg_match("/\{rand\:([\d]*)\}/i", $format, $matches)) { 257 | $format = preg_replace("/\{rand\:[\d]*\}/i", substr($randNum, 0, $matches[1]), $format); 258 | } 259 | $ext = $this->getFileExt(); 260 | return $format . $ext; 261 | } 262 | /** 263 | * 获取文件名 264 | * @return string 265 | */ 266 | private function getFileName() 267 | { 268 | return substr($this->filePath, strrpos($this->filePath, '/') + 1); 269 | } 270 | /** 271 | * 获取文件完整路径 272 | * @return string 273 | */ 274 | private function getFilePath() 275 | { 276 | $fullname = $this->fullName; 277 | $rootPath = isset($this->config['uploadFilePath'])&&!empty($this->config['uploadFilePath'])?$this->config['uploadFilePath']:$_SERVER['DOCUMENT_ROOT']; 278 | if (substr($fullname, 0, 1) != '/') { 279 | $fullname = '/' . $fullname; 280 | } 281 | return $rootPath . $fullname; 282 | } 283 | 284 | /** 285 | * 文件类型检测 286 | * @return bool 287 | */ 288 | private function checkType() 289 | { 290 | return in_array($this->getFileExt(), $this->config["allowFiles"]); 291 | } 292 | /** 293 | * 文件大小检测 294 | * @return bool 295 | */ 296 | private function checkSize() 297 | { 298 | return $this->fileSize <= ($this->config["maxSize"]); 299 | } 300 | /** 301 | * 获取当前上传成功文件的各项信息 302 | * @return array 303 | */ 304 | public function getFileInfo() 305 | { 306 | return array( 307 | "state" => $this->stateInfo, 308 | "url" => $this->fullName, 309 | "title" => $this->fileName, 310 | "original" => $this->oriName, 311 | "type" => $this->fileType, 312 | "size" => $this->fileSize, 313 | ); 314 | } 315 | } -------------------------------------------------------------------------------- /statics/js/jquery.form.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Form Plugin 3 | * version: 3.51.0-2014.06.20 4 | * Requires jQuery v1.5 or later 5 | * Copyright (c) 2014 M. Alsup 6 | * Examples and documentation at: http://malsup.com/jquery/form/ 7 | * Project repository: https://github.com/malsup/form 8 | * Dual licensed under the MIT and GPL licenses. 9 | * https://github.com/malsup/form#copyright-and-license 10 | */ 11 | /*global ActiveXObject */ 12 | 13 | // AMD support 14 | (function (factory) { 15 | "use strict"; 16 | if (typeof define === 'function' && define.amd) { 17 | // using AMD; register as anon module 18 | define(['jquery'], factory); 19 | } else { 20 | // no AMD; invoke directly 21 | factory( (typeof(jQuery) != 'undefined') ? jQuery : window.Zepto ); 22 | } 23 | } 24 | 25 | (function($) { 26 | "use strict"; 27 | 28 | /* 29 | Usage Note: 30 | ----------- 31 | Do not use both ajaxSubmit and ajaxForm on the same form. These 32 | functions are mutually exclusive. Use ajaxSubmit if you want 33 | to bind your own submit handler to the form. For example, 34 | 35 | $(document).ready(function() { 36 | $('#myForm').on('submit', function(e) { 37 | e.preventDefault(); // <-- important 38 | $(this).ajaxSubmit({ 39 | target: '#output' 40 | }); 41 | }); 42 | }); 43 | 44 | Use ajaxForm when you want the plugin to manage all the event binding 45 | for you. For example, 46 | 47 | $(document).ready(function() { 48 | $('#myForm').ajaxForm({ 49 | target: '#output' 50 | }); 51 | }); 52 | 53 | You can also use ajaxForm with delegation (requires jQuery v1.7+), so the 54 | form does not have to exist when you invoke ajaxForm: 55 | 56 | $('#myForm').ajaxForm({ 57 | delegation: true, 58 | target: '#output' 59 | }); 60 | 61 | When using ajaxForm, the ajaxSubmit function will be invoked for you 62 | at the appropriate time. 63 | */ 64 | 65 | /** 66 | * Feature detection 67 | */ 68 | var feature = {}; 69 | feature.fileapi = $("").get(0).files !== undefined; 70 | feature.formdata = window.FormData !== undefined; 71 | 72 | var hasProp = !!$.fn.prop; 73 | 74 | // attr2 uses prop when it can but checks the return type for 75 | // an expected string. this accounts for the case where a form 76 | // contains inputs with names like "action" or "method"; in those 77 | // cases "prop" returns the element 78 | $.fn.attr2 = function() { 79 | if ( ! hasProp ) { 80 | return this.attr.apply(this, arguments); 81 | } 82 | var val = this.prop.apply(this, arguments); 83 | if ( ( val && val.jquery ) || typeof val === 'string' ) { 84 | return val; 85 | } 86 | return this.attr.apply(this, arguments); 87 | }; 88 | 89 | /** 90 | * ajaxSubmit() provides a mechanism for immediately submitting 91 | * an HTML form using AJAX. 92 | */ 93 | $.fn.ajaxSubmit = function(options) { 94 | /*jshint scripturl:true */ 95 | 96 | // fast fail if nothing selected (http://dev.jquery.com/ticket/2752) 97 | if (!this.length) { 98 | log('ajaxSubmit: skipping submit process - no element selected'); 99 | return this; 100 | } 101 | 102 | var method, action, url, $form = this; 103 | 104 | if (typeof options == 'function') { 105 | options = { success: options }; 106 | } 107 | else if ( options === undefined ) { 108 | options = {}; 109 | } 110 | 111 | method = options.type || this.attr2('method'); 112 | action = options.url || this.attr2('action'); 113 | 114 | url = (typeof action === 'string') ? $.trim(action) : ''; 115 | url = url || window.location.href || ''; 116 | if (url) { 117 | // clean url (don't include hash vaue) 118 | url = (url.match(/^([^#]+)/)||[])[1]; 119 | } 120 | 121 | options = $.extend(true, { 122 | url: url, 123 | success: $.ajaxSettings.success, 124 | type: method || $.ajaxSettings.type, 125 | iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank' 126 | }, options); 127 | 128 | // hook for manipulating the form data before it is extracted; 129 | // convenient for use with rich editors like tinyMCE or FCKEditor 130 | var veto = {}; 131 | this.trigger('form-pre-serialize', [this, options, veto]); 132 | if (veto.veto) { 133 | log('ajaxSubmit: submit vetoed via form-pre-serialize trigger'); 134 | return this; 135 | } 136 | 137 | // provide opportunity to alter form data before it is serialized 138 | if (options.beforeSerialize && options.beforeSerialize(this, options) === false) { 139 | log('ajaxSubmit: submit aborted via beforeSerialize callback'); 140 | return this; 141 | } 142 | 143 | var traditional = options.traditional; 144 | if ( traditional === undefined ) { 145 | traditional = $.ajaxSettings.traditional; 146 | } 147 | 148 | var elements = []; 149 | var qx, a = this.formToArray(options.semantic, elements); 150 | if (options.data) { 151 | options.extraData = options.data; 152 | qx = $.param(options.data, traditional); 153 | } 154 | 155 | // give pre-submit callback an opportunity to abort the submit 156 | if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) { 157 | log('ajaxSubmit: submit aborted via beforeSubmit callback'); 158 | return this; 159 | } 160 | 161 | // fire vetoable 'validate' event 162 | this.trigger('form-submit-validate', [a, this, options, veto]); 163 | if (veto.veto) { 164 | log('ajaxSubmit: submit vetoed via form-submit-validate trigger'); 165 | return this; 166 | } 167 | 168 | var q = $.param(a, traditional); 169 | if (qx) { 170 | q = ( q ? (q + '&' + qx) : qx ); 171 | } 172 | if (options.type.toUpperCase() == 'GET') { 173 | options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; 174 | options.data = null; // data is null for 'get' 175 | } 176 | else { 177 | options.data = q; // data is the query string for 'post' 178 | } 179 | 180 | var callbacks = []; 181 | if (options.resetForm) { 182 | callbacks.push(function() { $form.resetForm(); }); 183 | } 184 | if (options.clearForm) { 185 | callbacks.push(function() { $form.clearForm(options.includeHidden); }); 186 | } 187 | 188 | // perform a load on the target only if dataType is not provided 189 | if (!options.dataType && options.target) { 190 | var oldSuccess = options.success || function(){}; 191 | callbacks.push(function(data) { 192 | var fn = options.replaceTarget ? 'replaceWith' : 'html'; 193 | $(options.target)[fn](data).each(oldSuccess, arguments); 194 | }); 195 | } 196 | else if (options.success) { 197 | callbacks.push(options.success); 198 | } 199 | 200 | options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg 201 | var context = options.context || this ; // jQuery 1.4+ supports scope context 202 | for (var i=0, max=callbacks.length; i < max; i++) { 203 | callbacks[i].apply(context, [data, status, xhr || $form, $form]); 204 | } 205 | }; 206 | 207 | if (options.error) { 208 | var oldError = options.error; 209 | options.error = function(xhr, status, error) { 210 | var context = options.context || this; 211 | oldError.apply(context, [xhr, status, error, $form]); 212 | }; 213 | } 214 | 215 | if (options.complete) { 216 | var oldComplete = options.complete; 217 | options.complete = function(xhr, status) { 218 | var context = options.context || this; 219 | oldComplete.apply(context, [xhr, status, $form]); 220 | }; 221 | } 222 | 223 | // are there files to upload? 224 | 225 | // [value] (issue #113), also see comment: 226 | // https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219 227 | var fileInputs = $('input[type=file]:enabled', this).filter(function() { return $(this).val() !== ''; }); 228 | 229 | var hasFileInputs = fileInputs.length > 0; 230 | var mp = 'multipart/form-data'; 231 | var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp); 232 | 233 | var fileAPI = feature.fileapi && feature.formdata; 234 | log("fileAPI :" + fileAPI); 235 | var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI; 236 | 237 | var jqxhr; 238 | 239 | // options.iframe allows user to force iframe mode 240 | // 06-NOV-09: now defaulting to iframe mode if file input is detected 241 | if (options.iframe !== false && (options.iframe || shouldUseFrame)) { 242 | // hack to fix Safari hang (thanks to Tim Molendijk for this) 243 | // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d 244 | if (options.closeKeepAlive) { 245 | $.get(options.closeKeepAlive, function() { 246 | jqxhr = fileUploadIframe(a); 247 | }); 248 | } 249 | else { 250 | jqxhr = fileUploadIframe(a); 251 | } 252 | } 253 | else if ((hasFileInputs || multipart) && fileAPI) { 254 | jqxhr = fileUploadXhr(a); 255 | } 256 | else { 257 | jqxhr = $.ajax(options); 258 | } 259 | 260 | $form.removeData('jqxhr').data('jqxhr', jqxhr); 261 | 262 | // clear element array 263 | for (var k=0; k < elements.length; k++) { 264 | elements[k] = null; 265 | } 266 | 267 | // fire 'notify' event 268 | this.trigger('form-submit-notify', [this, options]); 269 | return this; 270 | 271 | // utility fn for deep serialization 272 | function deepSerialize(extraData){ 273 | var serialized = $.param(extraData, options.traditional).split('&'); 274 | var len = serialized.length; 275 | var result = []; 276 | var i, part; 277 | for (i=0; i < len; i++) { 278 | // #252; undo param space replacement 279 | serialized[i] = serialized[i].replace(/\+/g,' '); 280 | part = serialized[i].split('='); 281 | // #278; use array instead of object storage, favoring array serializations 282 | result.push([decodeURIComponent(part[0]), decodeURIComponent(part[1])]); 283 | } 284 | return result; 285 | } 286 | 287 | // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz) 288 | function fileUploadXhr(a) { 289 | var formdata = new FormData(); 290 | 291 | for (var i=0; i < a.length; i++) { 292 | formdata.append(a[i].name, a[i].value); 293 | } 294 | 295 | if (options.extraData) { 296 | var serializedData = deepSerialize(options.extraData); 297 | for (i=0; i < serializedData.length; i++) { 298 | if (serializedData[i]) { 299 | formdata.append(serializedData[i][0], serializedData[i][1]); 300 | } 301 | } 302 | } 303 | 304 | options.data = null; 305 | 306 | var s = $.extend(true, {}, $.ajaxSettings, options, { 307 | contentType: false, 308 | processData: false, 309 | cache: false, 310 | type: method || 'POST' 311 | }); 312 | 313 | if (options.uploadProgress) { 314 | // workaround because jqXHR does not expose upload property 315 | s.xhr = function() { 316 | var xhr = $.ajaxSettings.xhr(); 317 | if (xhr.upload) { 318 | xhr.upload.addEventListener('progress', function(event) { 319 | var percent = 0; 320 | var position = event.loaded || event.position; /*event.position is deprecated*/ 321 | var total = event.total; 322 | if (event.lengthComputable) { 323 | percent = Math.ceil(position / total * 100); 324 | } 325 | options.uploadProgress(event, position, total, percent); 326 | }, false); 327 | } 328 | return xhr; 329 | }; 330 | } 331 | 332 | s.data = null; 333 | var beforeSend = s.beforeSend; 334 | s.beforeSend = function(xhr, o) { 335 | //Send FormData() provided by user 336 | if (options.formData) { 337 | o.data = options.formData; 338 | } 339 | else { 340 | o.data = formdata; 341 | } 342 | if(beforeSend) { 343 | beforeSend.call(this, xhr, o); 344 | } 345 | }; 346 | return $.ajax(s); 347 | } 348 | 349 | // private function for handling file uploads (hat tip to YAHOO!) 350 | function fileUploadIframe(a) { 351 | var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle; 352 | var deferred = $.Deferred(); 353 | 354 | // #341 355 | deferred.abort = function(status) { 356 | xhr.abort(status); 357 | }; 358 | 359 | if (a) { 360 | // ensure that every serialized input is still enabled 361 | for (i=0; i < elements.length; i++) { 362 | el = $(elements[i]); 363 | if ( hasProp ) { 364 | el.prop('disabled', false); 365 | } 366 | else { 367 | el.removeAttr('disabled'); 368 | } 369 | } 370 | } 371 | 372 | s = $.extend(true, {}, $.ajaxSettings, options); 373 | s.context = s.context || s; 374 | id = 'jqFormIO' + (new Date().getTime()); 375 | if (s.iframeTarget) { 376 | $io = $(s.iframeTarget); 377 | n = $io.attr2('name'); 378 | if (!n) { 379 | $io.attr2('name', id); 380 | } 381 | else { 382 | id = n; 383 | } 384 | } 385 | else { 386 | $io = $('