Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Document;
   4  
   5  use DateTime;
   6  use PhpOffice\PhpSpreadsheet\Shared\IntOrFloat;
   7  
   8  class Properties
   9  {
  10      /** constants */
  11      public const PROPERTY_TYPE_BOOLEAN = 'b';
  12      public const PROPERTY_TYPE_INTEGER = 'i';
  13      public const PROPERTY_TYPE_FLOAT = 'f';
  14      public const PROPERTY_TYPE_DATE = 'd';
  15      public const PROPERTY_TYPE_STRING = 's';
  16      public const PROPERTY_TYPE_UNKNOWN = 'u';
  17  
  18      private const VALID_PROPERTY_TYPE_LIST = [
  19          self::PROPERTY_TYPE_BOOLEAN,
  20          self::PROPERTY_TYPE_INTEGER,
  21          self::PROPERTY_TYPE_FLOAT,
  22          self::PROPERTY_TYPE_DATE,
  23          self::PROPERTY_TYPE_STRING,
  24      ];
  25  
  26      /**
  27       * Creator.
  28       *
  29       * @var string
  30       */
  31      private $creator = 'Unknown Creator';
  32  
  33      /**
  34       * LastModifiedBy.
  35       *
  36       * @var string
  37       */
  38      private $lastModifiedBy;
  39  
  40      /**
  41       * Created.
  42       *
  43       * @var float|int
  44       */
  45      private $created;
  46  
  47      /**
  48       * Modified.
  49       *
  50       * @var float|int
  51       */
  52      private $modified;
  53  
  54      /**
  55       * Title.
  56       *
  57       * @var string
  58       */
  59      private $title = 'Untitled Spreadsheet';
  60  
  61      /**
  62       * Description.
  63       *
  64       * @var string
  65       */
  66      private $description = '';
  67  
  68      /**
  69       * Subject.
  70       *
  71       * @var string
  72       */
  73      private $subject = '';
  74  
  75      /**
  76       * Keywords.
  77       *
  78       * @var string
  79       */
  80      private $keywords = '';
  81  
  82      /**
  83       * Category.
  84       *
  85       * @var string
  86       */
  87      private $category = '';
  88  
  89      /**
  90       * Manager.
  91       *
  92       * @var string
  93       */
  94      private $manager = '';
  95  
  96      /**
  97       * Company.
  98       *
  99       * @var string
 100       */
 101      private $company = '';
 102  
 103      /**
 104       * Custom Properties.
 105       *
 106       * @var array{value: mixed, type: string}[]
 107       */
 108      private $customProperties = [];
 109  
 110      /**
 111       * Create a new Document Properties instance.
 112       */
 113      public function __construct()
 114      {
 115          // Initialise values
 116          $this->lastModifiedBy = $this->creator;
 117          $this->created = self::intOrFloatTimestamp(null);
 118          $this->modified = $this->created;
 119      }
 120  
 121      /**
 122       * Get Creator.
 123       */
 124      public function getCreator(): string
 125      {
 126          return $this->creator;
 127      }
 128  
 129      /**
 130       * Set Creator.
 131       *
 132       * @return $this
 133       */
 134      public function setCreator(string $creator): self
 135      {
 136          $this->creator = $creator;
 137  
 138          return $this;
 139      }
 140  
 141      /**
 142       * Get Last Modified By.
 143       */
 144      public function getLastModifiedBy(): string
 145      {
 146          return $this->lastModifiedBy;
 147      }
 148  
 149      /**
 150       * Set Last Modified By.
 151       *
 152       * @return $this
 153       */
 154      public function setLastModifiedBy(string $modifiedBy): self
 155      {
 156          $this->lastModifiedBy = $modifiedBy;
 157  
 158          return $this;
 159      }
 160  
 161      /**
 162       * @param null|float|int|string $timestamp
 163       *
 164       * @return float|int
 165       */
 166      private static function intOrFloatTimestamp($timestamp)
 167      {
 168          if ($timestamp === null) {
 169              $timestamp = (float) (new DateTime())->format('U');
 170          } elseif (is_string($timestamp)) {
 171              if (is_numeric($timestamp)) {
 172                  $timestamp = (float) $timestamp;
 173              } else {
 174                  $timestamp = (string) preg_replace('/[.][0-9]*$/', '', $timestamp);
 175                  $timestamp = (string) preg_replace('/^(\\d{4})- (\\d)/', '$1-0$2', $timestamp);
 176                  $timestamp = (string) preg_replace('/^(\\d{4}-\\d{2})- (\\d)/', '$1-0$2', $timestamp);
 177                  $timestamp = (float) (new DateTime($timestamp))->format('U');
 178              }
 179          }
 180  
 181          return IntOrFloat::evaluate($timestamp);
 182      }
 183  
 184      /**
 185       * Get Created.
 186       *
 187       * @return float|int
 188       */
 189      public function getCreated()
 190      {
 191          return $this->created;
 192      }
 193  
 194      /**
 195       * Set Created.
 196       *
 197       * @param null|float|int|string $timestamp
 198       *
 199       * @return $this
 200       */
 201      public function setCreated($timestamp): self
 202      {
 203          $this->created = self::intOrFloatTimestamp($timestamp);
 204  
 205          return $this;
 206      }
 207  
 208      /**
 209       * Get Modified.
 210       *
 211       * @return float|int
 212       */
 213      public function getModified()
 214      {
 215          return $this->modified;
 216      }
 217  
 218      /**
 219       * Set Modified.
 220       *
 221       * @param null|float|int|string $timestamp
 222       *
 223       * @return $this
 224       */
 225      public function setModified($timestamp): self
 226      {
 227          $this->modified = self::intOrFloatTimestamp($timestamp);
 228  
 229          return $this;
 230      }
 231  
 232      /**
 233       * Get Title.
 234       */
 235      public function getTitle(): string
 236      {
 237          return $this->title;
 238      }
 239  
 240      /**
 241       * Set Title.
 242       *
 243       * @return $this
 244       */
 245      public function setTitle(string $title): self
 246      {
 247          $this->title = $title;
 248  
 249          return $this;
 250      }
 251  
 252      /**
 253       * Get Description.
 254       */
 255      public function getDescription(): string
 256      {
 257          return $this->description;
 258      }
 259  
 260      /**
 261       * Set Description.
 262       *
 263       * @return $this
 264       */
 265      public function setDescription(string $description): self
 266      {
 267          $this->description = $description;
 268  
 269          return $this;
 270      }
 271  
 272      /**
 273       * Get Subject.
 274       */
 275      public function getSubject(): string
 276      {
 277          return $this->subject;
 278      }
 279  
 280      /**
 281       * Set Subject.
 282       *
 283       * @return $this
 284       */
 285      public function setSubject(string $subject): self
 286      {
 287          $this->subject = $subject;
 288  
 289          return $this;
 290      }
 291  
 292      /**
 293       * Get Keywords.
 294       */
 295      public function getKeywords(): string
 296      {
 297          return $this->keywords;
 298      }
 299  
 300      /**
 301       * Set Keywords.
 302       *
 303       * @return $this
 304       */
 305      public function setKeywords(string $keywords): self
 306      {
 307          $this->keywords = $keywords;
 308  
 309          return $this;
 310      }
 311  
 312      /**
 313       * Get Category.
 314       */
 315      public function getCategory(): string
 316      {
 317          return $this->category;
 318      }
 319  
 320      /**
 321       * Set Category.
 322       *
 323       * @return $this
 324       */
 325      public function setCategory(string $category): self
 326      {
 327          $this->category = $category;
 328  
 329          return $this;
 330      }
 331  
 332      /**
 333       * Get Company.
 334       */
 335      public function getCompany(): string
 336      {
 337          return $this->company;
 338      }
 339  
 340      /**
 341       * Set Company.
 342       *
 343       * @return $this
 344       */
 345      public function setCompany(string $company): self
 346      {
 347          $this->company = $company;
 348  
 349          return $this;
 350      }
 351  
 352      /**
 353       * Get Manager.
 354       */
 355      public function getManager(): string
 356      {
 357          return $this->manager;
 358      }
 359  
 360      /**
 361       * Set Manager.
 362       *
 363       * @return $this
 364       */
 365      public function setManager(string $manager): self
 366      {
 367          $this->manager = $manager;
 368  
 369          return $this;
 370      }
 371  
 372      /**
 373       * Get a List of Custom Property Names.
 374       *
 375       * @return string[]
 376       */
 377      public function getCustomProperties(): array
 378      {
 379          return array_keys($this->customProperties);
 380      }
 381  
 382      /**
 383       * Check if a Custom Property is defined.
 384       */
 385      public function isCustomPropertySet(string $propertyName): bool
 386      {
 387          return array_key_exists($propertyName, $this->customProperties);
 388      }
 389  
 390      /**
 391       * Get a Custom Property Value.
 392       *
 393       * @return mixed
 394       */
 395      public function getCustomPropertyValue(string $propertyName)
 396      {
 397          if (isset($this->customProperties[$propertyName])) {
 398              return $this->customProperties[$propertyName]['value'];
 399          }
 400  
 401          return null;
 402      }
 403  
 404      /**
 405       * Get a Custom Property Type.
 406       *
 407       * @return null|string
 408       */
 409      public function getCustomPropertyType(string $propertyName)
 410      {
 411          return $this->customProperties[$propertyName]['type'] ?? null;
 412      }
 413  
 414      /**
 415       * @param mixed $propertyValue
 416       */
 417      private function identifyPropertyType($propertyValue): string
 418      {
 419          if (is_float($propertyValue)) {
 420              return self::PROPERTY_TYPE_FLOAT;
 421          }
 422          if (is_int($propertyValue)) {
 423              return self::PROPERTY_TYPE_INTEGER;
 424          }
 425          if (is_bool($propertyValue)) {
 426              return self::PROPERTY_TYPE_BOOLEAN;
 427          }
 428  
 429          return self::PROPERTY_TYPE_STRING;
 430      }
 431  
 432      /**
 433       * Set a Custom Property.
 434       *
 435       * @param mixed $propertyValue
 436       * @param string $propertyType
 437       *      'i'    : Integer
 438       *   'f' : Floating Point
 439       *   's' : String
 440       *   'd' : Date/Time
 441       *   'b' : Boolean
 442       *
 443       * @return $this
 444       */
 445      public function setCustomProperty(string $propertyName, $propertyValue = '', $propertyType = null): self
 446      {
 447          if (($propertyType === null) || (!in_array($propertyType, self::VALID_PROPERTY_TYPE_LIST))) {
 448              $propertyType = $this->identifyPropertyType($propertyValue);
 449          }
 450  
 451          if (!is_object($propertyValue)) {
 452              $this->customProperties[$propertyName] = [
 453                  'value' => self::convertProperty($propertyValue, $propertyType),
 454                  'type' => $propertyType,
 455              ];
 456          }
 457  
 458          return $this;
 459      }
 460  
 461      private const PROPERTY_TYPE_ARRAY = [
 462          'i' => self::PROPERTY_TYPE_INTEGER,      //    Integer
 463          'i1' => self::PROPERTY_TYPE_INTEGER,     //    1-Byte Signed Integer
 464          'i2' => self::PROPERTY_TYPE_INTEGER,     //    2-Byte Signed Integer
 465          'i4' => self::PROPERTY_TYPE_INTEGER,     //    4-Byte Signed Integer
 466          'i8' => self::PROPERTY_TYPE_INTEGER,     //    8-Byte Signed Integer
 467          'int' => self::PROPERTY_TYPE_INTEGER,    //    Integer
 468          'ui1' => self::PROPERTY_TYPE_INTEGER,    //    1-Byte Unsigned Integer
 469          'ui2' => self::PROPERTY_TYPE_INTEGER,    //    2-Byte Unsigned Integer
 470          'ui4' => self::PROPERTY_TYPE_INTEGER,    //    4-Byte Unsigned Integer
 471          'ui8' => self::PROPERTY_TYPE_INTEGER,    //    8-Byte Unsigned Integer
 472          'uint' => self::PROPERTY_TYPE_INTEGER,   //    Unsigned Integer
 473          'f' => self::PROPERTY_TYPE_FLOAT,        //    Real Number
 474          'r4' => self::PROPERTY_TYPE_FLOAT,       //    4-Byte Real Number
 475          'r8' => self::PROPERTY_TYPE_FLOAT,       //    8-Byte Real Number
 476          'decimal' => self::PROPERTY_TYPE_FLOAT,  //    Decimal
 477          's' => self::PROPERTY_TYPE_STRING,       //    String
 478          'empty' => self::PROPERTY_TYPE_STRING,   //    Empty
 479          'null' => self::PROPERTY_TYPE_STRING,    //    Null
 480          'lpstr' => self::PROPERTY_TYPE_STRING,   //    LPSTR
 481          'lpwstr' => self::PROPERTY_TYPE_STRING,  //    LPWSTR
 482          'bstr' => self::PROPERTY_TYPE_STRING,    //    Basic String
 483          'd' => self::PROPERTY_TYPE_DATE,         //    Date and Time
 484          'date' => self::PROPERTY_TYPE_DATE,      //    Date and Time
 485          'filetime' => self::PROPERTY_TYPE_DATE,  //    File Time
 486          'b' => self::PROPERTY_TYPE_BOOLEAN,      //    Boolean
 487          'bool' => self::PROPERTY_TYPE_BOOLEAN,   //    Boolean
 488      ];
 489  
 490      private const SPECIAL_TYPES = [
 491          'empty' => '',
 492          'null' => null,
 493      ];
 494  
 495      /**
 496       * Convert property to form desired by Excel.
 497       *
 498       * @param mixed $propertyValue
 499       *
 500       * @return mixed
 501       */
 502      public static function convertProperty($propertyValue, string $propertyType)
 503      {
 504          return self::SPECIAL_TYPES[$propertyType] ?? self::convertProperty2($propertyValue, $propertyType);
 505      }
 506  
 507      /**
 508       * Convert property to form desired by Excel.
 509       *
 510       * @param mixed $propertyValue
 511       *
 512       * @return mixed
 513       */
 514      private static function convertProperty2($propertyValue, string $type)
 515      {
 516          $propertyType = self::convertPropertyType($type);
 517          switch ($propertyType) {
 518              case self::PROPERTY_TYPE_INTEGER:
 519                  $intValue = (int) $propertyValue;
 520  
 521                  return ($type[0] === 'u') ? abs($intValue) : $intValue;
 522              case self::PROPERTY_TYPE_FLOAT:
 523                  return (float) $propertyValue;
 524              case self::PROPERTY_TYPE_DATE:
 525                  return self::intOrFloatTimestamp($propertyValue);
 526              case self::PROPERTY_TYPE_BOOLEAN:
 527                  return is_bool($propertyValue) ? $propertyValue : ($propertyValue === 'true');
 528              default: // includes string
 529                  return $propertyValue;
 530          }
 531      }
 532  
 533      public static function convertPropertyType(string $propertyType): string
 534      {
 535          return self::PROPERTY_TYPE_ARRAY[$propertyType] ?? self::PROPERTY_TYPE_UNKNOWN;
 536      }
 537  }