timthumb.php详解

WordPress26.1K阅读模式

用过timthumb这个类的都应该很熟悉,此类可以用来生成图片的缩略图并加以处理,如果在linux环境下安装了optipng或pngcrush工具,也可以进行网站的截图操作。功能非常的强大,所以趁着假期花了两天时间把源码从头到尾很详细的注释了一遍。简单说一下此类的使用方法:

访问地址为:http://localhost/timthumb.php?src=http://localhost/200.jpg&w=200&h=300&q=100&f=3,9|4,2&s=1&ct=1

参数都是get提交的,可选参数和说明如下:

src : 需要进行图片缩放的源图片地址,或者是需要进行截图操作的网页地址

webshot : 如果此值为真则进行截图操作

w : 生成图片的宽度,如果宽度或高度只设置了一个值,则根据其中一个值进行等比缩放

h : 生成图片的高度,如果高度和宽度都没有指定,则默认为100*100

zc : 生成图片的缩放模式,可选值0, 1, 2, 3, 默认为1,每个值的不同之处可看下面文件的第100行注释

q : 生成图片的质量,默认90

a : 超出部分的裁剪位置,和缩放模式有关,可选值t, b, l, r, 默认为从顶部裁剪

f : 需要对生成后的图片使用一些过滤器的话,则在这里传不同过滤器的代码和值,具体操作方法可见下面文件的第821行注解

s : 是否对生产的图片进行锐化处理

cc : 生成图片的背景画布颜色

ct : 生成png图片时背景是否透明

