|
1
|
|
|
<?php |
|
|
|
|
|
|
2
|
|
|
/** |
|
3
|
|
|
* XOOPS image access/edit |
|
4
|
|
|
* |
|
5
|
|
|
* You may not change or alter any portion of this comment or credits |
|
6
|
|
|
* of supporting developers from this source code or any supporting source code |
|
7
|
|
|
* which is considered copyrighted (c) material of the original comment or credit authors. |
|
8
|
|
|
* This program is distributed in the hope that it will be useful, |
|
9
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|
11
|
|
|
* |
|
12
|
|
|
* @copyright (c) 2000-2016 XOOPS Project (www.xoops.org) |
|
13
|
|
|
* @license GNU GPL 2 (http://www.gnu.org/licenses/gpl-2.0.html) |
|
14
|
|
|
* @package core |
|
15
|
|
|
* @since 2.5.7 |
|
16
|
|
|
* @author luciorota <[email protected]>, Joe Lencioni <[email protected]> |
|
17
|
|
|
* |
|
18
|
|
|
* Enhanced image access/edit |
|
19
|
|
|
* This enhanced version is very useful in many cases, for example when you need a |
|
20
|
|
|
* smallest version of an image. This script uses Xoops cache to minimize server load. |
|
21
|
|
|
* |
|
22
|
|
|
* |
|
23
|
|
|
* Parameters need to be passed in through the URL's query string: |
|
24
|
|
|
* @param int id Xoops image id; |
|
25
|
|
|
* @param string url relative to XOOPS_ROOT_PATH, path of local image starting with "/" |
|
26
|
|
|
* (e.g. /images/toast.jpg); |
|
27
|
|
|
* @param string src relative to XOOPS_ROOT_PATH, path of local image starting with "/" |
|
28
|
|
|
* @param int width (optional) maximum width of final image in pixels (e.g. 700); |
|
29
|
|
|
* @param int height (optional) maximum height of final image in pixels (e.g. 700); |
|
30
|
|
|
* @param string color (optional) background hex color for filling transparent PNGs (e.g. 900 or 16a942); |
|
31
|
|
|
* @param string cropratio (optional) ratio of width to height to crop final image (e.g. 1:1 or 3:2); |
|
32
|
|
|
* @param boolean nocache (optional) don't read image from the cache; |
|
33
|
|
|
* @param boolean noservercache (optional) don't read image from the server cache; |
|
34
|
|
|
* @param boolean nobrowsercache (optional) don't read image from the browser cache; |
|
35
|
|
|
* @param int quality (optional, 0-100, default: 90) quality of output image; |
|
36
|
|
|
* @param mixed filter (optional, imagefilter 2nd, 3rd, 4th, 5th arguments, more info on php.net |
|
37
|
|
|
* manual) a filter or an array of filters; |
|
38
|
|
|
* @param int radius (optional, 1, 2, 3 or 4 integer values, CW) round corner radius |
|
39
|
|
|
* @param float angle (optional), rotation angle) |
|
40
|
|
|
* |
|
41
|
|
|
*/ |
|
42
|
|
|
|
|
43
|
|
|
/* @example image.php |
|
44
|
|
|
* Resizing a JPEG: |
|
45
|
|
|
* <img src="/image.php?url=image-name.jpg&width=100&height=100" alt="Don't forget your alt text" /> |
|
46
|
|
|
* Resizing and cropping a JPEG into a square: |
|
47
|
|
|
* <img src="/image.php?url=image-name.jpg?width=100&height=100&cropratio=1:1" alt="Don't forget your alt text" /> |
|
48
|
|
|
* Matting a PNG with #990000: |
|
49
|
|
|
* <img src="/image.php?url=image-name.png?color=900&image=/path/to/image.png" alt="Don't forget your alt text" /> |
|
50
|
|
|
* Apply a filter: |
|
51
|
|
|
* <img src="/image.php?url=/path/to/image.png&filter=IMG_FILTER_COLORIZE,128,60,256" alt="Don't forget the alt text" /> |
|
52
|
|
|
* Apply more filters (array) : |
|
53
|
|
|
* <img src="/image.php?url=/path/to/image.png&filter[]=IMG_FILTER_GRAYSCALE&filter[]=IMG_FILTER_COLORIZE,128,60,256" /> |
|
54
|
|
|
* Round the image corners: |
|
55
|
|
|
* All corners with same radius: |
|
56
|
|
|
* <img src="/image.php?url=/path/to/image.png&radius=20" alt="Don't forget your alt text" /> |
|
57
|
|
|
* Left and right corners with different radius (20 for left corners and 40 for right corners) |
|
58
|
|
|
* <img src="/image.php?url=/path/to/image.png&radius=20,40" alt="Don't forget your alt text" /> |
|
59
|
|
|
* 4 corners, 4 radius, clockwise order |
|
60
|
|
|
* <img src="/image.php?url=/path/to/image.png&radius=20,40,0,10" alt="Don't forget your alt text" /> |
|
61
|
|
|
* |
|
62
|
|
|
*/ |
|
63
|
|
|
define('MEMORY_TO_ALLOCATE', '100M'); |
|
64
|
|
|
define('DEFAULT_IMAGE_QUALITY', 90); |
|
65
|
|
|
define('DEFAULT_BACKGROUND_COLOR', '000000'); |
|
66
|
|
|
define('ONLY_LOCAL_IMAGES', true); |
|
67
|
|
|
define('ENABLE_IMAGEFILTER', true); // Set to false to avoid excessive server load |
|
68
|
|
|
define('ENABLE_ROUNDCORNER', true); // Set to false to avoid excessive server load |
|
69
|
|
|
define('ENABLE_IMAGEROTATE', true); // Set to false to avoid excessive server load |
|
70
|
|
|
|
|
71
|
|
|
if (get_magic_quotes_runtime()) { |
|
72
|
|
|
set_magic_quotes_runtime(false); // will never get called on PHP 5.4+ |
|
|
|
|
|
|
73
|
|
|
} |
|
74
|
|
|
if (function_exists('mb_http_output')) { |
|
75
|
|
|
mb_http_output('pass'); |
|
76
|
|
|
} |
|
77
|
|
|
|
|
78
|
|
|
$xoopsOption['nocommon'] = true; |
|
79
|
|
|
require_once __DIR__ . '/mainfile.php'; |
|
80
|
|
|
|
|
81
|
|
|
include_once __DIR__ . '/include/defines.php'; |
|
82
|
|
|
include_once __DIR__ . '/include/functions.php'; |
|
83
|
|
|
include_once __DIR__ . '/include/version.php'; |
|
84
|
|
|
include_once __DIR__ . '/kernel/object.php'; |
|
85
|
|
|
include_once __DIR__ . '/class/xoopsload.php'; |
|
86
|
|
|
include_once __DIR__ . '/class/preload.php'; |
|
87
|
|
|
include_once __DIR__ . '/class/module.textsanitizer.php'; |
|
88
|
|
|
include_once __DIR__ . '/class/database/databasefactory.php'; |
|
89
|
|
|
require_once __DIR__ . '/class/criteria.php'; |
|
90
|
|
|
XoopsLoad::load('xoopslogger'); |
|
91
|
|
|
$xoopsLogger = XoopsLogger::getInstance(); |
|
92
|
|
|
$xoopsLogger->startTime(); |
|
93
|
|
|
error_reporting(0); |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* @param $etag |
|
97
|
|
|
* @param $lastModified |
|
98
|
|
|
* @return null |
|
99
|
|
|
*/ |
|
100
|
|
|
function doConditionalGet($etag, $lastModified) |
|
101
|
|
|
{ |
|
102
|
|
|
header("Last-Modified: $lastModified"); |
|
103
|
|
|
header("ETag: \"{$etag}\""); |
|
104
|
|
|
$ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : false; |
|
105
|
|
|
$ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) |
|
106
|
|
|
? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false; |
|
107
|
|
|
if (!$ifModifiedSince && !$ifNoneMatch) { |
|
|
|
|
|
|
108
|
|
|
return null; |
|
109
|
|
|
} |
|
110
|
|
|
if ($ifNoneMatch && $ifNoneMatch != $etag && $ifNoneMatch != '"' . $etag . '"') { |
|
|
|
|
|
|
111
|
|
|
return null; |
|
112
|
|
|
} // etag is there but doesn't match |
|
113
|
|
|
if ($ifModifiedSince && $ifModifiedSince != $lastModified) { |
|
|
|
|
|
|
114
|
|
|
return null; |
|
115
|
|
|
} // if-modified-since is there but doesn't match |
|
116
|
|
|
// Nothing has changed since their last request - serve a 304 and exit |
|
117
|
|
|
header('HTTP/1.1 304 Not Modified'); |
|
118
|
|
|
exit(); |
|
|
|
|
|
|
119
|
|
|
} |
|
120
|
|
|
|
|
121
|
|
|
/** |
|
122
|
|
|
* ref: http://www.tricksofit.com/2014/08/round-corners-on-image-using-php-and-gd-library |
|
123
|
|
|
* |
|
124
|
|
|
* @param resource $sourceImage GD Image resource |
|
125
|
|
|
* @param int[] $radii array(top left, top right, bottom left, bottom right) of pixel radius |
|
126
|
|
|
* for each corner. A 0 disables rounding on a corner. |
|
127
|
|
|
* |
|
128
|
|
|
* @return resource |
|
129
|
|
|
*/ |
|
130
|
|
|
function imageCreateCorners($sourceImage, $radii) |
|
131
|
|
|
{ |
|
132
|
|
|
$q = 2; // quality - improve alpha blending by using larger (*$q) image size |
|
133
|
|
|
|
|
134
|
|
|
// find a unique color |
|
135
|
|
|
$tryCounter = 0; |
|
136
|
|
|
do { |
|
137
|
|
|
if (++$tryCounter > 255) { |
|
138
|
|
|
$r = 2; |
|
139
|
|
|
$g = 254; |
|
140
|
|
|
$b = 0; |
|
141
|
|
|
break; |
|
142
|
|
|
} |
|
143
|
|
|
$r = rand(0, 255); |
|
144
|
|
|
$g = rand(0, 255); |
|
145
|
|
|
$b = rand(0, 255); |
|
146
|
|
|
} while (imagecolorexact($sourceImage, $r, $g, $b) < 0); |
|
147
|
|
|
|
|
148
|
|
|
$imageWidth = imagesx($sourceImage); |
|
149
|
|
|
$imageHeight = imagesy($sourceImage); |
|
150
|
|
|
|
|
151
|
|
|
$workingWidth = $imageWidth * $q; |
|
152
|
|
|
$workingHeight = $imageHeight * $q; |
|
153
|
|
|
|
|
154
|
|
|
$workingImage= imagecreatetruecolor($workingWidth, $workingHeight); |
|
155
|
|
|
$alphaColor = imagecolorallocatealpha($workingImage, $r, $g, $b, 127); |
|
156
|
|
|
imagealphablending($workingImage, false); |
|
|
|
|
|
|
157
|
|
|
imagesavealpha($workingImage, true); |
|
|
|
|
|
|
158
|
|
|
imagefilledrectangle($workingImage, 0, 0, $workingWidth, $workingHeight, $alphaColor); |
|
159
|
|
|
|
|
160
|
|
|
imagefill($workingImage, 0, 0, $alphaColor); |
|
161
|
|
|
imagecopyresampled($workingImage, $sourceImage, 0, 0, 0, 0, $workingWidth, $workingHeight, $imageWidth, $imageHeight); |
|
162
|
|
|
if (0 < ($radius = $radii[0] * $q)) { // left top |
|
163
|
|
|
imagearc($workingImage, $radius - 1, $radius - 1, $radius * 2, $radius * 2, 180, 270, $alphaColor); |
|
164
|
|
|
imagefilltoborder($workingImage, 0, 0, $alphaColor, $alphaColor); |
|
165
|
|
|
} |
|
166
|
|
View Code Duplication |
if (0 < ($radius = $radii[1] * $q)) { // right top |
|
167
|
|
|
imagearc($workingImage, $workingWidth - $radius, $radius - 1, $radius * 2, $radius * 2, 270, 0, $alphaColor); |
|
168
|
|
|
imagefilltoborder($workingImage, $workingWidth - 1, 0, $alphaColor, $alphaColor); |
|
169
|
|
|
} |
|
170
|
|
View Code Duplication |
if (0 < ($radius = $radii[2] * $q)) { // left bottom |
|
171
|
|
|
imagearc($workingImage, $radius - 1, $workingHeight - $radius, $radius * 2, $radius * 2, 90, 180, $alphaColor); |
|
172
|
|
|
imagefilltoborder($workingImage, 0, $workingHeight - 1, $alphaColor, $alphaColor); |
|
173
|
|
|
} |
|
174
|
|
View Code Duplication |
if (0 < ($radius = $radii[3] * $q)) { // right bottom |
|
175
|
|
|
imagearc($workingImage, $workingWidth - $radius, $workingHeight - $radius, $radius * 2, $radius * 2, 0, 90, $alphaColor); |
|
176
|
|
|
imagefilltoborder($workingImage, $workingWidth - 1, $workingHeight - 1, $alphaColor, $alphaColor); |
|
177
|
|
|
} |
|
178
|
|
|
imagealphablending($workingImage, true); |
|
|
|
|
|
|
179
|
|
|
imagecolortransparent($workingImage, $alphaColor); |
|
180
|
|
|
|
|
181
|
|
|
// scale back down to original size |
|
182
|
|
|
$destinationImage = imagecreatetruecolor($imageWidth, $imageHeight); |
|
183
|
|
|
imagealphablending($destinationImage, false); |
|
|
|
|
|
|
184
|
|
|
imagesavealpha($destinationImage, true); |
|
|
|
|
|
|
185
|
|
|
imagefilledrectangle($destinationImage, 0, 0, $imageWidth, $imageHeight, $alphaColor); |
|
186
|
|
|
imagecopyresampled($destinationImage, $workingImage, 0, 0, 0, 0, $imageWidth, $imageHeight, $workingWidth, $workingHeight); |
|
187
|
|
|
|
|
188
|
|
|
// imagedestroy($sourceImage); |
|
189
|
|
|
imagedestroy($workingImage); |
|
190
|
|
|
|
|
191
|
|
|
return $destinationImage; |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
/** |
|
195
|
|
|
* @param $orig |
|
196
|
|
|
* @param $final |
|
197
|
|
|
* |
|
198
|
|
|
* @return mixed |
|
199
|
|
|
*/ |
|
200
|
|
|
function findSharp($orig, $final) |
|
201
|
|
|
{ |
|
202
|
|
|
// Function from Ryan Rud (http://adryrun.com) |
|
203
|
|
|
$final *= (750.0 / $orig); |
|
204
|
|
|
$a = 52; |
|
205
|
|
|
$b = -0.27810650887573124; |
|
206
|
|
|
$c = .00047337278106508946; |
|
207
|
|
|
$result = $a + $b * $final + $c * $final * $final; |
|
208
|
|
|
|
|
209
|
|
|
return max(round($result), 0); |
|
210
|
|
|
} |
|
211
|
|
|
|
|
212
|
|
|
/** |
|
213
|
|
|
* issue an error for bad request |
|
214
|
|
|
* |
|
215
|
|
|
* Many different issues end up here, so message is generic 404. This keeps us from leaking info by probing |
|
216
|
|
|
*/ |
|
217
|
|
|
function exit404BadReq() |
|
218
|
|
|
{ |
|
219
|
|
|
header('HTTP/1.1 404 Not Found'); |
|
220
|
|
|
exit(); |
|
|
|
|
|
|
221
|
|
|
} |
|
222
|
|
|
|
|
223
|
|
|
/** |
|
224
|
|
|
* check local image url for possible issues |
|
225
|
|
|
* |
|
226
|
|
|
* @param string $imageUrl url to local image starting at site root with a '/' |
|
227
|
|
|
* |
|
228
|
|
|
* @return bool true if name is acceptable, exit if not |
|
229
|
|
|
*/ |
|
230
|
|
|
function imageFilenameCheck($imageUrl) |
|
231
|
|
|
{ |
|
232
|
|
|
if ($imageUrl[0] !== '/') { // must start with slash |
|
233
|
|
|
exit404BadReq(); |
|
234
|
|
|
} |
|
235
|
|
|
|
|
236
|
|
|
if ($imageUrl === '/') { // can't be empty |
|
237
|
|
|
exit404BadReq(); |
|
238
|
|
|
} |
|
239
|
|
|
|
|
240
|
|
|
if (preg_match('/(\.\.|<|>|\:|[[:cntrl:]])/', $imageUrl)) { // no "..", "<", ">", ":" or controls |
|
241
|
|
|
exit404BadReq(); |
|
242
|
|
|
} |
|
243
|
|
|
|
|
244
|
|
|
$fullPath = XOOPS_ROOT_PATH . $imageUrl; |
|
245
|
|
|
if (strpos($fullPath, XOOPS_VAR_PATH) === 0) { // no access to data (shouldn't be in root, but...) |
|
246
|
|
|
exit404BadReq(); |
|
247
|
|
|
} |
|
248
|
|
|
if (strpos($fullPath, XOOPS_PATH) === 0) { // no access to lib (shouldn't be in root, but...) |
|
249
|
|
|
exit404BadReq(); |
|
250
|
|
|
} |
|
251
|
|
|
|
|
252
|
|
|
return true; |
|
253
|
|
|
} |
|
254
|
|
|
|
|
255
|
|
|
/* |
|
256
|
|
|
* Get image |
|
257
|
|
|
*/ |
|
258
|
|
|
// Get id (Xoops image) or url or src (standard image) |
|
259
|
|
|
$imageId = isset($_GET['id']) ? (int)$_GET['id'] : false; |
|
260
|
|
|
$imageUrl = isset($_GET['url']) ? (string)$_GET['url'] : (isset($_GET['src']) ? (string)$_GET['src'] : false); |
|
261
|
|
|
if (!empty($imageId)) { |
|
262
|
|
|
// If image is a Xoops image |
|
263
|
|
|
$imageHandler = xoops_getHandler('image'); |
|
264
|
|
|
$criteria = new CriteriaCompo(new Criteria('i.image_display', true)); |
|
265
|
|
|
$criteria->add(new Criteria('i.image_id', $imageId)); |
|
266
|
|
|
$images = $imageHandler->getObjects($criteria, false, true); |
|
267
|
|
|
if (count($images) != 1) { |
|
268
|
|
|
// No Xoops images or to many Xoops images |
|
269
|
|
|
header('Content-type: image/gif'); |
|
270
|
|
|
readfile(XOOPS_UPLOAD_PATH . '/blank.gif'); |
|
271
|
|
|
exit(); |
|
272
|
|
|
} |
|
273
|
|
|
$image = $images[0]; |
|
274
|
|
|
// Get image category |
|
275
|
|
|
$imgcatId = $image->getVar('imgcat_id'); |
|
276
|
|
|
$imgcatHandler = xoops_getHandler('imagecategory'); |
|
277
|
|
|
if (!$imgcat = $imgcatHandler->get($imgcatId)) { |
|
278
|
|
|
// No Image category |
|
279
|
|
|
header('Content-type: image/gif'); |
|
280
|
|
|
readfile(XOOPS_UPLOAD_PATH . '/blank.gif'); |
|
281
|
|
|
exit(); |
|
282
|
|
|
} |
|
283
|
|
|
// Get image data |
|
284
|
|
|
$imageFilename = $image->getVar('image_name'); // image filename |
|
285
|
|
|
$imageMimetype = $image->getVar('image_mimetype'); |
|
286
|
|
|
$imageCreatedTime = $image->getVar('image_created'); // image creation date |
|
287
|
|
|
if ($imgcat->getVar('imgcat_storetype') === 'db') { |
|
288
|
|
|
$imagePath = null; |
|
289
|
|
|
$imageData = $image->getVar('image_body'); |
|
290
|
|
|
} else { |
|
291
|
|
|
$imagePath = XOOPS_UPLOAD_PATH . '/' . $image->getVar('image_name'); |
|
292
|
|
|
$imageData = file_get_contents($imagePath); |
|
293
|
|
|
} |
|
294
|
|
|
$sourceImage = imagecreatefromstring($imageData); |
|
295
|
|
|
$imageWidth = imagesx($sourceImage); |
|
296
|
|
|
$imageHeight = imagesy($sourceImage); |
|
297
|
|
|
} elseif (!empty($imageUrl)) { |
|
298
|
|
|
// If image is a standard image |
|
299
|
|
|
if (ONLY_LOCAL_IMAGES) { |
|
300
|
|
|
// Images must be local files, so for convenience we strip the domain if it's there |
|
301
|
|
|
$imageUrl = str_replace(XOOPS_URL, '', $imageUrl); |
|
302
|
|
|
|
|
303
|
|
|
// will exit on any unacceptable urls |
|
304
|
|
|
imageFilenameCheck($imageUrl); |
|
305
|
|
|
|
|
306
|
|
|
$imagePath = XOOPS_ROOT_PATH . $imageUrl; |
|
307
|
|
|
if (!file_exists($imagePath)) { |
|
308
|
|
|
exit404BadReq(); |
|
309
|
|
|
} |
|
310
|
|
|
} else { |
|
311
|
|
|
if ($imageUrl{0} === '/') { |
|
312
|
|
|
$imageUrl = substr($imageUrl, 0, 1); |
|
313
|
|
|
} |
|
314
|
|
|
$imagePath = $imageUrl; |
|
315
|
|
|
} |
|
316
|
|
|
// Get the size and MIME type of the requested image |
|
317
|
|
|
$imageFilename = basename($imagePath); // image filename |
|
318
|
|
|
$imagesize = getimagesize($imagePath); |
|
319
|
|
|
$imageWidth = $imagesize[0]; |
|
320
|
|
|
$imageHeight = $imagesize[1]; |
|
321
|
|
|
$imageMimetype = $imagesize['mime']; |
|
322
|
|
|
$imageCreatedTime = filemtime($imagePath); // image creation date |
|
323
|
|
|
$imageData = file_get_contents($imagePath); |
|
324
|
|
|
switch ($imageMimetype) { |
|
325
|
|
|
case 'image/gif': |
|
326
|
|
|
$sourceImage = imagecreatefromgif($imagePath); |
|
327
|
|
|
break; |
|
328
|
|
|
case 'image/png': |
|
329
|
|
|
$sourceImage = imagecreatefrompng($imagePath); |
|
330
|
|
|
break; |
|
331
|
|
|
case 'image/jpeg': |
|
332
|
|
|
$sourceImage = imagecreatefromjpeg($imagePath); |
|
333
|
|
|
break; |
|
334
|
|
|
default: |
|
335
|
|
|
exit404BadReq(); |
|
336
|
|
|
break; |
|
337
|
|
|
} |
|
338
|
|
|
} else { |
|
339
|
|
|
// No id, no url, no src parameters |
|
340
|
|
|
header('Content-type: image/gif'); |
|
341
|
|
|
readfile(XOOPS_ROOT_PATH . '/uploads/blank.gif'); |
|
342
|
|
|
exit(); |
|
343
|
|
|
} |
|
344
|
|
|
|
|
345
|
|
|
/* |
|
346
|
|
|
* Use Xoops cache |
|
347
|
|
|
*/ |
|
348
|
|
|
// Get image_data from the Xoops cache only if the edited image has been cached after the latest modification |
|
349
|
|
|
// of the original image |
|
350
|
|
|
xoops_load('XoopsCache'); |
|
351
|
|
|
$edited_image_filename = 'editedimage_' . md5($_SERVER['REQUEST_URI']) . '_' . $imageFilename; |
|
352
|
|
|
$cached_image = XoopsCache::read($edited_image_filename); |
|
353
|
|
|
if (!isset($_GET['nocache']) && !isset($_GET['noservercache']) && !empty($cached_image) |
|
354
|
|
|
&& ($cached_image['cached_time'] >= $imageCreatedTime)) { |
|
355
|
|
|
header("Content-type: {$imageMimetype}"); |
|
356
|
|
|
header('Content-Length: ' . strlen($cached_image['image_data'])); |
|
357
|
|
|
echo $cached_image['image_data']; |
|
358
|
|
|
exit(); |
|
359
|
|
|
} |
|
360
|
|
|
|
|
361
|
|
|
/* |
|
362
|
|
|
* Get/check editing parameters |
|
363
|
|
|
*/ |
|
364
|
|
|
// width, height |
|
365
|
|
|
$max_width = isset($_GET['width']) ? (int)$_GET['width'] : false; |
|
366
|
|
|
$max_height = isset($_GET['height']) ? (int)$_GET['height'] : false; |
|
367
|
|
|
// If either a max width or max height are not specified, we default to something large so the unspecified |
|
368
|
|
|
// dimension isn't a constraint on our resized image. |
|
369
|
|
|
// If neither are specified but the color is, we aren't going to be resizing at all, just coloring. |
|
370
|
|
|
if (!$max_width && $max_height) { |
|
|
|
|
|
|
371
|
|
|
$max_width = PHP_INT_MAX; |
|
372
|
|
|
} elseif ($max_width && !$max_height) { |
|
|
|
|
|
|
373
|
|
|
$max_height = PHP_INT_MAX; |
|
374
|
|
|
} elseif (!$max_width && !$max_height) { |
|
|
|
|
|
|
375
|
|
|
$max_width = $imageWidth; |
|
376
|
|
|
$max_height = $imageHeight; |
|
377
|
|
|
} |
|
378
|
|
|
|
|
379
|
|
|
// color |
|
380
|
|
|
$color = isset($_GET['color']) ? preg_replace('/[^0-9a-fA-F]/', '', (string)$_GET['color']) : false; |
|
381
|
|
|
|
|
382
|
|
|
// filter, radius, angle |
|
383
|
|
|
$filter = isset($_GET['filter']) ? $_GET['filter'] : false; |
|
384
|
|
|
$radius = isset($_GET['radius']) ? (string)$_GET['radius'] : false; |
|
385
|
|
|
$angle = isset($_GET['angle']) ? (float)$_GET['angle'] : false; |
|
386
|
|
|
|
|
387
|
|
|
// If we don't have a width or height or color or filter or radius or rotate we simply output the original |
|
388
|
|
|
// image and exit |
|
389
|
|
|
if (empty($_GET['width']) && empty($_GET['height']) && empty($_GET['color']) && empty($_GET['filter']) |
|
390
|
|
|
&& empty($_GET['radius']) && empty($_GET['angle'])) { |
|
391
|
|
|
$last_modified_string = gmdate('D, d M Y H:i:s', $imageCreatedTime) . ' GMT'; |
|
392
|
|
|
$etag = md5($imageData); |
|
393
|
|
|
doConditionalGet($etag, $last_modified_string); |
|
394
|
|
|
header("Content-type: {$imageMimetype}"); |
|
395
|
|
|
header('Content-Length: ' . strlen($imageData)); |
|
396
|
|
|
echo $imageData; |
|
397
|
|
|
exit(); |
|
398
|
|
|
} |
|
399
|
|
|
|
|
400
|
|
|
// cropratio |
|
401
|
|
|
$offset_x = 0; |
|
402
|
|
|
$offset_y = 0; |
|
403
|
|
|
if (isset($_GET['cropratio'])) { |
|
404
|
|
|
$crop_ratio = explode(':', (string)$_GET['cropratio']); |
|
405
|
|
|
if (count($crop_ratio) == 2) { |
|
406
|
|
|
$ratio_computed = $imageWidth / $imageHeight; |
|
407
|
|
|
$crop_radio_computed = (float)$crop_ratio[0] / (float)$crop_ratio[1]; |
|
408
|
|
|
if ($ratio_computed < $crop_radio_computed) { |
|
409
|
|
|
// Image is too tall so we will crop the top and bottom |
|
410
|
|
|
$orig_height = $imageHeight; |
|
411
|
|
|
$imageHeight = $imageWidth / $crop_radio_computed; |
|
412
|
|
|
$offset_y = ($orig_height - $imageHeight) / 2; |
|
413
|
|
|
} elseif ($ratio_computed > $crop_radio_computed) { |
|
414
|
|
|
// Image is too wide so we will crop off the left and right sides |
|
415
|
|
|
$orig_width = $imageWidth; |
|
416
|
|
|
$imageWidth = $imageHeight * $crop_radio_computed; |
|
417
|
|
|
$offset_x = ($orig_width - $imageWidth) / 2; |
|
418
|
|
|
} |
|
419
|
|
|
} |
|
420
|
|
|
} |
|
421
|
|
|
// Setting up the ratios needed for resizing. We will compare these below to determine how to resize the image |
|
422
|
|
|
// (based on height or based on width) |
|
423
|
|
|
$xRatio = $max_width / $imageWidth; |
|
424
|
|
|
$yRatio = $max_height / $imageHeight; |
|
425
|
|
|
if ($xRatio * $imageHeight < $max_height) { |
|
426
|
|
|
// Resize the image based on width |
|
427
|
|
|
$tn_height = ceil($xRatio * $imageHeight); |
|
428
|
|
|
$tn_width = $max_width; |
|
429
|
|
|
} else { |
|
430
|
|
|
// Resize the image based on height |
|
431
|
|
|
$tn_width = ceil($yRatio * $imageWidth); |
|
432
|
|
|
$tn_height = $max_height; |
|
433
|
|
|
} |
|
434
|
|
|
|
|
435
|
|
|
// quality |
|
436
|
|
|
$quality = isset($_GET['quality']) ? (int)$_GET['quality'] : DEFAULT_IMAGE_QUALITY; |
|
437
|
|
|
|
|
438
|
|
|
/* |
|
439
|
|
|
* Start image editing |
|
440
|
|
|
*/ |
|
441
|
|
|
// We don't want to run out of memory |
|
442
|
|
|
ini_set('memory_limit', MEMORY_TO_ALLOCATE); |
|
443
|
|
|
|
|
444
|
|
|
// Set up a blank canvas for our resized image (destination) |
|
445
|
|
|
$destination_image = imagecreatetruecolor($tn_width, $tn_height); |
|
446
|
|
|
|
|
447
|
|
|
// Set up the appropriate image handling functions based on the original image's mime type |
|
448
|
|
|
switch ($imageMimetype) { |
|
449
|
|
View Code Duplication |
case 'image/gif': |
|
450
|
|
|
// We will be converting GIFs to PNGs to avoid transparency issues when resizing GIFs |
|
451
|
|
|
// This is maybe not the ideal solution, but IE6 can suck it |
|
452
|
|
|
$output_function = 'imagepng'; |
|
453
|
|
|
$imageMimetype = 'image/png'; // We need to convert GIFs to PNGs |
|
454
|
|
|
$do_sharpen = false; |
|
455
|
|
|
$quality = round(10 - ($quality / 10)); // We are converting the GIF to a PNG and PNG needs a compression |
|
456
|
|
|
// level of 0 (no compression) through 9 (max) |
|
457
|
|
|
break; |
|
458
|
|
View Code Duplication |
case 'image/png': |
|
459
|
|
|
$output_function = 'imagepng'; |
|
460
|
|
|
$do_sharpen = false; |
|
461
|
|
|
$quality = round(10 - ($quality / 10)); // PNG needs a compression level of 0 (no compression) through 9 |
|
462
|
|
|
break; |
|
463
|
|
|
case 'image/jpeg': |
|
464
|
|
|
$output_function = 'imagejpeg'; |
|
465
|
|
|
$do_sharpen = true; |
|
466
|
|
|
break; |
|
467
|
|
|
default: |
|
468
|
|
|
exit400BadReq(); |
|
469
|
|
|
break; |
|
470
|
|
|
} |
|
471
|
|
|
|
|
472
|
|
|
// Resample the original image into the resized canvas we set up earlier |
|
473
|
|
|
imagecopyresampled($destination_image, $sourceImage, 0, 0, $offset_x, $offset_y, $tn_width, $tn_height, $imageWidth, $imageHeight); |
|
474
|
|
|
|
|
475
|
|
|
// Set background color |
|
476
|
|
|
if (in_array($imageMimetype, array('image/gif', 'image/png'))) { |
|
477
|
|
|
if (!$color) { |
|
|
|
|
|
|
478
|
|
|
// If this is a GIF or a PNG, we need to set up transparency |
|
479
|
|
|
imagealphablending($destination_image, false); |
|
|
|
|
|
|
480
|
|
|
imagesavealpha($destination_image, true); |
|
|
|
|
|
|
481
|
|
|
$png_transparency = imagecolorallocatealpha($destination_image, 0, 0, 0, 127); |
|
482
|
|
|
imagefill($destination_image, 0, 0, $png_transparency); |
|
483
|
|
View Code Duplication |
} else { |
|
484
|
|
|
// Fill the background with the specified color for matting purposes |
|
485
|
|
|
if ($color[0] === '#') { |
|
486
|
|
|
$color = substr($color, 1); |
|
487
|
|
|
} |
|
488
|
|
|
$background = false; |
|
489
|
|
|
if (strlen($color) == 6) { |
|
490
|
|
|
$background = imagecolorallocate( |
|
491
|
|
|
$destination_image, |
|
492
|
|
|
intval($color[0] . $color[1], 16), |
|
493
|
|
|
intval($color[2] . $color[3], 16), |
|
494
|
|
|
intval($color[4] . $color[5], 16) |
|
495
|
|
|
); |
|
496
|
|
|
} elseif (strlen($color) == 3) { |
|
497
|
|
|
$background = imagecolorallocate( |
|
498
|
|
|
$destination_image, |
|
499
|
|
|
intval($color[0] . $color[0], 16), |
|
500
|
|
|
intval($color[1] . $color[1], 16), |
|
501
|
|
|
intval($color[2] . $color[2], 16) |
|
502
|
|
|
); |
|
503
|
|
|
} |
|
504
|
|
|
if ($background) { |
|
|
|
|
|
|
505
|
|
|
imagefill($destination_image, 0, 0, $background); |
|
506
|
|
|
} |
|
507
|
|
|
} |
|
508
|
|
View Code Duplication |
} else { |
|
509
|
|
|
if (!$color) { |
|
|
|
|
|
|
510
|
|
|
$color = DEFAULT_BACKGROUND_COLOR; |
|
511
|
|
|
} |
|
512
|
|
|
// Fill the background with the specified color for matting purposes |
|
513
|
|
|
if ($color[0] === '#') { |
|
514
|
|
|
$color = substr($color, 1); |
|
515
|
|
|
} |
|
516
|
|
|
$background = false; |
|
517
|
|
|
if (strlen($color) == 6) { |
|
518
|
|
|
$background = imagecolorallocate( |
|
519
|
|
|
$destination_image, |
|
520
|
|
|
intval($color[0] . $color[1], 16), |
|
521
|
|
|
intval($color[2] . $color[3], 16), |
|
522
|
|
|
intval($color[4] . $color[5], 16) |
|
523
|
|
|
); |
|
524
|
|
|
} elseif (strlen($color) == 3) { |
|
525
|
|
|
$background = imagecolorallocate( |
|
526
|
|
|
$destination_image, |
|
527
|
|
|
intval($color[0] . $color[0], 16), |
|
528
|
|
|
intval($color[1] . $color[1], 16), |
|
529
|
|
|
intval($color[2] . $color[2], 16) |
|
530
|
|
|
); |
|
531
|
|
|
} |
|
532
|
|
|
if ($background) { |
|
|
|
|
|
|
533
|
|
|
imagefill($destination_image, 0, 0, $background); |
|
534
|
|
|
} |
|
535
|
|
|
} |
|
536
|
|
|
|
|
537
|
|
|
// Imagefilter |
|
538
|
|
|
if (ENABLE_IMAGEFILTER && !empty($filter)) { |
|
539
|
|
|
$filterSet = (array) $filter; |
|
540
|
|
|
foreach ($filterSet as $currentFilter) { |
|
541
|
|
|
$rawFilterArgs = explode(',', $currentFilter); |
|
542
|
|
|
$filterConst = constant(array_shift($rawFilterArgs)); |
|
543
|
|
|
if (null !== $filterConst) { // skip if unknown constant |
|
544
|
|
|
$filterArgs = array(); |
|
545
|
|
|
$filterArgs[] = $destination_image; |
|
546
|
|
|
$filterArgs[] = $filterConst; |
|
547
|
|
|
foreach ($rawFilterArgs as $tempValue) { |
|
548
|
|
|
$filterArgs[] = trim($tempValue); |
|
549
|
|
|
} |
|
550
|
|
|
call_user_func_array('imagefilter', $filterArgs); |
|
551
|
|
|
} |
|
552
|
|
|
} |
|
553
|
|
|
} |
|
554
|
|
|
|
|
555
|
|
|
// Round corners |
|
556
|
|
|
if (ENABLE_ROUNDCORNER && !empty($radius)) { |
|
557
|
|
|
$radii = explode(',', $radius); |
|
558
|
|
|
switch (count($radii)) { |
|
559
|
|
View Code Duplication |
case 1: |
|
560
|
|
|
$radii[3] = $radii[2] = $radii[1] = $radii[0]; |
|
561
|
|
|
break; |
|
562
|
|
View Code Duplication |
case 2: |
|
563
|
|
|
$radii[3] = $radii[0]; |
|
564
|
|
|
$radii[2] = $radii[1]; |
|
565
|
|
|
break; |
|
566
|
|
|
case 3: |
|
567
|
|
|
$radii[3] = $radii[0]; |
|
568
|
|
|
break; |
|
569
|
|
|
case 4: |
|
570
|
|
|
// NOP |
|
571
|
|
|
break; |
|
572
|
|
|
} |
|
573
|
|
|
|
|
574
|
|
|
$destination_image = imageCreateCorners($destination_image, $radii); |
|
575
|
|
|
// we need png to support the alpha corners correctly |
|
576
|
|
|
if ($imageMimetype === 'image/jpeg') { |
|
577
|
|
|
$output_function = 'imagepng'; |
|
578
|
|
|
$imageMimetype = 'image/png'; |
|
579
|
|
|
$do_sharpen = false; |
|
580
|
|
|
$quality = round(10 - ($quality / 10)); |
|
581
|
|
|
} |
|
582
|
|
|
} |
|
583
|
|
|
|
|
584
|
|
|
// Imagerotate |
|
585
|
|
|
if (ENABLE_IMAGEROTATE && !empty($angle)) { |
|
586
|
|
|
$destination_image = imagerotate($destination_image, $angle, $background, 0); |
|
587
|
|
|
} |
|
588
|
|
|
|
|
589
|
|
|
if ($do_sharpen) { |
|
590
|
|
|
// Sharpen the image based on two things: |
|
591
|
|
|
// (1) the difference between the original size and the final size |
|
592
|
|
|
// (2) the final size |
|
593
|
|
|
$sharpness = findSharp($imageWidth, $tn_width); |
|
594
|
|
|
$sharpen_matrix = array( |
|
595
|
|
|
array(-1, -2, -1), |
|
596
|
|
|
array(-2, $sharpness + 12, -2), |
|
597
|
|
|
array(-1, -2, -1)); |
|
598
|
|
|
$divisor = $sharpness; |
|
599
|
|
|
$offset = 0; |
|
600
|
|
|
imageconvolution($destination_image, $sharpen_matrix, $divisor, $offset); |
|
601
|
|
|
} |
|
602
|
|
|
|
|
603
|
|
|
// Put the data of the resized image into a variable |
|
604
|
|
|
ob_start(); |
|
605
|
|
|
$output_function($destination_image, null, $quality); |
|
606
|
|
|
$imageData = ob_get_contents(); |
|
607
|
|
|
ob_end_clean(); |
|
608
|
|
|
// Update $image_created_time |
|
609
|
|
|
$imageCreatedTime = time(); |
|
610
|
|
|
|
|
611
|
|
|
// Clean up the memory |
|
612
|
|
|
imagedestroy($sourceImage); |
|
613
|
|
|
imagedestroy($destination_image); |
|
614
|
|
|
|
|
615
|
|
|
/* |
|
616
|
|
|
* Write the just edited image into the Xoops cache |
|
617
|
|
|
*/ |
|
618
|
|
|
$cached_image['edited_image_filename'] = $edited_image_filename; |
|
619
|
|
|
$cached_image['image_data'] = $imageData; |
|
620
|
|
|
$cached_image['cached_time'] = $imageCreatedTime; |
|
621
|
|
|
XoopsCache::write($edited_image_filename, $cached_image); |
|
622
|
|
|
|
|
623
|
|
|
/* |
|
624
|
|
|
* Send the edited image to the browser |
|
625
|
|
|
*/ |
|
626
|
|
|
// See if the browser already has the image |
|
627
|
|
|
$last_modified_string = gmdate('D, d M Y H:i:s', $imageCreatedTime) . ' GMT'; |
|
628
|
|
|
$etag = md5($imageData); |
|
629
|
|
|
doConditionalGet($etag, $last_modified_string); |
|
630
|
|
|
|
|
631
|
|
|
header('HTTP/1.1 200 OK'); |
|
632
|
|
|
// if image is cacheable |
|
633
|
|
|
if (!isset($_GET['nocache']) && !isset($_GET['nobrowsercache'])) { |
|
634
|
|
|
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $imageCreatedTime) . 'GMT'); |
|
635
|
|
|
header('Cache-control: max-age=31536000'); |
|
636
|
|
|
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . 'GMT'); |
|
637
|
|
|
} else { |
|
638
|
|
|
// "Kill" the browser cache |
|
639
|
|
|
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // past date |
|
640
|
|
|
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified |
|
641
|
|
|
header('Cache-Control: no-store, no-cache, must-revalidate'); // HTTP/1.1 |
|
642
|
|
|
header('Cache-Control: post-check=0, pre-check=0', false); |
|
643
|
|
|
header('Pragma: no-cache'); // HTTP/1.0 |
|
644
|
|
|
} |
|
645
|
|
|
header("Content-type: {$imageMimetype}"); |
|
646
|
|
|
header("Content-disposition: filename={$imageFilename}"); |
|
647
|
|
|
header('Content-Length: ' . strlen($imageData)); |
|
648
|
|
|
echo $imageData; |
|
649
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.