]>
Commit | Line | Data |
---|---|---|
336a28fa A |
1 | <?php |
2 | ||
efb7d21b A |
3 | declare(strict_types=1); |
4 | ||
336a28fa A |
5 | namespace Shaarli\Bookmark; |
6 | ||
7 | use Shaarli\Bookmark\Exception\InvalidBookmarkException; | |
8 | ||
9 | /** | |
10 | * Class BookmarkArray | |
11 | * | |
12 | * Implementing ArrayAccess, this allows us to use the bookmark list | |
13 | * as an array and iterate over it. | |
14 | * | |
15 | * @package Shaarli\Bookmark | |
16 | */ | |
17 | class BookmarkArray implements \Iterator, \Countable, \ArrayAccess | |
18 | { | |
19 | /** | |
20 | * @var Bookmark[] | |
21 | */ | |
22 | protected $bookmarks; | |
23 | ||
24 | /** | |
25 | * @var array List of all bookmarks IDS mapped with their array offset. | |
26 | * Map: id->offset. | |
27 | */ | |
28 | protected $ids; | |
29 | ||
30 | /** | |
31 | * @var int Position in the $this->keys array (for the Iterator interface) | |
32 | */ | |
33 | protected $position; | |
34 | ||
35 | /** | |
36 | * @var array List of offset keys (for the Iterator interface implementation) | |
37 | */ | |
38 | protected $keys; | |
39 | ||
40 | /** | |
41 | * @var array List of all recorded URLs (key=url, value=bookmark offset) | |
42 | * for fast reserve search (url-->bookmark offset) | |
43 | */ | |
44 | protected $urls; | |
45 | ||
46 | public function __construct() | |
47 | { | |
48 | $this->ids = []; | |
49 | $this->bookmarks = []; | |
50 | $this->keys = []; | |
51 | $this->urls = []; | |
52 | $this->position = 0; | |
53 | } | |
54 | ||
55 | /** | |
56 | * Countable - Counts elements of an object | |
57 | * | |
58 | * @return int Number of bookmarks | |
59 | */ | |
60 | public function count() | |
61 | { | |
62 | return count($this->bookmarks); | |
63 | } | |
64 | ||
65 | /** | |
66 | * ArrayAccess - Assigns a value to the specified offset | |
67 | * | |
68 | * @param int $offset Bookmark ID | |
69 | * @param Bookmark $value instance | |
70 | * | |
71 | * @throws InvalidBookmarkException | |
72 | */ | |
73 | public function offsetSet($offset, $value) | |
74 | { | |
53054b2b A |
75 | if ( |
76 | ! $value instanceof Bookmark | |
336a28fa A |
77 | || $value->getId() === null || empty($value->getUrl()) |
78 | || ($offset !== null && ! is_int($offset)) || ! is_int($value->getId()) | |
79 | || $offset !== null && $offset !== $value->getId() | |
80 | ) { | |
81 | throw new InvalidBookmarkException($value); | |
82 | } | |
83 | ||
84 | // If the bookmark exists, we reuse the real offset, otherwise new entry | |
85 | if ($offset !== null) { | |
86 | $existing = $this->getBookmarkOffset($offset); | |
87 | } else { | |
88 | $existing = $this->getBookmarkOffset($value->getId()); | |
89 | } | |
90 | ||
91 | if ($existing !== null) { | |
92 | $offset = $existing; | |
93 | } else { | |
94 | $offset = count($this->bookmarks); | |
95 | } | |
96 | ||
97 | $this->bookmarks[$offset] = $value; | |
98 | $this->urls[$value->getUrl()] = $offset; | |
99 | $this->ids[$value->getId()] = $offset; | |
100 | } | |
101 | ||
102 | /** | |
103 | * ArrayAccess - Whether or not an offset exists | |
104 | * | |
105 | * @param int $offset Bookmark ID | |
106 | * | |
107 | * @return bool true if it exists, false otherwise | |
108 | */ | |
109 | public function offsetExists($offset) | |
110 | { | |
111 | return array_key_exists($this->getBookmarkOffset($offset), $this->bookmarks); | |
112 | } | |
113 | ||
114 | /** | |
115 | * ArrayAccess - Unsets an offset | |
116 | * | |
117 | * @param int $offset Bookmark ID | |
118 | */ | |
119 | public function offsetUnset($offset) | |
120 | { | |
121 | $realOffset = $this->getBookmarkOffset($offset); | |
122 | $url = $this->bookmarks[$realOffset]->getUrl(); | |
123 | unset($this->urls[$url]); | |
cf92b4dd | 124 | unset($this->ids[$offset]); |
336a28fa A |
125 | unset($this->bookmarks[$realOffset]); |
126 | } | |
127 | ||
128 | /** | |
129 | * ArrayAccess - Returns the value at specified offset | |
130 | * | |
131 | * @param int $offset Bookmark ID | |
132 | * | |
133 | * @return Bookmark|null The Bookmark if found, null otherwise | |
134 | */ | |
135 | public function offsetGet($offset) | |
136 | { | |
137 | $realOffset = $this->getBookmarkOffset($offset); | |
138 | return isset($this->bookmarks[$realOffset]) ? $this->bookmarks[$realOffset] : null; | |
139 | } | |
140 | ||
141 | /** | |
142 | * Iterator - Returns the current element | |
143 | * | |
144 | * @return Bookmark corresponding to the current position | |
145 | */ | |
146 | public function current() | |
147 | { | |
148 | return $this[$this->keys[$this->position]]; | |
149 | } | |
150 | ||
151 | /** | |
152 | * Iterator - Returns the key of the current element | |
153 | * | |
154 | * @return int Bookmark ID corresponding to the current position | |
155 | */ | |
156 | public function key() | |
157 | { | |
158 | return $this->keys[$this->position]; | |
159 | } | |
160 | ||
161 | /** | |
162 | * Iterator - Moves forward to next element | |
163 | */ | |
164 | public function next() | |
165 | { | |
166 | ++$this->position; | |
167 | } | |
168 | ||
169 | /** | |
170 | * Iterator - Rewinds the Iterator to the first element | |
171 | * | |
172 | * Entries are sorted by date (latest first) | |
173 | */ | |
174 | public function rewind() | |
175 | { | |
176 | $this->keys = array_keys($this->ids); | |
177 | $this->position = 0; | |
178 | } | |
179 | ||
180 | /** | |
181 | * Iterator - Checks if current position is valid | |
182 | * | |
183 | * @return bool true if the current Bookmark ID exists, false otherwise | |
184 | */ | |
185 | public function valid() | |
186 | { | |
187 | return isset($this->keys[$this->position]); | |
188 | } | |
189 | ||
190 | /** | |
191 | * Returns a bookmark offset in bookmarks array from its unique ID. | |
192 | * | |
efb7d21b | 193 | * @param int|null $id Persistent ID of a bookmark. |
336a28fa A |
194 | * |
195 | * @return int Real offset in local array, or null if doesn't exist. | |
196 | */ | |
efb7d21b | 197 | protected function getBookmarkOffset(?int $id): ?int |
336a28fa | 198 | { |
efb7d21b | 199 | if ($id !== null && isset($this->ids[$id])) { |
336a28fa A |
200 | return $this->ids[$id]; |
201 | } | |
202 | return null; | |
203 | } | |
204 | ||
205 | /** | |
206 | * Return the next key for bookmark creation. | |
207 | * E.g. If the last ID is 597, the next will be 598. | |
208 | * | |
209 | * @return int next ID. | |
210 | */ | |
efb7d21b | 211 | public function getNextId(): int |
336a28fa A |
212 | { |
213 | if (!empty($this->ids)) { | |
214 | return max(array_keys($this->ids)) + 1; | |
215 | } | |
216 | return 0; | |
217 | } | |
218 | ||
219 | /** | |
efb7d21b | 220 | * @param string $url |
336a28fa A |
221 | * |
222 | * @return Bookmark|null | |
223 | */ | |
efb7d21b | 224 | public function getByUrl(string $url): ?Bookmark |
336a28fa | 225 | { |
53054b2b A |
226 | if ( |
227 | ! empty($url) | |
336a28fa A |
228 | && isset($this->urls[$url]) |
229 | && isset($this->bookmarks[$this->urls[$url]]) | |
230 | ) { | |
231 | return $this->bookmarks[$this->urls[$url]]; | |
232 | } | |
233 | return null; | |
234 | } | |
235 | ||
236 | /** | |
237 | * Reorder links by creation date (newest first). | |
238 | * | |
239 | * Also update the urls and ids mapping arrays. | |
240 | * | |
a8e210fa A |
241 | * @param string $order ASC|DESC |
242 | * @param bool $ignoreSticky If set to true, sticky bookmarks won't be first | |
336a28fa | 243 | */ |
a8e210fa | 244 | public function reorder(string $order = 'DESC', bool $ignoreSticky = false): void |
336a28fa A |
245 | { |
246 | $order = $order === 'ASC' ? -1 : 1; | |
247 | // Reorder array by dates. | |
a8e210fa | 248 | usort($this->bookmarks, function ($a, $b) use ($order, $ignoreSticky) { |
336a28fa A |
249 | /** @var $a Bookmark */ |
250 | /** @var $b Bookmark */ | |
a8e210fa | 251 | if (false === $ignoreSticky && $a->isSticky() !== $b->isSticky()) { |
336a28fa A |
252 | return $a->isSticky() ? -1 : 1; |
253 | } | |
254 | return $a->getCreated() < $b->getCreated() ? 1 * $order : -1 * $order; | |
255 | }); | |
256 | ||
257 | $this->urls = []; | |
258 | $this->ids = []; | |
259 | foreach ($this->bookmarks as $key => $bookmark) { | |
260 | $this->urls[$bookmark->getUrl()] = $key; | |
261 | $this->ids[$bookmark->getId()] = $key; | |
262 | } | |
263 | } | |
264 | } |