]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - application/bookmark/BookmarkArray.php
67bb3b73d55fbcca2f68c3a651f76fb873f140a3
[github/shaarli/Shaarli.git] / application / bookmark / BookmarkArray.php
1 <?php
2
3 declare(strict_types=1);
4
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]);
123 unset($this->ids[$offset]);
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 *
192 * @param int|null $id Persistent ID of a bookmark.
193 *
194 * @return int Real offset in local array, or null if doesn't exist.
195 */
196 protected function getBookmarkOffset(?int $id): ?int
197 {
198 if ($id !== null && isset($this->ids[$id])) {
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 */
210 public function getNextId(): int
211 {
212 if (!empty($this->ids)) {
213 return max(array_keys($this->ids)) + 1;
214 }
215 return 0;
216 }
217
218 /**
219 * @param string $url
220 *
221 * @return Bookmark|null
222 */
223 public function getByUrl(string $url): ?Bookmark
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 *
239 * @param string $order ASC|DESC
240 * @param bool $ignoreSticky If set to true, sticky bookmarks won't be first
241 */
242 public function reorder(string $order = 'DESC', bool $ignoreSticky = false): void
243 {
244 $order = $order === 'ASC' ? -1 : 1;
245 // Reorder array by dates.
246 usort($this->bookmarks, function ($a, $b) use ($order, $ignoreSticky) {
247 /** @var $a Bookmark */
248 /** @var $b Bookmark */
249 if (false === $ignoreSticky && $a->isSticky() !== $b->isSticky()) {
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 }