关于截图操作就不多说了,文件里做了很详细的注释,下面就是注释完的文件:

  1. <?php
  2. //定义版本信息
  3. define ('VERSION', '2.8.10');
  4. //如果有配置文件,则加载timthumb-config.php,没有的话使用下面的值
  5. iffile_exists(dirname(__FILE__) . '/timthumb-config.php')){
  6.   require_once('timthumb-config.php');
  7. }
  8. //调试日志记录到web服务器日志中
  9. if(! defined('DEBUG_ON') ){
  10.   define ('DEBUG_ON', false);
  11. }
  12. //调试级别,高于这个值的level都不会记录,1最低,3最高
  13. if(! defined('DEBUG_LEVEL') ){
  14.   define ('DEBUG_LEVEL', 1);
  15. }
  16. //最大占用内存限制30M
  17. if(! defined('MEMORY_LIMIT') ){
  18.   define ('MEMORY_LIMIT', '30M');
  19. }
  20. //关闭仿盗链
  21. if(! defined('BLOCK_EXTERNAL_LEECHERS') ){
  22.   define ('BLOCK_EXTERNAL_LEECHERS', false);
  23. }
  24. // 允许从外部获取图片
  25. if(! defined('ALLOW_EXTERNAL') ){
  26.   define ('ALLOW_EXTERNAL', TRUE);
  27. }
  28. //允许获取所有外部站点url
  29. if(! defined('ALLOW_ALL_EXTERNAL_SITES') ){
  30.   define ('ALLOW_ALL_EXTERNAL_SITES', false);
  31. }
  32. //启用文件缓存
  33. if(! defined('FILE_CACHE_ENABLED') ){
  34.   define ('FILE_CACHE_ENABLED', TRUE);
  35. }
  36. //文件缓存更新时间,s
  37. if(! defined('FILE_CACHE_TIME_BETWEEN_CLEANS')){
  38.   define ('FILE_CACHE_TIME_BETWEEN_CLEANS', 86400);
  39. }
  40. //文件缓存生存时间,s,过了这个时间的缓存文件就会被删除
  41. if(! defined('FILE_CACHE_MAX_FILE_AGE') ){
  42.   define ('FILE_CACHE_MAX_FILE_AGE', 86400);
  43. }
  44. //缓存文件后缀
  45. if(! defined('FILE_CACHE_SUFFIX') ){
  46.   define ('FILE_CACHE_SUFFIX', '.timthumb.txt');
  47. }
  48. //缓存文件前缀
  49. if(! defined('FILE_CACHE_PREFIX') ){
  50.   define ('FILE_CACHE_PREFIX', 'timthumb');
  51. }
  52. //缓存文件目录,留空则使用系统临时目录(推荐)
  53. if(! defined('FILE_CACHE_DIRECTORY') ){
  54.   define ('FILE_CACHE_DIRECTORY', './cache');
  55. }
  56. //图片最大尺寸,此脚本最大能处理10485760字节的图片,也就是10M
  57. if(! defined('MAX_FILE_SIZE') ){
  58.   define ('MAX_FILE_SIZE', 10485760);
  59. }
  60. //CURL的超时时间
  61. if(! defined('CURL_TIMEOUT') ){
  62.   define ('CURL_TIMEOUT', 20);
  63. }
  64. //清理错误缓存的时间
  65. if(! defined('WAIT_BETWEEN_FETCH_ERRORS') ){
  66.   define ('WAIT_BETWEEN_FETCH_ERRORS', 3600);
  67. }
  68. //浏览器缓存时间
  69. if(! defined('BROWSER_CACHE_MAX_AGE') ){
  70.   define ('BROWSER_CACHE_MAX_AGE', 864000);
  71. }
  72. //关闭所有浏览器缓存
  73. if(! defined('BROWSER_CACHE_DISABLE') ){
  74.   define ('BROWSER_CACHE_DISABLE', false);
  75. }
  76. //最大图像宽度
  77. if(! defined('MAX_WIDTH') ){
  78.   define ('MAX_WIDTH', 1500);
  79. }
  80. //最大图像高度
  81. if(! defined('MAX_HEIGHT') ){
  82.   define ('MAX_HEIGHT', 1500);
  83. }
  84. //404错误时显示的提示图片地址,设置测值则不会显示具体的错误信息
  85. if(! defined('NOT_FOUND_IMAGE') ){
  86.   define ('NOT_FOUND_IMAGE', '');
  87. }
  88. //其他错误时显示的提示图片地址,设置测值则不会显示具体的错误信息
  89. if(! defined('ERROR_IMAGE') ){
  90.   define ('ERROR_IMAGE', '');
  91. }
  92. //PNG图片背景颜色,使用false代表透明
  93. if(! defined('PNG_IS_TRANSPARENT') ){
  94.   define ('PNG_IS_TRANSPARENT', FALSE);
  95. }
  96. //默认图片质量
  97. if(! defined('DEFAULT_Q') ){
  98.   define ('DEFAULT_Q', 90);
  99. }
  100. //默认 缩放/裁剪 模式,0:根据传入的值进行缩放(不裁剪), 1:以最合适的比例裁剪和调整大小(裁剪), 2:按比例调整大小,并添加边框(裁剪),2:按比例调整大小,不添加边框(裁剪)
  101. if(! defined('DEFAULT_ZC') ){
  102.   define ('DEFAULT_ZC', 1);
  103. }
  104. //默认需要对图片进行的处理操作,可选值和参数格式请参看processImageAndWriteToCache函数中的$filters和$imageFilters的注释
  105. if(! defined('DEFAULT_F') ){
  106.   define ('DEFAULT_F', '');
  107. }
  108. //是否对图片进行锐化
  109. if(! defined('DEFAULT_S') ){
  110.   define ('DEFAULT_S', 0);
  111. }
  112. //默认画布颜色
  113. if(! defined('DEFAULT_CC') ){
  114.   define ('DEFAULT_CC', 'ffffff');
  115. }
  116. //以下是图片压缩设置,前提是你的主机中有optipng或者pngcrush这两个工具,否则的话不会启用此功能
  117. //此功能只对png图片有效
  118. if(! defined('OPTIPNG_ENABLED') ){
  119.   define ('OPTIPNG_ENABLED', false);
  120. }
  121. if(! defined('OPTIPNG_PATH') ){
  122.   define ('OPTIPNG_PATH', '/usr/bin/optipng');
  123. //优先使用optipng,因为有更好的压缩比 
  124. if(! defined('PNGCRUSH_ENABLED') ){
  125.   define ('PNGCRUSH_ENABLED', false);
  126. }
  127. if(! defined('PNGCRUSH_PATH') ){
  128.   define ('PNGCRUSH_PATH', '/usr/bin/pngcrush');
  129. //optipng不存在的话,使用pngcrush
  130. //图片压缩设置结束
  131. /* 
  132.  * * 以下是网站截图配置
  133.  * 首先,网站截图需要root权限
  134.  * Ubuntu 上使用网站截图的步骤:
  135.  *  1.用这个命令安装Xvfb  sudo apt-get install subversion libqt4-webkit libqt4-dev g++ xvfb
  136.  *  2.新建一个文件夹,并下载下面的源码
  137.  *  3.用这个命令下载最新的CutyCapt  svn co https://cutycapt.svn.sourceforge.net/svnroot/cutycapt
  138.  *  4.进入CutyCapt文件夹
  139.  *  5.编译并安装CutyCapt
  140.  *  6.尝试运行以下命令:  xvfb-run --server-args="-screen 0, 1024x768x24" CutyCapt --url="http://markmaunder.com/" --out=test.png
  141.  *  7.如果生成了一个 test.php的图片,证明一切正常,现在通过浏览器试试,访问下面的地址:http://yoursite.com/path/to/timthumb.php?src=http://markmaunder.com/&webshot=1
  142.  *
  143.  * 需要注意的地方:
  144.  *  1.第一次webshot加载时,需要数秒钟,之后加载就很快了
  145.  *
  146.  * 高级用户:
  147.  *  1.如果想提速大约25%,并且你了解linux,可以运行以下命令:
  148.  *  nohup Xvfb :100 -ac -nolisten tcp -screen 0, 1024x768x24 > /dev/null 2>&1 &
  149.  *  并设置 WEBSHOT_XVFB_RUNNING 的值为true
  150.  *
  151.  * */
  152. //测试的功能,如果设置此值为true, 并在查询字符串中加上webshot=1,会让脚本返回浏览器的截图,而不是获取图像
  153. if(! defined('WEBSHOT_ENABLED') ){
  154.   define ('WEBSHOT_ENABLED', false);
  155. }
  156. //定义CutyCapt地址
  157. if(! defined('WEBSHOT_CUTYCAPT') ){
  158.   define ('WEBSHOT_CUTYCAPT', '/usr/local/bin/CutyCapt');
  159. }
  160. //Xvfb地址
  161. if(! defined('WEBSHOT_XVFB') ){
  162.   define ('WEBSHOT_XVFB', '/usr/bin/xvfb-run');
  163. }
  164. //截图屏幕宽度1024
  165. if(! defined('WEBSHOT_SCREEN_X') ){
  166.   define ('WEBSHOT_SCREEN_X', '1024');
  167. }
  168. //截图屏幕高度768
  169. if(! defined('WEBSHOT_SCREEN_Y') ){
  170.   define ('WEBSHOT_SCREEN_Y', '768');
  171. }
  172. //色深,作者说他只测试过24
  173. if(! defined('WEBSHOT_COLOR_DEPTH') ){
  174.   define ('WEBSHOT_COLOR_DEPTH', '24');
  175. }
  176. //截图格式
  177. if(! defined('WEBSHOT_IMAGE_FORMAT') ){
  178.   define ('WEBSHOT_IMAGE_FORMAT', 'png');
  179. }
  180. //截图超时时间,单位s
  181. if(! defined('WEBSHOT_TIMEOUT') ){
  182.   define ('WEBSHOT_TIMEOUT', '20');
  183. }
  184. //user_agent头
  185. if(! defined('WEBSHOT_USER_AGENT') ){
  186.   define ('WEBSHOT_USER_AGENT', "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18");
  187. }
  188. //是否启用JS
  189. if(! defined('WEBSHOT_JAVASCRIPT_ON') ){
  190.   define ('WEBSHOT_JAVASCRIPT_ON', true);
  191. }
  192. //是否启用java
  193. if(! defined('WEBSHOT_JAVA_ON') ){
  194.   define ('WEBSHOT_JAVA_ON', false);
  195. }
  196. //开启flash和其他插件
  197. if(! defined('WEBSHOT_PLUGINS_ON') ){
  198.   define ('WEBSHOT_PLUGINS_ON', true);
  199. }
  200. //代理服务器
  201. if(! defined('WEBSHOT_PROXY') ){
  202.   define ('WEBSHOT_PROXY', '');
  203. }
  204. //如果运行了XVFB,此项设为true
  205. if(! defined('WEBSHOT_XVFB_RUNNING') ){
  206.   define ('WEBSHOT_XVFB_RUNNING', false);
  207. }
  208. // 如果 ALLOW_EXTERNAL 的值为真 并且 ALLOW_ALL_EXTERNAL_SITES 的值为假,那么截图的图片只能从下面这些数组中的域和子域进行
  209. if(! isset($ALLOWED_SITES)){
  210.   $ALLOWED_SITES = array (
  211.     'flickr.com',
  212.     'staticflickr.com',
  213.     'picasa.com',
  214.     'img.youtube.com',
  215.     'upload.wikimedia.org',
  216.     'photobucket.com',
  217.     'imgur.com',
  218.     'imageshack.us',
  219.     'tinypic.com',
  220.   );
  221. }
  222. /*截图配置结束*/
  223. // -------------------------------------------------------------
  224. // -------------------------- 配置结束 ------------------------
  225. // -------------------------------------------------------------
  226. timthumb::start();
  227. class timthumb {
  228.     protected $src = "";  //需要获取的图片url
  229.     protected $is404 = false;  //404错误码
  230.     protected $docRoot = "";  //服务器文档根目录
  231.     protected $lastURLError = false; //上一次请求外部url的错误信息
  232.     protected $localImage = ""//如果请求的url是本地图片,则为本地图片的地址
  233.     protected $localImageMTime = 0;  //本地图片的修改时间
  234.     protected $url = false;  //用parse_url解析src后的数组
  235.         protected $myHost = "";  //本机域名
  236.     protected $isURL = false;  //是否为外部图片地址
  237.     protected $cachefile = ''//缓存文件地址
  238.     protected $errors = array();  //错误信息列表
  239.     protected $toDeletes = array(); //析构函数中需要删除的资源列表
  240.     protected $cacheDirectory = ''//缓存地址
  241.     protected $startTime = 0;  //开始时间
  242.     protected $lastBenchTime = 0; //上一次debug完成的时间
  243.     protected $cropTop = false;  //是否启用裁剪
  244.     protected $salt = "";  //文件修改时间和inode连接的字符串的盐值
  245.     protected $fileCacheVersion = 1; //文件缓存版本,当这个类升级或者被更改时,这个值应该改变,从而重新生成缓存
  246.     protected $filePrependSecurityBlock = "<?php die('Execution denied!'); //"; //缓存文件安全头,防止直接访问
  247.     protected static $curlDataWritten = 0;  //将curl获取到的数据写入缓存文件的长度
  248.     protected static $curlFH = false;  //curl请求成功后要将获取到的数据写到此文件内
  249.     /*外部调用接口*/
  250.     public static function start(){
  251.         //实例化模型
  252.         $tim = new timthumb();
  253.         //检查实例化模型时是否有错误
  254.         $tim->handleErrors();
  255.         //此函数为空,用做自定义的数据验证
  256.         $tim->securityChecks();
  257.         //尝试读取浏览器缓存
  258.         if($tim->tryBrowserCache()){
  259.             //成功的话就输出缓存
  260.             exit(0);
  261.         }
  262.         //检测错误
  263.         $tim->handleErrors();
  264.         //如果启用了文件缓存,并且读取服务端缓存
  265.         if(FILE_CACHE_ENABLED && $tim->tryServerCache()){
  266.             //成功的话输出缓存
  267.             exit(0);
  268.         }
  269.         //检测读取服务端缓存时的错误
  270.         $tim->handleErrors();
  271.         //生成和处理图片主函数
  272.         $tim->run();
  273.         //检测图片生成和处理时的错误
  274.         $tim->handleErrors();
  275.         //程序执行完毕运行析构方法并退出
  276.         exit(0);
  277.     }
  278.     /*构造方法,用来获取和设置一些基本属性*/
  279.     public function __construct(){
  280.         //将允许的域设为全局变量
  281.         global $ALLOWED_SITES;
  282.         //记录开始时间
  283.         $this->startTime = microtime(true);
  284.         //设置时区
  285.         date_default_timezone_set('UTC');
  286.         //写日志,记录请求IP和请求URL
  287.         $this->debug(1, "Starting new request from " . $this->getIP() . " to " . $_SERVER['REQUEST_URI']);
  288.         //获取服务器文档根目录
  289.         $this->calcDocRoot();
  290.         //获取文件的修改时间和inode,inode只在linux系统下有效
  291.         $this->salt = @filemtime(__FILE__) . '-' . @fileinode(__FILE__);
  292.         //记录salt信息,级别3
  293.         $this->debug(3, "Salt is: " . $this->salt);
  294.         //如果定义了缓存文件地址
  295.         if(FILE_CACHE_DIRECTORY){
  296.             //如果这个地址不存在,或为非目录
  297.             if(! is_dir(FILE_CACHE_DIRECTORY)){
  298.                     //建立这个目录
  299.                 @mkdir(FILE_CACHE_DIRECTORY);
  300.                 //如果建立失败
  301.                 if(! is_dir(FILE_CACHE_DIRECTORY)){
  302.                     //记录错误信息,停止执行
  303.                     $this->error("Could not create the file cache directory.");
  304.                     return false;
  305.                 }
  306.             }
  307.             //将缓存地址写入成员属性
  308.             $this->cacheDirectory = FILE_CACHE_DIRECTORY;
  309.             //在缓存目录下创建一个index.html文件,防止列目录
  310.             if (!touch($this->cacheDirectory . '/index.html')) {
  311.                 //如果出错,记录错误信息
  312.                 $this->error("Could not create the index.html file - to fix this create an empty file named index.html file in the cache directory.");
  313.             }
  314.         //如果没定义缓存文件地址,则用系统的临时文件目录做为缓存文件目录
  315.         } else {
  316.             $this->cacheDirectory = sys_get_temp_dir();
  317.         }
  318.         //进行缓存检查,清除过期缓存
  319.         $this->cleanCache();
  320.         //记录本机域名
  321.         $this->myHost = preg_replace('/^www\./i', ''$_SERVER['HTTP_HOST']);
  322.         //获取图片地址,此地址应该由$_GET中的src参数传递
  323.         $this->src = $this->param('src');
  324.         //此数组是解析src后的结果,包括scheme,host,port,user,pass,path,query,fragment其中一个或多个值
  325.         $this->url = parse_url($this->src);
  326.         //如果图片地址是本机的,则删除图片url中本机的域名部分
  327.         $this->src = preg_replace('/https?:\/\/(?:www\.)?' . $this->myHost . '/i', ''$this->src);
  328.         //如果图片地址的长度小于3,则是无效地址
  329.         if(strlen($this->src) <= 3){
  330.             //添加错误信息
  331.             $this->error("No image specified");
  332.             return false;
  333.         }
  334.         //如果开启了防盗链,并且存在来源地址,也就是HTTP_REFERER头,并且来源地址不是本机,则进行防盗链处理
  335.         if(BLOCK_EXTERNAL_LEECHERS && array_key_exists('HTTP_REFERER', $_SERVER) && (! preg_match('/^https?:\/\/(?:www\.)?' . $this->myHost . '(?:$|\/)/i', $_SERVER['HTTP_REFERER']))){
  336.             //此base64编码的内容是一张显示 No Hotlinking的图片
  337.             $imgData = base64_decode("R0lGODlhUAAMAIAAAP8AAP///yH5BAAHAP8ALAAAAABQAAwAAAJpjI+py+0Po5y0OgAMjjv01YUZ\nOGplhWXfNa6JCLnWkXplrcBmW+spbwvaVr/cDyg7IoFC2KbYVC2NQ5MQ4ZNao9Ynzjl9ScNYpneb\nDULB3RP6JuPuaGfuuV4fumf8PuvqFyhYtjdoeFgAADs=");
  338.             //显示内容为gif图片
  339.             header('Content-Type: image/gif');
  340.             //内容长度
  341.             header('Content-Length: ' . sizeof($imgData));
  342.             //无网页缓存
  343.             header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
  344.             //兼容http1.0和https
  345.             header("Pragma: no-cache");
  346.             //内容立即过期
  347.             header('Expires: ' . gmdate ('D, d M Y H:i:s', time()));
  348.             //输出图片
  349.             echo $imgData;
  350.             return false;
  351.             //退出脚本
  352.             exit(0);
  353.         }
  354.         //如果此时的src属性包含http等字符,说明是外部链接
  355.         if(preg_match('/^https?:\/\/[^\/]+/i', $this->src)){
  356.             //写日志,说明这个链接是外部的,级别2
  357.             $this->debug(2, "Is a request for an external URL: " . $this->src);
  358.             //将isURL设为true,说明是外部url
  359.             $this->isURL = true;
  360.         //如果不包含的话,说明是内部链接
  361.         } else {
  362.             $this->debug(2, "Is a request for an internal file: " . $this->src);
  363.         }
  364.         //如果图片的src是外部网站,并且配置文件不允许从外部获取图片,则退出
  365.         if($this->isURL && (! ALLOW_EXTERNAL)){
  366.             $this->error("You are not allowed to fetch images from an external website.");
  367.             return false;
  368.         }
  369.         //如果允许从外部网站获取图片
  370.         if($this->isURL){
  371.             //并且配置文件允许从所有的外部网站获取图片
  372.             if(ALLOW_ALL_EXTERNAL_SITES){
  373.                 //写日志,允许从外部网站取回图片,级别2
  374.                 $this->debug(2, "Fetching from all external sites is enabled.");
  375.             //如果配置文件不允许从所有的外部网站获取图片
  376.             } else {
  377.                 //写日志,只能从指定的外部网站获取图片,级别2
  378.                 $this->debug(2, "Fetching only from selected external sites is enabled.");
  379.                 //此为验证位,默认为false
  380.                 $allowed = false;
  381.                 //遍历配置文件中允许站点的列表
  382.                 foreach($ALLOWED_SITES as $site){
  383.                     //这里对图片的url跟允许访问站点的列表进行验证,前面的条件对应的是有主机名的,后面的内容对应的是没有主机名的,写的很精巧
  384.                     if ((strtolower(substr($this->url['host'],-strlen($site)-1)) === strtolower(".$site")) || (strtolower($this->url['host'])===strtolower($site))) {
  385.                         //通过验证则写一条日志,验证成功,级别3
  386.                         $this->debug(3, "URL hostname {$this->url['host']} matches $site so allowing.");
  387.                         //验证位为true
  388.                         $allowed = true;
  389.                     }
  390.                 }
  391.                 //如果没通过验证, 写错误信息并退出
  392.                 if(! $allowed){
  393.                     return $this->error("You may not fetch images from that site. To enable this site in timthumb, you can either add it to \$ALLOWED_SITES and set ALLOW_EXTERNAL=true. Or you can set ALLOW_ALL_EXTERNAL_SITES=true, depending on your security needs.");
  394.                 }
  395.             }
  396.         }
  397.         //缓存文件的前缀,如果是内部图片,用_int_,外部图片用_ext_
  398.         $cachePrefix = ($this->isURL ? '_ext_' : '_int_');
  399.         //如果是外部图片地址
  400.         if($this->isURL){
  401.             //得到GET字符串的数组
  402.             $arr = explode('&', $_SERVER ['QUERY_STRING']);
  403.             //按字母顺序对数组元素排序
  404.             asort($arr);
  405.             //生成缓存文件地址  缓存目录 + / + 缓存前缀 + $cachePrefix + 唯一散列值  + 缓存后缀
  406.             $this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . implode(''$arr) . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
  407.         //如果是本地图片
  408.         } else {
  409.             //获取本地图片地址
  410.             $this->localImage = $this->getLocalImagePath($this->src);
  411.             //如果获取不到地址
  412.             if(! $this->localImage){
  413.                 //写日志,没有找到此文件,级别1
  414.                 $this->debug(1, "Could not find the local image: {$this->localImage}");
  415.                 //记录错误信息
  416.                 $this->error("Could not find the internal image you specified.");
  417.                 //设置404错误信息
  418.                 $this->set404();
  419.                 //终止执行程序
  420.                 return false;
  421.             }
  422.             //写日志,记录本地图片信息,级别1
  423.             $this->debug(1, "Local image path is {$this->localImage}");
  424.             //获取文件修改时间
  425.             $this->localImageMTime = @filemtime($this->localImage);
  426.             //生成缓存文件地址,  缓存目录 + / + 缓存前缀 + $cachePrefix + 唯一散列值 + 缓存后缀
  427.             $this->cachefile = $this->cacheDirectory . '/' . FILE_CACHE_PREFIX . $cachePrefix . md5($this->salt . $this->localImageMTime . $_SERVER ['QUERY_STRING'] . $this->fileCacheVersion) . FILE_CACHE_SUFFIX;
  428.         }
  429.         //记录缓存文件地址
  430.         $this->debug(2, "Cache file is: " . $this->cachefile);
  431.         //构造函数完成
  432.         return true;
  433.     }
  434.     /*析构方法,删除toDeletes数组中的每一个文件*/
  435.     public function __destruct(){
  436.         foreach($this->toDeletes as $del){
  437.             $this->debug(2, "Deleting temp file $del");
  438.             @unlink($del);
  439.         }
  440.     }
  441.     /*主函数,通过不同参数调用不同的图片处理函数*/
  442.     public function run(){
  443.         //如果是外部的图片链接
  444.         if($this->isURL){
  445.             //但是配置文件不允许从外部获取链接
  446.             if(! ALLOW_EXTERNAL){
  447.                 //写日志,说明配置文件禁止访问外部图片,级别1
  448.                 $this->debug(1, "Got a request for an external image but ALLOW_EXTERNAL is disabled so returning error msg.");
  449.                 //写错误记录
  450.                 $this->error("You are not allowed to fetch images from an external website.");
  451.                 //退出执行
  452.                 return false;
  453.             }
  454.             //配置文件允许从外部获取链接,则写日志,接着运行,级别3
  455.             $this->debug(3, "Got request for external image. Starting serveExternalImage.");
  456.             //如果get了webshot参数并且为真,则进行截图操作
  457.             if($this->param('webshot')){
  458.                 //如果配置文件允许截图
  459.                 if(WEBSHOT_ENABLED){
  460.                         //写日志,说明要进行截图操作,级别3
  461.                     $this->debug(3, "webshot param is set, so we're going to take a webshot.");
  462.                     //截图操作
  463.                     $this->serveWebshot();
  464.                 //如果配置文件不允许截图
  465.                 } else {
  466.                     //记录错误信息并退出
  467.                     $this->error("You added the webshot parameter but webshots are disabled on this server. You need to set WEBSHOT_ENABLED == true to enable webshots.");
  468.                 }
  469.             //如果不存在sebshot参数或为假
  470.             } else {
  471.                 //写日志,记录不进行截图操作,级别3
  472.                 $this->debug(3, "webshot is NOT set so we're going to try to fetch a regular image.");
  473.                 //从外部URL获得图片并处理
  474.                 $this->serveExternalImage();
  475.             }
  476.         //否则的话就是内部图片
  477.         } else {
  478.             //写日志,记录是内部图片,级别3
  479.             $this->debug(3, "Got request for internal image. Starting serveInternalImage()");
  480.             //获得内部图片并处理
  481.             $this->serveInternalImage();
  482.         }
  483.         //程序执行完毕
  484.         return true;
  485.     }
  486.     /*此函数用来处理错误*/
  487.     protected function handleErrors(){
  488.         //如果错误列表中有内容
  489.         if($this->haveErrors()){
  490.             //首先检测404错误,如果设置了404图片地址并且的确有404错误
  491.             if(NOT_FOUND_IMAGE && $this->is404()){
  492.                 //那么输出错误图片,并退出脚本
  493.                 if($this->serveImg(NOT_FOUND_IMAGE)){
  494.                     exit(0);
  495.                 //输出失败的话记录错误信息
  496.                 } else {
  497.                     $this->error("Additionally, the 404 image that is configured could not be found or there was an error serving it.");
  498.                 }
  499.             }
  500.             //如果没有404错误,并且定义了错误图片,那么输出此图片
  501.             if(ERROR_IMAGE){
  502.                 //输出其他错误提示图片,并退出脚本
  503.                 if($this->serveImg(ERROR_IMAGE)){
  504.                     exit(0);
  505.                 //输出失败的话记录错误信息
  506.                 } else {
  507.                     $this->error("Additionally, the error image that is configured could not be found or there was an error serving it.");
  508.                 }
  509.             }
  510.             //如果上面两个常量都没定义,则根据模板输出详细错误信息
  511.             $this->serveErrors();
  512.             exit(0);
  513.         }
  514.         //没有错误的话返回假
  515.         return false;
  516.     }
  517.     /*此函数用来读取浏览器缓存文件,前提是浏览器缓存的文件有效,具体的实现请看函数内部*/
  518.     protected function tryBrowserCache(){
  519.         //如果配置文件关闭了所有浏览器缓存,则写日志,并返回假
  520.         if(BROWSER_CACHE_DISABLE){
  521.             $this->debug(3, "Browser caching is disabled"); return false;
  522.         }
  523.         //如果浏览器记录了页面上次修改的时间
  524.         if(!emptyempty($_SERVER['HTTP_IF_MODIFIED_SINCE']) ){
  525.             //写日志,记录,级别3
  526.             $this->debug(3, "Got a conditional get");
  527.             //缓存文件最后修改时间
  528.             $mtime = false;
  529.             //如果缓存地址无效
  530.             if(! is_file($this->cachefile)){
  531.                 //说明没有缓存,返回假
  532.                 return false;
  533.             }
  534.             //如果存在本地图片修改时间,也就是说所请求的图片是本机的
  535.             if($this->localImageMTime){
  536.                 //缓存文件修改时间设置为本地图片修改时间
  537.                 $mtime = $this->localImageMTime;
  538.                 //写日志,记录实际文件修改时间,级别3
  539.                 $this->debug(3, "Local real file's modification time is $mtime");
  540.             //如果请求的图片不是本地的
  541.             } else if(is_file($this->cachefile)){
  542.                 //获取缓存文件修改时间
  543.                 $mtime = @filemtime($this->cachefile);
  544.                 //写日志,记录缓存文件修改时间,级别3
  545.                 $this->debug(3, "Cached file's modification time is $mtime");
  546.             }
  547.             //如果没有获取到缓存文件最后修改时间,说明没有缓存,退出
  548.             if(! $mtime){ return false; }
  549.             //将浏览器中存储的上次修改时间转为时间戳
  550.             $iftime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
  551.             //写日志,记录UNIX时间戳,级别3
  552.             $this->debug(3, "The conditional get's if-modified-since unixtime is $iftime");
  553.             //如果这个时间小于1秒,说明值无效,退出
  554.             if($iftime < 1){
  555.                 //写日志,记录此值无效
  556.                 $this->debug(3, "Got an invalid conditional get modified since time. Returning false.");
  557.                 return false;
  558.             }
  559.             //如果浏览器存储的时间小于实际缓存文件的最后修改时间,也就是说距上次访问后,文件被更改了,要重新请求页面
  560.             if($iftime < $mtime){
  561.                 //写日志,记录文件已被更改,级别3
  562.                 $this->debug(3, "File has been modified since last fetch.");
  563.                 return false;
  564.             //否则就不用重新请求页面,直接读取缓存
  565.             } else {
  566.                 //写日志,记录缓存有效,直接读取缓存,级别3
  567.                 $this->debug(3, "File has not been modified since last get, so serving a 304.");
  568.                 //设置HTTP头响应码为304
  569.                 header ($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
  570.                 //记录此次操作,级别1
  571.                 $this->debug(1, "Returning 304 not modified");
  572.                 //读取成功返回真
  573.                 return true;
  574.             }
  575.         }
  576.         //没有读取到缓存,返回假
  577.         return false;
  578.     }
  579.     /*此函数用来运行缓存文件的GC和读取服务器上的缓存文件*/
  580.     protected function tryServerCache(){
  581.         //写日志,记录将读取服务端缓存,级别3
  582.         $this->debug(3, "Trying server cache");
  583.         //如果缓存文件存在
  584.         if(file_exists($this->cachefile)){
  585.             //写日志,记录缓存文件存在
  586.             $this->debug(3, "Cachefile {$this->cachefile} exists");
  587.             //如果请求的是外部图片地址
  588.             if($this->isURL){
  589.                 //写日志,记录这是一次外部请求,级别3
  590.                 $this->debug(3, "This is an external request, so checking if the cachefile is empty which means the request failed previously.");
  591.                 //如果缓存文件的大小小于1,也就是说是一个无效的缓存文件
  592.                 if(filesize($this->cachefile) < 1){
  593.                     //写日志,记录这是一个空的缓存文件,级别3
  594.                     $this->debug(3, "Found an empty cachefile indicating a failed earlier request. Checking how old it is.");
  595.                     //如果已到了配置文件中清理无效缓存的时间
  596.                     if(time() - @filemtime($this->cachefile) > WAIT_BETWEEN_FETCH_ERRORS){
  597.                         //写日志,记录这次删除操作,级别3
  598.                         $this->debug(3, "File is older than " . WAIT_BETWEEN_FETCH_ERRORS . " seconds. Deleting and returning false so app can try and load file.");
  599.                         //删除此缓存文件
  600.                         @unlink($this->cachefile);
  601.                         //返回假,说明没有读取到服务端缓存
  602.                         return false;
  603.                     //否则,空的缓存文件说明上次请求失败,所以要写错误记录
  604.                     } else {
  605.                         //写日志,记录空的缓存文件依然有效,级别3
  606.                         $this->debug(3, "Empty cachefile is still fresh so returning message saying we had an error fetching this image from remote host.");
  607.                         //设置404错误
  608.                         $this->set404();
  609.                         //设置错误信息
  610.                         $this->error("An error occured fetching image.");
  611.                         //返回假代表没有得到缓存
  612.                         return false;
  613.                     }
  614.                 }
  615.             //否则就是正确的缓存文件
  616.             } else {
  617.                 //写日志,记录将要直接读取缓存文件,级别3
  618.                 $this->debug(3, "Trying to serve cachefile {$this->cachefile}");
  619.             }
  620.             //如果输出图像缓存成功
  621.             if($this->serveCacheFile()){
  622.                 //写日志,记录缓存文件信息,级别3
  623.                 $this->debug(3, "Succesfully served cachefile {$this->cachefile}");
  624.                 return true;
  625.             //如果不成功
  626.             } else {
  627.                 //写日志,记录错误信息,级别3
  628.                 $this->debug(3, "Failed to serve cachefile {$this->cachefile} - Deleting it from cache.");
  629.                 //删除此无效缓存,以便下次请求能重新创建
  630.                 @unlink($this->cachefile);
  631.                 //同样返回真,因为在serverCacheFile已经记录了错误信息
  632.                 return true;
  633.             }
  634.         }
  635.     }
  636.     /*此函数用来记录错误信息*/
  637.     protected function error($err){
  638.         //写记录,记录错误信息,级别3
  639.         $this->debug(3, "Adding error message: $err");
  640.         //记录到错误信息数组
  641.         $this->errors[] = $err;
  642.         return false;
  643.     }
  644.     /*测函数用来检测存储错误信息的数组中是否有内容,也就是说在上一个操作中,是否有错误*/
  645.     protected function haveErrors(){
  646.         if(sizeof($this->errors) > 0){
  647.             return true;
  648.         }
  649.         return false;
  650.     }
  651.     /*此函数输出已存储的错误信息*/
  652.     protected function serveErrors(){
  653.         //设置http头
  654.       header ($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request');
  655.         //循环输出错误列表信息
  656.         $html = '<ul>';
  657.         foreach($this->errors as $err){
  658.             $html .= '<li>' . htmlentities($err) . '</li>';
  659.         }
  660.         $html .= '</ul>';
  661.         //输出其他错误信息
  662.         echo '<h1>A TimThumb error has occured</h1>The following error(s) occured:<br />' . $html . '<br />';
  663.         echo '<br />Query String : ' . htmlentities ($_SERVER['QUERY_STRING']);
  664.         echo '<br />TimThumb version : ' . VERSION . '</pre>';
  665.     }
  666.     /*此函数用来读取本地图片*/
  667.     protected function serveInternalImage(){
  668.         //写日志,记录本地图片地址
  669.         $this->debug(3, "Local image path is $this->localImage");
  670.         //如果地址无效
  671.         if(! $this->localImage){
  672.             //记录此错误,并退出执行
  673.             $this->sanityFail("localImage not set after verifying it earlier in the code.");
  674.             return false;
  675.         }
  676.         //获取本地图片大小
  677.         $fileSize = filesize($this->localImage);
  678.         //如果本地图片的尺寸超过配置文件的相关设置
  679.         if($fileSize > MAX_FILE_SIZE){
  680.             //记录错误原因,并退出
  681.             $this->error("The file you specified is greater than the maximum allowed file size.");
  682.             return false;
  683.         }
  684.         //如果获取到的图片尺寸无效
  685.         if($fileSize <= 0){
  686.             //记录错误并退出
  687.             $this->error("The file you specified is <= 0 bytes.");
  688.             return false;
  689.         }
  690.         //如果通过了以上验证,则写日志,记录将用processImageAndWriteToCache函数处理本地图片
  691.         $this->debug(3, "Calling processImageAndWriteToCache() for local image.");
  692.         //处理成功则从缓存返回图片
  693.         if($this->processImageAndWriteToCache($this->localImage)){
  694.             $this->serveCacheFile();
  695.             return true;
  696.         //失败则返回假
  697.         } else {
  698.             return false;
  699.         }
  700.     }
  701.     /*此函数用来清理缓存*/
  702.     protected function cleanCache(){
  703.         //如果定义的缓存时间小于0,则退出
  704.         if (FILE_CACHE_TIME_BETWEEN_CLEANS < 0) {
  705.             return;
  706.         }
  707.         //写日志,记录清除缓存操作,级别3
  708.         $this->debug(3, "cleanCache() called");
  709.         //此文件为记录上次进行清除缓存操作的时间戳文件
  710.         $lastCleanFile = $this->cacheDirectory . '/timthumb_cacheLastCleanTime.touch';
  711.         //如果上面定义的文件不存在,说明这是第一次进行清除缓存操作,创建此文件并返回空即可
  712.         if(! is_file($lastCleanFile)){
  713.             //写日志,记录创建文件,级别1
  714.             $this->debug(1, "File tracking last clean doesn't exist. Creating $lastCleanFile");
  715.             //创建此文件
  716.             if (!touch($lastCleanFile)) {
  717.                 //失败的话报错并退出
  718.                 $this->error("Could not create cache clean timestamp file.");
  719.             }
  720.             return;
  721.         }
  722.         //如果已超过缓存时间
  723.         if(@filemtime($lastCleanFile) < (time() - FILE_CACHE_TIME_BETWEEN_CLEANS) ){
  724.             //写日志,记录下面进行的清除缓存操作
  725.             $this->debug(1, "Cache was last cleaned more than " . FILE_CACHE_TIME_BETWEEN_CLEANS . " seconds ago. Cleaning now.");
  726.             //创建新的清除缓存时间戳文件
  727.             if (!touch($lastCleanFile)) {
  728.                 //失败的话记录错误信息
  729.                 $this->error("Could not create cache clean timestamp file.");
  730.             }
  731.             //此数组存的是所有缓存文件,根据前面定义的缓存文件目录和缓存文件后缀得到的
  732.             $files = glob($this->cacheDirectory . '/*' . FILE_CACHE_SUFFIX);
  733.             //如果有缓存文件
  734.             if ($files) {
  735.                 //计算当前时间和缓存最大生存时间的差值,用于下面判断缓存文件是否删除
  736.                 $timeAgo = time() - FILE_CACHE_MAX_FILE_AGE;
  737.                 //遍历缓存文件数组
  738.                 foreach($files as $file){
  739.                     //如果文件创建时间小于上面计算的值,也就是说此缓存文件的死期到了,就删除它
  740.                     if(@filemtime($file) < $timeAgo){
  741.                             //记录删除缓存文件,级别3
  742.                         $this->debug(3, "Deleting cache file $file older than max age: " . FILE_CACHE_MAX_FILE_AGE . " seconds");
  743.                         @unlink($file);
  744.                     }
  745.                 }
  746.             }
  747.             return true;
  748.         //如果没超过缓存时间,则不用清除
  749.         } else {
  750.             //写日志,记录不用清除缓存
  751.             $this->debug(3, "Cache was cleaned less than " . FILE_CACHE_TIME_BETWEEN_CLEANS . " seconds ago so no cleaning needed.");
  752.         }
  753.         return false;
  754.     }
  755.     /*核心函数,处理图片并写入缓存*/
  756.     protected function processImageAndWriteToCache($localImage){
  757.         //获取图片信息
  758.         $sData = getimagesize($localImage);
  759.         //图像类型标记
  760.         $origType = $sData[2];
  761.         //mime类型
  762.         $mimeType = $sData['mime'];
  763.         //写日志,记录传入图像的mime类型
  764.         $this->debug(3, "Mime type of image is $mimeType");
  765.         //进行图像mime类型验证,只允许gif , jpg 和 png
  766.         if(! preg_match('/^image\/(?:gif|jpg|jpeg|png)$/i', $mimeType)){
  767.             //如果不是这四种类型,记录错误信息,并退出脚本
  768.             return $this->error("The image being resized is not a valid gif, jpg or png.");
  769.         }
  770.         //图片处理需要GD库支持,这里检测是否安装了GD库
  771.         if (!function_exists ('imagecreatetruecolor')) {
  772.             //没有安装的话推出脚本
  773.             return $this->error('GD Library Error: imagecreatetruecolor does not exist - please contact your webhost and ask them to install the GD library');
  774.         }
  775.         //如果安装了GD库,并且支持图像过滤器函数imagefilter,且支持IMG_FILTER_NEGATE常量
  776.         if (function_exists ('imagefilter') && defined ('IMG_FILTER_NEGATE')) {
  777.             //定义一个过滤器效果数组,后面的数字代表需要额外传入的参数
  778.             $imageFilters = array (
  779.                     //负片
  780.                 1 => array (IMG_FILTER_NEGATE, 0),
  781.                 //黑白的
  782.                 2 => array (IMG_FILTER_GRAYSCALE, 0),
  783.                 //亮度级别
  784.                 3 => array (IMG_FILTER_BRIGHTNESS, 1),
  785.                 //对比度级别
  786.                 4 => array (IMG_FILTER_CONTRAST, 1),
  787.                 //图像转换为制定颜色
  788.                 5 => array (IMG_FILTER_COLORIZE, 4),
  789.                 //突出边缘
  790.                 6 => array (IMG_FILTER_EDGEDETECT, 0),
  791.                 //浮雕
  792.                 7 => array (IMG_FILTER_EMBOSS, 0),
  793.                 //用高斯算法模糊图像
  794.                 8 => array (IMG_FILTER_GAUSSIAN_BLUR, 0),
  795.                 //模糊图像
  796.                 9 => array (IMG_FILTER_SELECTIVE_BLUR, 0),
  797.                 //平均移除法来达到轮廓效果
  798.                 10 => array (IMG_FILTER_MEAN_REMOVAL, 0),
  799.                 //平滑处理
  800.                 11 => array (IMG_FILTER_SMOOTH, 0),
  801.             );
  802.         }
  803.         //生成图片宽度,由get中w参数指定,默认为0        
  804.         $new_width =  (int) abs ($this->param('w', 0));
  805.         //生成图片高度,由get中h参数指定,默认为0
  806.         $new_height = (int) abs ($this->param('h', 0));
  807.         //生成图片缩放模式,由get中zc参数指定,默认为配置文件中DEFAULT_ZC的值
  808.         $zoom_crop = (int) $this->param('zc', DEFAULT_ZC);
  809.         //生成图片的质量,由get中q参数指定,默认为配置文件中DEFAULT_Q的值
  810.         $quality = (int) abs ($this->param('q', DEFAULT_Q));
  811.         //裁剪的位置
  812.         $align = $this->cropTop ? 't' : $this->param('a', 'c');
  813.         //需要进行的图片处理操作,多个过滤器用"|"分割,可选参数请参看$imageFilters处的注释,由于不同的过滤器需要的参数不同,如一个过滤器需要多个参数,多个参数用,分隔。例:1,2|3,1,1  代表对图像分别应用1和3过滤效果,1和3所对应的过滤效果是由$imageFilters数组确定的,其中1号过滤器还需要一个额外的参数,这里传了1,3号过滤器还需要2个额外的参数,这里传了1和1.
  814.         $filters = $this->param('f', DEFAULT_F);
  815.         //是否对图片进行锐化,由get中s参数指定,默认为配置文件中DEFAULT_S的值
  816.         $sharpen = (bool) $this->param('s', DEFAULT_S);
  817.         //生成图片的默认背景画布颜色,由get中cc参数指定,默认为配置文件中DEFAULT_CC的值
  818.         $canvas_color = $this->param('cc', DEFAULT_CC);
  819.         //生成png图片的背景是否透明
  820.         $canvas_trans = (bool) $this->param('ct', '1');
  821.         // 如果高度和宽度都没有指定,设置他们为100*100
  822.         if ($new_width == 0 && $new_height == 0) {
  823.             $new_width = 100;
  824.             $new_height = 100;
  825.         }
  826.         // 限制最大高度和最大宽度
  827.         $new_width = min ($new_width, MAX_WIDTH);
  828.         $new_height = min ($new_height, MAX_HEIGHT);
  829.         // 检测并设置php运行最大占用内存
  830.         $this->setMemoryLimit();
  831.         // 打开图像资源
  832.         $image = $this->openImage ($mimeType$localImage);
  833.         //如果打开失败,记录信息并退出脚本
  834.         if ($image === false) {
  835.             return $this->error('Unable to open image.');
  836.         }
  837.         // 获得原始图片,也就是上面打开图片的宽和高
  838.         $width = imagesx ($image);
  839.         $height = imagesy ($image);
  840.         $origin_x = 0;
  841.         $origin_y = 0;
  842.         // 如果新生成图片的宽或高没有指定,则用此等比算法算出高或宽的值
  843.         if ($new_width && !$new_height) {
  844.             $new_height = floor ($height * ($new_width / $width));
  845.         } else if ($new_height && !$new_width) {
  846.             $new_width = floor ($width * ($new_height / $height));
  847.         }
  848.         // 如果缩放模式选择的是3,也就是说get中zc=3或者配置文件中DEFAULT_ZC=3,则进行等比缩放,不裁剪
  849.         if ($zoom_crop == 3) {
  850.             $final_height = $height * ($new_width / $width);
  851.             //根据等比算法设置等比计算后的宽或高
  852.             if ($final_height > $new_height) {
  853.                 $new_width = $width * ($new_height / $height);
  854.             } else {
  855.                 $new_height = $final_height;
  856.             }
  857.         }
  858.         // 利用处理完毕的长和宽创建新画布,
  859.         $canvas = imagecreatetruecolor ($new_width$new_height);
  860.         //关闭混色模式,也就是把PNG的alpha值保存,从而使背景透明
  861.         imagealphablending ($canvas, false);
  862.         //进行默认画布颜色的检测并转换,如果给出的是3个字符长度表示的颜色值
  863.         if (strlen($canvas_color) == 3) { //if is 3-char notation, edit string into 6-char notation
  864.             //转换为6个字符表示的颜色值
  865.             $canvas_color =  str_repeat(substr($canvas_color, 0, 1), 2) . str_repeat(substr($canvas_color, 1, 1), 2) . str_repeat(substr($canvas_color, 2, 1), 2);
  866.         //如果不是3个长度也不是6个长度的字符串,则为非法字符串,设置为默认值
  867.         } else if (strlen($canvas_color) != 6) {
  868.             $canvas_color = DEFAULT_CC;
  869.         }
  870.         //将上面得到的R 、G 、B 三种颜色值转换为10进制表示
  871.         $canvas_color_R = hexdec (substr ($canvas_color, 0, 2));
  872.         $canvas_color_G = hexdec (substr ($canvas_color, 2, 2));
  873.         $canvas_color_B = hexdec (substr ($canvas_color, 4, 2));
  874.         // 如果传入图片的格式是png,并且配置文件设置png背景颜色为透明,并且在get传入了ct的值为真,那么就设置背景颜色为透明
  875.         if(preg_match('/^image\/png$/i', $mimeType) && !PNG_IS_TRANSPARENT && $canvas_trans){
  876.             $color = imagecolorallocatealpha ($canvas$canvas_color_R$canvas_color_G$canvas_color_B, 127);
  877.         //反之设置为不透明
  878.         }else{
  879.             $color = imagecolorallocatealpha ($canvas$canvas_color_R$canvas_color_G$canvas_color_B, 0);
  880.         }
  881.         // 使用分配的颜色填充背景
  882.         imagefill ($canvas, 0, 0, $color);
  883.         // 如果缩放模式选择的是2,那么画布的体积是按传入的值创建的,并计算出边框的宽度
  884.         if ($zoom_crop == 2) {
  885.             //等比缩放的高度
  886.             $final_height = $height * ($new_width / $width);
  887.             //如果计算出的等比高度,大于传入的高度
  888.             if ($final_height > $new_height) {
  889.                 //origin_x等于传入的新高度的二分之一
  890.                 $origin_x = $new_width / 2;
  891.                 //设置新宽度为等比计算出的值
  892.                 $new_width = $width * ($new_height / $height);
  893.                 //计算出两次origin_x的差值
  894.                 $origin_x = round ($origin_x - ($new_width / 2));
  895.             //否则,计算出两次origin_y的差值
  896.             } else {
  897.                 $origin_y = $new_height / 2;
  898.                 $new_height = $final_height;
  899.                 $origin_y = round ($origin_y - ($new_height / 2));
  900.             }
  901.         }
  902.         // 保存图像时保存完整的alpha信息
  903.         imagesavealpha ($canvas, true);
  904.         //如果缩放模式选择的是1或2或3
  905.         if ($zoom_crop > 0) {
  906.             $src_x = $src_y = 0;
  907.             //图片原宽度
  908.             $src_w = $width;
  909.             //图片原高度
  910.             $src_h = $height;
  911.             //图片纵横比
  912.             $cmp_x = $width / $new_width;
  913.             $cmp_y = $height / $new_height;
  914.             //裁剪算法
  915.             if ($cmp_x > $cmp_y) {
  916.                 $src_w = round ($width / $cmp_x * $cmp_y);
  917.                 $src_x = round (($width - ($width / $cmp_x * $cmp_y)) / 2);
  918.             } else if ($cmp_y > $cmp_x) {
  919.                 $src_h = round ($height / $cmp_y * $cmp_x);
  920.                 $src_y = round (($height - ($height / $cmp_y * $cmp_x)) / 2);
  921.             }
  922.             // 根据传入参数算出裁剪的位置
  923.             if ($align) {
  924.                 if (strpos ($align, 't') !== false) {
  925.                     $src_y = 0;
  926.                 }
  927.                 if (strpos ($align, 'b') !== false) {
  928.                     $src_y = $height - $src_h;
  929.                 }
  930.                 if (strpos ($align, 'l') !== false) {
  931.                     $src_x = 0;
  932.                 }
  933.                 if (strpos ($align, 'r') !== false) {
  934.                     $src_x = $width - $src_w;
  935.                 }
  936.             }
  937.             //将图像根据算法进行裁剪,并拷贝到背景图片上
  938.             imagecopyresampled ($canvas$image$origin_x$origin_y$src_x$src_y$new_width$new_height$src_w$src_h);
  939.         } else {
  940.             //裁剪模式选择的是0,则不进行裁剪,并生成图像
  941.             imagecopyresampled ($canvas$image, 0, 0, 0, 0, $new_width$new_height$width$height);
  942.         }
  943.         //如果定义了图片处理操作,并且支持图片处理函数
  944.         if ($filters != '' && function_exists ('imagefilter') && defined ('IMG_FILTER_NEGATE')) {
  945.             // 分割每个过滤处理
  946.             $filterList = explode ('|', $filters);
  947.             foreach ($filterList as $fl) {
  948.                 //分割一个过滤操作中的参数
  949.                 $filterSettings = explode (',', $fl);
  950.                 //如果所选的过滤操作存在
  951.                 if (isset ($imageFilters[$filterSettings[0]])) {
  952.                     //将所有参数转为int类型
  953.                     for ($i = 0; $i < 4; $i ++) {
  954.                         if (!isset ($filterSettings[$i])) {
  955.                             $filterSettings[$i] = null;
  956.                         } else {
  957.                             $filterSettings[$i] = (int) $filterSettings[$i];
  958.                         }
  959.                     }
  960.                     //根据$imageFilters中定义的每个过滤效果需要的参数的不同,对图像应用过滤器效果
  961.                     switch ($imageFilters[$filterSettings[0]][1]) {
  962.                         case 1:
  963.                             imagefilter ($canvas$imageFilters[$filterSettings[0]][0], $filterSettings[1]);
  964.                             break;
  965.                         case 2:
  966.                             imagefilter ($canvas$imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2]);
  967.                             break;
  968.                         case 3:
  969.                             imagefilter ($canvas$imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3]);
  970.                             break;
  971.                         case 4:
  972.                             imagefilter ($canvas$imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3], $filterSettings[4]);
  973.                             break;
  974.                         default:
  975.                             imagefilter ($canvas$imageFilters[$filterSettings[0]][0]);
  976.                             break;
  977.                     }
  978.                 }
  979.             }
  980.         }
  981.         // 如果设置了锐化值,并且系统支持锐化函数,则进行锐化操作
  982.         if ($sharpen && function_exists ('imageconvolution')) {
  983.             $sharpenMatrix = array (
  984.                     array (-1,-1,-1),
  985.                     array (-1,16,-1),
  986.                     array (-1,-1,-1),
  987.                     );
  988.             $divisor = 8;
  989.             $offset = 0;
  990.             imageconvolution ($canvas$sharpenMatrix$divisor$offset);
  991.         }
  992.         //如果图片是PNG或者GIF,则用imagetruecolortopalette来减小他们的体积
  993.         if ( (IMAGETYPE_PNG == $origType || IMAGETYPE_GIF == $origType) && function_exists('imageistruecolor') && !imageistruecolor( $image ) && imagecolortransparent( $image ) > 0 ){
  994.             imagetruecolortopalette( $canvas, false, imagecolorstotal( $image ) );
  995.         }
  996.         //根据生成的不同图片类型,生成图片缓存,$imgType的值用于生成安全头
  997.         $imgType = "";
  998.         $tempfile = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
  999.         if(preg_match('/^image\/(?:jpg|jpeg)$/i', $mimeType)){
  1000.             $imgType = 'jpg';
  1001.             imagejpeg($canvas$tempfile$quality);
  1002.         } else if(preg_match('/^image\/png$/i', $mimeType)){
  1003.             $imgType = 'png';
  1004.             imagepng($canvas$tempfilefloor($quality * 0.09));
  1005.         } else if(preg_match('/^image\/gif$/i', $mimeType)){
  1006.             $imgType = 'gif';
  1007.             imagegif($canvas$tempfile);
  1008.         } else {
  1009.             //如果不是以上三种类型,记录这次错误并退出
  1010.             return $this->sanityFail("Could not match mime type after verifying it previously.");
  1011.         }
  1012.         //优先使用optipng工具进行png图片的压缩,前提是你装了这个工具
  1013.         if($imgType == 'png' && OPTIPNG_ENABLED && OPTIPNG_PATH && @is_file(OPTIPNG_PATH)){
  1014.             //记录optipng的地址
  1015.             $exec = OPTIPNG_PATH;
  1016.             //写日志,记录optipng将运行,级别3
  1017.             $this->debug(3, "optipng'ing $tempfile");
  1018.             //获取图片大小
  1019.             $presize = filesize($tempfile);
  1020.             //进行图片压缩操作
  1021.             $out = `$exec -o1 $tempfile`;
  1022.                 //清除文件状态缓存  
  1023.             clearstatcache();
  1024.             //获取压缩后的文件大小
  1025.             $aftersize = filesize($tempfile);
  1026.             //算出压缩了多大
  1027.             $sizeDrop = $presize - $aftersize;
  1028.             //根据算出的不同的值,写日志,级别1
  1029.             if($sizeDrop > 0){
  1030.                 $this->debug(1, "optipng reduced size by $sizeDrop");
  1031.             } else if($sizeDrop < 0){
  1032.                 $this->debug(1, "optipng increased size! Difference was: $sizeDrop");
  1033.             } else {
  1034.                 $this->debug(1, "optipng did not change image size.");
  1035.             }
  1036.         //optipng不存在,就尝试使用pngcrush
  1037.         } else if($imgType == 'png' && PNGCRUSH_ENABLED && PNGCRUSH_PATH && @is_file(PNGCRUSH_PATH)){
  1038.             $exec = PNGCRUSH_PATH;
  1039.             //和optipng不同的是,pngcrush会将处理完的文件新生成一个文件,所以这里新建个文件
  1040.             $tempfile2 = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
  1041.             //写日志,记录文件名
  1042.             $this->debug(3, "pngcrush'ing $tempfile to $tempfile2");
  1043.             //运行pngcrush
  1044.             $out = `$exec $tempfile $tempfile2`;
  1045.             $todel = "";
  1046.             //如果生成文件成功
  1047.             if(is_file($tempfile2)){
  1048.                 //算出压缩后的文件大小的差值
  1049.                 $sizeDrop = filesize($tempfile) - filesize($tempfile2);
  1050.                 //如果是一次有效的压缩,则将压缩后的文件作为缓存文件
  1051.                 if($sizeDrop > 0){
  1052.                     $this->debug(1, "pngcrush was succesful and gave a $sizeDrop byte size reduction");
  1053.                     $todel = $tempfile;
  1054.                     $tempfile = $tempfile2;
  1055.                 //否则的话则这个文件没有存在的必要
  1056.                 } else {
  1057.                     $this->debug(1, "pngcrush did not reduce file size. Difference was $sizeDrop bytes.");
  1058.                     $todel = $tempfile2;
  1059.                 }
  1060.             //没有运行成功也需要删除这个文件
  1061.             } else {
  1062.                 $this->debug(3, "pngcrush failed with output: $out");
  1063.                 $todel = $tempfile2;
  1064.             }
  1065.             //删除无效文件或压缩前比较大的文件
  1066.             @unlink($todel);
  1067.         }
  1068.         //在缓存图片上写入安全头
  1069.         $this->debug(3, "Rewriting image with security header.");
  1070.         //创建一个新的缓存文件
  1071.         $tempfile4 = tempnam($this->cacheDirectory, 'timthumb_tmpimg_');
  1072.         //
  1073.         $context = stream_context_create ();
  1074.         //读取生成的图片缓存内容
  1075.         $fp = fopen($tempfile,'r',0,$context);
  1076.         //向新缓存文件写入安全头,安全头的长度应该总是$this->filePrependSecurityBlock的长度 + 6
  1077.         file_put_contents($tempfile4$this->filePrependSecurityBlock . $imgType . ' ?' . '>');
  1078.         //将读取出来的缓存图片内容写入新缓存文件
  1079.         file_put_contents($tempfile4$fp, FILE_APPEND);
  1080.         //关闭文件资源
  1081.         fclose($fp);
  1082.         //删除之前不安全的图片缓存文件
  1083.         @unlink($tempfile);
  1084.         //写日志,给缓存文件加锁~~
  1085.         $this->debug(3, "Locking and replacing cache file.");
  1086.         //创建锁文件文件名
  1087.         $lockFile = $this->cachefile . '.lock';
  1088.         //创建或打开锁文件
  1089.         $fh = fopen($lockFile, 'w');
  1090.         //创建失败直接退出
  1091.         if(! $fh){
  1092.             return $this->error("Could not open the lockfile for writing an image.");
  1093.         }
  1094.         //如果给锁文件加入写入锁成功
  1095.         if(flock($fh, LOCK_EX)){
  1096.             //删除原缓存文件
  1097.             @unlink($this->cachefile);
  1098.             //重命名覆盖,将安全的缓存文件作为缓存文件
  1099.             rename($tempfile4$this->cachefile);
  1100.             //释放写入锁
  1101.             flock($fh, LOCK_UN);
  1102.             //释放资源
  1103.             fclose($fh);
  1104.             //删除锁文件
  1105.             @unlink($lockFile);
  1106.         //否则
  1107.         } else {
  1108.             //关闭资源
  1109.             fclose($fh);
  1110.             //删除锁文件
  1111.             @unlink($lockFile);
  1112.             //删除安全的缓存文件
  1113.             @unlink($tempfile4);
  1114.             //记录错误并退出
  1115.             return $this->error("Could not get a lock for writing.");
  1116.         }
  1117.         //写日志,记录操作完成
  1118.         $this->debug(3, "Done image replace with security header. Cleaning up and running cleanCache()");
  1119.         //释放图片资源
  1120.         imagedestroy($canvas);
  1121.         imagedestroy($image);
  1122.         //生成缓存成功返回真
  1123.         return true;
  1124.     }
  1125.     /*此函数用来获取服务器文档根目录*/
  1126.     protected function calcDocRoot(){
  1127.         //直接获取文档根目录
  1128.         $docRoot = @$_SERVER['DOCUMENT_ROOT'];
  1129.         //如果定义了LOCAL_FILE_BASE_DIRECTORY,则使用此值
  1130.         if (defined('LOCAL_FILE_BASE_DIRECTORY')) {
  1131.             $docRoot = LOCAL_FILE_BASE_DIRECTORY;
  1132.         }
  1133.         //如果没有获取到文档根目录,也就是DOCUMENT_ROOT的值
  1134.         if(!isset($docRoot)){
  1135.             //写一条记录,说明DOCUMENT_ROOT没找到,注意level是3
  1136.             $this->debug(3, "DOCUMENT_ROOT is not set. This is probably windows. Starting search 1.");
  1137.             //用SCRIPT_FILENAME和PHP_SELF来得到文档根目录
  1138.             if(isset($_SERVER['SCRIPT_FILENAME'])){
  1139.                 $docRoot = str_replace( '\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0-strlen($_SERVER['PHP_SELF'])));
  1140.                 //写一条记录,说明DOCUMENT_ROOT的值是通过SCRIPT_FILENAME和PHP_SELF来得的,级别3
  1141.                 $this->debug(3, "Generated docRoot using SCRIPT_FILENAME and PHP_SELF as: $docRoot");
  1142.             }
  1143.         }
  1144.         //如果还是没有获取到文档根目录
  1145.         if(!isset($docRoot)){
  1146.             //先写一条记录,说明还是没得到DOCUMENT_ROOT,级别3
  1147.             $this->debug(3, "DOCUMENT_ROOT still is not set. Starting search 2.");
  1148.             //通过PATH_TRANSLATED和PHP_SELF来得到文档根目录,关于PATH_TRANSLATED的说明可以看这里:http://blogs.msdn.com/b/david.wang/archive/2005/08/04/what-is-path-translated.aspx
  1149.             if(isset($_SERVER['PATH_TRANSLATED'])){
  1150.                 $docRoot = str_replace( '\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0-strlen($_SERVER['PHP_SELF'])));
  1151.                 //写记录,说明说明DOCUMENT_ROOT的值是通过PATH_TRANSLATED和PHP_SELF来得的,级别3
  1152.                 $this->debug(3, "Generated docRoot using PATH_TRANSLATED and PHP_SELF as: $docRoot");
  1153.             }
  1154.         }
  1155.         //如果文档根目录不是服务器根目录,则去掉最后一个 '/'
  1156.         if($docRoot && $_SERVER['DOCUMENT_ROOT'] != '/'){ $docRoot = preg_replace('/\/$/', ''$docRoot); }
  1157.         //写记录,说明文档根目录的值,级别3
  1158.         $this->debug(3, "Doc root is: " . $docRoot);
  1159.         //赋值给成员属性
  1160.         $this->docRoot = $docRoot;
  1161.     }
  1162.     /*此函数用来获取本地图片地址,参数src是相对与文档根目录的地址*/
  1163.     protected function getLocalImagePath($src){
  1164.         //去掉开头的 / 
  1165.          $src = ltrim($src, '/');
  1166.          //如果前面没有获取到文档根目录,出于安全考虑,那么这里只能对timthumbs.php所在的目录下的图片进行操作
  1167.          if(! $this->docRoot){
  1168.             //写日志,级别3,说明下面进行图片地址的检查
  1169.             $this->debug(3, "We have no document root set, so as a last resort, lets check if the image is in the current dir and serve that.");
  1170.             //获取去掉所有路径信息的文件名
  1171.             $file = preg_replace('/^.*?([^\/\\\\]+)$/', '$1', $src);
  1172.             //如果图片文件和timthumb.php在同一目录下
  1173.             if(is_file($file)){
  1174.                 //返回此图片的路径
  1175.                 return $this->realpath($file);
  1176.             }
  1177.             //如果图片文件和timthumb.php不在同一目录下,写错误信息,出于安全考虑,不会允许一个没有文档根目录并且在timthumbs.php所在的目录以外的文件
  1178.             return $this->error("Could not find your website document root and the file specified doesn't exist in timthumbs directory. We don't support serving files outside timthumb's directory without a document root for security reasons.");
  1179.         }
  1180.          //尝试找这张图片,如果图片地址存在
  1181.          if(file_exists ($this->docRoot . '/' . $src)) {
  1182.             //写日志,记录文件地址,级别3
  1183.             $this->debug(3, "Found file as " . $this->docRoot . '/' . $src);
  1184.             //返回图片路径
  1185.             $real = $this->realpath($this->docRoot . '/' . $src);
  1186.             //验证图片路径是否在本机
  1187.             if(stripos($real$this->docRoot) === 0){
  1188.                 //是的话返回图片地址
  1189.                 return $real;
  1190.             } else {
  1191.                 //否则写日志,没找到指定文件,级别1
  1192.                 $this->debug(1, "Security block: The file specified occurs outside the document root.");
  1193.             }
  1194.         }
  1195.         //接着找。。。
  1196.          $absolute = $this->realpath('/' . $src);
  1197.          //如果决定地址存在
  1198.          if($absolute && file_exists($absolute)){
  1199.             //写日志,记录图片绝对地址,级别3 
  1200.             $this->debug(3, "Found absolute path: $absolute");
  1201.             //如果文档根目录没有定义,记录这个错误信息
  1202.             if(! $this->docRoot){ $this->sanityFail("docRoot not set when checking absolute path."); }
  1203.             //验证图片路径是否在本机
  1204.             if(stripos($absolute$this->docRoot) === 0){
  1205.                 //在的话返回图片地址    
  1206.                 return $absolute;
  1207.             } else {
  1208.                 //否则写日志,没找到指定的文件,级别1
  1209.                 $this->debug(1, "Security block: The file specified occurs outside the document root.");
  1210.             }
  1211.         }
  1212.         //如果还没找到指定文件,则逐级向上查找
  1213.         $base = $this->docRoot;
  1214.         // 获取查询子目录列表
  1215.         if (strstr($_SERVER['SCRIPT_FILENAME'],':')) {
  1216.             $sub_directories = explode('\\', str_replace($this->docRoot, ''$_SERVER['SCRIPT_FILENAME']));
  1217.         } else {
  1218.             $sub_directories = explode('/', str_replace($this->docRoot, ''$_SERVER['SCRIPT_FILENAME']));
  1219.         }
  1220.         //遍历子目录数组
  1221.         foreach ($sub_directories as $sub){
  1222.             //重新组合请求地址
  1223.             $base .= $sub . '/';
  1224.             //写日志,记录搜索记录,级别3
  1225.             $this->debug(3, "Trying file as: " . $base . $src);
  1226.             //如果找到了这个文件
  1227.             if(file_exists($base . $src)){
  1228.                 //写日志,记录文件地址,级别3
  1229.                 $this->debug(3, "Found file as: " . $base . $src);
  1230.                 //得到实际地址
  1231.                 $real = $this->realpath($base . $src);
  1232.                 //如果实际地址的确在本机中,那么返回这个地址
  1233.                 if(stripos($real$this->realpath($this->docRoot)) === 0){
  1234.                     return $real;
  1235.                 } else {
  1236.                     //找不到就写日志,没找到,级别1
  1237.                     $this->debug(1, "Security block: The file specified occurs outside the document root.");
  1238.                 }
  1239.             }
  1240.         }
  1241.         //还找不到的话,就返回false;
  1242.         return false;
  1243.     }
  1244.     /*此函数用来获得传入文件参数的真实路径*/
  1245.     protected function realpath($path){
  1246.         //去除路径中带有..的相对路径
  1247.         $remove_relatives = '/\w+\/\.\.\//';
  1248.         while(preg_match($remove_relatives,$path)){
  1249.             $path = preg_replace($remove_relatives''$path);
  1250.         }
  1251.         //如果去除后路径中仍有..的相对路径,则用realpath函数返回路径,否则直接返回即可
  1252.         return preg_match('#^\.\./|/\.\./#', $path) ? realpath($path) : $path;
  1253.     }
  1254.     /*此函数用来记录在析构函数中需要删除的资源列表*/
  1255.     protected function toDelete($name){
  1256.         //写日志,记录需要删除的文件信息
  1257.         $this->debug(3, "Scheduling file $name to delete on destruct.");
  1258.         //添加到待删除数组
  1259.         $this->toDeletes[] = $name;
  1260.     }
  1261.     /*此函数是截图操作的具体实现*/
  1262.     protected function serveWebshot(){
  1263.         //写日志,记录开始截图操作,级别3
  1264.         $this->debug(3, "Starting serveWebshot");
  1265.         //一段提示文字,可以到http://code.google.com/p/timthumb/上按照教程进行网站截图设置
  1266.         $instr = "Please follow the instructions at http://code.google.com/p/timthumb/ to set your server up for taking website screenshots.";
  1267.         //如果CutyCapt不存在
  1268.         if(! is_file(WEBSHOT_CUTYCAPT)){
  1269.             //退出执行并记录,CutyCapt未被安装
  1270.             return $this->error("CutyCapt is not installed. $instr");
  1271.         }
  1272.         //如果xvfb不存在
  1273.         if(! is_file(WEBSHOT_XVFB)){
  1274.             //退出执行并记录,xvfb未被安装
  1275.             return $this->Error("Xvfb is not installed. $instr");
  1276.         }
  1277.         //CUTYCAPT地址
  1278.         $cuty = WEBSHOT_CUTYCAPT;
  1279.         //xvfb地址
  1280.         $xv = WEBSHOT_XVFB;
  1281.         //截图屏幕宽度
  1282.         $screenX = WEBSHOT_SCREEN_X;
  1283.         //截图屏幕高度
  1284.         $screenY = WEBSHOT_SCREEN_Y;
  1285.         //截图色深
  1286.         $colDepth = WEBSHOT_COLOR_DEPTH;
  1287.         //截图保存格式
  1288.         $format = WEBSHOT_IMAGE_FORMAT;
  1289.         //截图超时时间,单位ms
  1290.         $timeout = WEBSHOT_TIMEOUT * 1000;
  1291.         //USER_AGENT头
  1292.         $ua = WEBSHOT_USER_AGENT;
  1293.         //是否启用js
  1294.         $jsOn = WEBSHOT_JAVASCRIPT_ON ? 'on' : 'off';
  1295.         //是否启用java
  1296.         $javaOn = WEBSHOT_JAVA_ON ? 'on' : 'off';
  1297.         //是否启用其他插件
  1298.         $pluginsOn = WEBSHOT_PLUGINS_ON ? 'on' : 'off';
  1299.         //是否启用代理
  1300.         $proxy = WEBSHOT_PROXY ? ' --http-proxy=' . WEBSHOT_PROXY : '';
  1301.         //在缓存文件目录,建立一个具有唯一文件名的文件,文件名前缀为timthumb_webshot,用户存储截图后的图片
  1302.         $tempfile = tempnam($this->cacheDirectory, 'timthumb_webshot');
  1303.         //目标网站地址
  1304.         $url = $this->src;
  1305.         //验证url合法性
  1306.         if(! preg_match('/^https?:\/\/[a-zA-Z0-9\.\-]+/i', $url)){
  1307.             //不合法退出执行
  1308.             return $this->error("Invalid URL supplied.");
  1309.         }
  1310.         //过滤掉非法字符
  1311.         $url = preg_replace('/[^A-Za-z0-9\-\.\_\~:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=]+/', ''$url);
  1312.         //优先使用CUTYCAPT
  1313.         if(WEBSHOT_XVFB_RUNNING){
  1314.             //设置系统变量,配置图形输出显示。
  1315.             putenv('DISPLAY=:100.0');
  1316.             //组装shell命令
  1317.             $command = "$cuty $proxy --max-wait=$timeout --user-agent=\"$ua\" --javascript=$jsOn --java=$javaOn --plugins=$pluginsOn --js-can-open-windows=off --url=\"$url\" --out-format=$format --out=$tempfile";
  1318.         //否则使用XVFB
  1319.         } else {
  1320.             $command = "$xv --server-args=\"-screen 0, {$screenX}x{$screenY}x{$colDepth}\" $cuty $proxy --max-wait=$timeout --user-agent=\"$ua\" --javascript=$jsOn --java=$javaOn --plugins=$pluginsOn --js-can-open-windows=off --url=\"$url\" --out-format=$format --out=$tempfile";
  1321.         }
  1322.         //写日志,记录执行的命令,级别3
  1323.         $this->debug(3, "Executing command: $command");
  1324.         //执行命令并捕获输出
  1325.         $out = `$command`;
  1326.         //写日志,记录输出,级别3
  1327.         $this->debug(3, "Received output: $out");
  1328.         //如果刚刚创建的唯一文件名的文件失败
  1329.         if(! is_file($tempfile)){
  1330.             //设置404错误
  1331.             $this->set404();
  1332.             //推出脚本
  1333.             return $this->error("The command to create a thumbnail failed.");
  1334.         }
  1335.         //启用裁剪
  1336.         $this->cropTop = true;
  1337.         //对截取到的图片文件处理并生成缓存
  1338.         if($this->processImageAndWriteToCache($tempfile)){
  1339.             //成功的话写日志,并从缓存读取此图片
  1340.             $this->debug(3, "Image processed succesfully. Serving from cache");
  1341.             //返回从缓存中读取的文件内容
  1342.             return $this->serveCacheFile();
  1343.         //没成功就返回假咯
  1344.         } else {
  1345.             return false;
  1346.         }
  1347.     }
  1348.     /*此函数用来从外部url获取图像*/
  1349.     protected function serveExternalImage(){
  1350.         //验证url合法性
  1351.         if(! preg_match('/^https?:\/\/[a-zA-Z0-9\-\.]+/i', $this->src)){
  1352.             $this->error("Invalid URL supplied.");
  1353.             return false;
  1354.         }
  1355.         //生成临时缓存文件
  1356.         $tempfile = tempnam($this->cacheDirectory, 'timthumb');
  1357.         //写日志,记录读取外部图像到临时文件,级别3
  1358.         $this->debug(3, "Fetching external image into temporary file $tempfile");
  1359.         //将临时缓存文件加入到待删除列表
  1360.         $this->toDelete($tempfile);
  1361.         //请求url并将结果写入到临时缓存文件中,如果没有成功
  1362.         if(! $this->getURL($this->src, $tempfile)){
  1363.             //删除此缓存文件
  1364.             @unlink($this->cachefile);
  1365.             //再创建一个新的缓存文件
  1366.             touch($this->cachefile);
  1367.             //写日志,记录错误信息,级别3
  1368.             $this->debug(3, "Error fetching URL: " . $this->lastURLError);
  1369.             //写错误信息,并退出
  1370.             $this->error("Error reading the URL you specified from remote host." . $this->lastURLError);
  1371.             return false;
  1372.         }
  1373.         //得到获取到图片的MIME类型
  1374.         $mimeType = $this->getMimeType($tempfile);
  1375.         //如果不在这三种类型中
  1376.         if(! preg_match("/^image\/(?:jpg|jpeg|gif|png)$/i"$mimeType)){
  1377.             //写日志,记录错误信息,级别3
  1378.             $this->debug(3, "Remote file has invalid mime type: $mimeType");
  1379.             //删除现有缓存文件
  1380.             @unlink($this->cachefile);
  1381.             //创建新缓存文件
  1382.             touch($this->cachefile);
  1383.             //写错误信息并退出
  1384.             $this->error("The remote file is not a valid image.");
  1385.             return false;
  1386.         }
  1387.         //处理图像并缓存
  1388.         if($this->processImageAndWriteToCache($tempfile)){
  1389.             $this->debug(3, "Image processed succesfully. Serving from cache");
  1390.             //成功的话返回缓存信息
  1391.             return $this->serveCacheFile();
  1392.         } else {
  1393.             //失败返回假
  1394.             return false;
  1395.         }
  1396.     }
  1397.     /*此函数用来将curl获取到的数据写入对应文件中*/
  1398.     public static function curlWrite($h$d){
  1399.         //将数据写入文件
  1400.         fwrite(self::$curlFH$d);
  1401.         //记录数据长度
  1402.         self::$curlDataWritten += strlen($d);
  1403.         //如果图片大小超过了配置文件的限制,则返回0
  1404.         if(self::$curlDataWritten > MAX_FILE_SIZE){
  1405.             return 0;
  1406.         //否则返回图片大小
  1407.         } else {
  1408.             return strlen($d);
  1409.         }
  1410.     }
  1411.     /*此函数用来读取并输出服务端缓存*/
  1412.     protected function serveCacheFile(){
  1413.         //写日志,记录读取缓存的地址
  1414.         $this->debug(3, "Serving {$this->cachefile}");
  1415.         //如果缓存地址无效
  1416.         if(! is_file($this->cachefile)){
  1417.             //添加到错误记录
  1418.             $this->error("serveCacheFile called in timthumb but we couldn't find the cached file.");
  1419.             //停止执行脚本
  1420.             return false;
  1421.         }
  1422.         //缓存地址有效的话,已只读方式打开文件
  1423.         $fp = fopen($this->cachefile, 'rb');
  1424.         //如果打开失败,直接退出脚本,并记录错误信息
  1425.         if(! $fp){ return $this->error("Could not open cachefile."); }
  1426.         //设定文件指针跳过filePrependSecurityBlock值,也就是跳过安全头后开始读
  1427.         fseek($fpstrlen($this->filePrependSecurityBlock), SEEK_SET);
  1428.         //读出文件的mime类型
  1429.         $imgType = fread($fp, 3);
  1430.         //再跳过这个mime类型的值
  1431.         fseek($fp, 3, SEEK_CUR);
  1432.         //如果现在文件的指针不是在安全头后6个字符的位置,说明缓存文件可能已损坏
  1433.         if(ftell($fp) != strlen($this->filePrependSecurityBlock) + 6){
  1434.             //删除此缓存文件
  1435.             @unlink($this->cachefile);
  1436.             //记录错误并退出执行
  1437.             return $this->error("The cached image file seems to be corrupt.");
  1438.         }
  1439.         //缓存图片的实际大小应该是文件大小 - 安全头大小
  1440.         $imageDataSize = filesize($this->cachefile) - (strlen($this->filePrependSecurityBlock) + 6);
  1441.         //设置输出必要的HTTP头
  1442.         $this->sendImageHeaders($imgType$imageDataSize);
  1443.         //输出文件指针处所有剩余数据
  1444.         $bytesSent = @fpassthru($fp);
  1445.         //关闭文件资源
  1446.         fclose($fp);
  1447.         //如果此方法执行成功,则返回真
  1448.         if($bytesSent > 0){
  1449.             return true;
  1450.         }
  1451.         //如果fpassthru不成功,则用file_get_contents读取并输出
  1452.         $content = file_get_contents ($this->cachefile);
  1453.         //如果读取成功
  1454.         if ($content != FALSE) {
  1455.             //截取掉安全头
  1456.             $content = substr($contentstrlen($this->filePrependSecurityBlock) + 6);
  1457.             //输出图像
  1458.             echo $content;
  1459.             //写日志,记录读取缓存的方式
  1460.             $this->debug(3, "Served using file_get_contents and echo");
  1461.             return true;
  1462.         //读取失败的话记录错误信息并退出执行
  1463.         } else {
  1464.             $this->error("Cache file could not be loaded.");
  1465.             return false;
  1466.         }
  1467.     }
  1468.     /*此函数设置图片输出必要的http头*/
  1469.     protected function sendImageHeaders($mimeType$dataSize){
  1470.         //补全图片的mime信息
  1471.         if(! preg_match('/^image\//i', $mimeType)){
  1472.             $mimeType = 'image/' . $mimeType;
  1473.         }
  1474.         //将jpg的mime类型写标准,这里不标准的原因是在验证文件安全头时追求了便利性
  1475.         if(strtolower($mimeType) == 'image/jpg'){
  1476.             $mimeType = 'image/jpeg';
  1477.         }
  1478.         //浏览器缓存失效时间
  1479.         $gmdate_expires = gmdate ('D, d M Y H:i:s', strtotime ('now +10 days')) . ' GMT';
  1480.         //文档最后被修改时间,用来让浏览器判断是否需要重新请求页面
  1481.         $gmdate_modified = gmdate ('D, d M Y H:i:s') . ' GMT';
  1482.         // 设置HTTP头
  1483.         header ('Content-Type: ' . $mimeType);
  1484.         header ('Accept-Ranges: none');
  1485.         header ('Last-Modified: ' . $gmdate_modified);
  1486.         header ('Content-Length: ' . $dataSize);
  1487.         //如果配置文件禁止浏览器缓存,则设置相应的HTTP头信息
  1488.         if(BROWSER_CACHE_DISABLE){
  1489.             $this->debug(3, "Browser cache is disabled so setting non-caching headers.");
  1490.             header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
  1491.             header("Pragma: no-cache");
  1492.             header('Expires: ' . gmdate ('D, d M Y H:i:s', time()));
  1493.         //否则按配置文件设置缓存时间
  1494.         } else {
  1495.             $this->debug(3, "Browser caching is enabled");
  1496.             header('Cache-Control: max-age=' . BROWSER_CACHE_MAX_AGE . ', must-revalidate');
  1497.             header('Expires: ' . $gmdate_expires);
  1498.         }
  1499.         //运行成功返回真
  1500.         return true;
  1501.     }
  1502.     /*自定义的验证函数*/
  1503.     protected function securityChecks(){
  1504.     }
  1505.     /*此函数用来获取$_GET数组中的参数,并允许设置默认值*/
  1506.     protected function param($property$default = ''){
  1507.         //如果参数存在则返回此参数
  1508.         if (isset ($_GET[$property])) {
  1509.             return $_GET[$property];
  1510.         //不存在的话返回默认值
  1511.         } else {
  1512.             return $default;
  1513.         }
  1514.     }
  1515.     /*此函数根据传入mime类型,打开图像资源*/
  1516.     protected function openImage($mimeType$src){
  1517.         switch ($mimeType) {
  1518.             case 'image/jpeg':
  1519.                 $image = imagecreatefromjpeg ($src);
  1520.                 break;
  1521.             case 'image/png':
  1522.                 $image = imagecreatefrompng ($src);
  1523.                 break;
  1524.             case 'image/gif':
  1525.                 $image = imagecreatefromgif ($src);
  1526.                 break;
  1527.             //不是这三种的话,脚本退出
  1528.             default:
  1529.                 $this->error("Unrecognised mimeType");
  1530.         }
  1531.         //返回图像资源
  1532.         return $image;
  1533.     }
  1534.     /*没啥说的,获取客户端IP*/
  1535.     protected function getIP(){
  1536.         $rem = @$_SERVER["REMOTE_ADDR"];
  1537.         $ff = @$_SERVER["HTTP_X_FORWARDED_FOR"];
  1538.         $ci = @$_SERVER["HTTP_CLIENT_IP"];
  1539.         if(preg_match('/^(?:192\.168|172\.16|10\.|127\.)/', $rem)){
  1540.             if($ff){ return $ff; }
  1541.             if($ci){ return $ci; }
  1542.             return $rem;
  1543.         } else {
  1544.             if($rem){ return $rem; }
  1545.             if($ff){ return $ff; }
  1546.             if($ci){ return $ci; }
  1547.             return "UNKNOWN";
  1548.         }
  1549.     }
  1550.     /*debug运行日志函数,用来向系统日志记录操作信息*/
  1551.     protected function debug($level$msg){
  1552.         //如果开启了debug,并且$level也就是调试级别小于等于配置文件中的值,则开始记录
  1553.         if(DEBUG_ON && $level <= DEBUG_LEVEL){
  1554.             //格式化并记录开始时间,保留小数点后6位,这个时间代表实例化类后到这个debug执行所经历的时间
  1555.             $execTime = sprintf('%.6f', microtime(true) - $this->startTime);
  1556.             //这个值代表从上次debug结束,到这次debug的用时
  1557.             $tick = sprintf('%.6f', 0);
  1558.             //如果上次debug时间存在,则用当前时间减去上次debug时间,得出差值
  1559.             if($this->lastBenchTime > 0){
  1560.                 $tick = sprintf('%.6f', microtime(true) - $this->lastBenchTime);
  1561.             }
  1562.             //将时间更新
  1563.             $this->lastBenchTime = microtime(true);
  1564.             //将debug信息写到系统日志中
  1565.             error_log("TimThumb Debug line " . __LINE__ . " [$execTime : $tick]: $msg");
  1566.         }
  1567.     }
  1568.     /*此函数用来记录未知BUG*/
  1569.     protected function sanityFail($msg){
  1570.         //记录BUG信息
  1571.         return $this->error("There is a problem in the timthumb code. Message: Please report this error at <a href='http://code.google.com/p/timthumb/issues/list'>timthumb's bug tracking page</a>: $msg");
  1572.     }
  1573.     /*此函数用来返回图片文件的MIME信息*/
  1574.     protected function getMimeType($file){
  1575.         //获取图片文件的信息
  1576.         $info = getimagesize($file);
  1577.         //成功则返回MIME信息
  1578.         if(is_array($info) && $info['mime']){
  1579.             return $info['mime'];
  1580.         }
  1581.         //失败返回空
  1582.         return '';
  1583.     }
  1584.     /*此函数用来检测并设置php运行时最大占用内存的值*/
  1585.     protected function setMemoryLimit(){
  1586.         //获取php.ini中的最大内存占用的值
  1587.         $inimem = ini_get('memory_limit');
  1588.         //将上面得到的值转换为以字节为单位的数值
  1589.         $inibytes = timthumb::returnBytes($inimem);
  1590.         //算出配置文件中内存限制的值
  1591.         $ourbytes = timthumb::returnBytes(MEMORY_LIMIT);
  1592.         //如果php配置文件中的值小于自己设定的值
  1593.         if($inibytes < $ourbytes){
  1594.             //则将php.ini配置中关于最大内存的值设置为自己设定的值
  1595.             ini_set ('memory_limit', MEMORY_LIMIT);
  1596.             //写日志,记录改变内存操作,级别3
  1597.             $this->debug(3, "Increased memory from $inimem to " . MEMORY_LIMIT);
  1598.         //如果自己设置的值小于php.ini中的值
  1599.         } else {
  1600.             //则不进行任何操作,写日志记录此条信息即可,级别3
  1601.             $this->debug(3, "Not adjusting memory size because the current setting is " . $inimem . " and our size of " . MEMORY_LIMIT . " is smaller.");
  1602.         }
  1603.     }
  1604.     /*此函数将G, KB, MB 转为B(字节)*/
  1605.     protected static function returnBytes($size_str){
  1606.         //取最后一个单位值,进行转换操作,并返回转换后的值
  1607.         switch (substr ($size_str, -1))
  1608.         {
  1609.             case 'M': case 'm': return (int)$size_str * 1048576;
  1610.             case 'K': case 'k': return (int)$size_str * 1024;
  1611.             case 'G': case 'g': return (int)$size_str * 1073741824;
  1612.             defaultreturn $size_str;
  1613.         }
  1614.     }
  1615.     /*此函数用来将url中的资源读取到tempfile文件中*/
  1616.     protected function getURL($url$tempfile){
  1617.         //重置上次url请求错误信息
  1618.         $this->lastURLError = false;
  1619.         //进行url编码
  1620.         $url = preg_replace('/ /', '%20', $url);
  1621.         //优先使用curl扩展
  1622.         if(function_exists('curl_init')){
  1623.             //写日志,记录将使用curl扩展访问url,级别3
  1624.             $this->debug(3, "Curl is installed so using it to fetch URL.");
  1625.             //打开文件
  1626.             self::$curlFH = fopen($tempfile, 'w');
  1627.             //如果打开失败,记录错误信息并退出
  1628.             if(! self::$curlFH){
  1629.                 $this->error("Could not open $tempfile for writing.");
  1630.                 return false;
  1631.             }
  1632.             //重置写入长度
  1633.             self::$curlDataWritten = 0;
  1634.             //写日志,记录访问的url信息,级别3
  1635.             $this->debug(3, "Fetching url with curl: $url");
  1636.             //初始化curl
  1637.             $curl = curl_init($url);
  1638.             //curl选项设置
  1639.             curl_setopt ($curl, CURLOPT_TIMEOUT, CURL_TIMEOUT);
  1640.             curl_setopt ($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30");
  1641.             curl_setopt ($curl, CURLOPT_RETURNTRANSFER, TRUE);
  1642.             curl_setopt ($curl, CURLOPT_HEADER, 0);
  1643.             curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
  1644.             //关闭会话时执行curlWrite
  1645.             curl_setopt ($curl, CURLOPT_WRITEFUNCTION, 'timthumb::curlWrite');
  1646.             @curl_setopt ($curl, CURLOPT_FOLLOWLOCATION, true);
  1647.             @curl_setopt ($curl, CURLOPT_MAXREDIRS, 10);
  1648.             //执行本次请求,并将结果赋给$curlResult
  1649.             $curlResult = curl_exec($curl);
  1650.             //释放文件资源
  1651.             fclose(self::$curlFH);
  1652.             //获取最后一个受到的HTTP码
  1653.             $httpStatus = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  1654.             //如果是404,那么设置404错误并退出
  1655.             if($httpStatus == 404){
  1656.                 $this->set404();
  1657.             }
  1658.             //如果请求成功
  1659.             if($curlResult){
  1660.                 //关闭curl,并执行curlWrite将数据写到文件中
  1661.                 curl_close($curl);
  1662.                 //返回真,请求完成
  1663.                 return true;
  1664.             //如果请求不成功
  1665.             } else {
  1666.                 //记录错误信息
  1667.                 $this->lastURLError = curl_error($curl);
  1668.                 //关闭资源
  1669.                 curl_close($curl);
  1670.                 //执行不成功
  1671.                 return false;
  1672.             }
  1673.         //如果不支持curl,用file_get_contents获取数据
  1674.         } else {
  1675.             //获取数据
  1676.             $img = @file_get_contents ($url);
  1677.             //如果获取失败
  1678.             if($img === false){
  1679.                 //记录返回的错误信息数组
  1680.                 $err = error_get_last();
  1681.                 //如果记录到了,并且有错误信息
  1682.                 if(is_array($err) && $err['message']){
  1683.                     //则记录这个错误信息
  1684.                     $this->lastURLError = $err['message'];
  1685.                 //否则的话记录整个错误信息
  1686.                 } else {
  1687.                     $this->lastURLError = $err;
  1688.                 }
  1689.                 //如果错误信息中有404,则设置为404错误
  1690.                 if(preg_match('/404/', $this->lastURLError)){
  1691.                     $this->set404();
  1692.                 }
  1693.                 //返回假
  1694.                 return false;
  1695.             }
  1696.             //如果将读取的图片写入文件失败
  1697.             if(! file_put_contents($tempfile$img)){
  1698.                 //写错误信息并退出执行
  1699.                 $this->error("Could not write to $tempfile.");
  1700.                 return false;
  1701.             }
  1702.             //没问题的话执行成功
  1703.             return true;
  1704.         }
  1705.     }
  1706.     /*此函数输出指定的图片,用于输出错误信息中*/
  1707.     protected function serveImg($file){
  1708.         //获取图像信息
  1709.         $s = getimagesize($file);
  1710.         //如果获取不到图像信息,推出
  1711.         if(! ($s && $s['mime'])){
  1712.             return false;
  1713.         }
  1714.         //设置http头,输出图片
  1715.         header ('Content-Type: ' . $s['mime']);
  1716.         header ('Content-Length: ' . filesize($file) );
  1717.         header ('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
  1718.         header ("Pragma: no-cache");
  1719.         //使用readfile输出图片
  1720.         $bytes = @readfile($file);
  1721.         if($bytes > 0){
  1722.             return true;
  1723.         }
  1724.         //如果失败,使用file_get_contents和echo输出图片
  1725.         $content = @file_get_contents ($file);
  1726.         if ($content != FALSE){
  1727.             echo $content;
  1728.             return true;
  1729.         }
  1730.         //还失败的话返回假
  1731.         return false;
  1732.     }
  1733.     /*此函数设置404 错误码*/
  1734.     protected function set404(){
  1735.         $this->is404 = true;
  1736.     }
  1737.     /*此函数返回404错误码*/
  1738.     protected function is404(){
  1739.         return $this->is404;
  1740.     }
  1741. }

本站文章大部分始于原创,用于个人学习记录,可能对您有所帮助,仅供参考!

weinxin
我的微信
版权声明
本站原创文章转载请注明文章出处及链接,谢谢合作!
 
知更鸟
评论  2  访客  2
    • 米粒在线
      米粒在线 1

      你好,大部分缩略图地址多了%20,不能正常显示,要怎么处理解决呢?谢谢!
      正常显示的路径 /begin/timthumb.php?src=http://
      不能显示的路径 /begin/timthumb.php?src=%20http://

      • 大爷快来玩
        大爷快来玩 4

        鸟叔这插件能够添加裁切图片的格式吗?比如我想添加webp格式的图片裁切

      匿名

      发表评论

      匿名网友
      :?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

      拖动滑块以完成验证