2020/CVE-2020-25213/vultarget/wp-file-manager/lib/php/plugins/Watermark/plugin.php
<?php

/**
 * elFinder Plugin Watermark
 * Print watermark on file upload.
 * ex. binding, configure on connector options
 *    $opts = array(
 *        'bind' => array(
 *            'upload.presave' => array(
 *                'Plugin.Watermark.onUpLoadPreSave'
 *            )
 *        ),
 *        // global configure (optional)
 *        'plugin' => array(
 *            'Watermark' => array(
 *                'enable'         => true,       // For control by volume driver
 *                'source'         => 'logo.png', // Path to Water mark image
 *                'ratio'          => 0.2,        // Ratio to original image (ratio > 0 and ratio <= 1)
 *                'position'       => 'RB',       // Position L(eft)/C(enter)/R(ight) and T(op)/M(edium)/B(ottom)
 *                'marginX'        => 5,          // Margin horizontal pixel
 *                'marginY'        => 5,          // Margin vertical pixel
 *                'quality'        => 95,         // JPEG image save quality
 *                'transparency'   => 70,         // Water mark image transparency ( other than PNG )
 *                'targetType'     => IMG_GIF|IMG_JPG|IMG_PNG|IMG_WBMP, // Target image formats ( bit-field )
 *                'targetMinPixel' => 200,        // Target image minimum pixel size
 *                'interlace'      => IMG_GIF|IMG_JPG, // Set interlacebit image formats ( bit-field )
 *                'offDropWith'    => null,       // Enabled by default. To disable it if it is dropped with pressing the meta key
 *                                                // Alt: 8, Ctrl: 4, Meta: 2, Shift: 1 - sum of each value
 *                                                // In case of using any key, specify it as an array
 *                'onDropWith'     => null        // Disabled by default. To enable it if it is dropped with pressing the meta key
 *                                                // Alt: 8, Ctrl: 4, Meta: 2, Shift: 1 - sum of each value
 *                                                // In case of using any key, specify it as an array
 *            )
 *        ),
 *        // each volume configure (optional)
 *        'roots' => array(
 *            array(
 *                'driver' => 'LocalFileSystem',
 *                'path'   => '/path/to/files/',
 *                'URL'    => 'http://localhost/to/files/'
 *                'plugin' => array(
 *                    'Watermark' => array(
 *                        'enable'         => true,       // For control by volume driver
 *                        'source'         => 'logo.png', // Path to Water mark image
 *                        'ratio'          => 0.2,        // Ratio to original image (ratio > 0 and ratio <= 1)
 *                        'position'       => 'RB',       // Position L(eft)/C(enter)/R(ight) and T(op)/M(edium)/B(ottom)
 *                        'marginX'        => 5,          // Margin horizontal pixel
 *                        'marginY'        => 5,          // Margin vertical pixel
 *                        'quality'        => 95,         // JPEG image save quality
 *                        'transparency'   => 70,         // Water mark image transparency ( other than PNG )
 *                        'targetType'     => IMG_GIF|IMG_JPG|IMG_PNG|IMG_WBMP, // Target image formats ( bit-field )
 *                        'targetMinPixel' => 200,        // Target image minimum pixel size
 *                        'interlace'      => IMG_GIF|IMG_JPG, // Set interlacebit image formats ( bit-field )
 *                        'offDropWith'    => null,       // Enabled by default. To disable it if it is dropped with pressing the meta key
 *                                                        // Alt: 8, Ctrl: 4, Meta: 2, Shift: 1 - sum of each value
 *                                                        // In case of using any key, specify it as an array
 *                        'onDropWith'     => null        // Disabled by default. To enable it if it is dropped with pressing the meta key
 *                                                        // Alt: 8, Ctrl: 4, Meta: 2, Shift: 1 - sum of each value
 *                                                        // In case of using any key, specify it as an array
 *                    )
 *                )
 *            )
 *        )
 *    );
 *
 * @package elfinder
 * @author  Naoki Sawada
 * @license New BSD
 */
class elFinderPluginWatermark extends elFinderPlugin
{

    private $watermarkImgInfo = null;

