Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Worksheet;
   4  
   5  use GdImage;
   6  use PhpOffice\PhpSpreadsheet\Exception;
   7  use PhpOffice\PhpSpreadsheet\Shared\File;
   8  
   9  class MemoryDrawing extends BaseDrawing
  10  {
  11      // Rendering functions
  12      const RENDERING_DEFAULT = 'imagepng';
  13      const RENDERING_PNG = 'imagepng';
  14      const RENDERING_GIF = 'imagegif';
  15      const RENDERING_JPEG = 'imagejpeg';
  16  
  17      // MIME types
  18      const MIMETYPE_DEFAULT = 'image/png';
  19      const MIMETYPE_PNG = 'image/png';
  20      const MIMETYPE_GIF = 'image/gif';
  21      const MIMETYPE_JPEG = 'image/jpeg';
  22  
  23      const SUPPORTED_MIME_TYPES = [
  24          self::MIMETYPE_GIF,
  25          self::MIMETYPE_JPEG,
  26          self::MIMETYPE_PNG,
  27      ];
  28  
  29      /**
  30       * Image resource.
  31       *
  32       * @var null|GdImage|resource
  33       */
  34      private $imageResource;
  35  
  36      /**
  37       * Rendering function.
  38       *
  39       * @var string
  40       */
  41      private $renderingFunction;
  42  
  43      /**
  44       * Mime type.
  45       *
  46       * @var string
  47       */
  48      private $mimeType;
  49  
  50      /**
  51       * Unique name.
  52       *
  53       * @var string
  54       */
  55      private $uniqueName;
  56  
  57      /** @var null|resource */
  58      private $alwaysNull;
  59  
  60      /**
  61       * Create a new MemoryDrawing.
  62       */
  63      public function __construct()
  64      {
  65          // Initialise values
  66          $this->renderingFunction = self::RENDERING_DEFAULT;
  67          $this->mimeType = self::MIMETYPE_DEFAULT;
  68          $this->uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999));
  69          $this->alwaysNull = null;
  70  
  71          // Initialize parent
  72          parent::__construct();
  73      }
  74  
  75      public function __destruct()
  76      {
  77          if ($this->imageResource) {
  78              $rslt = @imagedestroy($this->imageResource);
  79              // "Fix" for Scrutinizer
  80              $this->imageResource = $rslt ? null : $this->alwaysNull;
  81          }
  82      }
  83  
  84      public function __clone()
  85      {
  86          parent::__clone();
  87          $this->cloneResource();
  88      }
  89  
  90      private function cloneResource(): void
  91      {
  92          if (!$this->imageResource) {
  93              return;
  94          }
  95  
  96          $width = (int) imagesx($this->imageResource);
  97          $height = (int) imagesy($this->imageResource);
  98  
  99          if (imageistruecolor($this->imageResource)) {
 100              $clone = imagecreatetruecolor($width, $height);
 101              if (!$clone) {
 102                  throw new Exception('Could not clone image resource');
 103              }
 104  
 105              imagealphablending($clone, false);
 106              imagesavealpha($clone, true);
 107          } else {
 108              $clone = imagecreate($width, $height);
 109              if (!$clone) {
 110                  throw new Exception('Could not clone image resource');
 111              }
 112  
 113              // If the image has transparency...
 114              $transparent = imagecolortransparent($this->imageResource);
 115              if ($transparent >= 0) {
 116                  $rgb = imagecolorsforindex($this->imageResource, $transparent);
 117                  if (empty($rgb)) {
 118                      throw new Exception('Could not get image colors');
 119                  }
 120  
 121                  imagesavealpha($clone, true);
 122                  $color = imagecolorallocatealpha($clone, $rgb['red'], $rgb['green'], $rgb['blue'], $rgb['alpha']);
 123                  if ($color === false) {
 124                      throw new Exception('Could not get image alpha color');
 125                  }
 126  
 127                  imagefill($clone, 0, 0, $color);
 128              }
 129          }
 130  
 131          //Create the Clone!!
 132          imagecopy($clone, $this->imageResource, 0, 0, 0, 0, $width, $height);
 133  
 134          $this->imageResource = $clone;
 135      }
 136  
 137      /**
 138       * @param resource $imageStream Stream data to be converted to a Memory Drawing
 139       *
 140       * @throws Exception
 141       */
 142      public static function fromStream($imageStream): self
 143      {
 144          $streamValue = stream_get_contents($imageStream);
 145          if ($streamValue === false) {
 146              throw new Exception('Unable to read data from stream');
 147          }
 148  
 149          return self::fromString($streamValue);
 150      }
 151  
 152      /**
 153       * @param string $imageString String data to be converted to a Memory Drawing
 154       *
 155       * @throws Exception
 156       */
 157      public static function fromString(string $imageString): self
 158      {
 159          $gdImage = @imagecreatefromstring($imageString);
 160          if ($gdImage === false) {
 161              throw new Exception('Value cannot be converted to an image');
 162          }
 163  
 164          $mimeType = self::identifyMimeType($imageString);
 165          $renderingFunction = self::identifyRenderingFunction($mimeType);
 166  
 167          $drawing = new self();
 168          $drawing->setImageResource($gdImage);
 169          $drawing->setRenderingFunction($renderingFunction);
 170          $drawing->setMimeType($mimeType);
 171  
 172          return $drawing;
 173      }
 174  
 175      private static function identifyRenderingFunction(string $mimeType): string
 176      {
 177          switch ($mimeType) {
 178              case self::MIMETYPE_PNG:
 179                  return self::RENDERING_PNG;
 180              case self::MIMETYPE_JPEG:
 181                  return self::RENDERING_JPEG;
 182              case self::MIMETYPE_GIF:
 183                  return self::RENDERING_GIF;
 184          }
 185  
 186          return self::RENDERING_DEFAULT;
 187      }
 188  
 189      /**
 190       * @throws Exception
 191       */
 192      private static function identifyMimeType(string $imageString): string
 193      {
 194          $temporaryFileName = File::temporaryFilename();
 195          file_put_contents($temporaryFileName, $imageString);
 196  
 197          $mimeType = self::identifyMimeTypeUsingExif($temporaryFileName);
 198          if ($mimeType !== null) {
 199              unlink($temporaryFileName);
 200  
 201              return $mimeType;
 202          }
 203  
 204          $mimeType = self::identifyMimeTypeUsingGd($temporaryFileName);
 205          if ($mimeType !== null) {
 206              unlink($temporaryFileName);
 207  
 208              return $mimeType;
 209          }
 210  
 211          unlink($temporaryFileName);
 212  
 213          return self::MIMETYPE_DEFAULT;
 214      }
 215  
 216      private static function identifyMimeTypeUsingExif(string $temporaryFileName): ?string
 217      {
 218          if (function_exists('exif_imagetype')) {
 219              $imageType = @exif_imagetype($temporaryFileName);
 220              $mimeType = ($imageType) ? image_type_to_mime_type($imageType) : null;
 221  
 222              return self::supportedMimeTypes($mimeType);
 223          }
 224  
 225          return null;
 226      }
 227  
 228      private static function identifyMimeTypeUsingGd(string $temporaryFileName): ?string
 229      {
 230          if (function_exists('getimagesize')) {
 231              $imageSize = @getimagesize($temporaryFileName);
 232              if (is_array($imageSize)) {
 233                  $mimeType = $imageSize['mime'] ?? null;
 234  
 235                  return self::supportedMimeTypes($mimeType);
 236              }
 237          }
 238  
 239          return null;
 240      }
 241  
 242      private static function supportedMimeTypes(?string $mimeType = null): ?string
 243      {
 244          if (in_array($mimeType, self::SUPPORTED_MIME_TYPES, true)) {
 245              return $mimeType;
 246          }
 247  
 248          return null;
 249      }
 250  
 251      /**
 252       * Get image resource.
 253       *
 254       * @return null|GdImage|resource
 255       */
 256      public function getImageResource()
 257      {
 258          return $this->imageResource;
 259      }
 260  
 261      /**
 262       * Set image resource.
 263       *
 264       * @param GdImage|resource $value
 265       *
 266       * @return $this
 267       */
 268      public function setImageResource($value)
 269      {
 270          $this->imageResource = $value;
 271  
 272          if ($this->imageResource !== null) {
 273              // Get width/height
 274              $this->width = (int) imagesx($this->imageResource);
 275              $this->height = (int) imagesy($this->imageResource);
 276          }
 277  
 278          return $this;
 279      }
 280  
 281      /**
 282       * Get rendering function.
 283       *
 284       * @return string
 285       */
 286      public function getRenderingFunction()
 287      {
 288          return $this->renderingFunction;
 289      }
 290  
 291      /**
 292       * Set rendering function.
 293       *
 294       * @param string $value see self::RENDERING_*
 295       *
 296       * @return $this
 297       */
 298      public function setRenderingFunction($value)
 299      {
 300          $this->renderingFunction = $value;
 301  
 302          return $this;
 303      }
 304  
 305      /**
 306       * Get mime type.
 307       *
 308       * @return string
 309       */
 310      public function getMimeType()
 311      {
 312          return $this->mimeType;
 313      }
 314  
 315      /**
 316       * Set mime type.
 317       *
 318       * @param string $value see self::MIMETYPE_*
 319       *
 320       * @return $this
 321       */
 322      public function setMimeType($value)
 323      {
 324          $this->mimeType = $value;
 325  
 326          return $this;
 327      }
 328  
 329      /**
 330       * Get indexed filename (using image index).
 331       */
 332      public function getIndexedFilename(): string
 333      {
 334          $extension = strtolower($this->getMimeType());
 335          $extension = explode('/', $extension);
 336          $extension = $extension[1];
 337  
 338          return $this->uniqueName . $this->getImageIndex() . '.' . $extension;
 339      }
 340  
 341      /**
 342       * Get hash code.
 343       *
 344       * @return string Hash code
 345       */
 346      public function getHashCode()
 347      {
 348          return md5(
 349              $this->renderingFunction .
 350              $this->mimeType .
 351              $this->uniqueName .
 352              parent::getHashCode() .
 353              __CLASS__
 354          );
 355      }
 356  }