1<?php
2
3/**
4 * elFinder Plugin Watermark
5 * Print watermark on file upload.
6 * ex. binding, configure on connector options
7 *    $opts = array(
8 *        'bind' => array(
9 *            'upload.presave' => array(
10 *                'Plugin.Watermark.onUpLoadPreSave'
11 *            )
12 *        ),
13 *        // global configure (optional)
14 *        'plugin' => array(
15 *            'Watermark' => array(
16 *                'enable'         => true,       // For control by volume driver
17 *                'source'         => 'logo.png', // Path to Water mark image
18 *                'ratio'          => 0.2,        // Ratio to original image (ratio > 0 and ratio <= 1)
19 *                'position'       => 'RB',       // Position L(eft)/C(enter)/R(ight) and T(op)/M(edium)/B(ottom)
20 *                'marginX'        => 5,          // Margin horizontal pixel
21 *                'marginY'        => 5,          // Margin vertical pixel
22 *                'quality'        => 95,         // JPEG image save quality
23 *                'transparency'   => 70,         // Water mark image transparency ( other than PNG )
24 *                'targetType'     => IMG_GIF|IMG_JPG|IMG_PNG|IMG_WBMP, // Target image formats ( bit-field )
25 *                'targetMinPixel' => 200,        // Target image minimum pixel size
26 *                'interlace'      => IMG_GIF|IMG_JPG, // Set interlacebit image formats ( bit-field )
27 *                'offDropWith'    => null,       // Enabled by default. To disable it if it is dropped with pressing the meta key
28 *                                                // Alt: 8, Ctrl: 4, Meta: 2, Shift: 1 - sum of each value
29 *                                                // In case of using any key, specify it as an array
30 *                'onDropWith'     => null        // Disabled by default. To enable it if it is dropped with pressing the meta key
31 *                                                // Alt: 8, Ctrl: 4, Meta: 2, Shift: 1 - sum of each value
32 *                                                // In case of using any key, specify it as an array
33 *            )
34 *        ),
35 *        // each volume configure (optional)
36 *        'roots' => array(
37 *            array(
38 *                'driver' => 'LocalFileSystem',
39 *                'path'   => '/path/to/files/',
40 *                'URL'    => 'http://localhost/to/files/'
41 *                'plugin' => array(
42 *                    'Watermark' => array(
43 *                        'enable'         => true,       // For control by volume driver
44 *                        'source'         => 'logo.png', // Path to Water mark image
45 *                        'ratio'          => 0.2,        // Ratio to original image (ratio > 0 and ratio <= 1)
46 *                        'position'       => 'RB',       // Position L(eft)/C(enter)/R(ight) and T(op)/M(edium)/B(ottom)
47 *                        'marginX'        => 5,          // Margin horizontal pixel
48 *                        'marginY'        => 5,          // Margin vertical pixel
49 *                        'quality'        => 95,         // JPEG image save quality
50 *                        'transparency'   => 70,         // Water mark image transparency ( other than PNG )
51 *                        'targetType'     => IMG_GIF|IMG_JPG|IMG_PNG|IMG_WBMP, // Target image formats ( bit-field )
52 *                        'targetMinPixel' => 200,        // Target image minimum pixel size
53 *                        'interlace'      => IMG_GIF|IMG_JPG, // Set interlacebit image formats ( bit-field )
54 *                        'offDropWith'    => null,       // Enabled by default. To disable it if it is dropped with pressing the meta key
55 *                                                        // Alt: 8, Ctrl: 4, Meta: 2, Shift: 1 - sum of each value
56 *                                                        // In case of using any key, specify it as an array
57 *                        'onDropWith'     => null        // Disabled by default. To enable it if it is dropped with pressing the meta key
58 *                                                        // Alt: 8, Ctrl: 4, Meta: 2, Shift: 1 - sum of each value
59 *                                                        // In case of using any key, specify it as an array
60 *                    )
61 *                )
62 *            )
63 *        )
64 *    );
65 *
66 * @package elfinder
67 * @author  Naoki Sawada
68 * @license New BSD
69 */
70class elFinderPluginWatermark extends elFinderPlugin
71{
72
73    private $watermarkImgInfo = null;
74
75    public function __construct($opts)
76    {
77        $defaults = array(
78            'enable' => true,       // For control by volume driver
79            'source' => 'logo.png', // Path to Water mark image
80            'ratio' => 0.2,        // Ratio to original image (ratio > 0 and ratio <= 1)
81            'position' => 'RB',       // Position L(eft)/C(enter)/R(ight) and T(op)/M(edium)/B(ottom)
82            'marginX' => 5,          // Margin horizontal pixel
83            'marginY' => 5,          // Margin vertical pixel
84            'quality' => 95,         // JPEG image save quality
85            'transparency' => 70,         // Water mark image transparency ( other than PNG )
86            'targetType' => IMG_GIF | IMG_JPG | IMG_PNG | IMG_WBMP, // Target image formats ( bit-field )
87            'targetMinPixel' => 200,        // Target image minimum pixel size
88            'interlace' => IMG_GIF | IMG_JPG, // Set interlacebit image formats ( bit-field )
89            'offDropWith' => null,       // To disable it if it is dropped with pressing the meta key
90            // Alt: 8, Ctrl: 4, Meta: 2, Shift: 1 - sum of each value
91            // In case of using any key, specify it as an array
92            'marginRight' => 0,          // Deprecated - marginX should be used
93            'marginBottom' => 0,          // Deprecated - marginY should be used
94            'disableWithContentSaveId' => true // Disable on URL upload with post data "contentSaveId"
95        );
96
97        $this->opts = array_merge($defaults, $opts);
98
99    }
100
101    public function onUpLoadPreSave(&$thash, &$name, $src, $elfinder, $volume)
102    {
103        if (!$src) {
104            return false;
105        }
106
107        $opts = $this->getCurrentOpts($volume);
108
109        if (!$this->iaEnabled($opts, $elfinder)) {
110            return false;
111        }
112
113        $imageType = null;
114        $srcImgInfo = null;
115        if (extension_loaded('fileinfo') && function_exists('mime_content_type')) {
116            $mime = mime_content_type($src);
117            if (substr($mime, 0, 5) !== 'image') {
118                return false;
119            }
120        }
121        if (extension_loaded('exif') && function_exists('exif_imagetype')) {
122            $imageType = exif_imagetype($src);
123            if ($imageType === false) {
124                return false;
125            }
126        } else {
127            $srcImgInfo = getimagesize($src);
128            if ($srcImgInfo === false) {
129                return false;
130            }
131            $imageType = $srcImgInfo[2];
132        }
133
134        // check target image type
135        $imgTypes = array(
136            IMAGETYPE_GIF => IMG_GIF,
137            IMAGETYPE_JPEG => IMG_JPEG,
138            IMAGETYPE_PNG => IMG_PNG,
139            IMAGETYPE_BMP => IMG_WBMP,
140            IMAGETYPE_WBMP => IMG_WBMP
141        );
142        if (!isset($imgTypes[$imageType]) || !($opts['targetType'] & $imgTypes[$imageType])) {
143            return false;
144        }
145
146        // check Animation Gif
147        if ($imageType === IMAGETYPE_GIF && elFinder::isAnimationGif($src)) {
148            return false;
149        }
150        // check Animation Png
151        if ($imageType === IMAGETYPE_PNG && elFinder::isAnimationPng($src)) {
152            return false;
153        }
154        // check water mark image
155        if (!file_exists($opts['source'])) {
156            $opts['source'] = dirname(__FILE__) . "/" . $opts['source'];
157        }
158        if (is_readable($opts['source'])) {
159            $watermarkImgInfo = getimagesize($opts['source']);
160            if (!$watermarkImgInfo) {
161                return false;
162            }
163        } else {
164            return false;
165        }
166
167        if (!$srcImgInfo) {
168            $srcImgInfo = getimagesize($src);
169        }
170
171        $watermark = $opts['source'];
172        $quality = $opts['quality'];
173        $transparency = $opts['transparency'];
174
175        // check target image size
176        if ($opts['targetMinPixel'] > 0 && $opts['targetMinPixel'] > min($srcImgInfo[0], $srcImgInfo[1])) {
177            return false;
178        }
179
180        $watermark_width = $watermarkImgInfo[0];
181        $watermark_height = $watermarkImgInfo[1];
182
183        // Specified as a ratio to the image size
184        if ($opts['ratio'] && $opts['ratio'] > 0 && $opts['ratio'] <= 1) {
185            $maxW = $srcImgInfo[0] * $opts['ratio'] - ($opts['marginX'] * 2);
186            $maxH = $srcImgInfo[1] * $opts['ratio'] - ($opts['marginY'] * 2);
187            $dx = $dy = 0;
188            if (($maxW >= $watermarkImgInfo[0] && $maxH >= $watermarkImgInfo[0]) || ($maxW <= $watermarkImgInfo[0] && $maxH <= $watermarkImgInfo[0])) {
189                $dx = abs($srcImgInfo[0] - $watermarkImgInfo[0]);
190                $dy = abs($srcImgInfo[1] - $watermarkImgInfo[1]);
191            } else if ($maxW < $watermarkImgInfo[0]) {
192                $dx = -1;
193            } else {
194                $dy = -1;
195            }
196            if ($dx < $dy) {
197                $ww = $maxW;
198                $wh = $watermarkImgInfo[1] * ($ww / $watermarkImgInfo[0]);
199            } else {
200                $wh = $maxH;
201                $ww = $watermarkImgInfo[0] * ($wh / $watermarkImgInfo[1]);
202            }
203            $watermarkImgInfo[0] = $ww;
204            $watermarkImgInfo[1] = $wh;
205        } else {
206            $opts['ratio'] = null;
207        }
208
209        $opts['position'] = strtoupper($opts['position']);
210
211        // Set vertical position
212        if (strpos($opts['position'], 'T') !== false) {
213            // Top
214            $dest_x = $opts['marginX'];
215        } else if (strpos($opts['position'], 'M') !== false) {
216            // Middle
217            $dest_x = ($srcImgInfo[0] - $watermarkImgInfo[0]) / 2;
218        } else {
219            // Bottom
220            $dest_x = $srcImgInfo[0] - $watermarkImgInfo[0] - max($opts['marginBottom'], $opts['marginX']);
221        }
222
223        // Set horizontal position
224        if (strpos($opts['position'], 'L') !== false) {
225            // Left
226            $dest_y = $opts['marginY'];
227        } else if (strpos($opts['position'], 'C') !== false) {
228            // Middle
229            $dest_y = ($srcImgInfo[1] - $watermarkImgInfo[1]) / 2;
230        } else {
231            // Right
232            $dest_y = $srcImgInfo[1] - $watermarkImgInfo[1] - max($opts['marginRight'], $opts['marginY']);
233        }
234
235
236        // check interlace
237        $opts['interlace'] = ($opts['interlace'] & $imgTypes[$imageType]);
238
239        // Repeated use of Imagick::compositeImage() may cause PHP to hang, so disable it
240        //if (class_exists('Imagick', false)) {
241        //    return $this->watermarkPrint_imagick($src, $watermark, $dest_x, $dest_y, $quality, $transparency, $watermarkImgInfo, $opts);
242        //} else {
243            elFinder::expandMemoryForGD(array($watermarkImgInfo, $srcImgInfo));
244            return $this->watermarkPrint_gd($src, $watermark, $dest_x, $dest_y, $quality, $transparency, $watermarkImgInfo, $srcImgInfo, $opts);
245        //}
246    }
247
248    private function watermarkPrint_imagick($src, $watermarkSrc, $dest_x, $dest_y, $quality, $transparency, $watermarkImgInfo, $opts)
249    {
250
251        try {
252
253            // Open the original image
254            $img = new Imagick($src);
255
256            // Open the watermark
257            $watermark = new Imagick($watermarkSrc);
258
259            // zoom
260            if ($opts['ratio']) {
261                $watermark->scaleImage($watermarkImgInfo[0], $watermarkImgInfo[1]);
262            }
263
264            // Set transparency
265            if (strtoupper($watermark->getImageFormat()) !== 'PNG') {
266                $watermark->setImageOpacity($transparency / 100);
267            }
268
269            // Overlay the watermark on the original image
270            $img->compositeImage($watermark, imagick::COMPOSITE_OVER, $dest_x, $dest_y);
271
272            // Set quality
273            if (strtoupper($img->getImageFormat()) === 'JPEG') {
274                $img->setImageCompression(imagick::COMPRESSION_JPEG);
275                $img->setCompressionQuality($quality);
276            }
277
278            // set interlace
279            $opts['interlace'] && $img->setInterlaceScheme(Imagick::INTERLACE_PLANE);
280
281            $result = $img->writeImage($src);
282
283            $img->clear();
284            $img->destroy();
285            $watermark->clear();
286            $watermark->destroy();
287
288            return $result ? true : false;
289        } catch (Exception $e) {
290            $ermsg = $e->getMessage();
291            $ermsg && trigger_error($ermsg);
292            return false;
293        }
294    }
295
296    private function watermarkPrint_gd($src, $watermark, $dest_x, $dest_y, $quality, $transparency, $watermarkImgInfo, $srcImgInfo, $opts)
297    {
298
299        $watermark_width = $watermarkImgInfo[0];
300        $watermark_height = $watermarkImgInfo[1];
301
302        $ermsg = '';
303        switch ($watermarkImgInfo['mime']) {
304            case 'image/gif':
305                if (imagetypes() & IMG_GIF) {
306                    $oWatermarkImg = imagecreatefromgif($watermark);
307                } else {
308                    $ermsg = 'GIF images are not supported as watermark image';
309                }
310                break;
311            case 'image/jpeg':
312                if (imagetypes() & IMG_JPG) {
313                    $oWatermarkImg = imagecreatefromjpeg($watermark);
314                } else {
315                    $ermsg = 'JPEG images are not supported as watermark image';
316                }
317                break;
318            case 'image/png':
319                if (imagetypes() & IMG_PNG) {
320                    $oWatermarkImg = imagecreatefrompng($watermark);
321                } else {
322                    $ermsg = 'PNG images are not supported as watermark image';
323                }
324                break;
325            case 'image/wbmp':
326                if (imagetypes() & IMG_WBMP) {
327                    $oWatermarkImg = imagecreatefromwbmp($watermark);
328                } else {
329                    $ermsg = 'WBMP images are not supported as watermark image';
330                }
331                break;
332            default:
333                $oWatermarkImg = false;
334                $ermsg = $watermarkImgInfo['mime'] . ' images are not supported as watermark image';
335                break;
336        }
337
338
339        if (!$ermsg) {
340            // zoom
341            if ($opts['ratio']) {
342                $tmpImg = imagecreatetruecolor($watermarkImgInfo[0], $watermarkImgInfo[1]);
343                imagealphablending($tmpImg, false);
344                imagesavealpha($tmpImg, true);
345                imagecopyresampled($tmpImg, $oWatermarkImg, 0, 0, 0, 0, $watermarkImgInfo[0], $watermarkImgInfo[1], imagesx($oWatermarkImg), imagesy($oWatermarkImg));
346                imageDestroy($oWatermarkImg);
347                $oWatermarkImg = $tmpImg;
348                $tmpImg = null;
349            }
350
351            switch ($srcImgInfo['mime']) {
352                case 'image/gif':
353                    if (imagetypes() & IMG_GIF) {
354                        $oSrcImg = imagecreatefromgif($src);
355                    } else {
356                        $ermsg = 'GIF images are not supported as source image';
357                    }
358                    break;
359                case 'image/jpeg':
360                    if (imagetypes() & IMG_JPG) {
361                        $oSrcImg = imagecreatefromjpeg($src);
362                    } else {
363                        $ermsg = 'JPEG images are not supported as source image';
364                    }
365                    break;
366                case 'image/png':
367                    if (imagetypes() & IMG_PNG) {
368                        $oSrcImg = imagecreatefrompng($src);
369                    } else {
370                        $ermsg = 'PNG images are not supported as source image';
371                    }
372                    break;
373                case 'image/wbmp':
374                    if (imagetypes() & IMG_WBMP) {
375                        $oSrcImg = imagecreatefromwbmp($src);
376                    } else {
377                        $ermsg = 'WBMP images are not supported as source image';
378                    }
379                    break;
380                default:
381                    $oSrcImg = false;
382                    $ermsg = $srcImgInfo['mime'] . ' images are not supported as source image';
383                    break;
384            }
385        }
386
387        if ($ermsg || false === $oSrcImg || false === $oWatermarkImg) {
388            $ermsg && trigger_error($ermsg);
389            return false;
390        }
391
392        if ($srcImgInfo['mime'] === 'image/png') {
393            if (function_exists('imagecolorallocatealpha')) {
394                $bg = imagecolorallocatealpha($oSrcImg, 255, 255, 255, 127);
395                imagefill($oSrcImg, 0, 0, $bg);
396            }
397        }
398
399        if ($watermarkImgInfo['mime'] === 'image/png') {
400            imagecopy($oSrcImg, $oWatermarkImg, $dest_x, $dest_y, 0, 0, $watermark_width, $watermark_height);
401        } else {
402            imagecopymerge($oSrcImg, $oWatermarkImg, $dest_x, $dest_y, 0, 0, $watermark_width, $watermark_height, $transparency);
403        }
404
405        // set interlace
406        $opts['interlace'] && imageinterlace($oSrcImg, true);
407
408        switch ($srcImgInfo['mime']) {
409            case 'image/gif':
410                imagegif($oSrcImg, $src);
411                break;
412            case 'image/jpeg':
413                imagejpeg($oSrcImg, $src, $quality);
414                break;
415            case 'image/png':
416                if (function_exists('imagesavealpha') && function_exists('imagealphablending')) {
417                    imagealphablending($oSrcImg, false);
418                    imagesavealpha($oSrcImg, true);
419                }
420                imagepng($oSrcImg, $src);
421                break;
422            case 'image/wbmp':
423                imagewbmp($oSrcImg, $src);
424                break;
425        }
426
427        imageDestroy($oSrcImg);
428        imageDestroy($oWatermarkImg);
429
430        return true;
431    }
432}
433