<?php /** * uploader * 通用上传类(post、base64、远程下载) * Create By Deepseath * $Author$ * $Id$ */ namespace Com; class Upload { /** * 文件上传成功 */ const ERR_OK = 0; /** * 上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值 */ const ERR_INI_SIZE = 1; /** * 上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值 */ const ERR_FORM_SIZE = 2; /** * 文件只有部分被上传 */ const ERR_PARTIAL = 3; /** * 没有文件被上传 */ const ERR_NO_FILE = 4; /** * 找不到临时文件夹 */ const ERR_NO_TMP_DIR = 6; /** * 文件写入失败 */ const ERR_CANT_WRITE = 7; /** * getimagesize() 图片格式映射 */ const GIF = 1; const JPG = 2; const PNG = 3; const SWF = 4; const PSD = 5; const BMP = 6; /** * intel byte order */ const TIFF = 7; /** * motorola byte order */ const TIFF2 = 8; const JPC = 9; const JP2 = 10; const JPX = 11; const JB2 = 12; const SWC = 13; const IFF = 14; const WBMP = 15; const XBM = 16; /** * 上传状态信息,可用于判断是否上传成功,SUCCESS为成功。也可以使用$this->get_file_info的'error_msg'键名来判断 * * @var string */ public $error_msg = ''; /** * 文件域名 * * @var string */ private $_file_field; /** * 文件上传对象信息 $_FILES[$this->_file_field] * * @var array */ private $_file; /** * 上传类型 * * @var string */ private $_upload_type; /** * 配置信息 * * @var array * @example <pre>$config = array( * 'save_dir_path' = > '',// 必须配置。定义基本储存目录 * 'allow_files' => array('png', 'jpg', 'jpeg', 'gif', 'bmp'),// 必须配置。允许上传的文件格式,默认为:array('png', 'jpg', 'jpeg', 'gif', 'bmp') * 'file_name_format' => '',// 定义文件名保存格式,见:$this->_get_full_name() 方法,默认为:auto(YYYY/mm/) * 'max_size' => '2048000',// 上传大小限制,单位B,默认:2048000 * 'source_name' => 'remote.png',// 原始文件名,可不配置 * )</pre> */ private $_config; /** * 上传的原始文件名 * * @var string */ private $_source_name; /** * 重命名后的纯文件名,不包含储存目录路径 * * @var string */ private $_file_name; /** * 重命名后的文件名,可能包含目录结构路径,不包含储存根目录路径信息 * * @var string */ private $_full_name; /** * 完整文件储存路径,(储存目录+储存文件名) * * @var string */ private $_file_path; /** * 文件大小 * * @var number */ private $_file_size; /** * 是否为图片文件 * * @var boolean */ private $_is_image = false; /** * 如果为图片,图片的宽度 * * @var number */ private $_image_width = 0; /** * 如果为图片,图片的高度 * * @var number */ private $_image_height = 0; /** * 文件类型(无前导“.”) * * @var string */ private $_file_type; /** * 当前进程允许的最大上传尺寸,单位:B * * @var number */ private $_max_size = 1024000; /** * 当前进程允许的文件类型 * * @var array */ private $_allow_type = array(); /** * 错误编码 * * @var number */ private $_error_code = - 1; /** * 文件类型与扩展名的映射关系 */ private $_media_type_maps = array( // 音频、声音文件 2 => array( 'mp3' ), 3 => array( 'mp4' ) ); /** * 上传状态文字提示映射表 * * @var array */ private $_error_map = array( self::ERR_OK => 'SUCCESS', // 上传成功标记,不可更改!!(在UEditor中内不可改变,否则flash判断会出错) self::ERR_INI_SIZE => '文件大小超出 upload_max_filesize 限制', self::ERR_FORM_SIZE => '文件大小超出 MAX_FILE_SIZE 限制', self::ERR_PARTIAL => '文件未被完整上传', self::ERR_NO_FILE => '没有文件被上传', self::ERR_NO_TMP_DIR => '上传文件为空', self::ERR_CANT_WRITE => '文件写入失败', 'ERROR_TMP_FILE' => '临时文件错误', 'ERROR_TMP_FILE_NOT_FOUND' => '找不到临时文件', 'ERROR_TMPFILE' => '非法上传的临时文件', 'ERROR_SIZE_EXCEED' => '文件大小(%s)超出系统限制(%s)', 'ERROR_TYPE_NOT_ALLOWED' => '文件类型不允许', 'ERROR_CREATE_DIR' => '目录创建失败', 'ERROR_DIR_NOT_WRITEABLE' => '目录没有写权限', 'ERROR_FILE_MOVE' => '文件保存时出错', 'ERROR_FILE_NOT_FOUND' => '找不到上传文件', 'ERROR_WRITE_CONTENT' => '写入文件内容错误', 'ERROR_UNKNOWN' => '未知错误', 'ERROR_BASE64_NULL' => '文件内容不存在或不合法', 'ERROR_DEAD_LINK' => '远程图片链接不可用', 'ERROR_HTTP_LINK' => '远程图片链接不是 http 协议', 'ERROR_HTTP_CONTENTTYPE' => '远程图片链接 contentType 不正确', 'ERROR_HTTP_GET_FAILED' => '获取远程图片链接(%s)发生错误', 'ERROR_SAVE_DIR_PATH_ERROR' => '储存目录未定义或不可写 %s', 'ERROR_NOT_IMAGE' => '只允许图片格式', 'ERROR_CREATE_TMP_FILE' => '创建临时文件发生错误', 'ERROR_BASE64_NULL_LOCAL' => '文件内容不合法', 'ERROR_CREATE_TMP_FILE_LOCAL' => '创建本地临时文件发生错误', 'ERROR_FILE_MOVE_LOCAL' => '文件转移保存时出错' ); /** * 构造函数,处理上传业务 * * @param string $file_field * 上传文件表单控件名称 或 base64编码字符串表单控件名 或 远程图片url 或 直接传入$_FILES[key]的数组(此方式用于处理多个文件上传) * @param array $config * 配置项,详见成员初始化介绍 * <pre> * + save_dir_path 必须。定义基本储存目录 * + allow_files 必须。允许上传的文件格式。默认为:array('png', 'jpg', 'jpeg', 'gif', 'bmp') * + file_name_format 可选。@see upload::_get_full_name() 方法,默认为:auto(YYYY/mm/) * + max_size 可选。上传大小限制。单位:B。默认:2048000 * + source_name 可选。原始文件名,可不配置 * </pre> * @param bool $type * 上传类型 remote|base64|upload|local,默认:upload。 * <pre> * remote : 远程抓取图片。使用此类型,$file_field 表示远程图片的 url 地址 * base64 : 处理base64编码上传图片。使用此类型,$file_field 表示存放base64编码字符串的表单控件名,只接受$_POST方式 * local : 将文件流写入到附件内。使用此类型,$file_field 表示文件经base64编码后的字符串 * upload : 默认。处理普通上传的文件。使用此类型,$file_field 表示上传表单控件的名 * </pre> */ public function __construct($file_field, $config, $type = 'upload') { $this->_file_field = $file_field; $this->_config = $config; $this->_upload_type = rstrtolower($type); if ($this->_upload_type == 'remote') { $this->_save_remote(); } elseif ($this->_upload_type == 'base64') { $this->_upload_base64(); } elseif ($this->_upload_type == 'local') { $this->_upload_local(); } else { $this->_upload_file(); } } /** * 获取当前上传成功文件的各项信息,该结果不可直接暴露在前端!输出给前端必须重新格式剔除某些路径信息 * * @return array */ public function get_file_info() { return array( 'error_code' => $this->_error_code, // 只用于输出PHP内置的错误常量值 'error' => $this->error_msg, // 错误信息,SUCCESS为成功,判断上传成功与否也可以直接使用$this->error_msg 'save_path' => $this->_full_name, // 文件储存路径,包含储存目录路径,但不包含储存根目录 'file_name' => $this->_file_name, // 重命名后的文件名 'source_name' => $this->_source_name, // 原始文件名,对于远程读取以及base64此值无具体意义 'file_type' => '.' . $this->_file_type, // 文件类型后缀,包含前缀“.” 'file_size' => $this->_file_size, // 文件尺寸值,单位:B,数值 'is_image' => $this->_is_image, // 是否为图片 'width' => $this->_image_width, // 图片宽度 'height' => $this->_image_height, // 图片高度 'file_path' => $this->_file_path, // 文件的物理绝对路径 'type_name' => $this->_file_type, // 文件类型 'size_string' => size_count($this->_file_size), // 易读的文件尺寸字符串 'upload_type' => $this->_upload_type, // 上传类型 'config' => $this->_config ); // 经过验证后的配置信息 } /** * 上传文件的主处理方法(普通上传) * * @return mixed */ private function _upload_file() { // 检查上传配置参数 if (! $this->_check_config()) { return; } if (is_array($this->_file_field) && isset($this->_file_field['error']) && isset($this->_file_field['tmp_name']) && isset($this->_file_field['name']) && $this->_file_field['size']) { // 传入的 $file_field 是一个上传的文件信息数组,则直接使用该值 // 此方式对于处理多个上传文件可能更灵活一些 $file = $this->_file = $this->_file_field; } else { // 传入的是上传表单控件名 // 赋值上传对象数组 $file = $this->_file = isset($_FILES[$this->_file_field]) ? $_FILES[$this->_file_field] : false; if (! $file || ! isset($file['error']) || ! isset($file['tmp_name']) || ! isset($file['name']) || ! isset($file['size'])) { $this->error_msg = $this->_get_error_msg('ERROR_FILE_NOT_FOUND'); return; } } if ($this->_file['error']) { // 上传发生错误 $this->error_msg = $this->_get_error_msg($this->_file['error']); return; } elseif (! is_file($file['tmp_name'])) { // 临时文件不存在 $this->error_msg = $this->_get_error_msg('ERROR_TMP_FILE_NOT_FOUND'); return; } elseif (! is_uploaded_file($file['tmp_name'])) { // 检查是否为上传的文件 // TODO 可能某些环境此判断会过于严格 $this->error_msg = $this->_get_error_msg('ERROR_TMPFILE'); return; } // 尝试判断图片格式 $_image_ext = $this->_get_image_ext($file['tmp_name']); $this->_is_image = $_image_ext ? true : false; // 如果是图片尝试使用真实的图片格式来命名原始文件名 $this->__convert_real_image_ext($file['name'], $_image_ext); $this->_file_size = $file['size']; $this->_file_type = $this->_get_file_ext(); $this->_full_name = $this->_get_full_name(); $this->_file_path = $this->_get_file_path(); $this->_file_name = $this->_get_file_name(); // 检查文件的尺寸、并尝试创建储存目录 if ($this->_check_file() !== true) { @unlink($file['tmp_name']); return; } // 移动文件 // TODO 未使用 move_uploaded_file() 因某些环境下使用此函数会过于严格 if (! rename($file['tmp_name'], $this->_file_path) && ! is_file($this->_file_path)) { // 移动失败 $this->error_msg = $this->_get_error_msg('ERROR_FILE_MOVE'); } else { // 移动成功 $this->error_msg = $this->_get_error_msg(self::ERR_OK); } @unlink($file['tmp_name']); } /** * 处理base64编码的图片上传 * * @return mixed */ private function _upload_base64() { // 检查上传配置参数 if (! $this->_check_config()) { return; } $base64Data = ! empty($_POST[$this->_file_field]) ? (string) $_POST[$this->_file_field] : ''; $img = base64_decode($base64Data); if ($base64Data === '' || $img === false) { $this->error_msg = $this->_get_error_msg('ERROR_BASE64_NULL'); return; } // @ 创建临时文件 $tmp_file = $this->_create_tmpfile($img); if (! $tmp_file) { $this->error_msg = $this->_get_error_msg('ERROR_CREATE_TMP_FILE'); return; } // 判断图片文件类型 $_image_ext = $this->_get_image_ext($tmp_file); if (! $_image_ext) { $this->error_msg = $this->_get_error_msg('ERROR_NOT_IMAGE'); } $this->_is_image = true; $this->__convert_real_image_ext($this->_config['source_name'], $_image_ext); $this->_file_size = strlen($img); $this->_file_type = $this->_get_file_ext(); $this->_full_name = $this->_get_full_name(); $this->_file_path = $this->_get_file_path(); $this->_file_name = $this->_get_file_name(); // 检查文件的格式、尺寸、并尝试创建储存目录 if ($this->_check_file() !== true) { @unlink($tmp_file); return; } // 移动文件 if (! rename($tmp_file, $this->_file_path) && ! is_file($this->_file_path)) { // 移动失败 $this->error_msg = $this->_get_error_msg('ERROR_FILE_MOVE'); } else { // 移动成功 $this->error_msg = $this->_get_error_msg(self::ERR_OK); } @unlink($tmp_file); } /** * 获取远程图片 * * @return mixed */ private function _save_remote() { // 检查上传配置参数 if (! $this->_check_config()) { return; } $imgUrl = htmlspecialchars($this->_file_field); $imgUrl = str_replace('&', '&', $imgUrl); // http开头验证 if (strpos($imgUrl, 'http') !== 0 || ($parse_url = parse_url($imgUrl)) === false || ! isset($parse_url['scheme']) || ! isset($parse_url['host'])) { $this->error_msg = $this->_get_error_msg('ERROR_HTTP_LINK'); return; } // 获取请求头并检测死链 $heads = get_headers($imgUrl, 1); if ($heads === false || (! (stristr($heads[0], '200') && stristr($heads[0], 'OK')))) { $this->error_msg = $this->_get_error_msg('ERROR_DEAD_LINK'); return; } // 通过url链接来判断格式 // TODO 扩展名验证可能过于严格,某些动态路径的图片可能无法被获取到 $file_type = $this->_get_file_ext($imgUrl); /* * if (!in_array($file_type, $this->_config['allow_files'])) { * $this->error_msg = $this->_get_error_msg('ERROR_HTTP_CONTENTTYPE'); * return; * } */ // 格式验证(扩展名验证和Content-Type验证) if (! isset($heads['Content-Type']) || ! stristr($heads['Content-Type'], 'image')) { $this->error_msg = $this->_get_error_msg('ERROR_HTTP_CONTENTTYPE'); return; } // 根据 Content-type 格式重构文件名 if (preg_match('/image\/(\w+)/i', $heads['Content-Type'], $match)) { $this->_config['source_name'] = 'remote.' . trim($match[1]); } // 伪造来路url避免被防盗链屏蔽 $referer = $parse_url['scheme'] . '://' . $parse_url['host'] . '/'; // 伪造浏览器:Accept $browser_accept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; // 伪造 User-Agent $browser_user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'Mozilla/5.0 (Windows NT 5.1; rv:29.0) Gecko/20100101 Firefox/29.0'; // 获取远程图片 // TODO 关于 stream context 的配置,后面可考虑也加入配置里,方便根据环境灵活使用调用 $context = stream_context_create(array( 'http' => array( 'follow_location' => false, // don't follow redirects 'timeout' => 30, 'header' => array( 'Referer: ' . $referer . "\r\n" . 'Accept: ' . $browser_accept . "\r\n" . 'User-Agent: ' . $browser_user_agent ) ) )); $img = file_get_contents($imgUrl, false, $context); if ($img === false) { $this->error_msg = $this->_get_error_msg('ERROR_HTTP_GET_FAILED', rhtmlspecialchars($imgUrl)); return; } // @ 创建临时文件 $tmp_file = $this->_create_tmpfile($img); if (! $tmp_file) { $this->error_msg = $this->_get_error_msg('ERROR_CREATE_TMP_FILE'); return; } // 判断图片文件类型 $_image_ext = $this->_get_image_ext($tmp_file); if (! $_image_ext) { $this->error_msg = $this->_get_error_msg('ERROR_NOT_IMAGE'); } $this->_is_image = true; $this->__convert_real_image_ext($this->_config['source_name'], $_image_ext); $this->_file_size = strlen($img); $this->_file_type = $this->_get_file_ext(); $this->_full_name = $this->_get_full_name(); $this->_file_path = $this->_get_file_path(); $this->_file_name = $this->_get_file_name(); // 检查文件的格式、尺寸、并尝试创建储存目录 if ($this->_check_file() !== true) { @unlink($tmp_file); return; } // 移动文件 if (! rename($tmp_file, $this->_file_path) || ! is_file($this->_file_path)) { // 移动失败 $this->error_msg = $this->_get_error_msg('ERROR_FILE_MOVE'); } else { // 移动成功 $this->error_msg = $this->_get_error_msg(self::ERR_OK); } @unlink($tmp_file); } /** * 检查上传配置参数 * * @return boolean */ private function _check_config() { // 检查储存目录是否定义且是否可写 if (empty($this->_config['save_dir_path']) || ! is_writable($this->_config['save_dir_path'])) { $this->error_msg = $this->_get_error_msg('ERROR_SAVE_DIR_PATH_ERROR', ! isset($this->_config['save_dir_path']) ? 'null' : $this->_config['save_dir_path']); return false; } // 未设置文件储存名格式,则使用自动设置"auto" if (empty($this->_config['file_name_format'])) { $this->_config['file_name_format'] = 'auto'; } // 未定义允许上传的文件类型,则使用空数组 if (empty($this->_config['allow_files'])) { $this->_config['allow_files'] = array(); } else { // 尝试剔除定义的扩展名前缀“.”,并转为小写字符 foreach ($this->_config['allow_files'] as &$ext) { if (($s = strpos($ext, '.')) !== false) { $ext = trim($ext); $ext = trim(substr($ext, $s + 1)); } $ext = rstrtolower($ext); } } // 未定义允许上传的最大尺寸,则默认为:1m if (empty($this->_config['max_size'])) { $this->_config['max_size'] = 1024000; // 1m } // 如果未定义原文件名,则给出一个假的文件名 if (empty($this->_config['source_name'])) { $this->_config['source_name'] = 'unknow.unknow'; } return true; } /** * 检查文件的限制:尺寸、尝试创建目录 */ private function _check_file() { // @ 检查是否不允许的文件格式 if (! $this->_check_type()) { $this->error_msg = $this->_get_error_msg('ERROR_TYPE_NOT_ALLOWED'); return; } // @ 检查文件大小是否超出限制 if (! $this->_check_size()) { $this->error_msg = $this->_get_error_msg('ERROR_SIZE_EXCEED', size_count($this->_file_size), size_count($this->_max_size)); return; } // 储存路径的目录 $dirname = dirname($this->_file_path); // @ 创建目录 rmkdir($dirname, 0777, true); // 创建目录失败 if (! is_dir($dirname)) { $this->error_msg = $this->_get_error_msg('ERROR_CREATE_DIR'); return; } // 目录不可写 if (! is_writeable($dirname)) { $this->error_msg = $this->_get_error_msg('ERROR_DIR_NOT_WRITEABLE'); return; } return true; } /** * 上传错误检查 * * @param * $errCode * @return string */ private function _get_error_msg($errCode) { $this->_error_code = $errCode; if ($errCode == 0) { return $this->_error_map[$errCode]; } $args = func_get_args(); $keys = is_array($args) ? $args : explode(',', preg_replace('/[\s|\'|\']*/e', '', $args)); if (! empty($keys)) { for ($i = 0; $i < sizeof($keys); $i ++) { if (! empty($keys[$i]) && $keys[$i] != '') { $keys[$i] = isset($this->_error_map[$keys[$i]]) ? $this->_error_map[$keys[$i]] : $keys[$i]; } } $format = array_shift($keys); $flen = sizeof(preg_split('/\%\w/i', $format)); while (sizeof($keys) < $flen) { // 使数组元素总数与占位符数目相对应,否则会报错 $keys[] = ''; } return vsprintf($format, $keys); } return empty($keys) ? $this->_error_map['ERROR_UNKNOWN'] : join('-', $keys); } /** * 获取文件扩展名 * * @return string */ private function _get_file_ext($filename = null) { // 未指定文件名则使用原始文件名 if ($filename === null) { $filename = $this->_source_name; } // 取得文件扩展名 return addslashes(rstrtolower(substr(strrchr($filename, '.'), 1, 10))); } /** * 重命名文件并确定储存目录 * * @return string */ private function _get_full_name() { // @ 储存目录和文件名路径(相对储存根目录的文件路径),输出此变量 $_full_name = ''; // 当前系统时间戳 $timestamp = NOW_TIME; // 当前文件扩展名 $file_ext = '.' . $this->_get_file_ext(); if (strtolower($this->_config['file_name_format']) == 'auto') { // 使用系统自动创建储存名和目录 // 构造形如:2014/05/2817534012345678 $format = '{yyyy}{dir}{mm}{dir}{dd}{hh}{ii}{ss}{rand:8}'; } else { // 使用config配置的自定义方式创建储存文件名和目录 $format = $this->_config['file_name_format']; } // 当前时间变量值 $d = explode('-', rgmdate($timestamp, 'Y-y-m-d-H-i-s')); // 替换关于时间标记的字符 $format = str_replace('{yyyy}', $d[0], $format); $format = str_replace('{yy}', $d[1], $format); $format = str_replace('{mm}', $d[2], $format); $format = str_replace('{dd}', $d[3], $format); $format = str_replace('{hh}', $d[4], $format); $format = str_replace('{ii}', $d[5], $format); $format = str_replace('{ss}', $d[6], $format); // 替换时间戳 $format = str_replace('{time}', $timestamp, $format); // 是否加入文件名 if (strpos($format, '{filename}') !== false) { // 取得无扩展名的文件名 $source_name = substr($this->_source_name, 0, strrpos($this->_source_name, '.')); // 剔除非法的文件名字符 $source_name = preg_replace('/[\|\?"\<\>\/\*\\\\]+/', '', $source_name); // 替换标记 $format = str_replace('{filename}', $source_name, $format); } // 是否需要加入随机数 if (preg_match('/\{rand\:([\d]*)\}/i', $format, $match)) { // 获取随机数需要的位数 $rand_count = $match[1] ? $match[1] : 8; // 真实的随机数 $randNum = sprintf("%0{$rand_count}s", substr(mt_rand(1, 10000000000) . mt_rand(1, 10000000000), 0, $rand_count)); // 替换标记\/:*?"<>| $format = str_replace($match[0], $randNum, $format); } // 替换非法文件名字符 $format = preg_replace('/[\|\?"\<\>\/\*\\\\]+/', '', $format); // 替换目录符号 $format = str_replace('{dir}', '/', $format); // 构造新文件名和扩展名 $_full_name = $format . $file_ext; return $_full_name; } /** * 获取文件名 * * @return string */ private function _get_file_name() { return basename($this->_file_path); return substr($this->_file_path, strrpos($this->_file_path, DIRECTORY_SEPARATOR) + 1); } /** * 获取文件完整储存路径(储存目录+储存文件名) * * @return string */ private function _get_file_path() { $fullname = $this->_full_name; $root_path = $this->_config['save_dir_path']; return $this->_format_path($root_path . '/' . $fullname); } /** * 文件类型检测 * * @return bool */ private function _check_type() { $this->_allow_type = $this->_config['allow_files']; return $this->_config['allow_files'] && in_array($this->_get_file_ext(), $this->_config['allow_files']); } /** * 文件大小检测 * * @return bool */ private function _check_size() { return ($this->_file_size) <= ($this->_proc_allow_max_size()); } /** * 返回当前进程允许的最大文件大小(获取所有配置内的最小值),单位:B * * @return number */ private function _proc_allow_max_size() { $max_size = 0; $system_max_size = 0; // 系统环境的大小限制 if (function_exists('ini_get')) { $max_size = min(count_size(ini_get('memory_limit')), count_size(ini_get('post_max_size')), count_size(ini_get('upload_max_filesize'))); $system_max_size = $max_size; } // 获取上传表单设置的 MAX_FILE_SIZE 尺寸 if (isset($_POST['MAX_FILE_SIZE']) && is_scalar($_POST['MAX_FILE_SIZE'])) { $max_size = min(count_size($_POST['MAX_FILE_SIZE']), $max_size); } // 自行配置的上传最大尺寸 if (isset($this->_config['max_size'])) { $max_size = min(count_size($this->_config['max_size']), $max_size); } if ($max_size <= 0) { $max_size = $system_max_size ? $system_max_size : count_size('1m'); } return $this->_max_size = $max_size; } /** * 格式化路径分隔符号为系统符号 * * @param string $path * @return string */ private function _format_path($path) { return str_replace('.' . DIRECTORY_SEPARATOR, '', preg_replace(array( '/\/+/', '/\\\+/' ), DIRECTORY_SEPARATOR, $path)); } /** * 获取图片文件扩展名 * * @param string $filename * @return string */ private function _get_image_ext($filename) { if (! is_file($filename)) { return false; } $get_image_size = getimagesize($filename); if ($get_image_size && is_array($get_image_size) && ! empty($get_image_size[2])) { $this->_image_width = $get_image_size[0]; $this->_image_height = $get_image_size[1]; switch ($get_image_size[2]) { case self::GIF: return 'gif'; case self::JPG: return 'jpg'; case self::PNG: return 'png'; case self::SWF: return 'swf'; case self::PSD: return 'psd'; case self::BMP: return 'bmp'; case self::TIFF: return 'tiff'; case self::TIFF2: return 'tiff'; case self::JPC: return 'jpc'; case self::JP2: return 'jp2'; case self::JPX: return 'jpx'; case self::JB2: return 'jb2'; case self::SWC: return 'swc'; case self::IFF: return 'iff'; case self::WBMP: return 'wbmp'; case self::XBM: return 'xbm'; } } unset($get_image_size); return false; } /** * 创建一个临时文件,用于远程抓取或者base64上传的情况 * * @param unknown $data * @return string | boolean */ private function _create_tmpfile($data) { // 建立一个临时文件,文件名使用时间戳+4位随机数,储存在系统临时目录内 $temp_file = tempnam(sys_get_temp_dir(), md5(NOW_TIME) . sprintf('%04s', mt_rand(1, 9999))); // 写入数据到此临时文件 if (! is_writable($temp_file) || file_put_contents($temp_file, $data) === false || ! is_file($temp_file)) { return false; } unset($data); // 返回该临时文件的路径 return $temp_file; } /** * 为源文件名加上真实的图片扩展名 * * @param string $filename * @param string $image_ext * @return boolean */ private function __convert_real_image_ext($filename, $image_ext) { if (! $this->_is_image) { $this->_source_name = $filename; return true; } if (strpos($filename, '.') === false) { $this->_source_name = $filename . '.' . $image_ext; return true; } $this->_source_name = preg_replace('/[^\.]+$/s', $image_ext, $filename); return true; } /** * 将本地文件写入到附件 */ protected function _upload_local() { // 检查上传配置参数 if (! $this->_check_config()) { return; } $file_content = base64_decode($this->_file_field); if (empty($this->_file_field) || $file_content === false) { $this->error_msg = $this->_get_error_msg('ERROR_BASE64_NULL_LOCAL'); return; } // @ 创建临时文件 $tmp_file = $this->_create_tmpfile($file_content); if (! $tmp_file) { $this->error_msg = $this->_get_error_msg('ERROR_CREATE_TMP_FILE_LOCAL'); return; } // 判断图片文件类型 $_image_ext = $this->_get_image_ext($tmp_file); if ($_image_ext) { $this->_is_image = true; } else { $this->_is_image = false; } $this->__convert_real_image_ext($this->_config['source_name'], $_image_ext); $this->_file_size = strlen($file_content); $this->_file_type = $this->_get_file_ext(); $this->_full_name = $this->_get_full_name(); $this->_file_path = $this->_get_file_path(); $this->_file_name = $this->_get_file_name(); // 检查文件的格式、尺寸、并尝试创建储存目录 if ($this->_check_file() !== true) { @unlink($tmp_file); return; } // 移动文件 if (! rename($tmp_file, $this->_file_path) && ! is_file($this->_file_path)) { // 移动失败 $this->error_msg = $this->_get_error_msg('ERROR_FILE_MOVE_LOCAL'); } else { // 移动成功 $this->error_msg = $this->_get_error_msg(self::ERR_OK); } @unlink($tmp_file); } }