    public function __construct($opts)
    {
        $defaults = array(
            'enable' => true,       // For control by volume driver
            'source' => 'logo.png', // Path to Water mark image
            'ratio' => 0.2,        // Ratio to original image (ratio > 0 and ratio <= 1)
            'position' => 'RB',       // Position L(eft)/C(enter)/R(ight) and T(op)/M(edium)/B(ottom)
            'marginX' => 5,          // Margin horizontal pixel
            'marginY' => 5,          // Margin vertical pixel
            'quality' => 95,         // JPEG image save quality
            'transparency' => 70,         // Water mark image transparency ( other than PNG )
            'targetType' => IMG_GIF | IMG_JPG | IMG_PNG | IMG_WBMP, // Target image formats ( bit-field )
            'targetMinPixel' => 200,        // Target image minimum pixel size
            'interlace' => IMG_GIF | IMG_JPG, // Set interlacebit image formats ( bit-field )
            'offDropWith' => null,       // To disable it if it is dropped with pressing the meta key
            // Alt: 8, Ctrl: 4, Meta: 2, Shift: 1 - sum of each value
            // In case of using any key, specify it as an array
            'marginRight' => 0,          // Deprecated - marginX should be used
            'marginBottom' => 0,          // Deprecated - marginY should be used
            'disableWithContentSaveId' => true // Disable on URL upload with post data "contentSaveId"
        );

        $this->opts = array_merge($defaults, $opts);

    }

    public function onUpLoadPreSave(&$thash, &$name, $src, $elfinder, $volume)
    {
        if (!$src) {
            return false;
        }

        $opts = $this->getCurrentOpts($volume);

        if (!$this->iaEnabled($opts, $elfinder)) {
            return false;
        }

        $imageType = null;
        $srcImgInfo = null;
        if (extension_loaded('fileinfo') && function_exists('mime_content_type')) {
            $mime = mime_content_type($src);
            if (substr($mime, 0, 5) !== 'image') {
                return false;
            }
        }
        if (extension_loaded('exif') && function_exists('exif_imagetype')) {
            $imageType = exif_imagetype($src);
            if ($imageType === false) {
                return false;
            }
        } else {
            $srcImgInfo = getimagesize($src);
            if ($srcImgInfo === false) {
                return false;
            }
            $imageType = $srcImgInfo[2];
        }

        // check target image type
        $imgTypes = array(
            IMAGETYPE_GIF => IMG_GIF,
            IMAGETYPE_JPEG => IMG_JPEG,
            IMAGETYPE_PNG => IMG_PNG,
            IMAGETYPE_BMP => IMG_WBMP,
            IMAGETYPE_WBMP => IMG_WBMP
        );
        if (!isset($imgTypes[$imageType]) || !($opts['targetType'] & $imgTypes[$imageType])) {
            return false;
        }

        // check Animation Gif
        if ($imageType === IMAGETYPE_GIF && elFinder::isAnimationGif($src)) {
            return false;
        }
        // check Animation Png
        if ($imageType === IMAGETYPE_PNG && elFinder::isAnimationPng($src)) {
            return false;
        }
        // check water mark image
        if (!file_exists($opts['source'])) {
            $opts['source'] = dirname(__FILE__) . "/" . $opts['source'];
        }
        if (is_readable($opts['source'])) {
            $watermarkImgInfo = getimagesize($opts['source']);
            if (!$watermarkImgInfo) {
                return false;
            }
        } else {
            return false;
        }

        if (!$srcImgInfo) {
            $srcImgInfo = getimagesize($src);
        }

        $watermark = $opts['source'];
        $quality = $opts['quality'];
        $transparency = $opts['transparency'];

        // check target image size
        if ($opts['targetMinPixel'] > 0 && $opts['targetMinPixel'] > min($srcImgInfo[0], $srcImgInfo[1])) {
            return false;
        }

        $watermark_width = $watermarkImgInfo[0];
        $watermark_height = $watermarkImgInfo[1];

        // Specified as a ratio to the image size
        if ($opts['ratio'] && $opts['ratio'] > 0 && $opts['ratio'] <= 1) {
            $maxW = $srcImgInfo[0] * $opts['ratio'] - ($opts['marginX'] * 2);
            $maxH = $srcImgInfo[1] * $opts['ratio'] - ($opts['marginY'] * 2);
            $dx = $dy = 0;
            if (($maxW >= $watermarkImgInfo[0] && $maxH >= $watermarkImgInfo[0]) || ($maxW <= $watermarkImgInfo[0] && $maxH <= $watermarkImgInfo[0])) {
                $dx = abs($srcImgInfo[0] - $watermarkImgInfo[0]);
                $dy = abs($srcImgInfo[1] - $watermarkImgInfo[1]);
            } else if ($maxW < $watermarkImgInfo[0]) {
                $dx = -1;
            } else {
                $dy = -1;
            }
            if ($dx < $dy) {
                $ww = $maxW;
                $wh = $watermarkImgInfo[1] * ($ww / $watermarkImgInfo[0]);
            } else {
                $wh = $maxH;
                $ww = $watermarkImgInfo[0] * ($wh / $watermarkImgInfo[1]);
            }
            $watermarkImgInfo[0] = $ww;
            $watermarkImgInfo[1] = $wh;
        } else {
            $opts['ratio'] = null;
        }

        $opts['position'] = strtoupper($opts['position']);

        // Set vertical position
        if (strpos($opts['position'], 'T') !== false) {
            // Top
            $dest_x = $opts['marginX'];
        } else if (strpos($opts['position'], 'M') !== false) {
            // Middle
            $dest_x = ($srcImgInfo[0] - $watermarkImgInfo[0]) / 2;
        } else {
            // Bottom
            $dest_x = $srcImgInfo[0] - $watermarkImgInfo[0] - max($opts['marginBottom'], $opts['marginX']);
        }

        // Set horizontal position
        if (strpos($opts['position'], 'L') !== false) {
            // Left
            $dest_y = $opts['marginY'];
        } else if (strpos($opts['position'], 'C') !== false) {
            // Middle
            $dest_y = ($srcImgInfo[1] - $watermarkImgInfo[1]) / 2;
        } else {
            // Right
            $dest_y = $srcImgInfo[1] - $watermarkImgInfo[1] - max($opts['marginRight'], $opts['marginY']);
        }


        // check interlace
        $opts['interlace'] = ($opts['interlace'] & $imgTypes[$imageType]);

        // Repeated use of Imagick::compositeImage() may cause PHP to hang, so disable it
        //if (class_exists('Imagick', false)) {
        //    return $this->watermarkPrint_imagick($src, $watermark, $dest_x, $dest_y, $quality, $transparency, $watermarkImgInfo, $opts);
        //} else {
            elFinder::expandMemoryForGD(array($watermarkImgInfo, $srcImgInfo));
            return $this->watermarkPrint_gd($src, $watermark, $dest_x, $dest_y, $quality, $transparency, $watermarkImgInfo, $srcImgInfo, $opts);
        //}
    }

