]>
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 | { | |
75 | if (! $value instanceof Bookmark | |
76 | || $value->getId() === null || empty($value->getUrl()) | |
77 | || ($offset !== null && ! is_int($offset)) || ! is_int($value->getId()) | |
78 | || $offset !== null && $offset !== $value->getId() | |
79 | ) { | |
80 | throw new InvalidBookmarkException($value); | |
81 | } | |
82 | ||
83 | // If the bookmark exists, we reuse the real offset, otherwise new entry | |
84 | if ($offset !== null) { | |
85 | $existing = $this->getBookmarkOffset($offset); | |
86 | } else { | |
87 | $existing = $this->getBookmarkOffset($value->getId()); | |
88 | } | |
89 | ||
90 | if ($existing !== null) { | |
91 | $offset = $existing; | |
92 | } else { | |
93 | $offset = count($this->bookmarks); | |
94 | } | |
95 | ||
96 | $this->bookmarks[$offset] = $value; | |
97 | $this->urls[$value->getUrl()] = $offset; | |
98 | $this->ids[$value->getId()] = $offset; | |
99 | } | |
100 | ||
101 | /** | |
102 | * ArrayAccess - Whether or not an offset exists | |
103 | * | |
104 | * @param int $offset Bookmark ID | |
105 | * | |
106 | * @return bool true if it exists, false otherwise | |
107 | */ | |
108 | public function offsetExists($offset) | |
109 | { | |
110 | return array_key_exists($this->getBookmarkOffset($offset), $this->bookmarks); | |
111 | } | |
112 | ||
113 | /** | |
114 | * ArrayAccess - Unsets an offset | |
115 | * | |
116 | * @param int $offset Bookmark ID | |
117 | */ | |
118 | public function offsetUnset($offset) | |
119 | { | |
120 | $realOffset = $this->getBookmarkOffset($offset); | |
121 | $url = $this->bookmarks[$realOffset]->getUrl(); | |
122 | unset($this->urls[$url]); | |
cf92b4dd | 123 | unset($this->ids[$offset]); |
336a28fa A |
124 | unset($this->bookmarks[$realOffset]); |
125 | } | |
126 | ||
127 | /** | |
128 | * ArrayAccess - Returns the value at specified offset | |
129 | * | |
130 | * @param int $offset Bookmark ID | |
131 | * | |
132 | * @return Bookmark|null The Bookmark if found, null otherwise | |
133 | */ | |
134 | public function offsetGet($offset) | |
135 | { | |
136 | $realOffset = $this->getBookmarkOffset($offset); | |
137 | return isset($this->bookmarks[$realOffset]) ? $this->bookmarks[$realOffset] : null; | |
138 | } | |
139 | ||
140 | /** | |
141 | * Iterator - Returns the current element | |
142 | * | |
143 | * @return Bookmark corresponding to the current position | |
144 | */ | |
145 | public function current() | |
146 | { | |
147 | return $this[$this->keys[$this->position]]; | |
148 | } | |
149 | ||
150 | /** | |
151 | * Iterator - Returns the key of the current element | |
152 | * | |
153 | * @return int Bookmark ID corresponding to the current position | |
154 | */ | |
155 | public function key() | |
156 | { | |
157 | return $this->keys[$this->position]; | |
158 | } | |
159 | ||
160 | /** | |
161 | * Iterator - Moves forward to next element | |
162 | */ | |
163 | public function next() | |
164 | { | |
165 | ++$this->position; | |
166 | } | |
167 | ||
168 | /** | |
169 | * Iterator - Rewinds the Iterator to the first element | |
170 | * | |
171 | * Entries are sorted by date (latest first) | |
172 | */ | |
173 | public function rewind() | |
174 | { | |
175 | $this->keys = array_keys($this->ids); | |
176 | $this->position = 0; | |
177 | } | |
178 | ||
179 | /** | |
180 | * Iterator - Checks if current position is valid | |
181 | * | |
182 | * @return bool true if the current Bookmark ID exists, false otherwise | |
183 | */ | |
184 | public function valid() | |
185 | { | |
186 | return isset($this->keys[$this->position]); | |
187 | } | |
188 | ||
189 | /** | |
190 | * Returns a bookmark offset in bookmarks array from its unique ID. | |
191 | * | |
efb7d21b | 192 | * @param int|null $id Persistent ID of a bookmark. |
336a28fa A |
193 | * |
194 | * @return int Real offset in local array, or null if doesn't exist. | |
195 | */ | |
efb7d21b | 196 | protected function getBookmarkOffset(?int $id): ?int |
336a28fa | 197 | { |
efb7d21b | 198 | if ($id !== null && isset($this->ids[$id])) { |
336a28fa A |
199 | return $this->ids[$id]; |
200 | } | |
201 | return null; | |
202 | } | |
203 | ||
204 | /** | |
205 | * Return the next key for bookmark creation. | |
206 | * E.g. If the last ID is 597, the next will be 598. | |
207 | * | |
208 | * @return int next ID. | |
209 | */ | |
efb7d21b | 210 | public function getNextId(): int |
336a28fa A |
211 | { |
212 | if (!empty($this->ids)) { | |
213 | return max(array_keys($this->ids)) + 1; | |
214 | } | |
215 | return 0; | |
216 | } | |
217 | ||
218 | /** | |
efb7d21b | 219 | * @param string $url |
336a28fa A |
220 | * |
221 | * @return Bookmark|null | |
222 | */ | |
efb7d21b | 223 | public function getByUrl(string $url): ?Bookmark |
336a28fa A |
224 | { |
225 | if (! empty($url) | |
226 | && isset($this->urls[$url]) | |
227 | && isset($this->bookmarks[$this->urls[$url]]) | |
228 | ) { | |
229 | return $this->bookmarks[$this->urls[$url]]; | |
230 | } | |
231 | return null; | |
232 | } | |
233 | ||
234 | /** | |
235 | * Reorder links by creation date (newest first). | |
236 | * | |
237 | * Also update the urls and ids mapping arrays. | |
238 | * | |
a8e210fa A |
239 | * @param string $order ASC|DESC |
240 | * @param bool $ignoreSticky If set to true, sticky bookmarks won't be first | |
336a28fa | 241 | */ |
a8e210fa | 242 | public function reorder(string $order = 'DESC', bool $ignoreSticky = false): void |
336a28fa A |
243 | { |
244 | $order = $order === 'ASC' ? -1 : 1; | |
245 | // Reorder array by dates. | |
a8e210fa | 246 | usort($this->bookmarks, function ($a, $b) use ($order, $ignoreSticky) { |
336a28fa A |
247 | /** @var $a Bookmark */ |
248 | /** @var $b Bookmark */ | |
a8e210fa | 249 | if (false === $ignoreSticky && $a->isSticky() !== $b->isSticky()) { |
336a28fa A |
250 | return $a->isSticky() ? -1 : 1; |
251 | } | |
252 | return $a->getCreated() < $b->getCreated() ? 1 * $order : -1 * $order; | |
253 | }); | |
254 | ||
255 | $this->urls = []; | |
256 | $this->ids = []; | |
257 | foreach ($this->bookmarks as $key => $bookmark) { | |
258 | $this->urls[$bookmark->getUrl()] = $key; | |
259 | $this->ids[$bookmark->getId()] = $key; | |
260 | } | |
261 | } | |
262 | } |