diff options
author | ArthurHoaro <arthur@hoa.ro> | 2018-07-28 11:07:55 +0200 |
---|---|---|
committer | ArthurHoaro <arthur@hoa.ro> | 2018-07-28 11:07:55 +0200 |
commit | 83faedadff76c5bdca036f39f13943f63b27e164 (patch) | |
tree | 6f44cede16ec6a60f10b9699e211e0818f06d2c8 /application/api/controllers | |
parent | 1d9eb22a3df85b67fe6652c0876cd7382c2fb525 (diff) | |
parent | 658988f3aeba7a5a938783249ccf2765251e5597 (diff) | |
download | Shaarli-83faedadff76c5bdca036f39f13943f63b27e164.tar.gz Shaarli-83faedadff76c5bdca036f39f13943f63b27e164.tar.zst Shaarli-83faedadff76c5bdca036f39f13943f63b27e164.zip |
Merge tag 'v0.9.7' into stable
Release v0.9.7
Diffstat (limited to 'application/api/controllers')
-rw-r--r-- | application/api/controllers/ApiController.php | 71 | ||||
-rw-r--r-- | application/api/controllers/History.php | 70 | ||||
-rw-r--r-- | application/api/controllers/Info.php | 42 | ||||
-rw-r--r-- | application/api/controllers/Links.php | 217 |
4 files changed, 400 insertions, 0 deletions
diff --git a/application/api/controllers/ApiController.php b/application/api/controllers/ApiController.php new file mode 100644 index 00000000..3be85b98 --- /dev/null +++ b/application/api/controllers/ApiController.php | |||
@@ -0,0 +1,71 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Api\Controllers; | ||
4 | |||
5 | use Shaarli\Config\ConfigManager; | ||
6 | use \Slim\Container; | ||
7 | |||
8 | /** | ||
9 | * Abstract Class ApiController | ||
10 | * | ||
11 | * Defines REST API Controller dependencies injected from the container. | ||
12 | * | ||
13 | * @package Api\Controllers | ||
14 | */ | ||
15 | abstract class ApiController | ||
16 | { | ||
17 | /** | ||
18 | * @var Container | ||
19 | */ | ||
20 | protected $ci; | ||
21 | |||
22 | /** | ||
23 | * @var ConfigManager | ||
24 | */ | ||
25 | protected $conf; | ||
26 | |||
27 | /** | ||
28 | * @var \LinkDB | ||
29 | */ | ||
30 | protected $linkDb; | ||
31 | |||
32 | /** | ||
33 | * @var \History | ||
34 | */ | ||
35 | protected $history; | ||
36 | |||
37 | /** | ||
38 | * @var int|null JSON style option. | ||
39 | */ | ||
40 | protected $jsonStyle; | ||
41 | |||
42 | /** | ||
43 | * ApiController constructor. | ||
44 | * | ||
45 | * Note: enabling debug mode displays JSON with readable formatting. | ||
46 | * | ||
47 | * @param Container $ci Slim container. | ||
48 | */ | ||
49 | public function __construct(Container $ci) | ||
50 | { | ||
51 | $this->ci = $ci; | ||
52 | $this->conf = $ci->get('conf'); | ||
53 | $this->linkDb = $ci->get('db'); | ||
54 | $this->history = $ci->get('history'); | ||
55 | if ($this->conf->get('dev.debug', false)) { | ||
56 | $this->jsonStyle = JSON_PRETTY_PRINT; | ||
57 | } else { | ||
58 | $this->jsonStyle = null; | ||
59 | } | ||
60 | } | ||
61 | |||
62 | /** | ||
63 | * Get the container. | ||
64 | * | ||
65 | * @return Container | ||
66 | */ | ||
67 | public function getCi() | ||
68 | { | ||
69 | return $this->ci; | ||
70 | } | ||
71 | } | ||
diff --git a/application/api/controllers/History.php b/application/api/controllers/History.php new file mode 100644 index 00000000..2ff9deaf --- /dev/null +++ b/application/api/controllers/History.php | |||
@@ -0,0 +1,70 @@ | |||
1 | <?php | ||
2 | |||
3 | |||
4 | namespace Shaarli\Api\Controllers; | ||
5 | |||
6 | use Shaarli\Api\Exceptions\ApiBadParametersException; | ||
7 | use Slim\Http\Request; | ||
8 | use Slim\Http\Response; | ||
9 | |||
10 | /** | ||
11 | * Class History | ||
12 | * | ||
13 | * REST API Controller: /history | ||
14 | * | ||
15 | * @package Shaarli\Api\Controllers | ||
16 | */ | ||
17 | class History extends ApiController | ||
18 | { | ||
19 | /** | ||
20 | * Service providing operation regarding Shaarli datastore and settings. | ||
21 | * | ||
22 | * @param Request $request Slim request. | ||
23 | * @param Response $response Slim response. | ||
24 | * | ||
25 | * @return Response response. | ||
26 | * | ||
27 | * @throws ApiBadParametersException Invalid parameters. | ||
28 | */ | ||
29 | public function getHistory($request, $response) | ||
30 | { | ||
31 | $history = $this->history->getHistory(); | ||
32 | |||
33 | // Return history operations from the {offset}th, starting from {since}. | ||
34 | $since = \DateTime::createFromFormat(\DateTime::ATOM, $request->getParam('since')); | ||
35 | $offset = $request->getParam('offset'); | ||
36 | if (empty($offset)) { | ||
37 | $offset = 0; | ||
38 | } | ||
39 | else if (ctype_digit($offset)) { | ||
40 | $offset = (int) $offset; | ||
41 | } else { | ||
42 | throw new ApiBadParametersException('Invalid offset'); | ||
43 | } | ||
44 | |||
45 | // limit parameter is either a number of links or 'all' for everything. | ||
46 | $limit = $request->getParam('limit'); | ||
47 | if (empty($limit)) { | ||
48 | $limit = count($history); | ||
49 | } else if (ctype_digit($limit)) { | ||
50 | $limit = (int) $limit; | ||
51 | } else { | ||
52 | throw new ApiBadParametersException('Invalid limit'); | ||
53 | } | ||
54 | |||
55 | $out = []; | ||
56 | $i = 0; | ||
57 | foreach ($history as $entry) { | ||
58 | if ((! empty($since) && $entry['datetime'] <= $since) || count($out) >= $limit) { | ||
59 | break; | ||
60 | } | ||
61 | if (++$i > $offset) { | ||
62 | $out[$i] = $entry; | ||
63 | $out[$i]['datetime'] = $out[$i]['datetime']->format(\DateTime::ATOM); | ||
64 | } | ||
65 | } | ||
66 | $out = array_values($out); | ||
67 | |||
68 | return $response->withJson($out, 200, $this->jsonStyle); | ||
69 | } | ||
70 | } | ||
diff --git a/application/api/controllers/Info.php b/application/api/controllers/Info.php new file mode 100644 index 00000000..25433f72 --- /dev/null +++ b/application/api/controllers/Info.php | |||
@@ -0,0 +1,42 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Api\Controllers; | ||
4 | |||
5 | use Slim\Http\Request; | ||
6 | use Slim\Http\Response; | ||
7 | |||
8 | /** | ||
9 | * Class Info | ||
10 | * | ||
11 | * REST API Controller: /info | ||
12 | * | ||
13 | * @package Api\Controllers | ||
14 | * @see http://shaarli.github.io/api-documentation/#links-instance-information-get | ||
15 | */ | ||
16 | class Info extends ApiController | ||
17 | { | ||
18 | /** | ||
19 | * Service providing various information about Shaarli instance. | ||
20 | * | ||
21 | * @param Request $request Slim request. | ||
22 | * @param Response $response Slim response. | ||
23 | * | ||
24 | * @return Response response. | ||
25 | */ | ||
26 | public function getInfo($request, $response) | ||
27 | { | ||
28 | $info = [ | ||
29 | 'global_counter' => count($this->linkDb), | ||
30 | 'private_counter' => count_private($this->linkDb), | ||
31 | 'settings' => array( | ||
32 | 'title' => $this->conf->get('general.title', 'Shaarli'), | ||
33 | 'header_link' => $this->conf->get('general.header_link', '?'), | ||
34 | 'timezone' => $this->conf->get('general.timezone', 'UTC'), | ||
35 | 'enabled_plugins' => $this->conf->get('general.enabled_plugins', []), | ||
36 | 'default_private_links' => $this->conf->get('privacy.default_private_links', false), | ||
37 | ), | ||
38 | ]; | ||
39 | |||
40 | return $response->withJson($info, 200, $this->jsonStyle); | ||
41 | } | ||
42 | } | ||
diff --git a/application/api/controllers/Links.php b/application/api/controllers/Links.php new file mode 100644 index 00000000..eb78dd26 --- /dev/null +++ b/application/api/controllers/Links.php | |||
@@ -0,0 +1,217 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Api\Controllers; | ||
4 | |||
5 | use Shaarli\Api\ApiUtils; | ||
6 | use Shaarli\Api\Exceptions\ApiBadParametersException; | ||
7 | use Shaarli\Api\Exceptions\ApiLinkNotFoundException; | ||
8 | use Slim\Http\Request; | ||
9 | use Slim\Http\Response; | ||
10 | |||
11 | /** | ||
12 | * Class Links | ||
13 | * | ||
14 | * REST API Controller: all services related to links collection. | ||
15 | * | ||
16 | * @package Api\Controllers | ||
17 | * @see http://shaarli.github.io/api-documentation/#links-links-collection | ||
18 | */ | ||
19 | class Links extends ApiController | ||
20 | { | ||
21 | /** | ||
22 | * @var int Number of links returned if no limit is provided. | ||
23 | */ | ||
24 | public static $DEFAULT_LIMIT = 20; | ||
25 | |||
26 | /** | ||
27 | * Retrieve a list of links, allowing different filters. | ||
28 | * | ||
29 | * @param Request $request Slim request. | ||
30 | * @param Response $response Slim response. | ||
31 | * | ||
32 | * @return Response response. | ||
33 | * | ||
34 | * @throws ApiBadParametersException Invalid parameters. | ||
35 | */ | ||
36 | public function getLinks($request, $response) | ||
37 | { | ||
38 | $private = $request->getParam('visibility'); | ||
39 | $links = $this->linkDb->filterSearch( | ||
40 | [ | ||
41 | 'searchtags' => $request->getParam('searchtags', ''), | ||
42 | 'searchterm' => $request->getParam('searchterm', ''), | ||
43 | ], | ||
44 | false, | ||
45 | $private | ||
46 | ); | ||
47 | |||
48 | // Return links from the {offset}th link, starting from 0. | ||
49 | $offset = $request->getParam('offset'); | ||
50 | if (! empty($offset) && ! ctype_digit($offset)) { | ||
51 | throw new ApiBadParametersException('Invalid offset'); | ||
52 | } | ||
53 | $offset = ! empty($offset) ? intval($offset) : 0; | ||
54 | if ($offset > count($links)) { | ||
55 | return $response->withJson([], 200, $this->jsonStyle); | ||
56 | } | ||
57 | |||
58 | // limit parameter is either a number of links or 'all' for everything. | ||
59 | $limit = $request->getParam('limit'); | ||
60 | if (empty($limit)) { | ||
61 | $limit = self::$DEFAULT_LIMIT; | ||
62 | } else if (ctype_digit($limit)) { | ||
63 | $limit = intval($limit); | ||
64 | } else if ($limit === 'all') { | ||
65 | $limit = count($links); | ||
66 | } else { | ||
67 | throw new ApiBadParametersException('Invalid limit'); | ||
68 | } | ||
69 | |||
70 | // 'environment' is set by Slim and encapsulate $_SERVER. | ||
71 | $index = index_url($this->ci['environment']); | ||
72 | |||
73 | $out = []; | ||
74 | $cpt = 0; | ||
75 | foreach ($links as $link) { | ||
76 | if (count($out) >= $limit) { | ||
77 | break; | ||
78 | } | ||
79 | if ($cpt++ >= $offset) { | ||
80 | $out[] = ApiUtils::formatLink($link, $index); | ||
81 | } | ||
82 | } | ||
83 | |||
84 | return $response->withJson($out, 200, $this->jsonStyle); | ||
85 | } | ||
86 | |||
87 | /** | ||
88 | * Return a single formatted link by its ID. | ||
89 | * | ||
90 | * @param Request $request Slim request. | ||
91 | * @param Response $response Slim response. | ||
92 | * @param array $args Path parameters. including the ID. | ||
93 | * | ||
94 | * @return Response containing the link array. | ||
95 | * | ||
96 | * @throws ApiLinkNotFoundException generating a 404 error. | ||
97 | */ | ||
98 | public function getLink($request, $response, $args) | ||
99 | { | ||
100 | if (!isset($this->linkDb[$args['id']])) { | ||
101 | throw new ApiLinkNotFoundException(); | ||
102 | } | ||
103 | $index = index_url($this->ci['environment']); | ||
104 | $out = ApiUtils::formatLink($this->linkDb[$args['id']], $index); | ||
105 | |||
106 | return $response->withJson($out, 200, $this->jsonStyle); | ||
107 | } | ||
108 | |||
109 | /** | ||
110 | * Creates a new link from posted request body. | ||
111 | * | ||
112 | * @param Request $request Slim request. | ||
113 | * @param Response $response Slim response. | ||
114 | * | ||
115 | * @return Response response. | ||
116 | */ | ||
117 | public function postLink($request, $response) | ||
118 | { | ||
119 | $data = $request->getParsedBody(); | ||
120 | $link = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links')); | ||
121 | // duplicate by URL, return 409 Conflict | ||
122 | if (! empty($link['url']) && ! empty($dup = $this->linkDb->getLinkFromUrl($link['url']))) { | ||
123 | return $response->withJson( | ||
124 | ApiUtils::formatLink($dup, index_url($this->ci['environment'])), | ||
125 | 409, | ||
126 | $this->jsonStyle | ||
127 | ); | ||
128 | } | ||
129 | |||
130 | $link['id'] = $this->linkDb->getNextId(); | ||
131 | $link['shorturl'] = link_small_hash($link['created'], $link['id']); | ||
132 | |||
133 | // note: general relative URL | ||
134 | if (empty($link['url'])) { | ||
135 | $link['url'] = '?' . $link['shorturl']; | ||
136 | } | ||
137 | |||
138 | if (empty($link['title'])) { | ||
139 | $link['title'] = $link['url']; | ||
140 | } | ||
141 | |||
142 | $this->linkDb[$link['id']] = $link; | ||
143 | $this->linkDb->save($this->conf->get('resource.page_cache')); | ||
144 | $this->history->addLink($link); | ||
145 | $out = ApiUtils::formatLink($link, index_url($this->ci['environment'])); | ||
146 | $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $link['id']]); | ||
147 | return $response->withAddedHeader('Location', $redirect) | ||
148 | ->withJson($out, 201, $this->jsonStyle); | ||
149 | } | ||
150 | |||
151 | /** | ||
152 | * Updates an existing link from posted request body. | ||
153 | * | ||
154 | * @param Request $request Slim request. | ||
155 | * @param Response $response Slim response. | ||
156 | * @param array $args Path parameters. including the ID. | ||
157 | * | ||
158 | * @return Response response. | ||
159 | * | ||
160 | * @throws ApiLinkNotFoundException generating a 404 error. | ||
161 | */ | ||
162 | public function putLink($request, $response, $args) | ||
163 | { | ||
164 | if (! isset($this->linkDb[$args['id']])) { | ||
165 | throw new ApiLinkNotFoundException(); | ||
166 | } | ||
167 | |||
168 | $index = index_url($this->ci['environment']); | ||
169 | $data = $request->getParsedBody(); | ||
170 | |||
171 | $requestLink = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links')); | ||
172 | // duplicate URL on a different link, return 409 Conflict | ||
173 | if (! empty($requestLink['url']) | ||
174 | && ! empty($dup = $this->linkDb->getLinkFromUrl($requestLink['url'])) | ||
175 | && $dup['id'] != $args['id'] | ||
176 | ) { | ||
177 | return $response->withJson( | ||
178 | ApiUtils::formatLink($dup, $index), | ||
179 | 409, | ||
180 | $this->jsonStyle | ||
181 | ); | ||
182 | } | ||
183 | |||
184 | $responseLink = $this->linkDb[$args['id']]; | ||
185 | $responseLink = ApiUtils::updateLink($responseLink, $requestLink); | ||
186 | $this->linkDb[$responseLink['id']] = $responseLink; | ||
187 | $this->linkDb->save($this->conf->get('resource.page_cache')); | ||
188 | $this->history->updateLink($responseLink); | ||
189 | |||
190 | $out = ApiUtils::formatLink($responseLink, $index); | ||
191 | return $response->withJson($out, 200, $this->jsonStyle); | ||
192 | } | ||
193 | |||
194 | /** | ||
195 | * Delete an existing link by its ID. | ||
196 | * | ||
197 | * @param Request $request Slim request. | ||
198 | * @param Response $response Slim response. | ||
199 | * @param array $args Path parameters. including the ID. | ||
200 | * | ||
201 | * @return Response response. | ||
202 | * | ||
203 | * @throws ApiLinkNotFoundException generating a 404 error. | ||
204 | */ | ||
205 | public function deleteLink($request, $response, $args) | ||
206 | { | ||
207 | if (! isset($this->linkDb[$args['id']])) { | ||
208 | throw new ApiLinkNotFoundException(); | ||
209 | } | ||
210 | $link = $this->linkDb[$args['id']]; | ||
211 | unset($this->linkDb[(int) $args['id']]); | ||
212 | $this->linkDb->save($this->conf->get('resource.page_cache')); | ||
213 | $this->history->deleteLink($link); | ||
214 | |||
215 | return $response->withStatus(204); | ||
216 | } | ||
217 | } | ||