PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / repositories / OldPermalinkStructureStore.php

OldPermalinkStructureStore.php in 404 Solution trunk, at includes/repositories/OldPermalinkStructureStore.php

123 lines 3.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Stores a small bounded list of permalink structures that were active before
9 * a WordPress permalink settings change.
10 */
11 class ABJ_404_Solution_OldPermalinkStructureStore {
12
13 public const OPTION_NAME = 'abj404_previous_permalink_structures';
14 public const MAX_ITEMS = 5;
15 private const MAX_STRUCTURE_LENGTH = 512;
16
17 /**
18 * @return array<int, array{structure: string, captured_at: int, source: string}>
19 */
20 public function getObservedStructures(): array {
21 $stored = function_exists('get_option') ? get_option(self::OPTION_NAME, array()) : array();
22 return $this->normalizeStoredOption($stored)['items'];
23 }
24
25 /**
26 * @param string $structure
27 * @param int|null $capturedAt
28 * @return void
29 */
30 public function recordPreviousStructure(string $structure, ?int $capturedAt = null): void {
31 $normalizedStructure = $this->normalizeStructure($structure);
32 if ($normalizedStructure === '') {
33 return;
34 }
35
36 $items = $this->getObservedStructures();
37 $items = array_values(array_filter($items, static function($item) use ($normalizedStructure): bool {
38 return $item['structure'] !== $normalizedStructure;
39 }));
40
41 array_unshift($items, array(
42 'structure' => $normalizedStructure,
43 'captured_at' => $capturedAt ?? $this->now(),
44 'source' => 'observed',
45 ));
46
47 $items = array_slice($items, 0, self::MAX_ITEMS);
48 if (function_exists('update_option')) {
49 update_option(self::OPTION_NAME, array(
50 'version' => 1,
51 'items' => $items,
52 ));
53 }
54 }
55
56 /**
57 * @param mixed $stored
58 * @return array{version: int, items: array<int, array{structure: string, captured_at: int, source: string}>}
59 */
60 public function normalizeStoredOption($stored): array {
61 if (!is_array($stored)) {
62 return array('version' => 1, 'items' => array());
63 }
64
65 $rawItems = isset($stored['items']) && is_array($stored['items']) ? $stored['items'] : array();
66 $items = array();
67 $seen = array();
68
69 foreach ($rawItems as $rawItem) {
70 if (!is_array($rawItem)) {
71 continue;
72 }
73 $structure = isset($rawItem['structure']) && is_scalar($rawItem['structure'])
74 ? $this->normalizeStructure((string)$rawItem['structure'])
75 : '';
76 if ($structure === '' || isset($seen[$structure])) {
77 continue;
78 }
79 $seen[$structure] = true;
80 $source = isset($rawItem['source']) && $rawItem['source'] === 'observed' ? 'observed' : 'observed';
81 $capturedAt = isset($rawItem['captured_at']) && is_numeric($rawItem['captured_at'])
82 ? (int)$rawItem['captured_at']
83 : 0;
84
85 $items[] = array(
86 'structure' => $structure,
87 'captured_at' => $capturedAt,
88 'source' => $source,
89 );
90
91 if (count($items) >= self::MAX_ITEMS) {
92 break;
93 }
94 }
95
96 return array('version' => 1, 'items' => $items);
97 }
98
99 /** @param string $structure @return string */
100 private function normalizeStructure(string $structure): string {
101 $structure = trim($structure);
102 if ($structure === '' || strlen($structure) > self::MAX_STRUCTURE_LENGTH) {
103 return '';
104 }
105
106 $path = parse_url($structure, PHP_URL_PATH);
107 if (is_string($path) && $path !== '') {
108 $structure = $path;
109 }
110
111 if ($structure[0] !== '/') {
112 $structure = '/' . $structure;
113 }
114
115 return $structure;
116 }
117
118 /** @return int */
119 private function now(): int {
120 return abj_clock()->now();
121 }
122 }
123