PluginProbe
wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin / 6.5.1.7
wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin v6.5.1.7
6.5.1.7 6.5.1.6 6.5.1.5 6.5.1.4 6.5.1.3 6.5.1.2 6.5.1.1 6.5.0.9 6.5.0.8 6.5.0.7 6.5.0.6 trunk 3.4.2.40 3.4.2.41 3.4.2.42 3.4.2.43 3.4.2.44 3.4.2.45 3.4.2.46 3.4.2.47 3.4.2.48 3.4.2.49 3.4.2.50 6.3.2 6.3.3.1 All 47 releases
wpdatatables / lib / phpoffice / phpspreadsheet / src / PhpSpreadsheet / Collection / Cells.php

Cells.php in wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin 6.5.1.7, at lib/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php

479 lines 13.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace PhpOffice\PhpSpreadsheet\Collection;
4
5 use Generator;
6 use PhpOffice\PhpSpreadsheet\Cell\Cell;
7 use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
8 use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
9 use PhpOffice\PhpSpreadsheet\Settings;
10 use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
11 use Psr\SimpleCache\CacheInterface;
12
13 class Cells
14 {
15 protected const MAX_COLUMN_ID = 16384;
16
17 /**
18 * @var CacheInterface
19 */
20 private $cache;
21
22 /**
23 * Parent worksheet.
24 *
25 * @var null|Worksheet
26 */
27 private $parent;
28
29 /**
30 * The currently active Cell.
31 *
32 * @var null|Cell
33 */
34 private $currentCell;
35
36 /**
37 * Coordinate of the currently active Cell.
38 *
39 * @var null|string
40 */
41 private $currentCoordinate;
42
43 /**
44 * Flag indicating whether the currently active Cell requires saving.
45 *
46 * @var bool
47 */
48 private $currentCellIsDirty = false;
49
50 /**
51 * An index of existing cells. int pointer to the coordinate (0-base-indexed row * 16,384 + 1-base indexed column)
52 * indexed by their coordinate.
53 *
54 * @var int[]
55 */
56 private $index = [];
57
58 /**
59 * Prefix used to uniquely identify cache data for this worksheet.
60 *
61 * @var string
62 */
63 private $cachePrefix;
64
65 /**
66 * Initialise this new cell collection.
67 *
68 * @param Worksheet $parent The worksheet for this cell collection
69 */
70 public function __construct(Worksheet $parent, CacheInterface $cache)
71 {
72 // Set our parent worksheet.
73 // This is maintained here to facilitate re-attaching it to Cell objects when
74 // they are woken from a serialized state
75 $this->parent = $parent;
76 $this->cache = $cache;
77 $this->cachePrefix = $this->getUniqueID();
78 }
79
80 /**
81 * Return the parent worksheet for this cell collection.
82 *
83 * @return null|Worksheet
84 */
85 public function getParent()
86 {
87 return $this->parent;
88 }
89
90 /**
91 * Whether the collection holds a cell for the given coordinate.
92 *
93 * @param string $cellCoordinate Coordinate of the cell to check
94 */
95 public function has($cellCoordinate): bool
96 {
97 return ($cellCoordinate === $this->currentCoordinate) || isset($this->index[$cellCoordinate]);
98 }
99
100 /**
101 * Add or update a cell in the collection.
102 *
103 * @param Cell $cell Cell to update
104 */
105 public function update(Cell $cell): Cell
106 {
107 return $this->add($cell->getCoordinate(), $cell);
108 }
109
110 /**
111 * Delete a cell in cache identified by coordinate.
112 *
113 * @param string $cellCoordinate Coordinate of the cell to delete
114 */
115 public function delete($cellCoordinate): void
116 {
117 if ($cellCoordinate === $this->currentCoordinate && $this->currentCell !== null) {
118 $this->currentCell->detach();
119 $this->currentCoordinate = null;
120 $this->currentCell = null;
121 $this->currentCellIsDirty = false;
122 }
123
124 unset($this->index[$cellCoordinate]);
125
126 // Delete the entry from cache
127 $this->cache->delete($this->cachePrefix . $cellCoordinate);
128 }
129
130 /**
131 * Get a list of all cell coordinates currently held in the collection.
132 *
133 * @return string[]
134 */
135 public function getCoordinates()
136 {
137 return array_keys($this->index);
138 }
139
140 /**
141 * Get a sorted list of all cell coordinates currently held in the collection by row and column.
142 *
143 * @return string[]
144 */
145 public function getSortedCoordinates()
146 {
147 asort($this->index);
148
149 return array_keys($this->index);
150 }
151
152 /**
153 * Return the cell coordinate of the currently active cell object.
154 *
155 * @return null|string
156 */
157 public function getCurrentCoordinate()
158 {
159 return $this->currentCoordinate;
160 }
161
162 /**
163 * Return the column coordinate of the currently active cell object.
164 */
165 public function getCurrentColumn(): string
166 {
167 $column = 0;
168 $row = '';
169 sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row);
170
171 return (string) $column;
172 }
173
174 /**
175 * Return the row coordinate of the currently active cell object.
176 */
177 public function getCurrentRow(): int
178 {
179 $column = 0;
180 $row = '';
181 sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row);
182
183 return (int) $row;
184 }
185
186 /**
187 * Get highest worksheet column and highest row that have cell records.
188 *
189 * @return array Highest column name and highest row number
190 */
191 public function getHighestRowAndColumn()
192 {
193 // Lookup highest column and highest row
194 $maxRow = $maxColumn = 1;
195 foreach ($this->index as $coordinate) {
196 $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1;
197 $maxRow = ($maxRow > $row) ? $maxRow : $row;
198 $column = $coordinate % self::MAX_COLUMN_ID;
199 $maxColumn = ($maxColumn > $column) ? $maxColumn : $column;
200 }
201
202 return [
203 'row' => $maxRow,
204 'column' => Coordinate::stringFromColumnIndex($maxColumn),
205 ];
206 }
207
208 /**
209 * Get highest worksheet column.
210 *
211 * @param null|int|string $row Return the highest column for the specified row,
212 * or the highest column of any row if no row number is passed
213 *
214 * @return string Highest column name
215 */
216 public function getHighestColumn($row = null)
217 {
218 if ($row === null) {
219 return $this->getHighestRowAndColumn()['column'];
220 }
221
222 $row = (int) $row;
223 if ($row <= 0) {
224 throw new PhpSpreadsheetException('Row number must be a positive integer');
225 }
226
227 $maxColumn = 1;
228 $toRow = $row * self::MAX_COLUMN_ID;
229 $fromRow = --$row * self::MAX_COLUMN_ID;
230 foreach ($this->index as $coordinate) {
231 if ($coordinate < $fromRow || $coordinate >= $toRow) {
232 continue;
233 }
234 $column = $coordinate % self::MAX_COLUMN_ID;
235 $maxColumn = $maxColumn > $column ? $maxColumn : $column;
236 }
237
238 return Coordinate::stringFromColumnIndex($maxColumn);
239 }
240
241 /**
242 * Get highest worksheet row.
243 *
244 * @param null|string $column Return the highest row for the specified column,
245 * or the highest row of any column if no column letter is passed
246 *
247 * @return int Highest row number
248 */
249 public function getHighestRow($column = null)
250 {
251 if ($column === null) {
252 return $this->getHighestRowAndColumn()['row'];
253 }
254
255 $maxRow = 1;
256 $columnIndex = Coordinate::columnIndexFromString($column);
257 foreach ($this->index as $coordinate) {
258 if ($coordinate % self::MAX_COLUMN_ID !== $columnIndex) {
259 continue;
260 }
261 $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1;
262 $maxRow = ($maxRow > $row) ? $maxRow : $row;
263 }
264
265 return $maxRow;
266 }
267
268 /**
269 * Generate a unique ID for cache referencing.
270 *
271 * @return string Unique Reference
272 */
273 private function getUniqueID()
274 {
275 $cacheType = Settings::getCache();
276
277 return ($cacheType instanceof Memory\SimpleCache1 || $cacheType instanceof Memory\SimpleCache3)
278 ? random_bytes(7) . ':'
279 : uniqid('phpspreadsheet.', true) . '.';
280 }
281
282 /**
283 * Clone the cell collection.
284 *
285 * @return self
286 */
287 public function cloneCellCollection(Worksheet $worksheet)
288 {
289 $this->storeCurrentCell();
290 $newCollection = clone $this;
291
292 $newCollection->parent = $worksheet;
293 $newCollection->cachePrefix = $newCollection->getUniqueID();
294
295 foreach ($this->index as $key => $value) {
296 $newCollection->index[$key] = $value;
297 $stored = $newCollection->cache->set(
298 $newCollection->cachePrefix . $key,
299 clone $this->cache->get($this->cachePrefix . $key)
300 );
301 if ($stored === false) {
302 $this->destructIfNeeded($newCollection, 'Failed to copy cells in cache');
303 }
304 }
305
306 return $newCollection;
307 }
308
309 /**
310 * Remove a row, deleting all cells in that row.
311 *
312 * @param int|string $row Row number to remove
313 */
314 public function removeRow($row): void
315 {
316 $this->storeCurrentCell();
317 $row = (int) $row;
318 if ($row <= 0) {
319 throw new PhpSpreadsheetException('Row number must be a positive integer');
320 }
321
322 $toRow = $row * self::MAX_COLUMN_ID;
323 $fromRow = --$row * self::MAX_COLUMN_ID;
324 foreach ($this->index as $coordinate) {
325 if ($coordinate >= $fromRow && $coordinate < $toRow) {
326 $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1;
327 $column = Coordinate::stringFromColumnIndex($coordinate % self::MAX_COLUMN_ID);
328 $this->delete("{$column}{$row}");
329 }
330 }
331 }
332
333 /**
334 * Remove a column, deleting all cells in that column.
335 *
336 * @param string $column Column ID to remove
337 */
338 public function removeColumn($column): void
339 {
340 $this->storeCurrentCell();
341
342 $columnIndex = Coordinate::columnIndexFromString($column);
343 foreach ($this->index as $coordinate) {
344 if ($coordinate % self::MAX_COLUMN_ID === $columnIndex) {
345 $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1;
346 $column = Coordinate::stringFromColumnIndex($coordinate % self::MAX_COLUMN_ID);
347 $this->delete("{$column}{$row}");
348 }
349 }
350 }
351
352 /**
353 * Store cell data in cache for the current cell object if it's "dirty",
354 * and the 'nullify' the current cell object.
355 */
356 private function storeCurrentCell(): void
357 {
358 if ($this->currentCellIsDirty && isset($this->currentCoordinate, $this->currentCell)) {
359 $this->currentCell->/** @scrutinizer ignore-call */ detach();
360
361 $stored = $this->cache->set($this->cachePrefix . $this->currentCoordinate, $this->currentCell);
362 if ($stored === false) {
363 $this->destructIfNeeded($this, "Failed to store cell {$this->currentCoordinate} in cache");
364 }
365 $this->currentCellIsDirty = false;
366 }
367
368 $this->currentCoordinate = null;
369 $this->currentCell = null;
370 }
371
372 private function destructIfNeeded(self $cells, string $message): void
373 {
374 $cells->__destruct();
375
376 throw new PhpSpreadsheetException($message);
377 }
378
379 /**
380 * Add or update a cell identified by its coordinate into the collection.
381 *
382 * @param string $cellCoordinate Coordinate of the cell to update
383 * @param Cell $cell Cell to update
384 *
385 * @return Cell
386 */
387 public function add($cellCoordinate, Cell $cell)
388 {
389 if ($cellCoordinate !== $this->currentCoordinate) {
390 $this->storeCurrentCell();
391 }
392 $column = 0;
393 $row = '';
394 sscanf($cellCoordinate, '%[A-Z]%d', $column, $row);
395 $this->index[$cellCoordinate] = (--$row * self::MAX_COLUMN_ID) + Coordinate::columnIndexFromString((string) $column);
396
397 $this->currentCoordinate = $cellCoordinate;
398 $this->currentCell = $cell;
399 $this->currentCellIsDirty = true;
400
401 return $cell;
402 }
403
404 /**
405 * Get cell at a specific coordinate.
406 *
407 * @param string $cellCoordinate Coordinate of the cell
408 *
409 * @return null|Cell Cell that was found, or null if not found
410 */
411 public function get($cellCoordinate)
412 {
413 if ($cellCoordinate === $this->currentCoordinate) {
414 return $this->currentCell;
415 }
416 $this->storeCurrentCell();
417
418 // Return null if requested entry doesn't exist in collection
419 if ($this->has($cellCoordinate) === false) {
420 return null;
421 }
422
423 // Check if the entry that has been requested actually exists in the cache
424 $cell = $this->cache->get($this->cachePrefix . $cellCoordinate);
425 if ($cell === null) {
426 throw new PhpSpreadsheetException("Cell entry {$cellCoordinate} no longer exists in cache. This probably means that the cache was cleared by someone else.");
427 }
428
429 // Set current entry to the requested entry
430 $this->currentCoordinate = $cellCoordinate;
431 $this->currentCell = $cell;
432 // Re-attach this as the cell's parent
433 $this->currentCell->attach($this);
434
435 // Return requested entry
436 return $this->currentCell;
437 }
438
439 /**
440 * Clear the cell collection and disconnect from our parent.
441 */
442 public function unsetWorksheetCells(): void
443 {
444 if ($this->currentCell !== null) {
445 $this->currentCell->detach();
446 $this->currentCell = null;
447 $this->currentCoordinate = null;
448 }
449
450 // Flush the cache
451 $this->__destruct();
452
453 $this->index = [];
454
455 // detach ourself from the worksheet, so that it can then delete this object successfully
456 $this->parent = null;
457 }
458
459 /**
460 * Destroy this cell collection.
461 */
462 public function __destruct()
463 {
464 $this->cache->deleteMultiple($this->getAllCacheKeys());
465 }
466
467 /**
468 * Returns all known cache keys.
469 *
470 * @return Generator|string[]
471 */
472 private function getAllCacheKeys()
473 {
474 foreach ($this->index as $coordinate => $value) {
475 yield $this->cachePrefix . $coordinate;
476 }
477 }
478 }
479