    private function watermarkPrint_imagick($src, $watermarkSrc, $dest_x, $dest_y, $quality, $transparency, $watermarkImgInfo, $opts)
    {

        try {

            // Open the original image
            $img = new Imagick($src);

            // Open the watermark
            $watermark = new Imagick($watermarkSrc);

            // zoom
            if ($opts['ratio']) {
                $watermark->scaleImage($watermarkImgInfo[0], $watermarkImgInfo[1]);
            }

            // Set transparency
            if (strtoupper($watermark->getImageFormat()) !== 'PNG') {
                $watermark->setImageOpacity($transparency / 100);
            }

            // Overlay the watermark on the original image
            $img->compositeImage($watermark, imagick::COMPOSITE_OVER, $dest_x, $dest_y);

            // Set quality
            if (strtoupper($img->getImageFormat()) === 'JPEG') {
                $img->setImageCompression(imagick::COMPRESSION_JPEG);
                $img->setCompressionQuality($quality);
            }

            // set interlace
            $opts['interlace'] && $img->setInterlaceScheme(Imagick::INTERLACE_PLANE);

            $result = $img->writeImage($src);

            $img->clear();
            $img->destroy();
            $watermark->clear();
            $watermark->destroy();

            return $result ? true : false;
        } catch (Exception $e) {
            $ermsg = $e->getMessage();
            $ermsg && trigger_error($ermsg);
            return false;
        }
    }

