From 984e87690cfe36d39e4ad0d5da26fe46aca3b325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BA=A6=E5=BD=93=E8=8B=97=E5=84=BF?= Date: Mon, 15 Apr 2013 14:35:25 +0800 Subject: [PATCH] =?UTF-8?q?Org=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4?= =?UTF-8?q?=E7=B1=BB=E5=BA=93=E7=A7=BB=E5=9B=9EThink=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E7=A9=BA=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Think/Crypt.php | 74 ++ Library/Think/Image.php | 64 + Library/Think/Image/Driver/Gd.php | 549 ++++++++ Library/Think/Image/Driver/Gif.php | 570 ++++++++ Library/Think/Image/Driver/Imagick.php | 591 +++++++++ Library/Think/Oauth.php | 72 + Library/Think/Oauth/Driver.php | 233 ++++ Library/Think/Oauth/Driver/Baidu.php | 93 ++ Library/Think/Oauth/Driver/Diandian.php | 93 ++ Library/Think/Oauth/Driver/Douban.php | 91 ++ Library/Think/Oauth/Driver/Github.php | 93 ++ Library/Think/Oauth/Driver/Google.php | 99 ++ Library/Think/Oauth/Driver/Kaixin.php | 94 ++ Library/Think/Oauth/Driver/Msn.php | 100 ++ Library/Think/Oauth/Driver/Qq.php | 102 ++ Library/Think/Oauth/Driver/Renren.php | 116 ++ Library/Think/Oauth/Driver/Sina.php | 93 ++ Library/Think/Oauth/Driver/Sohu.php | 93 ++ Library/Think/Oauth/Driver/T163.php | 95 ++ Library/Think/Oauth/Driver/Taobao.php | 97 ++ Library/Think/Oauth/Driver/Tencent.php | 98 ++ Library/Think/Oauth/Driver/X360.php | 93 ++ Library/Think/Parser.php | 32 + Library/Think/Parser/Driver/Markdown.php | 1534 ++++++++++++++++++++++ Library/Think/Parser/Driver/Ubb.php | 291 ++++ Library/Think/Upload.php | 515 ++++++++ 26 files changed, 5975 insertions(+) create mode 100644 Library/Think/Crypt.php create mode 100644 Library/Think/Image.php create mode 100644 Library/Think/Image/Driver/Gd.php create mode 100644 Library/Think/Image/Driver/Gif.php create mode 100644 Library/Think/Image/Driver/Imagick.php create mode 100644 Library/Think/Oauth.php create mode 100644 Library/Think/Oauth/Driver.php create mode 100644 Library/Think/Oauth/Driver/Baidu.php create mode 100644 Library/Think/Oauth/Driver/Diandian.php create mode 100644 Library/Think/Oauth/Driver/Douban.php create mode 100644 Library/Think/Oauth/Driver/Github.php create mode 100644 Library/Think/Oauth/Driver/Google.php create mode 100644 Library/Think/Oauth/Driver/Kaixin.php create mode 100644 Library/Think/Oauth/Driver/Msn.php create mode 100644 Library/Think/Oauth/Driver/Qq.php create mode 100644 Library/Think/Oauth/Driver/Renren.php create mode 100644 Library/Think/Oauth/Driver/Sina.php create mode 100644 Library/Think/Oauth/Driver/Sohu.php create mode 100644 Library/Think/Oauth/Driver/T163.php create mode 100644 Library/Think/Oauth/Driver/Taobao.php create mode 100644 Library/Think/Oauth/Driver/Tencent.php create mode 100644 Library/Think/Oauth/Driver/X360.php create mode 100644 Library/Think/Parser.php create mode 100644 Library/Think/Parser/Driver/Markdown.php create mode 100644 Library/Think/Parser/Driver/Ubb.php create mode 100644 Library/Think/Upload.php diff --git a/Library/Think/Crypt.php b/Library/Think/Crypt.php new file mode 100644 index 00000000..4ed47232 --- /dev/null +++ b/Library/Think/Crypt.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- + +namespace Think; + +class Crypt { + /** + * 加密字符串 + * @access public + * @param string $str 字符串 + * @param string $key 加密key + * @return string + */ + static public function encrypt($data,$key,$expire=0){ + $key = md5($key); + $data = base64_encode($data); + $x = 0; + $len = strlen($data); + $l = strlen($key); + $char = ''; + for ($i = 0; $i< $len; $i++) { + if ($x == $l) $x = 0; + $char .=substr($key, $x, 1); + $x++; + } + $str = sprintf('%010d', $expire ? $expire + time() : 0); + for ($i=0; $i< $len; $i++) { + $str .= chr(ord(substr($data, $i, 1)) + (ord(substr($char, $i, 1))) % 256); + } + return str_replace('=', '', base64_encode($str)); + } + + /** + * 解密字符串 + * @access public + * @param string $str 字符串 + * @param string $key 加密key + * @return string + */ + static public function decrypt($data,$key){ + $key = md5($key); + $x = 0; + $data = base64_decode($data); + $expire = substr($data,0,10); + $data = substr($data,10); + if($expire > 0 && $expire +// +---------------------------------------------------------------------- + +namespace Think; + +/* 缩略图相关常量定义 */ +define('THINKIMAGE_THUMB_SCALING', 1); //常量,标识缩略图等比例缩放类型 +define('THINKIMAGE_THUMB_FILLED', 2); //常量,标识缩略图缩放后填充类型 +define('THINKIMAGE_THUMB_CENTER', 3); //常量,标识缩略图居中裁剪类型 +define('THINKIMAGE_THUMB_NORTHWEST', 4); //常量,标识缩略图左上角裁剪类型 +define('THINKIMAGE_THUMB_SOUTHEAST', 5); //常量,标识缩略图右下角裁剪类型 +define('THINKIMAGE_THUMB_FIXED', 6); //常量,标识缩略图固定尺寸缩放类型 + +/* 水印相关常量定义 */ +define('THINKIMAGE_WATER_NORTHWEST', 1); //常量,标识左上角水印 +define('THINKIMAGE_WATER_NORTH', 2); //常量,标识上居中水印 +define('THINKIMAGE_WATER_NORTHEAST', 3); //常量,标识右上角水印 +define('THINKIMAGE_WATER_WEST', 4); //常量,标识左居中水印 +define('THINKIMAGE_WATER_CENTER', 5); //常量,标识居中水印 +define('THINKIMAGE_WATER_EAST', 6); //常量,标识右居中水印 +define('THINKIMAGE_WATER_SOUTHWEST', 7); //常量,标识左下角水印 +define('THINKIMAGE_WATER_SOUTH', 8); //常量,标识下居中水印 +define('THINKIMAGE_WATER_SOUTHEAST', 9); //常量,标识右下角水印 + +/** + * 图片处理驱动类,可配置图片处理库 + * 目前支持GD库和imagick + * @author 麦当苗儿 + */ +class Image { + /** + * 图片资源 + * @var resource + */ + private static $im; + + /** + * 构造方法,用于实例化一个图片处理对象 + * @param string $type 要使用的类库,默认使用GD库 + */ + public function init($type = 'Gd', $imgname = null){ + /* 引入处理库,实例化图片处理对象 */ + $class = '\\Think\\Image\\Driver\\'.ucwords($type); + self::$im = new $class($imgname); + return self::$im; + } + + // 调用驱动类的方法 + static public function __callStatic($method, $params){ + if(empty(self::$im)) { + self::init(); + } + return call_user_func_array(array(self::$im, $method), $params); + } + +} \ No newline at end of file diff --git a/Library/Think/Image/Driver/Gd.php b/Library/Think/Image/Driver/Gd.php new file mode 100644 index 00000000..359d1afb --- /dev/null +++ b/Library/Think/Image/Driver/Gd.php @@ -0,0 +1,549 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Image\Driver; + +class Gd{ + /** + * 图像资源对象 + * @var resource + */ + private $im; + private $gif; + /** + * 图像信息,包括width,height,type,mime,size + * @var array + */ + private $info; + + /** + * 构造方法,可用于打开一张图像 + * @param string $imgname 图像路径 + */ + public function __construct($imgname = null) { + $imgname && $this->open($imgname); + } + + /** + * 打开一张图像 + * @param string $imgname 图像路径 + */ + public function open($imgname){ + //检测图像文件 + if(!is_file($imgname)) throw new Exception('不存在的图像文件'); + + //获取图像信息 + $info = getimagesize($imgname); + + //检测图像合法性 + if(false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))){ + throw new Exception('非法图像文件'); + } + + //设置图像信息 + $this->info = [ + 'width' => $info[0], + 'height' => $info[1], + 'type' => image_type_to_extension($info[2], false), + 'mime' => $info['mime'], + ]; + + //销毁已存在的图像 + empty($this->im) || imagedestroy($this->im); + + //打开图像 + if('gif' == $this->info['type']){ + $class = '\\Think\\Image\\Driver\\Gif'; + $this->gif = new $class($imgname); + $this->im = imagecreatefromstring($this->gif->image()); + } else { + $fun = "imagecreatefrom{$this->info['type']}"; + $this->im = $fun($imgname); + } + return $this; + } + + /** + * 保存图像 + * @param string $imgname 图像保存名称 + * @param string $type 图像类型 + * @param boolean $interlace 是否对JPEG类型图像设置隔行扫描 + */ + public function save($imgname, $type = null, $interlace = true){ + if(empty($this->im)) throw new \Exception('没有可以被保存的图像资源'); + + //自动获取图像类型 + if(is_null($type)){ + $type = $this->info['type']; + } else { + $type = strtolower($type); + } + + //JPEG图像设置隔行扫描 + if('jpeg' == $type || 'jpg' == $type){ + $type = 'jpeg'; + imageinterlace($this->im, $interlace); + } + + //保存图像 + if('gif' == $type && !empty($this->gif)){ + $this->gif->save($imgname); + } else { + $fun = "image{$type}"; + $fun($this->im, $imgname); + } + return $this; + } + + /** + * 返回图像宽度 + * @return integer 图像宽度 + */ + public function width(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['width']; + } + + /** + * 返回图像高度 + * @return integer 图像高度 + */ + public function height(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['height']; + } + + /** + * 返回图像类型 + * @return string 图像类型 + */ + public function type(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['type']; + } + + /** + * 返回图像MIME类型 + * @return string 图像MIME类型 + */ + public function mime(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['mime']; + } + + /** + * 返回图像尺寸数组 0 - 图像宽度,1 - 图像高度 + * @return array 图像尺寸 + */ + public function size(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return [$this->info['width'], $this->info['height']]; + } + + /** + * 裁剪图像 + * @param integer $w 裁剪区域宽度 + * @param integer $h 裁剪区域高度 + * @param integer $x 裁剪区域x坐标 + * @param integer $y 裁剪区域y坐标 + * @param integer $width 图像保存宽度 + * @param integer $height 图像保存高度 + */ + public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null){ + if(empty($this->im)) throw new Exception('没有可以被裁剪的图像资源'); + + //设置保存尺寸 + empty($width) && $width = $w; + empty($height) && $height = $h; + + do { + //创建新图像 + $img = imagecreatetruecolor($width, $height); + // 调整默认颜色 + $color = imagecolorallocate($img, 255, 255, 255); + imagefill($img, 0, 0, $color); + + //裁剪 + imagecopyresampled($img, $this->im, 0, 0, $x, $y, $width, $height, $w, $h); + imagedestroy($this->im); //销毁原图 + + //设置新图像 + $this->im = $img; + } while(!empty($this->gif) && $this->gifNext()); + + $this->info['width'] = $width; + $this->info['height'] = $height; + return $this; + } + + /** + * 生成缩略图 + * @param integer $width 缩略图最大宽度 + * @param integer $height 缩略图最大高度 + * @param integer $type 缩略图裁剪类型 + */ + public function thumb($width, $height, $type = THINKIMAGE_THUMB_SCALE){ + if(empty($this->im)) throw new Exception('没有可以被缩略的图像资源'); + + //原图宽度和高度 + $w = $this->info['width']; + $h = $this->info['height']; + + /* 计算缩略图生成的必要参数 */ + switch ($type) { + /* 等比例缩放 */ + case THINKIMAGE_THUMB_SCALING: + //原图尺寸小于缩略图尺寸则不进行缩略 + if($w < $width && $h < $height) return; + + //计算缩放比例 + $scale = min($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $width = $w * $scale; + $height = $h * $scale; + break; + + /* 居中裁剪 */ + case THINKIMAGE_THUMB_CENTER: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $w = $width/$scale; + $h = $height/$scale; + $x = ($this->info['width'] - $w)/2; + $y = ($this->info['height'] - $h)/2; + break; + + /* 左上角裁剪 */ + case THINKIMAGE_THUMB_NORTHWEST: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $w = $width/$scale; + $h = $height/$scale; + break; + + /* 右下角裁剪 */ + case THINKIMAGE_THUMB_SOUTHEAST: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $w = $width/$scale; + $h = $height/$scale; + $x = $this->info['width'] - $w; + $y = $this->info['height'] - $h; + break; + + /* 填充 */ + case THINKIMAGE_THUMB_FILLED: + //计算缩放比例 + if($w < $width && $h < $height){ + $scale = 1; + } else { + $scale = min($width/$w, $height/$h); + } + + //设置缩略图的坐标及宽度和高度 + $neww = $w * $scale; + $newh = $h * $scale; + $posx = ($width - $w * $scale)/2; + $posy = ($height - $h * $scale)/2; + + do{ + //创建新图像 + $img = imagecreatetruecolor($width, $height); + // 调整默认颜色 + $color = imagecolorallocate($img, 255, 255, 255); + imagefill($img, 0, 0, $color); + + //裁剪 + imagecopyresampled($img, $this->im, $posx, $posy, $x, $y, $neww, $newh, $w, $h); + imagedestroy($this->im); //销毁原图 + $this->im = $img; + } while(!empty($this->gif) && $this->gifNext()); + + $this->info['width'] = $width; + $this->info['height'] = $height; + return; + + /* 固定 */ + case THINKIMAGE_THUMB_FIXED: + $x = $y = 0; + break; + + default: + throw new Exception('不支持的缩略图裁剪类型'); + } + + /* 裁剪图像 */ + $this->crop($w, $h, $x, $y, $width, $height); + return $this; + } + + /** + * 添加水印 + * @param string $source 水印图片路径 + * @param integer $locate 水印位置 + * @param integer $alpha 水印透明度 + */ + public function water($source, $locate = THINKIMAGE_WATER_SOUTHEAST){ + //资源检测 + if(empty($this->im)) throw new Exception('没有可以被添加水印的图像资源'); + if(!is_file($source)) throw new Exception('水印图像不存在'); + + //获取水印图像信息 + $info = getimagesize($source); + if(false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))){ + throw new Exception('非法水印文件'); + } + + //创建水印图像资源 + $fun = 'imagecreatefrom' . image_type_to_extension($info[2], false); + $water = $fun($source); + + //设定水印图像的混色模式 + imagealphablending($water, true); + + /* 设定水印位置 */ + switch ($locate) { + /* 右下角水印 */ + case THINKIMAGE_WATER_SOUTHEAST: + $x = $this->info['width'] - $info[0]; + $y = $this->info['height'] - $info[1]; + break; + + /* 左下角水印 */ + case THINKIMAGE_WATER_SOUTHWEST: + $x = 0; + $y = $this->info['height'] - $info[1]; + break; + + /* 左上角水印 */ + case THINKIMAGE_WATER_NORTHWEST: + $x = $y = 0; + break; + + /* 右上角水印 */ + case THINKIMAGE_WATER_NORTHEAST: + $x = $this->info['width'] - $info[0]; + $y = 0; + break; + + /* 居中水印 */ + case THINKIMAGE_WATER_CENTER: + $x = ($this->info['width'] - $info[0])/2; + $y = ($this->info['height'] - $info[1])/2; + break; + + /* 下居中水印 */ + case THINKIMAGE_WATER_SOUTH: + $x = ($this->info['width'] - $info[0])/2; + $y = $this->info['height'] - $info[1]; + break; + + /* 右居中水印 */ + case THINKIMAGE_WATER_EAST: + $x = $this->info['width'] - $info[0]; + $y = ($this->info['height'] - $info[1])/2; + break; + + /* 上居中水印 */ + case THINKIMAGE_WATER_NORTH: + $x = ($this->info['width'] - $info[0])/2; + $y = 0; + break; + + /* 左居中水印 */ + case THINKIMAGE_WATER_WEST: + $x = 0; + $y = ($this->info['height'] - $info[1])/2; + break; + + default: + /* 自定义水印坐标 */ + if(is_array($locate)){ + list($x, $y) = $locate; + } else { + throw new Exception('不支持的水印位置类型'); + } + } + + do{ + //添加水印 + $src = imagecreatetruecolor($info[0], $info[1]); + // 调整默认颜色 + $color = imagecolorallocate($src, 255, 255, 255); + imagefill($src, 0, 0, $color); + + imagecopy($src, $this->im, 0, 0, $x, $y, $info[0], $info[1]); + imagecopy($src, $water, 0, 0, 0, 0, $info[0], $info[1]); + imagecopymerge($this->im, $src, $x, $y, 0, 0, $info[0], $info[1], 100); + + //销毁零时图片资源 + imagedestroy($src); + } while(!empty($this->gif) && $this->gifNext()); + + //销毁水印资源 + imagedestroy($water); + return $this; + } + + /** + * 图像添加文字 + * @param string $text 添加的文字 + * @param string $font 字体路径 + * @param integer $size 字号 + * @param string $color 文字颜色 + * @param integer $locate 文字写入位置 + * @param integer $offset 文字相对当前位置的偏移量 + * @param integer $angle 文字倾斜角度 + */ + public function text($text, $font, $size, $color = '#00000000', + $locate = THINKIMAGE_WATER_SOUTHEAST, $offset = 0, $angle = 0){ + //资源检测 + if(empty($this->im)) throw new Exception('没有可以被写入文字的图像资源'); + if(!is_file($font)) throw new Exception("不存在的字体文件:{$font}"); + + //获取文字信息 + $info = imagettfbbox($size, $angle, $font, $text); + $minx = min($info[0], $info[2], $info[4], $info[6]); + $maxx = max($info[0], $info[2], $info[4], $info[6]); + $miny = min($info[1], $info[3], $info[5], $info[7]); + $maxy = max($info[1], $info[3], $info[5], $info[7]); + + /* 计算文字初始坐标和尺寸 */ + $x = $minx; + $y = abs($miny); + $w = $maxx - $minx; + $h = $maxy - $miny; + + /* 设定文字位置 */ + switch ($locate) { + /* 右下角文字 */ + case THINKIMAGE_WATER_SOUTHEAST: + $x += $this->info['width'] - $w; + $y += $this->info['height'] - $h; + break; + + /* 左下角文字 */ + case THINKIMAGE_WATER_SOUTHWEST: + $y += $this->info['height'] - $h; + break; + + /* 左上角文字 */ + case THINKIMAGE_WATER_NORTHWEST: + // 起始坐标即为左上角坐标,无需调整 + break; + + /* 右上角文字 */ + case THINKIMAGE_WATER_NORTHEAST: + $x += $this->info['width'] - $w; + break; + + /* 居中文字 */ + case THINKIMAGE_WATER_CENTER: + $x += ($this->info['width'] - $w)/2; + $y += ($this->info['height'] - $h)/2; + break; + + /* 下居中文字 */ + case THINKIMAGE_WATER_SOUTH: + $x += ($this->info['width'] - $w)/2; + $y += $this->info['height'] - $h; + break; + + /* 右居中文字 */ + case THINKIMAGE_WATER_EAST: + $x += $this->info['width'] - $w; + $y += ($this->info['height'] - $h)/2; + break; + + /* 上居中文字 */ + case THINKIMAGE_WATER_NORTH: + $x += ($this->info['width'] - $w)/2; + break; + + /* 左居中文字 */ + case THINKIMAGE_WATER_WEST: + $y += ($this->info['height'] - $h)/2; + break; + + default: + /* 自定义文字坐标 */ + if(is_array($locate)){ + list($posx, $posy) = $locate; + $x += $posx; + $y += $posy; + } else { + throw new Exception('不支持的文字位置类型'); + } + } + + /* 设置偏移量 */ + if(is_array($offset)){ + $offset = array_map('intval', $offset); + list($ox, $oy) = $offset; + } else{ + $offset = intval($offset); + $ox = $oy = $offset; + } + + /* 设置颜色 */ + if(is_string($color) && 0 === strpos($color, '#')){ + $color = str_split(substr($color, 1), 2); + $color = array_map('hexdec', $color); + if(empty($color[3]) || $color[3] > 127){ + $color[3] = 0; + } + } elseif (!is_array($color)) { + throw new Exception('错误的颜色值'); + } + + do{ + /* 写入文字 */ + $col = imagecolorallocatealpha($this->im, $color[0], $color[1], $color[2], $color[3]); + imagettftext($this->im, $size, $angle, $x + $ox, $y + $oy, $col, $font, $text); + } while(!empty($this->gif) && $this->gifNext()); + return $this; + } + + /* 切换到GIF的下一帧并保存当前帧,内部使用 */ + private function gifNext(){ + ob_start(); + ob_implicit_flush(0); + imagegif($this->im); + $img = ob_get_clean(); + + $this->gif->image($img); + $next = $this->gif->nextImage(); + + if($next){ + $this->im = imagecreatefromstring($next); + return $next; + } else { + $this->im = imagecreatefromstring($this->gif->image()); + return false; + } + } + + /** + * 析构方法,用于销毁图像资源 + */ + public function __destruct() { + empty($this->im) || imagedestroy($this->im); + } +} \ No newline at end of file diff --git a/Library/Think/Image/Driver/Gif.php b/Library/Think/Image/Driver/Gif.php new file mode 100644 index 00000000..caa2f6de --- /dev/null +++ b/Library/Think/Image/Driver/Gif.php @@ -0,0 +1,570 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Image\Driver; + +class Gif{ + /** + * GIF帧列表 + * @var array + */ + private $frames = []; + + /** + * 每帧等待时间列表 + * @var array + */ + private $delays = []; + + /** + * 构造方法,用于解码GIF图片 + * @param string $src GIF图片数据 + * @param string $mod 图片数据类型 + */ + public function __construct($src = null, $mod = 'url') { + if(!is_null($src)){ + if('url' == $mod && is_file($src)){ + $src = file_get_contents($src); + } + + /* 解码GIF图片 */ + try{ + $de = new GIFDecoder($src); + $this->frames = $de->GIFGetFrames(); + $this->delays = $de->GIFGetDelays(); + } catch(Exception $e){ + throw new Exception("解码GIF图片出错"); + } + } + } + + /** + * 设置或获取当前帧的数据 + * @param string $stream 二进制数据流 + * @return boolean 获取到的数据 + */ + public function image($stream = null){ + if(is_null($stream)){ + $current = current($this->frames); + return false === $current ? reset($this->frames) : $current; + } else { + $this->frames[key($this->frames)] = $stream; + } + } + + /** + * 将当前帧移动到下一帧 + * @return string 当前帧数据 + */ + public function nextImage(){ + return next($this->frames); + } + + /** + * 编码并保存当前GIF图片 + * @param string $gifname 图片名称 + */ + public function save($gifname){ + $gif = new GIFEncoder($this->frames, $this->delays, 0, 2, 0, 0, 0, 'bin'); + file_put_contents($gifname, $gif->GetAnimation()); + } + +} + + +/* +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: GIFEncoder Version 2.0 by László Zsidi, http://gifs.hu +:: +:: This class is a rewritten 'GifMerge.class.php' version. +:: +:: Modification: +:: - Simplified and easy code, +:: - Ultra fast encoding, +:: - Built-in errors, +:: - Stable working +:: +:: +:: Updated at 2007. 02. 13. '00.05.AM' +:: +:: +:: +:: Try on-line GIFBuilder Form demo based on GIFEncoder. +:: +:: http://gifs.hu/phpclasses/demos/GifBuilder/ +:: +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +*/ + +Class GIFEncoder { + var $GIF = "GIF89a"; /* GIF header 6 bytes */ + var $VER = "GIFEncoder V2.05"; /* Encoder version */ + + var $BUF = array ( ); + var $LOP = 0; + var $DIS = 2; + var $COL = -1; + var $IMG = -1; + + var $ERR = array ( + 'ERR00'=>"Does not supported function for only one image!", + 'ERR01'=>"Source is not a GIF image!", + 'ERR02'=>"Unintelligible flag ", + 'ERR03'=>"Does not make animation from animated GIF source", + ); + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFEncoder... + :: + */ + function GIFEncoder ( + $GIF_src, $GIF_dly, $GIF_lop, $GIF_dis, + $GIF_red, $GIF_grn, $GIF_blu, $GIF_mod + ) { + if ( ! is_array ( $GIF_src ) && ! is_array ( $GIF_tim ) ) { + printf ( "%s: %s", $this->VER, $this->ERR [ 'ERR00' ] ); + exit ( 0 ); + } + $this->LOP = ( $GIF_lop > -1 ) ? $GIF_lop : 0; + $this->DIS = ( $GIF_dis > -1 ) ? ( ( $GIF_dis < 3 ) ? $GIF_dis : 3 ) : 2; + $this->COL = ( $GIF_red > -1 && $GIF_grn > -1 && $GIF_blu > -1 ) ? + ( $GIF_red | ( $GIF_grn << 8 ) | ( $GIF_blu << 16 ) ) : -1; + + for ( $i = 0; $i < count ( $GIF_src ); $i++ ) { + if ( strToLower ( $GIF_mod ) == "url" ) { + $this->BUF [ ] = fread ( fopen ( $GIF_src [ $i ], "rb" ), filesize ( $GIF_src [ $i ] ) ); + } + else if ( strToLower ( $GIF_mod ) == "bin" ) { + $this->BUF [ ] = $GIF_src [ $i ]; + } + else { + printf ( "%s: %s ( %s )!", $this->VER, $this->ERR [ 'ERR02' ], $GIF_mod ); + exit ( 0 ); + } + if ( substr ( $this->BUF [ $i ], 0, 6 ) != "GIF87a" && substr ( $this->BUF [ $i ], 0, 6 ) != "GIF89a" ) { + printf ( "%s: %d %s", $this->VER, $i, $this->ERR [ 'ERR01' ] ); + exit ( 0 ); + } + for ( $j = ( 13 + 3 * ( 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ) ) ), $k = TRUE; $k; $j++ ) { + switch ( $this->BUF [ $i ] { $j } ) { + case "!": + if ( ( substr ( $this->BUF [ $i ], ( $j + 3 ), 8 ) ) == "NETSCAPE" ) { + printf ( "%s: %s ( %s source )!", $this->VER, $this->ERR [ 'ERR03' ], ( $i + 1 ) ); + exit ( 0 ); + } + break; + case ";": + $k = FALSE; + break; + } + } + } + GIFEncoder::GIFAddHeader ( ); + for ( $i = 0; $i < count ( $this->BUF ); $i++ ) { + GIFEncoder::GIFAddFrames ( $i, $GIF_dly [ $i ] ); + } + GIFEncoder::GIFAddFooter ( ); + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFAddHeader... + :: + */ + function GIFAddHeader ( ) { + $cmap = 0; + + if ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x80 ) { + $cmap = 3 * ( 2 << ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x07 ) ); + + $this->GIF .= substr ( $this->BUF [ 0 ], 6, 7 ); + $this->GIF .= substr ( $this->BUF [ 0 ], 13, $cmap ); + $this->GIF .= "!\377\13NETSCAPE2.0\3\1" . GIFEncoder::GIFWord ( $this->LOP ) . "\0"; + } + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFAddFrames... + :: + */ + function GIFAddFrames ( $i, $d ) { + + $Locals_str = 13 + 3 * ( 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ) ); + + $Locals_end = strlen ( $this->BUF [ $i ] ) - $Locals_str - 1; + $Locals_tmp = substr ( $this->BUF [ $i ], $Locals_str, $Locals_end ); + + $Global_len = 2 << ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x07 ); + $Locals_len = 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ); + + $Global_rgb = substr ( $this->BUF [ 0 ], 13, + 3 * ( 2 << ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x07 ) ) ); + $Locals_rgb = substr ( $this->BUF [ $i ], 13, + 3 * ( 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ) ) ); + + $Locals_ext = "!\xF9\x04" . chr ( ( $this->DIS << 2 ) + 0 ) . + chr ( ( $d >> 0 ) & 0xFF ) . chr ( ( $d >> 8 ) & 0xFF ) . "\x0\x0"; + + if ( $this->COL > -1 && ord ( $this->BUF [ $i ] { 10 } ) & 0x80 ) { + for ( $j = 0; $j < ( 2 << ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ) ); $j++ ) { + if ( + ord ( $Locals_rgb { 3 * $j + 0 } ) == ( ( $this->COL >> 16 ) & 0xFF ) && + ord ( $Locals_rgb { 3 * $j + 1 } ) == ( ( $this->COL >> 8 ) & 0xFF ) && + ord ( $Locals_rgb { 3 * $j + 2 } ) == ( ( $this->COL >> 0 ) & 0xFF ) + ) { + $Locals_ext = "!\xF9\x04" . chr ( ( $this->DIS << 2 ) + 1 ) . + chr ( ( $d >> 0 ) & 0xFF ) . chr ( ( $d >> 8 ) & 0xFF ) . chr ( $j ) . "\x0"; + break; + } + } + } + switch ( $Locals_tmp { 0 } ) { + case "!": + $Locals_img = substr ( $Locals_tmp, 8, 10 ); + $Locals_tmp = substr ( $Locals_tmp, 18, strlen ( $Locals_tmp ) - 18 ); + break; + case ",": + $Locals_img = substr ( $Locals_tmp, 0, 10 ); + $Locals_tmp = substr ( $Locals_tmp, 10, strlen ( $Locals_tmp ) - 10 ); + break; + } + if ( ord ( $this->BUF [ $i ] { 10 } ) & 0x80 && $this->IMG > -1 ) { + if ( $Global_len == $Locals_len ) { + if ( GIFEncoder::GIFBlockCompare ( $Global_rgb, $Locals_rgb, $Global_len ) ) { + $this->GIF .= ( $Locals_ext . $Locals_img . $Locals_tmp ); + } + else { + $byte = ord ( $Locals_img { 9 } ); + $byte |= 0x80; + $byte &= 0xF8; + $byte |= ( ord ( $this->BUF [ 0 ] { 10 } ) & 0x07 ); + $Locals_img { 9 } = chr ( $byte ); + $this->GIF .= ( $Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp ); + } + } + else { + $byte = ord ( $Locals_img { 9 } ); + $byte |= 0x80; + $byte &= 0xF8; + $byte |= ( ord ( $this->BUF [ $i ] { 10 } ) & 0x07 ); + $Locals_img { 9 } = chr ( $byte ); + $this->GIF .= ( $Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp ); + } + } + else { + $this->GIF .= ( $Locals_ext . $Locals_img . $Locals_tmp ); + } + $this->IMG = 1; + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFAddFooter... + :: + */ + function GIFAddFooter ( ) { + $this->GIF .= ";"; + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFBlockCompare... + :: + */ + function GIFBlockCompare ( $GlobalBlock, $LocalBlock, $Len ) { + + for ( $i = 0; $i < $Len; $i++ ) { + if ( + $GlobalBlock { 3 * $i + 0 } != $LocalBlock { 3 * $i + 0 } || + $GlobalBlock { 3 * $i + 1 } != $LocalBlock { 3 * $i + 1 } || + $GlobalBlock { 3 * $i + 2 } != $LocalBlock { 3 * $i + 2 } + ) { + return ( 0 ); + } + } + + return ( 1 ); + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFWord... + :: + */ + function GIFWord ( $int ) { + + return ( chr ( $int & 0xFF ) . chr ( ( $int >> 8 ) & 0xFF ) ); + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GetAnimation... + :: + */ + function GetAnimation ( ) { + return ( $this->GIF ); + } +} + + +/* +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: GIFDecoder Version 2.0 by László Zsidi, http://gifs.hu +:: +:: Created at 2007. 02. 01. '07.47.AM' +:: +:: +:: +:: +:: Try on-line GIFBuilder Form demo based on GIFDecoder. +:: +:: http://gifs.hu/phpclasses/demos/GifBuilder/ +:: +::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +*/ + +Class GIFDecoder { + var $GIF_buffer = array ( ); + var $GIF_arrays = array ( ); + var $GIF_delays = array ( ); + var $GIF_stream = ""; + var $GIF_string = ""; + var $GIF_bfseek = 0; + + var $GIF_screen = array ( ); + var $GIF_global = array ( ); + var $GIF_sorted; + var $GIF_colorS; + var $GIF_colorC; + var $GIF_colorF; + + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFDecoder ( $GIF_pointer ) + :: + */ + function GIFDecoder ( $GIF_pointer ) { + $this->GIF_stream = $GIF_pointer; + + GIFDecoder::GIFGetByte ( 6 ); // GIF89a + GIFDecoder::GIFGetByte ( 7 ); // Logical Screen Descriptor + + $this->GIF_screen = $this->GIF_buffer; + $this->GIF_colorF = $this->GIF_buffer [ 4 ] & 0x80 ? 1 : 0; + $this->GIF_sorted = $this->GIF_buffer [ 4 ] & 0x08 ? 1 : 0; + $this->GIF_colorC = $this->GIF_buffer [ 4 ] & 0x07; + $this->GIF_colorS = 2 << $this->GIF_colorC; + + if ( $this->GIF_colorF == 1 ) { + GIFDecoder::GIFGetByte ( 3 * $this->GIF_colorS ); + $this->GIF_global = $this->GIF_buffer; + } + /* + * + * 05.06.2007. + * Made a little modification + * + * + - for ( $cycle = 1; $cycle; ) { + + if ( GIFDecoder::GIFGetByte ( 1 ) ) { + - switch ( $this->GIF_buffer [ 0 ] ) { + - case 0x21: + - GIFDecoder::GIFReadExtensions ( ); + - break; + - case 0x2C: + - GIFDecoder::GIFReadDescriptor ( ); + - break; + - case 0x3B: + - $cycle = 0; + - break; + - } + - } + + else { + + $cycle = 0; + + } + - } + */ + for ( $cycle = 1; $cycle; ) { + if ( GIFDecoder::GIFGetByte ( 1 ) ) { + switch ( $this->GIF_buffer [ 0 ] ) { + case 0x21: + GIFDecoder::GIFReadExtensions ( ); + break; + case 0x2C: + GIFDecoder::GIFReadDescriptor ( ); + break; + case 0x3B: + $cycle = 0; + break; + } + } + else { + $cycle = 0; + } + } + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFReadExtension ( ) + :: + */ + function GIFReadExtensions ( ) { + GIFDecoder::GIFGetByte ( 1 ); + for ( ; ; ) { + GIFDecoder::GIFGetByte ( 1 ); + if ( ( $u = $this->GIF_buffer [ 0 ] ) == 0x00 ) { + break; + } + GIFDecoder::GIFGetByte ( $u ); + /* + * 07.05.2007. + * Implemented a new line for a new function + * to determine the originaly delays between + * frames. + * + */ + if ( $u == 4 ) { + $this->GIF_delays [ ] = ( $this->GIF_buffer [ 1 ] | $this->GIF_buffer [ 2 ] << 8 ); + } + } + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFReadExtension ( ) + :: + */ + function GIFReadDescriptor ( ) { + $GIF_screen = array ( ); + + GIFDecoder::GIFGetByte ( 9 ); + $GIF_screen = $this->GIF_buffer; + $GIF_colorF = $this->GIF_buffer [ 8 ] & 0x80 ? 1 : 0; + if ( $GIF_colorF ) { + $GIF_code = $this->GIF_buffer [ 8 ] & 0x07; + $GIF_sort = $this->GIF_buffer [ 8 ] & 0x20 ? 1 : 0; + } + else { + $GIF_code = $this->GIF_colorC; + $GIF_sort = $this->GIF_sorted; + } + $GIF_size = 2 << $GIF_code; + $this->GIF_screen [ 4 ] &= 0x70; + $this->GIF_screen [ 4 ] |= 0x80; + $this->GIF_screen [ 4 ] |= $GIF_code; + if ( $GIF_sort ) { + $this->GIF_screen [ 4 ] |= 0x08; + } + $this->GIF_string = "GIF87a"; + GIFDecoder::GIFPutByte ( $this->GIF_screen ); + if ( $GIF_colorF == 1 ) { + GIFDecoder::GIFGetByte ( 3 * $GIF_size ); + GIFDecoder::GIFPutByte ( $this->GIF_buffer ); + } + else { + GIFDecoder::GIFPutByte ( $this->GIF_global ); + } + $this->GIF_string .= chr ( 0x2C ); + $GIF_screen [ 8 ] &= 0x40; + GIFDecoder::GIFPutByte ( $GIF_screen ); + GIFDecoder::GIFGetByte ( 1 ); + GIFDecoder::GIFPutByte ( $this->GIF_buffer ); + for ( ; ; ) { + GIFDecoder::GIFGetByte ( 1 ); + GIFDecoder::GIFPutByte ( $this->GIF_buffer ); + if ( ( $u = $this->GIF_buffer [ 0 ] ) == 0x00 ) { + break; + } + GIFDecoder::GIFGetByte ( $u ); + GIFDecoder::GIFPutByte ( $this->GIF_buffer ); + } + $this->GIF_string .= chr ( 0x3B ); + /* + Add frames into $GIF_stream array... + */ + $this->GIF_arrays [ ] = $this->GIF_string; + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFGetByte ( $len ) + :: + */ + + /* + * + * 05.06.2007. + * Made a little modification + * + * + - function GIFGetByte ( $len ) { + - $this->GIF_buffer = array ( ); + - + - for ( $i = 0; $i < $len; $i++ ) { + + if ( $this->GIF_bfseek > strlen ( $this->GIF_stream ) ) { + + return 0; + + } + - $this->GIF_buffer [ ] = ord ( $this->GIF_stream { $this->GIF_bfseek++ } ); + - } + + return 1; + - } + */ + function GIFGetByte ( $len ) { + $this->GIF_buffer = array ( ); + + for ( $i = 0; $i < $len; $i++ ) { + if ( $this->GIF_bfseek > strlen ( $this->GIF_stream ) ) { + return 0; + } + $this->GIF_buffer [ ] = ord ( $this->GIF_stream { $this->GIF_bfseek++ } ); + } + return 1; + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFPutByte ( $bytes ) + :: + */ + function GIFPutByte ( $bytes ) { + for ( $i = 0; $i < count ( $bytes ); $i++ ) { + $this->GIF_string .= chr ( $bytes [ $i ] ); + } + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: PUBLIC FUNCTIONS + :: + :: + :: GIFGetFrames ( ) + :: + */ + function GIFGetFrames ( ) { + return ( $this->GIF_arrays ); + } + /* + ::::::::::::::::::::::::::::::::::::::::::::::::::: + :: + :: GIFGetDelays ( ) + :: + */ + function GIFGetDelays ( ) { + return ( $this->GIF_delays ); + } +} diff --git a/Library/Think/Image/Driver/Imagick.php b/Library/Think/Image/Driver/Imagick.php new file mode 100644 index 00000000..0ddb2440 --- /dev/null +++ b/Library/Think/Image/Driver/Imagick.php @@ -0,0 +1,591 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Image\Driver; + +class Imagick{ + /** + * 图像资源对象 + * @var resource + */ + private $im; + + /** + * 图像信息,包括width,height,type,mime,size + * @var array + */ + private $info; + + /** + * 构造方法,可用于打开一张图像 + * @param string $imgname 图像路径 + */ + public function __construct($imgname = null) { + if ( !extension_loaded('Imagick') ) { + E(L('_NOT_SUPPERT_').':Imagick'); + } + $imgname && $this->open($imgname); + } + + /** + * 打开一张图像 + * @param string $imgname 图像路径 + */ + public function open($imgname){ + //检测图像文件 + if(!is_file($imgname)) throw new Exception('不存在的图像文件'); + + //销毁已存在的图像 + empty($this->im) || $this->im->destroy(); + + //载入图像 + $this->im = new \Imagick(realpath($imgname)); + + //设置图像信息 + $this->info = [ + 'width' => $this->im->getImageWidth(), + 'height' => $this->im->getImageHeight(), + 'type' => strtolower($this->im->getImageFormat()), + 'mime' => $this->im->getImageMimeType(), + ]; + } + + /** + * 保存图像 + * @param string $imgname 图像保存名称 + * @param string $type 图像类型 + * @param boolean $interlace 是否对JPEG类型图像设置隔行扫描 + */ + public function save($imgname, $type = null, $interlace = true){ + if(empty($this->im)) throw new Exception('没有可以被保存的图像资源'); + + //设置图片类型 + if(is_null($type)){ + $type = $this->info['type']; + } else { + $type = strtolower($type); + $this->im->setImageFormat($type); + } + + //JPEG图像设置隔行扫描 + if('jpeg' == $type || 'jpg' == $type){ + $this->im->setImageInterlaceScheme(1); + } + + //去除图像配置信息 + $this->im->stripImage(); + + //保存图像 + $imgname = realpath(dirname($imgname)) . '/' . basename($imgname); //强制绝对路径 + if ('gif' == $type) { + $this->im->writeImages($imgname, true); + } else { + $this->im->writeImage($imgname); + } + } + + /** + * 返回图像宽度 + * @return integer 图像宽度 + */ + public function width(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['width']; + } + + /** + * 返回图像高度 + * @return integer 图像高度 + */ + public function height(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['height']; + } + + /** + * 返回图像类型 + * @return string 图像类型 + */ + public function type(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['type']; + } + + /** + * 返回图像MIME类型 + * @return string 图像MIME类型 + */ + public function mime(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return $this->info['mime']; + } + + /** + * 返回图像尺寸数组 0 - 图像宽度,1 - 图像高度 + * @return array 图像尺寸 + */ + public function size(){ + if(empty($this->im)) throw new Exception('没有指定图像资源'); + return [$this->info['width'], $this->info['height']]; + } + + /** + * 裁剪图像 + * @param integer $w 裁剪区域宽度 + * @param integer $h 裁剪区域高度 + * @param integer $x 裁剪区域x坐标 + * @param integer $y 裁剪区域y坐标 + * @param integer $width 图像保存宽度 + * @param integer $height 图像保存高度 + */ + public function crop($w, $h, $x = 0, $y = 0, $width = null, $height = null){ + if(empty($this->im)) throw new Exception('没有可以被裁剪的图像资源'); + + //设置保存尺寸 + empty($width) && $width = $w; + empty($height) && $height = $h; + + //裁剪图片 + if('gif' == $this->info['type']){ + $img = $this->im->coalesceImages(); + $this->im->destroy(); //销毁原图 + + //循环裁剪每一帧 + do { + $this->_crop($w, $h, $x, $y, $width, $height, $img); + } while ($img->nextImage()); + + //压缩图片 + $this->im = $img->deconstructImages(); + $img->destroy(); //销毁零时图片 + } else { + $this->_crop($w, $h, $x, $y, $width, $height); + } + } + + /* 裁剪图片,内部调用 */ + private function _crop($w, $h, $x, $y, $width, $height, $img = null){ + is_null($img) && $img = $this->im; + + //裁剪 + $info = $this->info; + if($x != 0 || $y != 0 || $w != $info['width'] || $h != $info['height']){ + $img->cropImage($w, $h, $x, $y); + $img->setImagePage($w, $h, 0, 0); //调整画布和图片一致 + } + + //调整大小 + if($w != $width || $h != $height){ + $img->sampleImage($width, $height); + } + + //设置缓存尺寸 + $this->info['width'] = $w; + $this->info['height'] = $h; + } + + /** + * 生成缩略图 + * @param integer $width 缩略图最大宽度 + * @param integer $height 缩略图最大高度 + * @param integer $type 缩略图裁剪类型 + */ + public function thumb($width, $height, $type = THINKIMAGE_THUMB_SCALE){ + if(empty($this->im)) throw new Exception('没有可以被缩略的图像资源'); + + //原图宽度和高度 + $w = $this->info['width']; + $h = $this->info['height']; + + /* 计算缩略图生成的必要参数 */ + switch ($type) { + /* 等比例缩放 */ + case THINKIMAGE_THUMB_SCALING: + //原图尺寸小于缩略图尺寸则不进行缩略 + if($w < $width && $h < $height) return; + + //计算缩放比例 + $scale = min($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $width = $w * $scale; + $height = $h * $scale; + break; + + /* 居中裁剪 */ + case THINKIMAGE_THUMB_CENTER: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $w = $width/$scale; + $h = $height/$scale; + $x = ($this->info['width'] - $w)/2; + $y = ($this->info['height'] - $h)/2; + break; + + /* 左上角裁剪 */ + case THINKIMAGE_THUMB_NORTHWEST: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $x = $y = 0; + $w = $width/$scale; + $h = $height/$scale; + break; + + /* 右下角裁剪 */ + case THINKIMAGE_THUMB_SOUTHEAST: + //计算缩放比例 + $scale = max($width/$w, $height/$h); + + //设置缩略图的坐标及宽度和高度 + $w = $width/$scale; + $h = $height/$scale; + $x = $this->info['width'] - $w; + $y = $this->info['height'] - $h; + break; + + /* 填充 */ + case THINKIMAGE_THUMB_FILLED: + //计算缩放比例 + if($w < $width && $h < $height){ + $scale = 1; + } else { + $scale = min($width/$w, $height/$h); + } + + //设置缩略图的坐标及宽度和高度 + $neww = $w * $scale; + $newh = $h * $scale; + $posx = ($width - $w * $scale)/2; + $posy = ($height - $h * $scale)/2; + + //创建一张新图像 + $newimg = new Imagick(); + $newimg->newImage($width, $height, 'white', $this->info['type']); + + + if('gif' == $this->info['type']){ + $imgs = $this->im->coalesceImages(); + $img = new Imagick(); + $this->im->destroy(); //销毁原图 + + //循环填充每一帧 + do { + //填充图像 + $image = $this->_fill($newimg, $posx, $posy, $neww, $newh, $imgs); + + $img->addImage($image); + $img->setImageDelay($imgs->getImageDelay()); + $img->setImagePage($width, $height, 0, 0); + + $image->destroy(); //销毁零时图片 + + } while ($imgs->nextImage()); + + //压缩图片 + $this->im->destroy(); + $this->im = $img->deconstructImages(); + $imgs->destroy(); //销毁零时图片 + $img->destroy(); //销毁零时图片 + + } else { + //填充图像 + $img = $this->_fill($newimg, $posx, $posy, $neww, $newh); + //销毁原图 + $this->im->destroy(); + $this->im = $img; + } + + //设置新图像属性 + $this->info['width'] = $width; + $this->info['height'] = $height; + return; + + /* 固定 */ + case THINKIMAGE_THUMB_FIXED: + $x = $y = 0; + break; + + default: + throw new Exception('不支持的缩略图裁剪类型'); + } + + /* 裁剪图像 */ + $this->crop($w, $h, $x, $y, $width, $height); + } + + /* 填充指定图像,内部使用 */ + private function _fill($newimg, $posx, $posy, $neww, $newh, $img = null){ + is_null($img) && $img = $this->im; + + /* 将指定图片绘入空白图片 */ + $draw = new ImagickDraw(); + $draw->composite($img->getImageCompose(), $posx, $posy, $neww, $newh, $img); + $image = $newimg->clone(); + $image->drawImage($draw); + $draw->destroy(); + + return $image; + } + + /** + * 添加水印 + * @param string $source 水印图片路径 + * @param integer $locate 水印位置 + * @param integer $alpha 水印透明度 + */ + public function water($source, $locate = THINKIMAGE_WATER_SOUTHEAST){ + //资源检测 + if(empty($this->im)) throw new Exception('没有可以被添加水印的图像资源'); + if(!is_file($source)) throw new Exception('水印图像不存在'); + + //创建水印图像资源 + $water = new Imagick(realpath($source)); + $info = [$water->getImageWidth(), $water->getImageHeight()]; + + /* 设定水印位置 */ + switch ($locate) { + /* 右下角水印 */ + case THINKIMAGE_WATER_SOUTHEAST: + $x = $this->info['width'] - $info[0]; + $y = $this->info['height'] - $info[1]; + break; + + /* 左下角水印 */ + case THINKIMAGE_WATER_SOUTHWEST: + $x = 0; + $y = $this->info['height'] - $info[1]; + break; + + /* 左上角水印 */ + case THINKIMAGE_WATER_NORTHWEST: + $x = $y = 0; + break; + + /* 右上角水印 */ + case THINKIMAGE_WATER_NORTHEAST: + $x = $this->info['width'] - $info[0]; + $y = 0; + break; + + /* 居中水印 */ + case THINKIMAGE_WATER_CENTER: + $x = ($this->info['width'] - $info[0])/2; + $y = ($this->info['height'] - $info[1])/2; + break; + + /* 下居中水印 */ + case THINKIMAGE_WATER_SOUTH: + $x = ($this->info['width'] - $info[0])/2; + $y = $this->info['height'] - $info[1]; + break; + + /* 右居中水印 */ + case THINKIMAGE_WATER_EAST: + $x = $this->info['width'] - $info[0]; + $y = ($this->info['height'] - $info[1])/2; + break; + + /* 上居中水印 */ + case THINKIMAGE_WATER_NORTH: + $x = ($this->info['width'] - $info[0])/2; + $y = 0; + break; + + /* 左居中水印 */ + case THINKIMAGE_WATER_WEST: + $x = 0; + $y = ($this->info['height'] - $info[1])/2; + break; + + default: + /* 自定义水印坐标 */ + if(is_array($locate)){ + list($x, $y) = $locate; + } else { + throw new Exception('不支持的水印位置类型'); + } + } + + //创建绘图资源 + $draw = new ImagickDraw(); + $draw->composite($water->getImageCompose(), $x, $y, $info[0], $info[1], $water); + + if('gif' == $this->info['type']){ + $img = $this->im->coalesceImages(); + $this->im->destroy(); //销毁原图 + + do{ + //添加水印 + $img->drawImage($draw); + } while ($img->nextImage()); + + //压缩图片 + $this->im = $img->deconstructImages(); + $img->destroy(); //销毁零时图片 + + } else { + //添加水印 + $this->im->drawImage($draw); + } + + //销毁水印资源 + $draw->destroy(); + $water->destroy(); + } + + /** + * 图像添加文字 + * @param string $text 添加的文字 + * @param string $font 字体路径 + * @param integer $size 字号 + * @param string $color 文字颜色 + * @param integer $locate 文字写入位置 + * @param integer $offset 文字相对当前位置的偏移量 + * @param integer $angle 文字倾斜角度 + */ + public function text($text, $font, $size, $color = '#00000000', + $locate = THINKIMAGE_WATER_SOUTHEAST, $offset = 0, $angle = 0){ + //资源检测 + if(empty($this->im)) throw new Exception('没有可以被写入文字的图像资源'); + if(!is_file($font)) throw new Exception("不存在的字体文件:{$font}"); + + //获取颜色和透明度 + if(is_array($color)){ + $color = array_map('dechex', $color); + foreach ($color as &$value) { + $value = str_pad($value, 2, '0', STR_PAD_LEFT); + } + $color = '#' . implode('', $color); + } elseif(!is_string($color) || 0 !== strpos($color, '#')) { + throw new Exception('错误的颜色值'); + } + $col = substr($color, 0, 7); + $alp = strlen($color) == 9 ? substr($color, -2) : 0; + + + //获取文字信息 + $draw = new ImagickDraw(); + $draw->setFont(realpath($font)); + $draw->setFontSize($size); + $draw->setFillColor($col); + $draw->setFillAlpha(1-hexdec($alp)/127); + $draw->setTextAntialias(true); + $draw->setStrokeAntialias(true); + + $metrics = $this->im->queryFontMetrics($draw, $text); + + /* 计算文字初始坐标和尺寸 */ + $x = 0; + $y = $metrics['ascender']; + $w = $metrics['textWidth']; + $h = $metrics['textHeight']; + + /* 设定文字位置 */ + switch ($locate) { + /* 右下角文字 */ + case THINKIMAGE_WATER_SOUTHEAST: + $x += $this->info['width'] - $w; + $y += $this->info['height'] - $h; + break; + + /* 左下角文字 */ + case THINKIMAGE_WATER_SOUTHWEST: + $y += $this->info['height'] - $h; + break; + + /* 左上角文字 */ + case THINKIMAGE_WATER_NORTHWEST: + // 起始坐标即为左上角坐标,无需调整 + break; + + /* 右上角文字 */ + case THINKIMAGE_WATER_NORTHEAST: + $x += $this->info['width'] - $w; + break; + + /* 居中文字 */ + case THINKIMAGE_WATER_CENTER: + $x += ($this->info['width'] - $w)/2; + $y += ($this->info['height'] - $h)/2; + break; + + /* 下居中文字 */ + case THINKIMAGE_WATER_SOUTH: + $x += ($this->info['width'] - $w)/2; + $y += $this->info['height'] - $h; + break; + + /* 右居中文字 */ + case THINKIMAGE_WATER_EAST: + $x += $this->info['width'] - $w; + $y += ($this->info['height'] - $h)/2; + break; + + /* 上居中文字 */ + case THINKIMAGE_WATER_NORTH: + $x += ($this->info['width'] - $w)/2; + break; + + /* 左居中文字 */ + case THINKIMAGE_WATER_WEST: + $y += ($this->info['height'] - $h)/2; + break; + + default: + /* 自定义文字坐标 */ + if(is_array($locate)){ + list($posx, $posy) = $locate; + $x += $posx; + $y += $posy; + } else { + throw new Exception('不支持的文字位置类型'); + } + } + + /* 设置偏移量 */ + if(is_array($offset)){ + $offset = array_map('intval', $offset); + list($ox, $oy) = $offset; + } else{ + $offset = intval($offset); + $ox = $oy = $offset; + } + + /* 写入文字 */ + if('gif' == $this->info['type']){ + $img = $this->im->coalesceImages(); + $this->im->destroy(); //销毁原图 + do{ + $img->annotateImage($draw, $x + $ox, $y + $oy, $angle, $text); + } while ($img->nextImage()); + + //压缩图片 + $this->im = $img->deconstructImages(); + $img->destroy(); //销毁零时图片 + + } else { + $this->im->annotateImage($draw, $x + $ox, $y + $oy, $angle, $text); + } + $draw->destroy(); + } + + /** + * 析构方法,用于销毁图像资源 + */ + public function __destruct() { + empty($this->im) || $this->im->destroy(); + } +} \ No newline at end of file diff --git a/Library/Think/Oauth.php b/Library/Think/Oauth.php new file mode 100644 index 00000000..99d3c8a0 --- /dev/null +++ b/Library/Think/Oauth.php @@ -0,0 +1,72 @@ + +// +---------------------------------------------------------------------- + +namespace Think; + +// oauth登录接口 +// +// Oauth::connect('qq',['app_key'=>'','app_secret'=>'','callback'=>'','authorize'=>'']); // 链接QQ登录 +// Oauth::login(); // 跳转到授权登录页面 或者 Oauth::login($callbackUrl); +// Oauth::call('api','params'); // 调用API接口 +// +class Oauth { + + /** + * 操作句柄 + * @var object + * @access protected + */ + static protected $handler = null; + + /** + * 连接oauth + * @access public + * @param string $type Oauth类型 + * @param array $options 配置数组 + * @return object + */ + static public function connect($type,$options=[]) { + $class = 'Think\\Oauth\\Driver\\'.ucwords($type); + self::$handler = new $class($options); + return self::$handler; + } + + // 跳转到授权登录页面 + static public function login($callback=''){ + self::$handler->login($callback); + } + + // 获取access_token + static public function getAccessToken($code){ + self::$handler->getAccessToken($code); + } + + // 设置保存过的token信息 + static public function setToken($token){ + self::$handler->setToken($token); + } + + // 获取oauth用户信息 + static public function getOauthInfo(){ + return self::$handler->getOauthInfo(); + } + + // 获取openid信息 + static public function getOpenId(){ + return self::$handler->getOpenId(); + } + + // 调用oauth接口API + static public function call($api,$param='',$method='GET'){ + return self::$handler->call($api,$param,$method); + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver.php b/Library/Think/Oauth/Driver.php new file mode 100644 index 00000000..764e37fc --- /dev/null +++ b/Library/Think/Oauth/Driver.php @@ -0,0 +1,233 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth; +abstract class Driver { + + /** + * oauth版本 + * @var string + */ + protected $version = '2.0'; + + /** + * 申请应用时分配的app_key + * @var string + */ + protected $appKey = ''; + + /** + * 申请应用时分配的 app_secret + * @var string + */ + protected $appSecret = ''; + + /** + * 授权类型 response_type 目前只能为code + * @var string + */ + protected $responseType = 'code'; + + /** + * grant_type 目前只能为 authorization_code + * @var string + */ + protected $grantType = 'authorization_code'; + + /** + * 获取request_code请求的URL + * @var string + */ + protected $getRequestCodeURL = ''; + + /** + * 获取access_token请求的URL + * @var string + */ + protected $getAccessTokenURL = ''; + + /** + * API根路径 + * @var string + */ + protected $apiBase = ''; + + /** + * 授权后获取到的TOKEN信息 + * @var array + */ + protected $token = null; + + /** + * 回调页面URL 可以通过配置文件配置 + * @var string + */ + protected $callback = ''; + + /** + * 构造方法,配置应用信息 + * @param array $config + */ + public function __construct($config = []){ + $this->appKey = $config['app_key']; + $this->appSecret = $config['app_secret']; + $this->authorize = isset($config['authorize'])?$config['authorize']:''; + $this->callback = isset($config['callback'])?$config['callback']:''; + } + + // 跳转到授权登录页面 + public function login($callback=''){ + if($callback) { + $this->callback = $callback; + } + //跳转到授权页面 + header('Location: ' . $this->getRequestCodeURL()); + exit; + } + + /** + * 请求code + */ + public function getRequestCodeURL(){ + //Oauth 标准参数 + $params = array( + 'client_id' => $this->appKey, + 'redirect_uri' => $this->callback, + 'response_type' => $this->responseType, + ); + + //获取额外参数 + if($this->authorize){ + parse_str($this->authorize, $_param); + if(is_array($_param)){ + $params = array_merge($params, $_param); + } else { + throw new Exception('AUTHORIZE配置不正确!'); + } + } + return $this->getRequestCodeURL . '?' . http_build_query($params); + } + + /** + * 获取access_token + * @param string $code 授权登录成功后得到的code信息 + */ + public function getAccessToken($code){ + $params = array( + 'client_id' => $this->appKey, + 'client_secret' => $this->appSecret, + 'grant_type' => $this->grantType, + 'redirect_uri' => $this->callback, + 'code' => $code, + ); + // 获取token信息 + $data = $this->http($this->getAccessTokenURL, $params, 'POST'); + // 解析token + $this->token = $this->parseToken($data); + return $this->token; + } + + /** + * 设置access_token + * @param string $token + */ + public function setToken($token){ + $this->token = $token; + } + + /** + * 合并默认参数和额外参数 + * @param array $params 默认参数 + * @param array/string $param 额外参数 + * @return array: + */ + protected function param($params, $param){ + if(is_string($param)) + parse_str($param, $param); + return array_merge($params, $param); + } + + /** + * 获取指定API请求的URL + * @param string $api API名称 + * @param string $fix api后缀 + * @return string 请求的完整URL + */ + protected function url($api, $fix = ''){ + return $this->apiBase . $api . $fix; + } + + /** + * 发送HTTP请求方法,目前只支持CURL发送请求 + * @param string $url 请求URL + * @param array $params 请求参数 + * @param string $method 请求方法GET/POST + * @return array $data 响应数据 + */ + protected function http($url, $params, $method = 'GET', $header = [], $multi = false){ + $opts = array( + CURLOPT_TIMEOUT => 30, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_HTTPHEADER => $header + ); + + /* 根据请求类型设置特定参数 */ + switch(strtoupper($method)){ + case 'GET': + $opts[CURLOPT_URL] = $url . '?' . http_build_query($params); + break; + case 'POST': + //判断是否传输文件 ++ $params = $multi ? $params : http_build_query($params); + $opts[CURLOPT_URL] = $url; + $opts[CURLOPT_POST] = 1; + $opts[CURLOPT_POSTFIELDS] = $params; + break; + default: + throw new Exception('不支持的请求方式!'); + } + + /* 初始化并执行curl请求 */ + $ch = curl_init(); + curl_setopt_array($ch, $opts); + $data = curl_exec($ch); + $error = curl_error($ch); + curl_close($ch); + if($error) throw new Exception('请求发生错误:' . $error); + return $data; + } + + /** + * 抽象方法,在SNSSDK中实现 + * 组装接口调用参数 并调用接口 + */ + abstract protected function call($api, $param = '', $method = 'GET', $multi = false); + + /** + * 抽象方法,在SNSSDK中实现 + * 解析access_token方法请求后的返回值 + */ + abstract protected function parseToken($result); + + /** + * 抽象方法,在SNSSDK中实现 + * 获取当前授权用户的SNS标识 + */ + abstract public function getOpenId(); + + /** + * 抽象方法 + * 获取当前授权用户的用户信息 + */ + abstract public function getOauthInfo(); +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Baidu.php b/Library/Think/Oauth/Driver/Baidu.php new file mode 100644 index 00000000..3aaecc62 --- /dev/null +++ b/Library/Think/Oauth/Driver/Baidu.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Baidu extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://openapi.baidu.com/oauth/2.0/authorize'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://openapi.baidu.com/oauth/2.0/token'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://openapi.baidu.com/rest/2.0/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 百度API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* 百度调用公共参数 */ + $params = array( + 'access_token' => $this->token['access_token'], + ); + + $data = $this->http($this->url($api), $this->param($params, $param), $method); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + $data = json_decode($result, true); + if($data['access_token'] && $data['expires_in'] && $data['refresh_token']){ + $data['openid'] = $this->openid(); + return $data; + } else + throw new Exception("获取百度ACCESS_TOKEN出错:{$data['error']}"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + $data = $this->call('passport/users/getLoggedInUser'); + return !empty($data['uid'])?$data['uid']:NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('passport/users/getLoggedInUser'); + + if(!empty($data['uid'])){ + $userInfo['type'] = 'BAIDU'; + $userInfo['name'] = $data['uid']; + $userInfo['nick'] = $data['uname']; + $userInfo['avatar'] = "http://tb.himg.baidu.com/sys/portrait/item/{$data['portrait']}"; + return $userInfo; + } else { + throw new Exception("获取百度用户信息失败:{$data['error_msg']}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Diandian.php b/Library/Think/Oauth/Driver/Diandian.php new file mode 100644 index 00000000..8e3da8e4 --- /dev/null +++ b/Library/Think/Oauth/Driver/Diandian.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Diandian extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://api.diandian.com/oauth/authorize'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://api.diandian.com/oauth/token'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://api.diandian.com/v1/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 点点网API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* 点点网调用公共参数 */ + $params = array( + 'access_token' => $this->token['access_token'], + ); + + $data = $this->http($this->url($api, '.json'), $this->param($params, $param), $method); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + $data = json_decode($result, true); + if($data['access_token'] && $data['expires_in'] && $data['token_type'] && $data['uid']){ + $data['openid'] = $data['uid']; + unset($data['uid']); + return $data; + } else + throw new Exception("获取点点网ACCESS_TOKEN出错:{$data['error']}"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + return NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('user/info'); + + if(!empty($data['meta']['status']) && $data['meta']['status'] == 200){ + $userInfo['type'] = 'DIANDIAN'; + $userInfo['name'] = $data['response']['name']; + $userInfo['nick'] = $data['response']['name']; + $userInfo['avatar'] = "https://api.diandian.com/v1/blog/{$data['response']['blogs'][0]['blogUuid']}/avatar/144"; + return $userInfo; + } else { + E("获取点点用户信息失败:{$data}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Douban.php b/Library/Think/Oauth/Driver/Douban.php new file mode 100644 index 00000000..060490fd --- /dev/null +++ b/Library/Think/Oauth/Driver/Douban.php @@ -0,0 +1,91 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Douban extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://www.douban.com/service/auth2/auth'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://www.douban.com/service/auth2/token'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://api.douban.com/v2/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 微博API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* 豆瓣调用公共参数 */ + $params = []; + $header = array("Authorization: Bearer {$this->token['access_token']}"); + $data = $this->http($this->url($api), $this->param($params, $param), $method, $header); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + $data = json_decode($result, true); + if($data['access_token'] && $data['expires_in'] && $data['refresh_token'] && $data['douban_user_id']){ + $data['openid'] = $data['douban_user_id']; + unset($data['douban_user_id']); + return $data; + } else + throw new Exception("获取豆瓣ACCESS_TOKEN出错:{$data['msg']}"); + } + + /** + * 获取当前授权应用的openid + * @return string|null + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + return NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('user/~me'); + + if(empty($data['code'])){ + $userInfo['type'] = 'DOUBAN'; + $userInfo['name'] = $data['name']; + $userInfo['nick'] = $data['name']; + $userInfo['avatar'] = $data['avatar']; + return $userInfo; + } else { + E("获取豆瓣用户信息失败:{$data['msg']}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Github.php b/Library/Think/Oauth/Driver/Github.php new file mode 100644 index 00000000..6bf762a5 --- /dev/null +++ b/Library/Think/Oauth/Driver/Github.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Github extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://github.com/login/oauth/authorize'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://github.com/login/oauth/access_token'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://api.github.com/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 微博API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* Github 调用公共参数 */ + $params = []; + $header = array("Authorization: bearer {$this->token['access_token']}"); + + $data = $this->http($this->url($api), $this->param($params, $param), $method, $header); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + parse_str($result, $data); + if($data['access_token'] && $data['token_type']){ + $data['openid'] = $this->getOpenId(); + return $data; + } else + throw new Exception("获取 Github ACCESS_TOKEN出错:未知错误"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + + $data = $this->call('user'); + return !empty($data['id'])?$data['id']:NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('user'); + + if(empty($data['code'])){ + $userInfo['type'] = 'GITHUB'; + $userInfo['name'] = $data['login']; + $userInfo['nick'] = $data['name']; + $userInfo['avatar'] = $data['avatar_url']; + return $userInfo; + } else { + E("获取Github用户信息失败:{$data}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Google.php b/Library/Think/Oauth/Driver/Google.php new file mode 100644 index 00000000..b70eda2a --- /dev/null +++ b/Library/Think/Oauth/Driver/Google.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Google extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://accounts.google.com/o/oauth2/auth'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://accounts.google.com/o/oauth2/token'; + + /** + * 获取request_code的额外参数 URL查询字符串格式 + * @var srting + */ + protected $authorize = 'scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://www.googleapis.com/oauth2/v1/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 微博API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* Google 调用公共参数 */ + $params = []; + $header = array("Authorization: Bearer {$this->token['access_token']}"); + + $data = $this->http($this->url($api), $this->param($params, $param), $method, $header); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + $data = json_decode($result, true); + if($data['access_token'] && $data['token_type'] && $data['expires_in']){ + $data['openid'] = $this->getOpenId(); + return $data; + } else + throw new Exception("获取 Google ACCESS_TOKEN出错:未知错误"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + + $data = $this->call('userinfo'); + return !empty($data['id'])?$data['id']:NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('userinfo'); + + if(!empty($data['id'])){ + $userInfo['type'] = 'GOOGLE'; + $userInfo['name'] = $data['name']; + $userInfo['nick'] = $data['name']; + $userInfo['avatar'] = $data['picture']; + return $userInfo; + } else { + E("获取Google用户信息失败:{$data}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Kaixin.php b/Library/Think/Oauth/Driver/Kaixin.php new file mode 100644 index 00000000..332e012f --- /dev/null +++ b/Library/Think/Oauth/Driver/Kaixin.php @@ -0,0 +1,94 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Kaixin extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'http://api.kaixin001.com/oauth2/authorize'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://api.kaixin001.com/oauth2/access_token'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://api.kaixin001.com/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 开心网API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* 开心网调用公共参数 */ + $params = array( + 'access_token' => $this->token['access_token'], + ); + + $data = $this->http($this->url($api, '.json'), $this->param($params, $param), $method); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + $data = json_decode($result, true); + if($data['access_token'] && $data['expires_in'] && $data['refresh_token']){ + $data['openid'] = $this->getOpenId(); + return $data; + } else + throw new Exception("获取开心网ACCESS_TOKEN出错:{$data['error']}"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + + $data = $this->call('users/me'); + return !empty($data['uid'])?$data['uid']:NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('users/me'); + + if(!empty($data['uid'])){ + $userInfo['type'] = 'KAIXIN'; + $userInfo['name'] = $data['uid']; + $userInfo['nick'] = $data['name']; + $userInfo['avatar'] = $data['logo50']; + return $userInfo; + } else { + E("获取开心网用户信息失败:{$data['error']}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Msn.php b/Library/Think/Oauth/Driver/Msn.php new file mode 100644 index 00000000..46102629 --- /dev/null +++ b/Library/Think/Oauth/Driver/Msn.php @@ -0,0 +1,100 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Msn extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://login.live.com/oauth20_authorize.srf'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://login.live.com/oauth20_token.srf'; + + /** + * 获取request_code的额外参数 URL查询字符串格式 + * @var srting + */ + protected $authorize = 'scope=wl.basic wl.offline_access wl.signin wl.emails wl.photos'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://apis.live.net/v5.0/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 微博API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* MSN 调用公共参数 */ + $params = array( + 'access_token' => $this->token['access_token'], + ); + + $data = $this->http($this->url($api), $this->param($params, $param), $method); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + $data = json_decode($result, true); + if($data['access_token'] && $data['token_type'] && $data['expires_in']){ + $data['openid'] = $this->getOpenId(); + return $data; + } else + throw new Exception("获取 MSN ACCESS_TOKEN出错:未知错误"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + + $data = $this->call('me'); + return !empty($data['id'])?$data['id']:NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('me'); + + if(!empty($data['id'])){ + $userInfo['type'] = 'MSN'; + $userInfo['name'] = $data['name']; + $userInfo['nick'] = $data['name']; + $userInfo['avatar'] = '微软暂未提供头像URL,请通过 me/picture 接口下载'; + return $userInfo; + } else { + E("获取msn用户信息失败:{$data}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Qq.php b/Library/Think/Oauth/Driver/Qq.php new file mode 100644 index 00000000..a34fb23b --- /dev/null +++ b/Library/Think/Oauth/Driver/Qq.php @@ -0,0 +1,102 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Qq extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://graph.qq.com/oauth2.0/authorize'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://graph.qq.com/oauth2.0/token'; + + /** + * 获取request_code的额外参数,可在配置中修改 URL查询字符串格式 + * @var srting + */ + protected $authorize = 'scope=get_user_info,add_share'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://graph.qq.com/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 微博API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* 腾讯QQ调用公共参数 */ + $params = array( + 'oauth_consumer_key' => $this->AppKey, + 'access_token' => $this->token['access_token'], + 'openid' => $this->openid(), + 'format' => 'json' + ); + + $data = $this->http($this->url($api), $this->param($params, $param), $method); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + parse_str($result, $data); + if($data['access_token'] && $data['expires_in']){ + $data['openid'] = $this->getOpenId(); + return $data; + } else + throw new Exception("获取腾讯QQ ACCESS_TOKEN 出错:{$result}"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + if($data['access_token']){ + $data = $this->http($this->url('oauth2.0/me'), array('access_token' => $data['access_token'])); + $data = json_decode(trim(substr($data, 9), " );\n"), true); + if(isset($data['openid'])) + return $data['openid']; + } + return NULL; + } + + public function getOauthInfo(){ + $data = $this->call('user/get_user_info'); + + if($data['ret'] == 0){ + $userInfo['type'] = 'QQ'; + $userInfo['name'] = $data['nickname']; + $userInfo['nick'] = $data['nickname']; + $userInfo['avatar'] = $data['figureurl_2']; + return $userInfo; + } else { + E("获取腾讯QQ用户信息失败:{$data['msg']}"); + } + } +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Renren.php b/Library/Think/Oauth/Driver/Renren.php new file mode 100644 index 00000000..7e59ae67 --- /dev/null +++ b/Library/Think/Oauth/Driver/Renren.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Renren extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://graph.renren.com/oauth/authorize'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://graph.renren.com/oauth/token'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'http://api.renren.com/restserver.do'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 微博API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'POST'){ + /* 人人网调用公共参数 */ + $params = array( + 'method' => $api, + 'access_token' => $this->token['access_token'], + 'v' => '1.0', + 'format' => 'json', + ); + + $data = $this->http($this->url(''), $this->param($params, $param), $method); + return json_decode($data, true); + } + + /** + * 合并默认参数和额外参数 + * @param array $params 默认参数 + * @param array/string $param 额外参数 + * @return array: + */ + protected function param($params, $param){ + $params = parent::param($params, $param); + + /* 签名 */ + ksort($params); + $param = []; + foreach ($params as $key => $value){ + $param[] = "{$key}={$value}"; + } + $sign = implode('', $param).$this->AppSecret; + $params['sig'] = md5($sign); + + return $params; + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + $data = json_decode($result, true); + if($data['access_token'] && $data['expires_in'] && $data['refresh_token'] && $data['user']['id']){ + $data['openid'] = $data['user']['id']; + unset($data['user']); + return $data; + } else + throw new Exception("获取人人网ACCESS_TOKEN出错:{$data['error_description']}"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + return NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('users.getInfo'); + + if(!isset($data['error_code'])){ + $userInfo['type'] = 'RENREN'; + $userInfo['name'] = $data[0]['name']; + $userInfo['nick'] = $data[0]['name']; + $userInfo['avatar'] = $data[0]['headurl']; + return $userInfo; + } else { + E("获取人人网用户信息失败:{$data['error_msg']}"); + } + } +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Sina.php b/Library/Think/Oauth/Driver/Sina.php new file mode 100644 index 00000000..86245a6d --- /dev/null +++ b/Library/Think/Oauth/Driver/Sina.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Sina extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://api.weibo.com/oauth2/authorize'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://api.weibo.com/oauth2/access_token'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://api.weibo.com/2/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 微博API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* 新浪微博调用公共参数 */ + $params = array( + 'access_token' => $this->token['access_token'], + ); + + $data = $this->http($this->url($api, '.json'), $this->param($params, $param), $method); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + $data = json_decode($result, true); + if($data['access_token'] && $data['expires_in'] && $data['remind_in'] && $data['uid']){ + $data['openid'] = $data['uid']; + unset($data['uid']); + return $data; + } else + throw new Exception("获取新浪微博ACCESS_TOKEN出错:{$data['error']}"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + return NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('users.getInfo'); + + if(!isset($data['error_code'])){ + $userInfo['type'] = 'RENREN'; + $userInfo['name'] = $data[0]['name']; + $userInfo['nick'] = $data[0]['name']; + $userInfo['avatar'] = $data[0]['headurl']; + return $userInfo; + } else { + E("获取人人网用户信息失败:{$data['error_msg']}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Sohu.php b/Library/Think/Oauth/Driver/Sohu.php new file mode 100644 index 00000000..fd8a569c --- /dev/null +++ b/Library/Think/Oauth/Driver/Sohu.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Sohu extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://api.sohu.com/oauth2/authorize'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://api.sohu.com/oauth2/token'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://api.sohu.com/rest/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 搜狐API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* 搜狐调用公共参数 */ + $params = array( + 'access_token' => $this->token['access_token'], + ); + + $data = $this->http($this->url($api), $this->param($params, $param), $method); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + $data = json_decode($result, true); + if($data['access_token'] && $data['expires_in'] && $data['refresh_token'] && $data['open_id']){ + $data['openid'] = $data['open_id']; + unset($data['open_id']); + return $data; + } else + throw new Exception("获取搜狐ACCESS_TOKEN出错:{$data['error']}"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + return NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('i/prv/1/user/get-basic-info'); + + if('success' == $data['message'] && !empty($data['data'])){ + $userInfo['type'] = 'SOHU'; + $userInfo['name'] = $data['data']['open_id']; + $userInfo['nick'] = $data['data']['nick']; + $userInfo['avatar'] = $data['data']['icon']; + return $userInfo; + } else { + E("获取搜狐用户信息失败:{$data['message']}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/T163.php b/Library/Think/Oauth/Driver/T163.php new file mode 100644 index 00000000..86849f0f --- /dev/null +++ b/Library/Think/Oauth/Driver/T163.php @@ -0,0 +1,95 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class T163 extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://api.t.163.com/oauth2/authorize'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://api.t.163.com/oauth2/access_token'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://api.t.163.com/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 微博API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* 新浪微博调用公共参数 */ + $params = array( + 'oauth_token' => $this->token['access_token'], + ); + + $data = $this->http($this->url($api, '.json'), $this->param($params, $param), $method); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + $data = json_decode($result, true); + if($data['uid'] && $data['access_token'] && $data['expires_in'] && $data['refresh_token']){ + $data['openid'] = $data['uid']; + unset($data['uid']); + return $data; + } else + throw new Exception("获取网易微博ACCESS_TOKEN出错:{$data['error']}"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + + $data = $this->call('users/show'); + return !empty($data['id'])?$data['id']:NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('users/show'); + + if($data['error_code'] == 0){ + $userInfo['type'] = 'T163'; + $userInfo['name'] = $data['name']; + $userInfo['nick'] = $data['screen_name']; + $userInfo['avatar'] = str_replace('w=48&h=48', 'w=180&h=180', $data['profile_image_url']); + return $userInfo; + } else { + E("获取网易微博用户信息失败:{$data['error']}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Taobao.php b/Library/Think/Oauth/Driver/Taobao.php new file mode 100644 index 00000000..2d0f0d95 --- /dev/null +++ b/Library/Think/Oauth/Driver/Taobao.php @@ -0,0 +1,97 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Taobao extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://oauth.taobao.com/authorize'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://oauth.taobao.com/token'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://eco.taobao.com/router/rest'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 微博API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* 淘宝网调用公共参数 */ + $params = array( + 'method' => $api, + 'access_token' => $this->token['access_token'], + 'format' => 'json', + 'v' => '2.0', + ); + $data = $this->http($this->url(''), $this->param($params, $param), $method); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + $data = json_decode($result, true); + if($data['access_token'] && $data['expires_in'] && $data['taobao_user_id']){ + $data['openid'] = $data['taobao_user_id']; + unset($data['taobao_user_id']); + return $data; + } else + throw new Exception("获取淘宝网ACCESS_TOKEN出错:{$data['error']}"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + return NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $fields = 'user_id,nick,sex,buyer_credit,avatar,has_shop,vip_info'; + $data = $this->call('taobao.user.buyer.get', "fields={$fields}"); + + if(!empty($data['user_buyer_get_response']['user'])){ + $user = $data['user_buyer_get_response']['user']; + $userInfo['type'] = 'TAOBAO'; + $userInfo['name'] = $user['user_id']; + $userInfo['nick'] = $user['nick']; + $userInfo['avatar'] = $user['avatar']; + return $userInfo; + } else { + E("获取淘宝网用户信息失败:{$data['error_response']['msg']}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/Tencent.php b/Library/Think/Oauth/Driver/Tencent.php new file mode 100644 index 00000000..a59d2372 --- /dev/null +++ b/Library/Think/Oauth/Driver/Tencent.php @@ -0,0 +1,98 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class Tencent extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://open.t.qq.com/cgi-bin/oauth2/authorize'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://open.t.qq.com/cgi-bin/oauth2/access_token'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://open.t.qq.com/api/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 微博API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* 腾讯微博调用公共参数 */ + $params = array( + 'oauth_consumer_key' => $this->AppKey, + 'access_token' => $this->token['access_token'], + 'openid' => $this->openid(), + 'clientip' => get_client_ip(), + 'oauth_version' => '2.a', + 'scope' => 'all', + 'format' => 'json' + ); + + $data = $this->http($this->url($api), $this->param($params, $param), $method); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + parse_str($result, $data); + $data = array_merge($data, ['openid' => $_GET['openid'], 'openkey' => $_GET['openkey']]); + if($data['access_token'] && $data['expires_in'] && $data['openid']) + return $data; + else + throw new Exception("获取腾讯微博 ACCESS_TOKEN 出错:{$result}"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + return NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('users.getInfo'); + + if(!isset($data['error_code'])){ + $userInfo['type'] = 'RENREN'; + $userInfo['name'] = $data[0]['name']; + $userInfo['nick'] = $data[0]['name']; + $userInfo['avatar'] = $data[0]['headurl']; + return $userInfo; + } else { + E("获取人人网用户信息失败:{$data['error_msg']}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Oauth/Driver/X360.php b/Library/Think/Oauth/Driver/X360.php new file mode 100644 index 00000000..a2d169a0 --- /dev/null +++ b/Library/Think/Oauth/Driver/X360.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- + +namespace Think\Oauth\Driver; +use Think\Oauth\Driver; + +class X360 extends Driver{ + /** + * 获取requestCode的api接口 + * @var string + */ + protected $getRequestCodeURL = 'https://openapi.360.cn/oauth2/authorize'; + + /** + * 获取access_token的api接口 + * @var string + */ + protected $getAccessTokenURL = 'https://openapi.360.cn/oauth2/access_token'; + + /** + * API根路径 + * @var string + */ + protected $apiBase = 'https://openapi.360.cn/'; + + /** + * 组装接口调用参数 并调用接口 + * @param string $api 360开放平台API + * @param string $param 调用API的额外参数 + * @param string $method HTTP请求方法 默认为GET + * @return json + */ + public function call($api, $param = '', $method = 'GET'){ + /* 360开放平台调用公共参数 */ + $params = array( + 'access_token' => $this->token['access_token'], + ); + + $data = $this->http($this->url($api, '.json'), $this->param($params, $param), $method); + return json_decode($data, true); + } + + /** + * 解析access_token方法请求后的返回值 + * @param string $result 获取access_token的方法的返回值 + */ + protected function parseToken($result){ + $data = json_decode($result, true); + if($data['access_token'] && $data['expires_in'] && $data['refresh_token']){ + $data['openid'] = $this->getOpenId(); + return $data; + } else + throw new Exception("获取360开放平台ACCESS_TOKEN出错:{$data['error']}"); + } + + /** + * 获取当前授权应用的openid + * @return string + */ + public function getOpenId(){ + if(!empty($this->token['openid'])) + return $this->token['openid']; + $data = $this->call('user/me'); + return !empty($data['id'])?$data['id']:NULL; + } + + /** + * 获取当前登录的用户信息 + * @return array + */ + public function getOauthInfo(){ + $data = $this->call('user/me'); + + if($data['error_code'] == 0){ + $userInfo['type'] = 'X360'; + $userInfo['name'] = $data['name']; + $userInfo['nick'] = $data['name']; + $userInfo['avatar'] = $data['avatar']; + return $userInfo; + } else { + E("获取360用户信息失败:{$data['error']}"); + } + } + +} \ No newline at end of file diff --git a/Library/Think/Parser.php b/Library/Think/Parser.php new file mode 100644 index 00000000..2701b39b --- /dev/null +++ b/Library/Think/Parser.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- + +namespace Think; + +// 内容解析类 +class Parser { + + static private $handler = []; + + // 解析内容 + static public function parse($content,$type){ + if(!isset(self::$handler[$type])) { + $class = '\\Think\\Parser\\Driver\\'.ucwords($type); + self::$handler[$type] = new $class(); + } + return self::$handler[$type]->parse($content); + } + + // 调用驱动类的方法 + static public function __callStatic($method, $params){ + return self::parse($params[0],$method); + } +} \ No newline at end of file diff --git a/Library/Think/Parser/Driver/Markdown.php b/Library/Think/Parser/Driver/Markdown.php new file mode 100644 index 00000000..ff1865ed --- /dev/null +++ b/Library/Think/Parser/Driver/Markdown.php @@ -0,0 +1,1534 @@ + +# +# Original Markdown +# Copyright (c) 2004-2006 John Gruber +# +# + +# 应用到ThinkPHP中,因而修改为ThinkPHP规范的命名空间 +# namespace Michelf; +namespace Think\Parser\Driver; + +# +# The following two constants are deprecated: avoid using them, they'll +# disappear when the Lib branch becomes the only one to be updated. +# +# You can get the parser's version using the constant inside of the parser +# class: \Michelf\Markdown::MARKDOWNLIB_VERSION. +# + +const MARKDOWN_VERSION = "1.0.1p"; # Sun 13 Jan 2013 +const MARKDOWNEXTRA_VERSION = "1.2.6"; # Sun 13 Jan 2013 + +# +# Markdown Parser Class +# + +class Markdown { + + ### Version ### + + public const MARKDOWNLIB_VERSION = "1.3-beta4"; + + ### Simple Function Interface ### + + public static function defaultTransform($text) { + # + # Initialize the parser and return the result of its transform method. + # This will work fine for derived classes too. + # + # Take parser class on which this function was called. + $parser_class = \get_called_class(); + + # try to take parser from the static parser list + static $parser_list; + $parser =& $parser_list[$parser_class]; + + # create the parser it not already set + if (!$parser) + $parser = new $parser_class; + + # Transform text using parser. + return $parser->transform($text); + } + + ### Configuration Variables ### + + # Change to ">" for HTML output. + public $empty_element_suffix = " />"; + public $tab_width = 4; + + # Change to `true` to disallow markup or entities. + public $no_markup = false; + public $no_entities = false; + + # Predefined urls and titles for reference links and images. + public $predef_urls = array(); + public $predef_titles = array(); + + + ### Parser Implementation ### + + # Regex to match balanced [brackets]. + # Needed to insert a maximum bracked depth while converting to PHP. + protected $nested_brackets_depth = 6; + protected $nested_brackets_re; + + protected $nested_url_parenthesis_depth = 4; + protected $nested_url_parenthesis_re; + + # Table of hash values for escaped characters: + protected $escape_chars = '\`*_{}[]()>#+-.!'; + protected $escape_chars_re; + + + public function __construct() { + # + # Constructor function. Initialize appropriate member variables. + # + $this->_initDetab(); + $this->prepareItalicsAndBold(); + + $this->nested_brackets_re = + str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth). + str_repeat('\])*', $this->nested_brackets_depth); + + $this->nested_url_parenthesis_re = + str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth). + str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth); + + $this->escape_chars_re = '['.preg_quote($this->escape_chars).']'; + + # Sort document, block, and span gamut in ascendent priority order. + asort($this->document_gamut); + asort($this->block_gamut); + asort($this->span_gamut); + } + + + # Internal hashes used during transformation. + protected $urls = array(); + protected $titles = array(); + protected $html_hashes = array(); + + # Status flag to avoid invalid nesting. + protected $in_anchor = false; + + + protected function setup() { + # + # Called before the transformation process starts to setup parser + # states. + # + # Clear global hashes. + $this->urls = $this->predef_urls; + $this->titles = $this->predef_titles; + $this->html_hashes = array(); + + $this->in_anchor = false; + } + + protected function teardown() { + # + # Called after the transformation process to clear any variable + # which may be taking up memory unnecessarly. + # + $this->urls = array(); + $this->titles = array(); + $this->html_hashes = array(); + } + + /** + * 提供给ThinkPHP外部调用的方法,麦当苗儿为ThinkPHP添加 + * @author + * @param string $content 需要解析的Markdown字符串 + * @return string 解析后的HTML字符串 + */ + public function parse($content){ + return $this->transform($content); + } + + protected function transform($text) { + # + # Main function. Performs some preprocessing on the input text + # and pass it through the document gamut. + # + $this->setup(); + + # Remove UTF-8 BOM and marker character in input, if present. + $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text); + + # Standardize line endings: + # DOS to Unix and Mac to Unix + $text = preg_replace('{\r\n?}', "\n", $text); + + # Make sure $text ends with a couple of newlines: + $text .= "\n\n"; + + # Convert all tabs to spaces. + $text = $this->detab($text); + + # Turn block-level HTML blocks into hash entries + $text = $this->hashHTMLBlocks($text); + + # Strip any lines consisting only of spaces and tabs. + # This makes subsequent regexen easier to write, because we can + # match consecutive blank lines with /\n+/ instead of something + # contorted like /[ ]*\n+/ . + $text = preg_replace('/^[ ]+$/m', '', $text); + + # Run document gamut methods. + foreach ($this->document_gamut as $method => $priority) { + $text = $this->$method($text); + } + + $this->teardown(); + + return $text . "\n"; + } + + protected $document_gamut = array( + # Strip link definitions, store in hashes. + "stripLinkDefinitions" => 20, + + "runBasicBlockGamut" => 30, + ); + + + protected function stripLinkDefinitions($text) { + # + # Strips link definitions from text, stores the URLs and titles in + # hash references. + # + $less_than_tab = $this->tab_width - 1; + + # Link defs are in the form: ^[id]: url "optional title" + $text = preg_replace_callback('{ + ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1 + [ ]* + \n? # maybe *one* newline + [ ]* + (?: + <(.+?)> # url = $2 + | + (\S+?) # url = $3 + ) + [ ]* + \n? # maybe one newline + [ ]* + (?: + (?<=\s) # lookbehind for whitespace + ["(] + (.*?) # title = $4 + [")] + [ ]* + )? # title is optional + (?:\n+|\Z) + }xm', + array(&$this, '_stripLinkDefinitions_callback'), + $text); + return $text; + } + protected function _stripLinkDefinitions_callback($matches) { + $link_id = strtolower($matches[1]); + $url = $matches[2] == '' ? $matches[3] : $matches[2]; + $this->urls[$link_id] = $url; + $this->titles[$link_id] =& $matches[4]; + return ''; # String that will replace the block + } + + + protected function hashHTMLBlocks($text) { + if ($this->no_markup) return $text; + + $less_than_tab = $this->tab_width - 1; + + # Hashify HTML blocks: + # We only want to do this for block-level HTML tags, such as headers, + # lists, and tables. That's because we still want to wrap

s around + # "paragraphs" that are wrapped in non-block-level tags, such as anchors, + # phrase emphasis, and spans. The list of tags we're looking for is + # hard-coded: + # + # * List "a" is made of tags which can be both inline or block-level. + # These will be treated block-level when the start tag is alone on + # its line, otherwise they're not matched here and will be taken as + # inline later. + # * List "b" is made of tags which are always block-level; + # + $block_tags_a_re = 'ins|del'; + $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. + 'script|noscript|form|fieldset|iframe|math|svg|'. + 'article|section|nav|aside|hgroup|header|footer|'. + 'figure'; + + # Regular expression for the content of a block tag. + $nested_tags_level = 4; + $attr = ' + (?> # optional tag attributes + \s # starts with whitespace + (?> + [^>"/]+ # text outside quotes + | + /+(?!>) # slash not followed by ">" + | + "[^"]*" # text inside double quotes (tolerate ">") + | + \'[^\']*\' # text inside single quotes (tolerate ">") + )* + )? + '; + $content = + str_repeat(' + (?> + [^<]+ # content without tag + | + <\2 # nested opening tag + '.$attr.' # attributes + (?> + /> + | + >', $nested_tags_level). # end of opening tag + '.*?'. # last level nested tag content + str_repeat(' + # closing nested tag + ) + | + <(?!/\2\s*> # other tags with a different name + ) + )*', + $nested_tags_level); + $content2 = str_replace('\2', '\3', $content); + + # First, look for nested blocks, e.g.: + #

+ #
+ # tags for inner block must be indented. + #
+ #
+ # + # The outermost tags must start at the left margin for this to match, and + # the inner nested divs must be indented. + # We need to do this before the next, more liberal match, because the next + # match will start at the first `
` and stop at the first `
`. + $text = preg_replace_callback('{(?> + (?> + (?<=\n\n) # Starting after a blank line + | # or + \A\n? # the beginning of the doc + ) + ( # save in $1 + + # Match from `\n` to `\n`, handling nested tags + # in between. + + [ ]{0,'.$less_than_tab.'} + <('.$block_tags_b_re.')# start tag = $2 + '.$attr.'> # attributes followed by > and \n + '.$content.' # content, support nesting + # the matching end tag + [ ]* # trailing spaces/tabs + (?=\n+|\Z) # followed by a newline or end of document + + | # Special version for tags of group a. + + [ ]{0,'.$less_than_tab.'} + <('.$block_tags_a_re.')# start tag = $3 + '.$attr.'>[ ]*\n # attributes followed by > + '.$content2.' # content, support nesting + # the matching end tag + [ ]* # trailing spaces/tabs + (?=\n+|\Z) # followed by a newline or end of document + + | # Special case just for
. It was easier to make a special + # case than to make the other regex more complicated. + + [ ]{0,'.$less_than_tab.'} + <(hr) # start tag = $2 + '.$attr.' # attributes + /?> # the matching end tag + [ ]* + (?=\n{2,}|\Z) # followed by a blank line or end of document + + | # Special case for standalone HTML comments: + + [ ]{0,'.$less_than_tab.'} + (?s: + + ) + [ ]* + (?=\n{2,}|\Z) # followed by a blank line or end of document + + | # PHP and ASP-style processor instructions ( + ) + [ ]* + (?=\n{2,}|\Z) # followed by a blank line or end of document + + ) + )}Sxmi', + array(&$this, '_hashHTMLBlocks_callback'), + $text); + + return $text; + } + protected function _hashHTMLBlocks_callback($matches) { + $text = $matches[1]; + $key = $this->hashBlock($text); + return "\n\n$key\n\n"; + } + + + protected function hashPart($text, $boundary = 'X') { + # + # Called whenever a tag must be hashed when a function insert an atomic + # element in the text stream. Passing $text to through this function gives + # a unique text-token which will be reverted back when calling unhash. + # + # The $boundary argument specify what character should be used to surround + # the token. By convension, "B" is used for block elements that needs not + # to be wrapped into paragraph tags at the end, ":" is used for elements + # that are word separators and "X" is used in the general case. + # + # Swap back any tag hash found in $text so we do not have to `unhash` + # multiple times at the end. + $text = $this->unhash($text); + + # Then hash the block. + static $i = 0; + $key = "$boundary\x1A" . ++$i . $boundary; + $this->html_hashes[$key] = $text; + return $key; # String that will replace the tag. + } + + + protected function hashBlock($text) { + # + # Shortcut function for hashPart with block-level boundaries. + # + return $this->hashPart($text, 'B'); + } + + + protected $block_gamut = array( + # + # These are all the transformations that form block-level + # tags like paragraphs, headers, and list items. + # + "doHeaders" => 10, + "doHorizontalRules" => 20, + + "doLists" => 40, + "doCodeBlocks" => 50, + "doBlockQuotes" => 60, + ); + + protected function runBlockGamut($text) { + # + # Run block gamut tranformations. + # + # We need to escape raw HTML in Markdown source before doing anything + # else. This need to be done for each block, and not only at the + # begining in the Markdown function since hashed blocks can be part of + # list items and could have been indented. Indented blocks would have + # been seen as a code block in a previous pass of hashHTMLBlocks. + $text = $this->hashHTMLBlocks($text); + + return $this->runBasicBlockGamut($text); + } + + protected function runBasicBlockGamut($text) { + # + # Run block gamut tranformations, without hashing HTML blocks. This is + # useful when HTML blocks are known to be already hashed, like in the first + # whole-document pass. + # + foreach ($this->block_gamut as $method => $priority) { + $text = $this->$method($text); + } + + # Finally form paragraph and restore hashed blocks. + $text = $this->formParagraphs($text); + + return $text; + } + + + protected function doHorizontalRules($text) { + # Do Horizontal Rules: + return preg_replace( + '{ + ^[ ]{0,3} # Leading space + ([-*_]) # $1: First marker + (?> # Repeated marker group + [ ]{0,2} # Zero, one, or two spaces. + \1 # Marker character + ){2,} # Group repeated at least twice + [ ]* # Tailing spaces + $ # End of line. + }mx', + "\n".$this->hashBlock("empty_element_suffix")."\n", + $text); + } + + + protected $span_gamut = array( + # + # These are all the transformations that occur *within* block-level + # tags like paragraphs, headers, and list items. + # + # Process character escapes, code spans, and inline HTML + # in one shot. + "parseSpan" => -30, + + # Process anchor and image tags. Images must come first, + # because ![foo][f] looks like an anchor. + "doImages" => 10, + "doAnchors" => 20, + + # Make links out of things like `` + # Must come after doAnchors, because you can use < and > + # delimiters in inline links like [this](). + "doAutoLinks" => 30, + "encodeAmpsAndAngles" => 40, + + "doItalicsAndBold" => 50, + "doHardBreaks" => 60, + ); + + protected function runSpanGamut($text) { + # + # Run span gamut tranformations. + # + foreach ($this->span_gamut as $method => $priority) { + $text = $this->$method($text); + } + + return $text; + } + + + protected function doHardBreaks($text) { + # Do hard breaks: + return preg_replace_callback('/ {2,}\n/', + array(&$this, '_doHardBreaks_callback'), $text); + } + protected function _doHardBreaks_callback($matches) { + return $this->hashPart("empty_element_suffix\n"); + } + + + protected function doAnchors($text) { + # + # Turn Markdown link shortcuts into XHTML tags. + # + if ($this->in_anchor) return $text; + $this->in_anchor = true; + + # + # First, handle reference-style links: [link text] [id] + # + $text = preg_replace_callback('{ + ( # wrap whole match in $1 + \[ + ('.$this->nested_brackets_re.') # link text = $2 + \] + + [ ]? # one optional space + (?:\n[ ]*)? # one optional newline followed by spaces + + \[ + (.*?) # id = $3 + \] + ) + }xs', + array(&$this, '_doAnchors_reference_callback'), $text); + + # + # Next, inline-style links: [link text](url "optional title") + # + $text = preg_replace_callback('{ + ( # wrap whole match in $1 + \[ + ('.$this->nested_brackets_re.') # link text = $2 + \] + \( # literal paren + [ \n]* + (?: + <(.+?)> # href = $3 + | + ('.$this->nested_url_parenthesis_re.') # href = $4 + ) + [ \n]* + ( # $5 + ([\'"]) # quote char = $6 + (.*?) # Title = $7 + \6 # matching quote + [ \n]* # ignore any spaces/tabs between closing quote and ) + )? # title is optional + \) + ) + }xs', + array(&$this, '_doAnchors_inline_callback'), $text); + + # + # Last, handle reference-style shortcuts: [link text] + # These must come last in case you've also got [link text][1] + # or [link text](/foo) + # + $text = preg_replace_callback('{ + ( # wrap whole match in $1 + \[ + ([^\[\]]+) # link text = $2; can\'t contain [ or ] + \] + ) + }xs', + array(&$this, '_doAnchors_reference_callback'), $text); + + $this->in_anchor = false; + return $text; + } + protected function _doAnchors_reference_callback($matches) { + $whole_match = $matches[1]; + $link_text = $matches[2]; + $link_id =& $matches[3]; + + if ($link_id == "") { + # for shortcut links like [this][] or [this]. + $link_id = $link_text; + } + + # lower-case and turn embedded newlines into spaces + $link_id = strtolower($link_id); + $link_id = preg_replace('{[ ]?\n}', ' ', $link_id); + + if (isset($this->urls[$link_id])) { + $url = $this->urls[$link_id]; + $url = $this->encodeAttribute($url); + + $result = "titles[$link_id] ) ) { + $title = $this->titles[$link_id]; + $title = $this->encodeAttribute($title); + $result .= " title=\"$title\""; + } + + $link_text = $this->runSpanGamut($link_text); + $result .= ">$link_text"; + $result = $this->hashPart($result); + } + else { + $result = $whole_match; + } + return $result; + } + protected function _doAnchors_inline_callback($matches) { + $whole_match = $matches[1]; + $link_text = $this->runSpanGamut($matches[2]); + $url = $matches[3] == '' ? $matches[4] : $matches[3]; + $title =& $matches[7]; + + $url = $this->encodeAttribute($url); + + $result = "encodeAttribute($title); + $result .= " title=\"$title\""; + } + + $link_text = $this->runSpanGamut($link_text); + $result .= ">$link_text"; + + return $this->hashPart($result); + } + + + protected function doImages($text) { + # + # Turn Markdown image shortcuts into tags. + # + # + # First, handle reference-style labeled images: ![alt text][id] + # + $text = preg_replace_callback('{ + ( # wrap whole match in $1 + !\[ + ('.$this->nested_brackets_re.') # alt text = $2 + \] + + [ ]? # one optional space + (?:\n[ ]*)? # one optional newline followed by spaces + + \[ + (.*?) # id = $3 + \] + + ) + }xs', + array(&$this, '_doImages_reference_callback'), $text); + + # + # Next, handle inline images: ![alt text](url "optional title") + # Don't forget: encode * and _ + # + $text = preg_replace_callback('{ + ( # wrap whole match in $1 + !\[ + ('.$this->nested_brackets_re.') # alt text = $2 + \] + \s? # One optional whitespace character + \( # literal paren + [ \n]* + (?: + <(\S*)> # src url = $3 + | + ('.$this->nested_url_parenthesis_re.') # src url = $4 + ) + [ \n]* + ( # $5 + ([\'"]) # quote char = $6 + (.*?) # title = $7 + \6 # matching quote + [ \n]* + )? # title is optional + \) + ) + }xs', + array(&$this, '_doImages_inline_callback'), $text); + + return $text; + } + protected function _doImages_reference_callback($matches) { + $whole_match = $matches[1]; + $alt_text = $matches[2]; + $link_id = strtolower($matches[3]); + + if ($link_id == "") { + $link_id = strtolower($alt_text); # for shortcut links like ![this][]. + } + + $alt_text = $this->encodeAttribute($alt_text); + if (isset($this->urls[$link_id])) { + $url = $this->encodeAttribute($this->urls[$link_id]); + $result = "\"$alt_text\"";titles[$link_id])) { + $title = $this->titles[$link_id]; + $title = $this->encodeAttribute($title); + $result .= " title=\"$title\""; + } + $result .= $this->empty_element_suffix; + $result = $this->hashPart($result); + } + else { + # If there's no such link ID, leave intact: + $result = $whole_match; + } + + return $result; + } + protected function _doImages_inline_callback($matches) { + $whole_match = $matches[1]; + $alt_text = $matches[2]; + $url = $matches[3] == '' ? $matches[4] : $matches[3]; + $title =& $matches[7]; + + $alt_text = $this->encodeAttribute($alt_text); + $url = $this->encodeAttribute($url); + $result = "\"$alt_text\"";encodeAttribute($title); + $result .= " title=\"$title\""; # $title already quoted + } + $result .= $this->empty_element_suffix; + + return $this->hashPart($result); + } + + + protected function doHeaders($text) { + # Setext-style headers: + # Header 1 + # ======== + # + # Header 2 + # -------- + # + $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', + array(&$this, '_doHeaders_callback_setext'), $text); + + # atx-style headers: + # # Header 1 + # ## Header 2 + # ## Header 2 with closing hashes ## + # ... + # ###### Header 6 + # + $text = preg_replace_callback('{ + ^(\#{1,6}) # $1 = string of #\'s + [ ]* + (.+?) # $2 = Header text + [ ]* + \#* # optional closing #\'s (not counted) + \n+ + }xm', + array(&$this, '_doHeaders_callback_atx'), $text); + + return $text; + } + protected function _doHeaders_callback_setext($matches) { + # Terrible hack to check we haven't found an empty list item. + if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) + return $matches[0]; + + $level = $matches[2]{0} == '=' ? 1 : 2; + $block = "".$this->runSpanGamut($matches[1]).""; + return "\n" . $this->hashBlock($block) . "\n\n"; + } + protected function _doHeaders_callback_atx($matches) { + $level = strlen($matches[1]); + $block = "".$this->runSpanGamut($matches[2]).""; + return "\n" . $this->hashBlock($block) . "\n\n"; + } + + + protected function doLists($text) { + # + # Form HTML ordered (numbered) and unordered (bulleted) lists. + # + $less_than_tab = $this->tab_width - 1; + + # Re-usable patterns to match list item bullets and number markers: + $marker_ul_re = '[*+-]'; + $marker_ol_re = '\d+[\.]'; + $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; + + $markers_relist = array( + $marker_ul_re => $marker_ol_re, + $marker_ol_re => $marker_ul_re, + ); + + foreach ($markers_relist as $marker_re => $other_marker_re) { + # Re-usable pattern to match any entirel ul or ol list: + $whole_list_re = ' + ( # $1 = whole list + ( # $2 + ([ ]{0,'.$less_than_tab.'}) # $3 = number of spaces + ('.$marker_re.') # $4 = first list item marker + [ ]+ + ) + (?s:.+?) + ( # $5 + \z + | + \n{2,} + (?=\S) + (?! # Negative lookahead for another list item marker + [ ]* + '.$marker_re.'[ ]+ + ) + | + (?= # Lookahead for another kind of list + \n + \3 # Must have the same indentation + '.$other_marker_re.'[ ]+ + ) + ) + ) + '; // mx + + # We use a different prefix before nested lists than top-level lists. + # See extended comment in _ProcessListItems(). + + if ($this->list_level) { + $text = preg_replace_callback('{ + ^ + '.$whole_list_re.' + }mx', + array(&$this, '_doLists_callback'), $text); + } + else { + $text = preg_replace_callback('{ + (?:(?<=\n)\n|\A\n?) # Must eat the newline + '.$whole_list_re.' + }mx', + array(&$this, '_doLists_callback'), $text); + } + } + + return $text; + } + protected function _doLists_callback($matches) { + # Re-usable patterns to match list item bullets and number markers: + $marker_ul_re = '[*+-]'; + $marker_ol_re = '\d+[\.]'; + $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; + + $list = $matches[1]; + $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol"; + + $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re ); + + $list .= "\n"; + $result = $this->processListItems($list, $marker_any_re); + + $result = $this->hashBlock("<$list_type>\n" . $result . ""); + return "\n". $result ."\n\n"; + } + + protected $list_level = 0; + + protected function processListItems($list_str, $marker_any_re) { + # + # Process the contents of a single ordered or unordered list, splitting it + # into individual list items. + # + # The $this->list_level global keeps track of when we're inside a list. + # Each time we enter a list, we increment it; when we leave a list, + # we decrement. If it's zero, we're not in a list anymore. + # + # We do this because when we're not inside a list, we want to treat + # something like this: + # + # I recommend upgrading to version + # 8. Oops, now this line is treated + # as a sub-list. + # + # As a single paragraph, despite the fact that the second line starts + # with a digit-period-space sequence. + # + # Whereas when we're inside a list (or sub-list), that line will be + # treated as the start of a sub-list. What a kludge, huh? This is + # an aspect of Markdown's syntax that's hard to parse perfectly + # without resorting to mind-reading. Perhaps the solution is to + # change the syntax rules such that sub-lists must start with a + # starting cardinal number; e.g. "1." or "a.". + + $this->list_level++; + + # trim trailing blank lines: + $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); + + $list_str = preg_replace_callback('{ + (\n)? # leading line = $1 + (^[ ]*) # leading whitespace = $2 + ('.$marker_any_re.' # list marker and space = $3 + (?:[ ]+|(?=\n)) # space only required if item is not empty + ) + ((?s:.*?)) # list item text = $4 + (?:(\n+(?=\n))|\n) # tailing blank line = $5 + (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n)))) + }xm', + array(&$this, '_processListItems_callback'), $list_str); + + $this->list_level--; + return $list_str; + } + protected function _processListItems_callback($matches) { + $item = $matches[4]; + $leading_line =& $matches[1]; + $leading_space =& $matches[2]; + $marker_space = $matches[3]; + $tailing_blank_line =& $matches[5]; + + if ($leading_line || $tailing_blank_line || + preg_match('/\n{2,}/', $item)) + { + # Replace marker with the appropriate whitespace indentation + $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item; + $item = $this->runBlockGamut($this->outdent($item)."\n"); + } + else { + # Recursion for sub-lists: + $item = $this->doLists($this->outdent($item)); + $item = preg_replace('/\n+$/', '', $item); + $item = $this->runSpanGamut($item); + } + + return "
  • " . $item . "
  • \n"; + } + + + protected function doCodeBlocks($text) { + # + # Process Markdown `
    ` blocks.
    +	#
    +		$text = preg_replace_callback('{
    +				(?:\n\n|\A\n?)
    +				(	            # $1 = the code block -- one or more lines, starting with a space/tab
    +				  (?>
    +					[ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
    +					.*\n+
    +				  )+
    +				)
    +				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
    +			}xm',
    +			array(&$this, '_doCodeBlocks_callback'), $text);
    +
    +		return $text;
    +	}
    +	protected function _doCodeBlocks_callback($matches) {
    +		$codeblock = $matches[1];
    +
    +		$codeblock = $this->outdent($codeblock);
    +		$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
    +
    +		# trim leading newlines and trailing newlines
    +		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
    +
    +		$codeblock = "
    $codeblock\n
    "; + return "\n\n".$this->hashBlock($codeblock)."\n\n"; + } + + + protected function makeCodeSpan($code) { + # + # Create a code span markup for $code. Called from handleSpanToken. + # + $code = htmlspecialchars(trim($code), ENT_NOQUOTES); + return $this->hashPart("$code"); + } + + + protected $em_relist = array( + '' => '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(?em_relist as $em => $em_re) { + foreach ($this->strong_relist as $strong => $strong_re) { + # Construct list of allowed token expressions. + $token_relist = array(); + if (isset($this->em_strong_relist["$em$strong"])) { + $token_relist[] = $this->em_strong_relist["$em$strong"]; + } + $token_relist[] = $em_re; + $token_relist[] = $strong_re; + + # Construct master expression from list. + $token_re = '{('. implode('|', $token_relist) .')}'; + $this->em_strong_prepared_relist["$em$strong"] = $token_re; + } + } + } + + protected function doItalicsAndBold($text) { + $token_stack = array(''); + $text_stack = array(''); + $em = ''; + $strong = ''; + $tree_char_em = false; + + while (1) { + # + # Get prepared regular expression for seraching emphasis tokens + # in current context. + # + $token_re = $this->em_strong_prepared_relist["$em$strong"]; + + # + # Each loop iteration search for the next emphasis token. + # Each token is then passed to handleSpanToken. + # + $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE); + $text_stack[0] .= $parts[0]; + $token =& $parts[1]; + $text =& $parts[2]; + + if (empty($token)) { + # Reached end of text span: empty stack without emitting. + # any more emphasis. + while ($token_stack[0]) { + $text_stack[1] .= array_shift($token_stack); + $text_stack[0] .= array_shift($text_stack); + } + break; + } + + $token_len = strlen($token); + if ($tree_char_em) { + # Reached closing marker while inside a three-char emphasis. + if ($token_len == 3) { + # Three-char closing marker, close em and strong. + array_shift($token_stack); + $span = array_shift($text_stack); + $span = $this->runSpanGamut($span); + $span = "$span"; + $text_stack[0] .= $this->hashPart($span); + $em = ''; + $strong = ''; + } else { + # Other closing marker: close one em or strong and + # change current token state to match the other + $token_stack[0] = str_repeat($token{0}, 3-$token_len); + $tag = $token_len == 2 ? "strong" : "em"; + $span = $text_stack[0]; + $span = $this->runSpanGamut($span); + $span = "<$tag>$span"; + $text_stack[0] = $this->hashPart($span); + $$tag = ''; # $$tag stands for $em or $strong + } + $tree_char_em = false; + } else if ($token_len == 3) { + if ($em) { + # Reached closing marker for both em and strong. + # Closing strong marker: + for ($i = 0; $i < 2; ++$i) { + $shifted_token = array_shift($token_stack); + $tag = strlen($shifted_token) == 2 ? "strong" : "em"; + $span = array_shift($text_stack); + $span = $this->runSpanGamut($span); + $span = "<$tag>$span"; + $text_stack[0] .= $this->hashPart($span); + $$tag = ''; # $$tag stands for $em or $strong + } + } else { + # Reached opening three-char emphasis marker. Push on token + # stack; will be handled by the special condition above. + $em = $token{0}; + $strong = "$em$em"; + array_unshift($token_stack, $token); + array_unshift($text_stack, ''); + $tree_char_em = true; + } + } else if ($token_len == 2) { + if ($strong) { + # Unwind any dangling emphasis marker: + if (strlen($token_stack[0]) == 1) { + $text_stack[1] .= array_shift($token_stack); + $text_stack[0] .= array_shift($text_stack); + } + # Closing strong marker: + array_shift($token_stack); + $span = array_shift($text_stack); + $span = $this->runSpanGamut($span); + $span = "$span"; + $text_stack[0] .= $this->hashPart($span); + $strong = ''; + } else { + array_unshift($token_stack, $token); + array_unshift($text_stack, ''); + $strong = $token; + } + } else { + # Here $token_len == 1 + if ($em) { + if (strlen($token_stack[0]) == 1) { + # Closing emphasis marker: + array_shift($token_stack); + $span = array_shift($text_stack); + $span = $this->runSpanGamut($span); + $span = "$span"; + $text_stack[0] .= $this->hashPart($span); + $em = ''; + } else { + $text_stack[0] .= $token; + } + } else { + array_unshift($token_stack, $token); + array_unshift($text_stack, ''); + $em = $token; + } + } + } + return $text_stack[0]; + } + + + protected function doBlockQuotes($text) { + $text = preg_replace_callback('/ + ( # Wrap whole match in $1 + (?> + ^[ ]*>[ ]? # ">" at the start of a line + .+\n # rest of the first line + (.+\n)* # subsequent consecutive lines + \n* # blanks + )+ + ) + /xm', + array(&$this, '_doBlockQuotes_callback'), $text); + + return $text; + } + protected function _doBlockQuotes_callback($matches) { + $bq = $matches[1]; + # trim one level of quoting - trim whitespace-only lines + $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq); + $bq = $this->runBlockGamut($bq); # recurse + + $bq = preg_replace('/^/m', " ", $bq); + # These leading spaces cause problem with
     content, 
    +		# so we need to fix that:
    +		$bq = preg_replace_callback('{(\s*
    .+?
    )}sx', + array(&$this, '_doBlockQuotes_callback2'), $bq); + + return "\n". $this->hashBlock("
    \n$bq\n
    ")."\n\n"; + } + protected function _doBlockQuotes_callback2($matches) { + $pre = $matches[1]; + $pre = preg_replace('/^ /m', '', $pre); + return $pre; + } + + + protected function formParagraphs($text) { + # + # Params: + # $text - string to process with html

    tags + # + # Strip leading and trailing lines: + $text = preg_replace('/\A\n+|\n+\z/', '', $text); + + $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); + + # + # Wrap

    tags and unhashify HTML blocks + # + foreach ($grafs as $key => $value) { + if (!preg_match('/^B\x1A[0-9]+B$/', $value)) { + # Is a paragraph. + $value = $this->runSpanGamut($value); + $value = preg_replace('/^([ ]*)/', "

    ", $value); + $value .= "

    "; + $grafs[$key] = $this->unhash($value); + } + else { + # Is a block. + # Modify elements of @grafs in-place... + $graf = $value; + $block = $this->html_hashes[$graf]; + $graf = $block; +// if (preg_match('{ +// \A +// ( # $1 =
    tag +//
    ]* +// \b +// markdown\s*=\s* ([\'"]) # $2 = attr quote char +// 1 +// \2 +// [^>]* +// > +// ) +// ( # $3 = contents +// .* +// ) +// (
    ) # $4 = closing tag +// \z +// }xs', $block, $matches)) +// { +// list(, $div_open, , $div_content, $div_close) = $matches; +// +// # We can't call Markdown(), because that resets the hash; +// # that initialization code should be pulled into its own sub, though. +// $div_content = $this->hashHTMLBlocks($div_content); +// +// # Run document gamut methods on the content. +// foreach ($this->document_gamut as $method => $priority) { +// $div_content = $this->$method($div_content); +// } +// +// $div_open = preg_replace( +// '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open); +// +// $graf = $div_open . "\n" . $div_content . "\n" . $div_close; +// } + $grafs[$key] = $graf; + } + } + + return implode("\n\n", $grafs); + } + + + protected function encodeAttribute($text) { + # + # Encode text for a double-quoted HTML attribute. This function + # is *not* suitable for attributes enclosed in single quotes. + # + $text = $this->encodeAmpsAndAngles($text); + $text = str_replace('"', '"', $text); + return $text; + } + + + protected function encodeAmpsAndAngles($text) { + # + # Smart processing for ampersands and angle brackets that need to + # be encoded. Valid character entities are left alone unless the + # no-entities mode is set. + # + if ($this->no_entities) { + $text = str_replace('&', '&', $text); + } else { + # Ampersand-encoding based entirely on Nat Irons's Amputator + # MT plugin: + $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', + '&', $text);; + } + # Encode remaining <'s + $text = str_replace('<', '<', $text); + + return $text; + } + + + protected function doAutoLinks($text) { + $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i', + array(&$this, '_doAutoLinks_url_callback'), $text); + + # Email addresses: + $text = preg_replace_callback('{ + < + (?:mailto:)? + ( + (?: + [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+ + | + ".*?" + ) + \@ + (?: + [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+ + | + \[[\d.a-fA-F:]+\] # IPv4 & IPv6 + ) + ) + > + }xi', + array(&$this, '_doAutoLinks_email_callback'), $text); + + return $text; + } + protected function _doAutoLinks_url_callback($matches) { + $url = $this->encodeAttribute($matches[1]); + $link = "$url"; + return $this->hashPart($link); + } + protected function _doAutoLinks_email_callback($matches) { + $address = $matches[1]; + $link = $this->encodeEmailAddress($address); + return $this->hashPart($link); + } + + + protected function encodeEmailAddress($addr) { + # + # Input: an email address, e.g. "foo@example.com" + # + # Output: the email address as a mailto link, with each character + # of the address encoded as either a decimal or hex entity, in + # the hopes of foiling most address harvesting spam bots. E.g.: + # + #

    foo@exampl + # e.com

    + # + # Based by a filter by Matthew Wickline, posted to BBEdit-Talk. + # With some optimizations by Milian Wolff. + # + $addr = "mailto:" . $addr; + $chars = preg_split('/(? $char) { + $ord = ord($char); + # Ignore non-ascii chars. + if ($ord < 128) { + $r = ($seed * (1 + $key)) % 100; # Pseudo-random function. + # roughly 10% raw, 45% hex, 45% dec + # '@' *must* be encoded. I insist. + if ($r > 90 && $char != '@') /* do nothing */; + else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';'; + else $chars[$key] = '&#'.$ord.';'; + } + } + + $addr = implode('', $chars); + $text = implode('', array_slice($chars, 7)); # text without `mailto:` + $addr = "$text"; + + return $addr; + } + + + protected function parseSpan($str) { + # + # Take the string $str and parse it into tokens, hashing embeded HTML, + # escaped characters and handling code spans. + # + $output = ''; + + $span_re = '{ + ( + \\\\'.$this->escape_chars_re.' + | + (?no_markup ? '' : ' + | + # comment + | + <\?.*?\?> | <%.*?%> # processing instruction + | + <[!$]?[-a-zA-Z0-9:_]+ # regular tags + (?> + \s + (?>[^"\'>]+|"[^"]*"|\'[^\']*\')* + )? + > + | + <[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag + | + # closing tag + ').' + ) + }xs'; + + while (1) { + # + # Each loop iteration seach for either the next tag, the next + # openning code span marker, or the next escaped character. + # Each token is then passed to handleSpanToken. + # + $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE); + + # Create token from text preceding tag. + if ($parts[0] != "") { + $output .= $parts[0]; + } + + # Check if we reach the end. + if (isset($parts[1])) { + $output .= $this->handleSpanToken($parts[1], $parts[2]); + $str = $parts[2]; + } + else { + break; + } + } + + return $output; + } + + + protected function handleSpanToken($token, &$str) { + # + # Handle $token provided by parseSpan by determining its nature and + # returning the corresponding value that should replace it. + # + switch ($token{0}) { + case "\\": + return $this->hashPart("&#". ord($token{1}). ";"); + case "`": + # Search for end marker in remaining text. + if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm', + $str, $matches)) + { + $str = $matches[2]; + $codespan = $this->makeCodeSpan($matches[1]); + return $this->hashPart($codespan); + } + return $token; // return as text since no ending marker found. + default: + return $this->hashPart($token); + } + } + + + protected function outdent($text) { + # + # Remove one level of line-leading tabs or spaces + # + return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text); + } + + + # String length function for detab. `_initDetab` will create a function to + # hanlde UTF-8 if the default function does not exist. + protected $utf8_strlen = 'mb_strlen'; + + protected function detab($text) { + # + # Replace tabs with the appropriate amount of space. + # + # For each line we separate the line in blocks delemited by + # tab characters. Then we reconstruct every line by adding the + # appropriate number of space between each blocks. + + $text = preg_replace_callback('/^.*\t.*$/m', + array(&$this, '_detab_callback'), $text); + + return $text; + } + protected function _detab_callback($matches) { + $line = $matches[0]; + $strlen = $this->utf8_strlen; # strlen function for UTF-8. + + # Split in blocks. + $blocks = explode("\t", $line); + # Add each blocks to the line. + $line = $blocks[0]; + unset($blocks[0]); # Do not add first block twice. + foreach ($blocks as $block) { + # Calculate amount of space, insert spaces, insert block. + $amount = $this->tab_width - + $strlen($line, 'UTF-8') % $this->tab_width; + $line .= str_repeat(" ", $amount) . $block; + } + return $line; + } + protected function _initDetab() { + # + # Check for the availability of the function in the `utf8_strlen` property + # (initially `mb_strlen`). If the function is not available, create a + # function that will loosely count the number of UTF-8 characters with a + # regular expression. + # + if (function_exists($this->utf8_strlen)) return; + $this->utf8_strlen = create_function('$text', 'return preg_match_all( + "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", + $text, $m);'); + } + + + protected function unhash($text) { + # + # Swap back in all the tags hashed by _HashHTMLBlocks. + # + return preg_replace_callback('/(.)\x1A[0-9]+\1/', + array(&$this, '_unhash_callback'), $text); + } + protected function _unhash_callback($matches) { + return $this->html_hashes[$matches[0]]; + } + +} diff --git a/Library/Think/Parser/Driver/Ubb.php b/Library/Think/Parser/Driver/Ubb.php new file mode 100644 index 00000000..6ff1c1f7 --- /dev/null +++ b/Library/Think/Parser/Driver/Ubb.php @@ -0,0 +1,291 @@ + +// +---------------------------------------------------------------------- +// | Ubb.php 2013-04-03 +// +---------------------------------------------------------------------- + +namespace Think\Parser\Driver; + +class Ubb{ + /** + * UBB标签匹配规则 + * @var array + */ + private $ubb = [ + ['table' , '\[table(?:=([\d%]*))?\]', '\[\/table\]', 'width'], + ['tr' , '\[tr\]', '\[\/tr\]', 'tag'], + ['th' , '\[th(?:=([\d%]*)(?:,([\d%]*))?)?\]', '\[\/th\]', 'widthAndHeight'], + ['td' , '\[td(?:=([\d%]*)(?:,([\d%]*))?)?\]', '\[\/td\]', 'widthAndHeight'], + ['img' , '\[img(?:=([\d%]*)(?:,([\d%]*))?)?\]', '\[\/img\]', 'imgWidthAndHeight'], + ['img' , '\[img=(.*?)(?:,([\d%]*)(?:,([\d%]*))?)?\/\]', 'img'], + ['a' , '\[url(?:=(.*?)(?:,([\w\-]*))?)?\]', '\[\/url\]', 'urlClass'], + ['a' , '\[a(?:=(.*?)(?:,([\w\-]*))?)?\]', '\[\/a\]', 'urlClass'], + ['a' , '\[url=(.*?)(?:,([\w\-]*))?\/\]', 'url'], + ['a' , '\[a=(.*?)(?:,([\w\-]*))?\/\]', 'url'], + ['a' , '\[email(?:=([\w\-]*))?\]', '\[\/email\]', 'emailClass'], + ['ul' , '\[ul(?:=([\w\-]*))?\]', '\[\/ul\]', 'class'], + ['ol' , '\[ol(?:=([\w\-]*))?\]', '\[\/ol\]', 'class'], + ['li' , '\[li(?:=([\w\-]*))?\]', '\[\/li\]', 'class'], + ['span' , '\[span(?:=([\w\-]*))?\]', '\[\/span\]', 'class'], + ['div' , '\[div(?:=([\w\-]*))?\]', '\[\/div\]', 'class'], + ['p' , '\[p(?:=([\w\-]*))?\]', '\[\/p\]', 'class'], + ['strong' , '\[b\]', '\[\/b\]', 'tag'], + ['strong' , '\[strong\]', '\[\/strong\]', 'tag'], + ['i' , '\[i\]', '\[\/i\]', 'tag'], + ['em' , '\[em\]', '\[\/em\]', 'tag'], + ['sub' , '\[sub\]', '\[\/sub\]', 'tag'], + ['sup' , '\[sup\]', '\[\/sup\]', 'tag'], + ['pre' , '\[code(?:=([a-z#\+\/]*))?\]', '\[\/code\]', 'code'], + ['code' , '\[line(?:=([a-z#\+\/]*))?\]', '\[\/line\]', 'code'], + ]; + + /** + * 解析UBB代码为HTML + * @param string $content 要解析的UBB代码 + * @return string 解析后的HTML代码 + */ + public function parse($content = ''){ + if(empty($content)) return ''; + + for($i = 0, $count = count($this->ubb); $i < $count; $i++){ + if(count($this->ubb[$i]) == 4){ //解析闭合标签 + $content = $this->closeTag($content, $this->ubb[$i]); + } else { + $content = $this->onceTag($content, $this->ubb[$i]); + } + } + + return nl2br($content); + } + + /** + * 解析闭合标签,支持嵌套 + * @param string $data 要解析的数据 + * @param array $rule 解析规则 + * @return string 解析后的内容 + */ + private function closeTag($data, $rule = ''){ + static $tag, $reg, $func, $count = 0; + if(is_string($data)){ + list($tag, $reg[0], $reg[1], $func) = $rule; + do{ + $data = preg_replace_callback("/({$reg[0]})(.*?)({$reg[1]})/is", + [$this, 'closeTag'], $data); + } while ($count && $count--); //递归解析,直到嵌套解析完毕 + return $data; + } elseif(is_array($data)){ + $num = count($data); + if(preg_match("/{$reg[0]}/is", $data[$num-2])){ //存在嵌套,进一步解析 + $count = 1; + $data[$num-2] = preg_replace_callback("/({$reg[0]})(.*?)({$reg[1]})/is", + [$this, 'closeTag'], $data[$num-2] . $data[$num-1]); + return $data[1] . $data[$num-2]; + } else { //不存在嵌套,直接解析内容 + $parse = '_' . $func; + $data[$num-2] = trim($data[$num-2], "\r\n"); //去掉标签内容两端的换行符 + return $this->$parse($tag, $data); + } + } + } + + /** + * 解析单标签 + * @param string $data 要解析的数据 + * @param array $rule 解析规则 + * @return string 解析后的内容 + */ + private function onceTag($data, $rule = ''){ + list($tag, $reg, $func) = $rule; + return preg_replace_callback("/{$reg}/is", [$this, '_' . $func], $data); + } + + /** + * 解析img单标签 + * @param array $data 解析数据 + * @return string 解析后的标签 + */ + private function _img($data){ + $data[4] = $data[1]; + return $this->_imgWidthAndHeight('', $data); + } + + /** + * 解析url单标签 + * @param array $data 解析数据 + * @return string 解析后的标签 + */ + private function _url($data){ + $data[3] = $data[2]; + $data[4] = $data[2] = $data[1]; + return $this->_urlClass('', $data); + } + + /** + * 解析没有属性的标签 + * @param string $name 标签名 + * @param array $data 解析数据 [2] - 标签内容 + * @return string 解析后的标签 + */ + private function _tag($name, $data){ + return "<{$name}>{$data[2]}"; + } + + /** + * 解析代码 + * @param string $name 标签名 + * @param array $data 解析数据 [2] - 语言类型,[3] - 代码内容 + * @return string 解析后的标签 + */ + private function _code($name, $data){ + $fix = ($name == 'pre') ? ['
    ', '
    '] : ['', '']; + if(empty($data[2])){ + $data = "{$fix[0]}{$data[3]}{$fix[1]}"; + } else { + $data = "{$fix[0]}{$data[3]}{$fix[1]}"; + } + return $data; + } + + /** + * 解析含有width属性的标签 + * @param string $name 标签名 + * @param array $data 解析数据 [2] - width, [3] - 标签内容 + * @return string 解析后的标签 + */ + private function _width($name, $data){ + if(empty($data[2])){ + $data = "<{$name}>{$data[3]}"; + } else { + $data = "<{$name} width=\"{$data[2]}\">{$data[3]}"; + } + return $data; + } + + /** + * 解析含有width和height属性的标签 + * @param string $name 标签名 + * @param array $data 解析数据 [2] - width, [3] - height, [4] - 标签内容 + * @return string 解析后的标签 + */ + private function _widthAndHeight($name, $data){ + if(empty($data[2]) && empty($data[3])){ + $data = "<{$name}>{$data[4]}"; + } elseif(!empty($data[2]) && empty($data[3])) { + $data = "<{$name} width=\"{$data[2]}\">{$data[4]}"; + } elseif(empty($data[2]) && !empty($data[3])) { + $data = "<{$name} height=\"{$data[3]}\">{$data[4]}"; + } else { + $data = "<{$name} width=\"{$data[2]}\" height=\"{$data[3]}\">{$data[4]}"; + } + return $data; + } + + /** + * 解析含有width和height属性的图片标签 + * @param string $name 标签名 + * @param array $data 解析数据 [2] - width, [3] - height, [4] - 图片URL + * @return string 解析后的标签 + */ + private function _imgWidthAndHeight($name, $data){ + if(empty($data[2]) && empty($data[3])){ + $data = ""; + } elseif(!empty($data[2]) && empty($data[3])) { + $data = ""; + } elseif(empty($data[2]) && !empty($data[3])) { + $data = ""; + } else { + $data = ""; + } + return $data; + } + + /** + * 解析含有class属性的标签 + * @param string $name 标签名 + * @param array $data 解析数据 [2] - class, [3] - 标签内容 + * @return string 解析后的标签 + */ + private function _class($name, $data){ + if(empty($data[2])){ + $data = "<{$name}>{$data[3]}"; + } else { + $data = "<{$name} class=\"{$data[2]}\">{$data[3]}"; + } + return $data; + } + + /** + * 解析含有class属性的url标签 + * @param string $name 标签名 + * @param array $data 解析数据 [2] - url, [3] - text + * @return string 解析后的标签 + */ + private function _urlClass($name, $data){ + empty($data[2]) && $data[2] = $data[4]; + if(empty($data[3])){ + $data = "{$data[4]}"; + } else { + $data = "{$data[4]}"; + } + return $data; + } + + /** + * 解析含有class属性的email标签 + * @param string $name 标签名 + * @param array $data 解析数据 [2] - class, [3] - email地址 + * @return string 解析后的标签 + */ + private function _emailClass($name, $data){ + //不是正确的EMAIL则不解析 + if(preg_match('/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/', $data[3])) + return $data[0]; + + //编码email地址,防治被采集 + $email = $this->encodeEmailAddress($data[3]); + + if(empty($data[2])){ + $data = "{$email[1]}"; + } else { + $data = "{$email[1]}"; + } + return $data; + } + + /** + * 编码EMAIL地址,可以防治部分采集软件 + * @param string $addr EMAIL地址 + * @return array 编码后的EMAIL地址 [0] - 带mailto, [1] - 不带mailto + */ + private function encodeEmailAddress($addr) { + $addr = "mailto:" . $addr; + $chars = preg_split('/(? $char) { + $ord = ord($char); + # Ignore non-ascii chars. + if ($ord < 128) { + $r = ($seed * (1 + $key)) % 100; # Pseudo-random function. + # roughly 10% raw, 45% hex, 45% dec + # '@' *must* be encoded. I insist. + if ($r > 90 && $char != '@') /* do nothing */; + else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';'; + else $chars[$key] = '&#'.$ord.';'; + } + } + + $addr = implode('', $chars); + $text = implode('', array_slice($chars, 7)); # text without `mailto:` + + return [$addr, $text]; + } + +} diff --git a/Library/Think/Upload.php b/Library/Think/Upload.php new file mode 100644 index 00000000..dc9696a8 --- /dev/null +++ b/Library/Think/Upload.php @@ -0,0 +1,515 @@ + +// +---------------------------------------------------------------------- + +namespace Think; + +class Upload { + protected $config = [ + 'max_size' => -1, // 上传文件的最大值 + 'support_multi' => true, // 是否支持多文件上传 + 'allow_exts' => [], // 允许上传的文件后缀 留空不作后缀检查 + 'allow_types' => [], // 允许上传的文件类型 留空不做检查 + 'thumb' => false, // 使用对上传图片进行缩略图处理 + 'thumb_max_width' => '',// 缩略图最大宽度 + 'thumb_max_height' => '',// 缩略图最大高度 + 'thumb_prefix' => 'thumb_',// 缩略图前缀 + 'thumb_suffix' => '', + 'thumb_path' => '',// 缩略图保存路径 + 'thumb_file' => '',// 缩略图文件名 + 'thumb_ext' => '',// 缩略图扩展名 + 'thumb_remove_origin' => false,// 是否移除原图 + 'zip_images' => false,// 压缩图片文件上传 + 'auto_sub' => false,// 启用子目录保存文件 + 'sub_type' => 'hash',// 子目录创建方式 可以使用hash date custom + 'sub_dir' => '', // 子目录名称 subType为custom方式后有效 + 'date_format' => 'Ymd', + 'hash_level' => 1, // hash的目录层次 + 'save_path' => '',// 上传文件保存路径 + 'auto_check' => true, // 是否自动检查附件 + 'upload_replace' => false,// 存在同名是否覆盖 + 'save_rule' => 'uniqid',// 上传文件命名规则 + 'hash_type' => 'md5_file',// 上传文件Hash规则函数名 + ]; + + // 错误信息 + private $error = ''; + // 上传成功的文件信息 + private $uploadFileInfo ; + + public function __get($name){ + if(isset($this->config[$name])) { + return $this->config[$name]; + } + return null; + } + + public function __set($name,$value){ + if(isset($this->config[$name])) { + $this->config[$name] = $value; + } + } + + public function __isset($name){ + return isset($this->config[$name]); + } + + /** + * 架构函数 + * @access public + * @param array $config 上传参数 + */ + public function __construct($config=[]) { + if(is_array($config)) { + $this->config = array_merge($this->config,$config); + } + } + + /** + * 上传一个文件 + * @access protected + * @param mixed $name 数据 + * @param string $value 数据表名 + * @return string + */ + protected function save($file) { + $filename = $file['save_path'].$file['savename']; + if(!$this->upload_replace && is_file($filename)) { + // 不覆盖同名文件 + $this->error = '文件已经存在!'.$filename; + return false; + } + // 如果是图像文件 检测文件格式 + if( in_array(strtolower($file['extension']),['gif','jpg','jpeg','bmp','png','swf'])) { + $info = getimagesize($file['tmp_name']); + if(false === $info || ('gif' == strtolower($file['extension']) && empty($info['bits']))){ + $this->error = '非法图像文件'; + return false; + } + } + if(!move_uploaded_file($file['tmp_name'], $this->autoCharset($filename,'utf-8','gbk'))) { + $this->error = '文件上传保存错误!'; + return false; + } + if($this->thumb && in_array(strtolower($file['extension']),['gif','jpg','jpeg','bmp','png'])) { + $image = getimagesize($filename); + if(false !== $image) { + //是图像文件生成缩略图 + $thumbWidth = explode(',',$this->thumb_max_width); + $thumbHeight = explode(',',$this->thumb_max_height); + $thumb_prefix = explode(',',$this->thumb_prefix); + $thumb_suffix = explode(',',$this->thumb_suffix); + $thumb_file = explode(',',$this->thumb_file); + $thumb_path = $this->thumb_path?$this->thumb_path:dirname($filename).'/'; + $thumb_ext = $this->thumb_ext ? $this->thumb_ext : $file['extension']; //自定义缩略图扩展名 + // 生成图像缩略图 + for($i=0,$len=count($thumbWidth); $i<$len; $i++) { + if(!empty($thumb_file[$i])) { + $thumbname = $thumb_file[$i]; + }else{ + $prefix = isset($thumb_prefix[$i])?$thumb_prefix[$i]:$thumb_prefix[0]; + $suffix = isset($thumb_suffix[$i])?$thumb_suffix[$i]:$thumb_suffix[0]; + $thumbname = $prefix.basename($filename,'.'.$file['extension']).$suffix; + } + ThinkImage::thumb($filename,$thumb_path.$thumbname.'.'.$thumb_ext,'',$thumbWidth[$i],$thumbHeight[$i],true); + } + if($this->thumb_remove_origin) { + // 生成缩略图之后删除原图 + unlink($filename); + } + } + } + if($this->zipImags) { + // TODO 对图片压缩包在线解压 + + } + return true; + } + + /** + * 上传所有文件 + * @access public + * @param string $savePath 上传文件保存路径 + * @return string + */ + public function upload($savePath ='') { + //如果不指定保存文件名,则由系统默认 + if(empty($savePath)) + $savePath = $this->save_path; + // 检查上传目录 + if(!is_dir($savePath)) { + // 检查目录是否编码后的 + if(is_dir(base64_decode($savePath))) { + $savePath = base64_decode($savePath); + }else{ + // 尝试创建目录 + if(!mkdir($savePath)){ + $this->error = '上传目录'.$savePath.'不存在'; + return false; + } + } + }else { + if(!is_writeable($savePath)) { + $this->error = '上传目录'.$savePath.'不可写'; + return false; + } + } + $fileInfo = []; + $isUpload = false; + + // 获取上传的文件信息 + // 对$_FILES数组信息处理 + $files = $this->dealFiles($_FILES); + foreach($files as $key => $file) { + //过滤无效的上传 + if(!empty($file['name'])) { + //登记上传文件的扩展信息 + if(!isset($file['key'])) $file['key'] = $key; + $file['extension'] = $this->getExt($file['name']); + $file['savepath'] = $savePath; + $file['savename'] = $this->getSaveName($file); + + // 自动检查附件 + if($this->auto_check) { + if(!$this->check($file)) + return false; + } + + //保存上传文件 + if(!$this->save($file)) return false; + if(function_exists($this->hash_type)) { + $fun = $this->hash_type; + $file['hash'] = $fun($this->autoCharset($file['savepath'].$file['savename'],'utf-8','gbk')); + } + //上传成功后保存文件信息,供其他地方调用 + unset($file['tmp_name'],$file['error']); + $fileInfo[] = $file; + $isUpload = true; + } + } + if($isUpload) { + $this->uploadFileInfo = $fileInfo; + return true; + }else { + $this->error = '没有选择上传文件'; + return false; + } + } + + /** + * 上传单个上传字段中的文件 支持多附件 + * @access public + * @param array $file 上传文件信息 + * @param string $savePath 上传文件保存路径 + * @return string + */ + public function uploadOne($file,$savePath=''){ + //如果不指定保存文件名,则由系统默认 + if(empty($savePath)) + $savePath = $this->save_path; + // 检查上传目录 + if(!is_dir($savePath)) { + // 尝试创建目录 + if(!mkdir($savePath,0777,true)){ + $this->error = '上传目录'.$savePath.'不存在'; + return false; + } + }else { + if(!is_writeable($savePath)) { + $this->error = '上传目录'.$savePath.'不可写'; + return false; + } + } + //过滤无效的上传 + if(!empty($file['name'])) { + $fileArray = []; + if(is_array($file['name'])) { + $keys = array_keys($file); + $count = count($file['name']); + for ($i=0; $i<$count; $i++) { + foreach ($keys as $key) + $fileArray[$i][$key] = $file[$key][$i]; + } + }else{ + $fileArray[] = $file; + } + $info = []; + foreach ($fileArray as $key=>$file){ + //登记上传文件的扩展信息 + $file['extension'] = $this->getExt($file['name']); + $file['savepath'] = $savePath; + $file['savename'] = $this->getSaveName($file); + // 自动检查附件 + if($this->auto_check) { + if(!$this->check($file)) + return false; + } + //保存上传文件 + if(!$this->save($file)) return false; + if(function_exists($this->hash_type)) { + $fun = $this->hash_type; + $file['hash'] = $fun($this->autoCharset($file['savepath'].$file['savename'],'utf-8','gbk')); + } + unset($file['tmp_name'],$file['error']); + $info[] = $file; + } + // 返回上传的文件信息 + return $info; + }else { + $this->error = '没有选择上传文件'; + return false; + } + } + + /** + * 转换上传文件数组变量为正确的方式 + * @access protected + * @param array $files 上传的文件变量 + * @return array + */ + protected function dealFiles($files) { + $fileArray = []; + $n = 0; + foreach ($files as $key=>$file){ + if(is_array($file['name'])) { + $keys = array_keys($file); + $count = count($file['name']); + for ($i=0; $i<$count; $i++) { + $fileArray[$n]['key'] = $key; + foreach ($keys as $_key){ + $fileArray[$n][$_key] = $file[$_key][$i]; + } + $n++; + } + }else{ + $fileArray[$key] = $file; + } + } + return $fileArray; + } + + /** + * 获取错误代码信息 + * @access public + * @param string $errorNo 错误号码 + * @return void + */ + protected function error($errorNo) { + switch($errorNo) { + case 1: + $this->error = '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值'; + break; + case 2: + $this->error = '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值'; + break; + case 3: + $this->error = '文件只有部分被上传'; + break; + case 4: + $this->error = '没有文件被上传'; + break; + case 6: + $this->error = '找不到临时文件夹'; + break; + case 7: + $this->error = '文件写入失败'; + break; + default: + $this->error = '未知上传错误!'; + } + return ; + } + + /** + * 根据上传文件命名规则取得保存文件名 + * @access protected + * @param string $filename 数据 + * @return string + */ + protected function getSaveName($filename) { + $rule = $this->save_rule; + if(empty($rule)) {//没有定义命名规则,则保持文件名不变 + $saveName = $filename['name']; + }else { + if(function_exists($rule)) { + //使用函数生成一个唯一文件标识号 + $saveName = $rule().".".$filename['extension']; + }else { + //使用给定的文件名作为标识号 + $saveName = $rule.".".$filename['extension']; + } + } + if($this->auto_sub) { + // 使用子目录保存文件 + $filename['savename'] = $saveName; + $saveName = $this->getSubName($filename).$saveName; + } + return $saveName; + } + + /** + * 获取子目录的名称 + * @access protected + * @param array $file 上传的文件信息 + * @return string + */ + protected function getSubName($file) { + switch($this->sub_type) { + case 'custom': + $dir = $this->sub_dir; + break; + case 'date': + $dir = date($this->date_format,time()).'/'; + break; + case 'hash': + default: + $name = md5($file['savename']); + $dir = ''; + for($i=0;$i<$this->hash_level;$i++) { + $dir .= $name{$i}.'/'; + } + break; + } + if(!is_dir($file['savepath'].$dir)) { + mkdir($file['savepath'].$dir,0777,true); + } + return $dir; + } + + /** + * 检查上传的文件 + * @access protected + * @param array $file 文件信息 + * @return boolean + */ + protected function check($file) { + if($file['error']!== 0) { + //文件上传失败 + //捕获错误代码 + $this->error($file['error']); + return false; + } + //文件上传成功,进行自定义规则检查 + //检查文件大小 + if(!$this->checkSize($file['size'])) { + $this->error = '上传文件大小不符!'; + return false; + } + + //检查文件Mime类型 + if(!$this->checkType($file['type'])) { + $this->error = '上传文件MIME类型不允许!'; + return false; + } + //检查文件类型 + if(!$this->checkExt($file['extension'])) { + $this->error ='上传文件类型不允许'; + return false; + } + + //检查是否合法上传 + if(!$this->checkUpload($file['tmp_name'])) { + $this->error = '非法上传文件!'; + return false; + } + return true; + } + + // 自动转换字符集 支持数组转换 + protected function autoCharset($fContents, $from='gbk', $to='utf-8') { + $from = strtoupper($from) == 'UTF8' ? 'utf-8' : $from; + $to = strtoupper($to) == 'UTF8' ? 'utf-8' : $to; + if (strtoupper($from) === strtoupper($to) || empty($fContents) || (is_scalar($fContents) && !is_string($fContents))) { + //如果编码相同或者非字符串标量则不转换 + return $fContents; + } + if (function_exists('mb_convert_encoding')) { + return mb_convert_encoding($fContents, $to, $from); + } elseif (function_exists('iconv')) { + return iconv($from, $to, $fContents); + } else { + return $fContents; + } + } + + /** + * 检查上传的文件类型是否合法 + * @access protected + * @param string $type 数据 + * @return boolean + */ + protected function checkType($type) { + if(!empty($this->allow_types)) + return in_array(strtolower($type),$this->allow_types); + return true; + } + + + /** + * 检查上传的文件后缀是否合法 + * @access protected + * @param string $ext 后缀名 + * @return boolean + */ + protected function checkExt($ext) { + if(!empty($this->allow_exts)) + return in_array(strtolower($ext),$this->allow_exts,true); + return true; + } + + /** + * 检查文件大小是否合法 + * @access protected + * @param integer $size 数据 + * @return boolean + */ + protected function checkSize($size) { + return !($size > $this->max_size) || (-1 == $this->max_size); + } + + /** + * 检查文件是否非法提交 + * @access protected + * @param string $filename 文件名 + * @return boolean + */ + protected function checkUpload($filename) { + return is_uploaded_file($filename); + } + + /** + * 取得上传文件的后缀 + * @access protected + * @param string $filename 文件名 + * @return boolean + */ + protected function getExt($filename) { + $pathinfo = pathinfo($filename); + return $pathinfo['extension']; + } + + /** + * 取得上传文件的信息 + * @access public + * @return array + */ + public function getUploadFileInfo() { + return $this->uploadFileInfo; + } + + /** + * 取得最后一次错误信息 + * @access public + * @return string + */ + public function getErrorMsg() { + return $this->error; + } +} \ No newline at end of file