Properties.php 12 KB

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