    private function watermarkPrint_gd($src, $watermark, $dest_x, $dest_y, $quality, $transparency, $watermarkImgInfo, $srcImgInfo, $opts)
    {

        $watermark_width = $watermarkImgInfo[0];
        $watermark_height = $watermarkImgInfo[1];

        $ermsg = '';
        switch ($watermarkImgInfo['mime']) {
            case 'image/gif':
                if (imagetypes() & IMG_GIF) {
                    $oWatermarkImg = imagecreatefromgif($watermark);
                } else {
                    $ermsg = 'GIF images are not supported as watermark image';
                }
                break;
            case 'image/jpeg':
                if (imagetypes() & IMG_JPG) {
                    $oWatermarkImg = imagecreatefromjpeg($watermark);
                } else {
                    $ermsg = 'JPEG images are not supported as watermark image';
                }
                break;
            case 'image/png':
                if (imagetypes() & IMG_PNG) {
                    $oWatermarkImg = imagecreatefrompng($watermark);
                } else {
                    $ermsg = 'PNG images are not supported as watermark image';
                }
                break;
            case 'image/wbmp':
                if (imagetypes() & IMG_WBMP) {
                    $oWatermarkImg = imagecreatefromwbmp($watermark);
                } else {
                    $ermsg = 'WBMP images are not supported as watermark image';
                }
                break;
            default:
                $oWatermarkImg = false;
                $ermsg = $watermarkImgInfo['mime'] . ' images are not supported as watermark image';
                break;
        }


        if (!$ermsg) {
            // zoom
            if ($opts['ratio']) {
                $tmpImg = imagecreatetruecolor($watermarkImgInfo[0], $watermarkImgInfo[1]);
                imagealphablending($tmpImg, false);
                imagesavealpha($tmpImg, true);
                imagecopyresampled($tmpImg, $oWatermarkImg, 0, 0, 0, 0, $watermarkImgInfo[0], $watermarkImgInfo[1], imagesx($oWatermarkImg), imagesy($oWatermarkImg));
                imageDestroy($oWatermarkImg);
                $oWatermarkImg = $tmpImg;
                $tmpImg = null;
            }

            switch ($srcImgInfo['mime']) {
                case 'image/gif':
                    if (imagetypes() & IMG_GIF) {
                        $oSrcImg = imagecreatefromgif($src);
                    } else {
                        $ermsg = 'GIF images are not supported as source image';
                    }
                    break;
                case 'image/jpeg':
                    if (imagetypes() & IMG_JPG) {
                        $oSrcImg = imagecreatefromjpeg($src);
                    } else {
                        $ermsg = 'JPEG images are not supported as source image';
                    }
                    break;
                case 'image/png':
                    if (imagetypes() & IMG_PNG) {
                        $oSrcImg = imagecreatefrompng($src);
                    } else {
                        $ermsg = 'PNG images are not supported as source image';
                    }
                    break;
                case 'image/wbmp':
                    if (imagetypes() & IMG_WBMP) {
                        $oSrcImg = imagecreatefromwbmp($src);
                    } else {
                        $ermsg = 'WBMP images are not supported as source image';
                    }
                    break;
                default:
                    $oSrcImg = false;
                    $ermsg = $srcImgInfo['mime'] . ' images are not supported as source image';
                    break;
            }
        }

        if ($ermsg || false === $oSrcImg || false === $oWatermarkImg) {
            $ermsg && trigger_error($ermsg);
            return false;
        }

        if ($srcImgInfo['mime'] === 'image/png') {
            if (function_exists('imagecolorallocatealpha')) {
                $bg = imagecolorallocatealpha($oSrcImg, 255, 255, 255, 127);
                imagefill($oSrcImg, 0, 0, $bg);
            }
        }

        if ($watermarkImgInfo['mime'] === 'image/png') {
            imagecopy($oSrcImg, $oWatermarkImg, $dest_x, $dest_y, 0, 0, $watermark_width, $watermark_height);
        } else {
            imagecopymerge($oSrcImg, $oWatermarkImg, $dest_x, $dest_y, 0, 0, $watermark_width, $watermark_height, $transparency);
        }

        // set interlace
        $opts['interlace'] && imageinterlace($oSrcImg, true);

        switch ($srcImgInfo['mime']) {
            case 'image/gif':
                imagegif($oSrcImg, $src);
                break;
            case 'image/jpeg':
                imagejpeg($oSrcImg, $src, $quality);
                break;
            case 'image/png':
                if (function_exists('imagesavealpha') && function_exists('imagealphablending')) {
                    imagealphablending($oSrcImg, false);
                    imagesavealpha($oSrcImg, true);
                }
                imagepng($oSrcImg, $src);
                break;
            case 'image/wbmp':
                imagewbmp($oSrcImg, $src);
                break;
        }

        imageDestroy($oSrcImg);
        imageDestroy($oWatermarkImg);

        return true;
    }
}
