Links.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. <?php
  2. namespace Shaarli\Api\Controllers;
  3. use Shaarli\Api\ApiUtils;
  4. use Shaarli\Api\Exceptions\ApiBadParametersException;
  5. use Shaarli\Api\Exceptions\ApiLinkNotFoundException;
  6. use Slim\Http\Request;
  7. use Slim\Http\Response;
  8. /**
  9. * Class Links
  10. *
  11. * REST API Controller: all services related to links collection.
  12. *
  13. * @package Api\Controllers
  14. * @see http://shaarli.github.io/api-documentation/#links-links-collection
  15. */
  16. class Links extends ApiController
  17. {
  18. /**
  19. * @var int Number of links returned if no limit is provided.
  20. */
  21. public static $DEFAULT_LIMIT = 20;
  22. /**
  23. * Retrieve a list of links, allowing different filters.
  24. *
  25. * @param Request $request Slim request.
  26. * @param Response $response Slim response.
  27. *
  28. * @return Response response.
  29. *
  30. * @throws ApiBadParametersException Invalid parameters.
  31. */
  32. public function getLinks($request, $response)
  33. {
  34. $private = $request->getParam('visibility');
  35. $links = $this->linkDb->filterSearch(
  36. [
  37. 'searchtags' => $request->getParam('searchtags', ''),
  38. 'searchterm' => $request->getParam('searchterm', ''),
  39. ],
  40. false,
  41. $private
  42. );
  43. // Return links from the {offset}th link, starting from 0.
  44. $offset = $request->getParam('offset');
  45. if (! empty($offset) && ! ctype_digit($offset)) {
  46. throw new ApiBadParametersException('Invalid offset');
  47. }
  48. $offset = ! empty($offset) ? intval($offset) : 0;
  49. if ($offset > count($links)) {
  50. return $response->withJson([], 200, $this->jsonStyle);
  51. }
  52. // limit parameter is either a number of links or 'all' for everything.
  53. $limit = $request->getParam('limit');
  54. if (empty($limit)) {
  55. $limit = self::$DEFAULT_LIMIT;
  56. } elseif (ctype_digit($limit)) {
  57. $limit = intval($limit);
  58. } elseif ($limit === 'all') {
  59. $limit = count($links);
  60. } else {
  61. throw new ApiBadParametersException('Invalid limit');
  62. }
  63. // 'environment' is set by Slim and encapsulate $_SERVER.
  64. $indexUrl = index_url($this->ci['environment']);
  65. $out = [];
  66. $index = 0;
  67. foreach ($links as $link) {
  68. if (count($out) >= $limit) {
  69. break;
  70. }
  71. if ($index++ >= $offset) {
  72. $out[] = ApiUtils::formatLink($link, $indexUrl);
  73. }
  74. }
  75. return $response->withJson($out, 200, $this->jsonStyle);
  76. }
  77. /**
  78. * Return a single formatted link by its ID.
  79. *
  80. * @param Request $request Slim request.
  81. * @param Response $response Slim response.
  82. * @param array $args Path parameters. including the ID.
  83. *
  84. * @return Response containing the link array.
  85. *
  86. * @throws ApiLinkNotFoundException generating a 404 error.
  87. */
  88. public function getLink($request, $response, $args)
  89. {
  90. if (!isset($this->linkDb[$args['id']])) {
  91. throw new ApiLinkNotFoundException();
  92. }
  93. $index = index_url($this->ci['environment']);
  94. $out = ApiUtils::formatLink($this->linkDb[$args['id']], $index);
  95. return $response->withJson($out, 200, $this->jsonStyle);
  96. }
  97. /**
  98. * Creates a new link from posted request body.
  99. *
  100. * @param Request $request Slim request.
  101. * @param Response $response Slim response.
  102. *
  103. * @return Response response.
  104. */
  105. public function postLink($request, $response)
  106. {
  107. $data = $request->getParsedBody();
  108. $link = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
  109. // duplicate by URL, return 409 Conflict
  110. if (! empty($link['url']) && ! empty($dup = $this->linkDb->getLinkFromUrl($link['url']))) {
  111. return $response->withJson(
  112. ApiUtils::formatLink($dup, index_url($this->ci['environment'])),
  113. 409,
  114. $this->jsonStyle
  115. );
  116. }
  117. $link['id'] = $this->linkDb->getNextId();
  118. $link['shorturl'] = link_small_hash($link['created'], $link['id']);
  119. // note: general relative URL
  120. if (empty($link['url'])) {
  121. $link['url'] = '?' . $link['shorturl'];
  122. }
  123. if (empty($link['title'])) {
  124. $link['title'] = $link['url'];
  125. }
  126. $this->linkDb[$link['id']] = $link;
  127. $this->linkDb->save($this->conf->get('resource.page_cache'));
  128. $this->history->addLink($link);
  129. $out = ApiUtils::formatLink($link, index_url($this->ci['environment']));
  130. $redirect = $this->ci->router->relativePathFor('getLink', ['id' => $link['id']]);
  131. return $response->withAddedHeader('Location', $redirect)
  132. ->withJson($out, 201, $this->jsonStyle);
  133. }
  134. /**
  135. * Updates an existing link from posted request body.
  136. *
  137. * @param Request $request Slim request.
  138. * @param Response $response Slim response.
  139. * @param array $args Path parameters. including the ID.
  140. *
  141. * @return Response response.
  142. *
  143. * @throws ApiLinkNotFoundException generating a 404 error.
  144. */
  145. public function putLink($request, $response, $args)
  146. {
  147. if (! isset($this->linkDb[$args['id']])) {
  148. throw new ApiLinkNotFoundException();
  149. }
  150. $index = index_url($this->ci['environment']);
  151. $data = $request->getParsedBody();
  152. $requestLink = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
  153. // duplicate URL on a different link, return 409 Conflict
  154. if (! empty($requestLink['url'])
  155. && ! empty($dup = $this->linkDb->getLinkFromUrl($requestLink['url']))
  156. && $dup['id'] != $args['id']
  157. ) {
  158. return $response->withJson(
  159. ApiUtils::formatLink($dup, $index),
  160. 409,
  161. $this->jsonStyle
  162. );
  163. }
  164. $responseLink = $this->linkDb[$args['id']];
  165. $responseLink = ApiUtils::updateLink($responseLink, $requestLink);
  166. $this->linkDb[$responseLink['id']] = $responseLink;
  167. $this->linkDb->save($this->conf->get('resource.page_cache'));
  168. $this->history->updateLink($responseLink);
  169. $out = ApiUtils::formatLink($responseLink, $index);
  170. return $response->withJson($out, 200, $this->jsonStyle);
  171. }
  172. /**
  173. * Delete an existing link by its ID.
  174. *
  175. * @param Request $request Slim request.
  176. * @param Response $response Slim response.
  177. * @param array $args Path parameters. including the ID.
  178. *
  179. * @return Response response.
  180. *
  181. * @throws ApiLinkNotFoundException generating a 404 error.
  182. */
  183. public function deleteLink($request, $response, $args)
  184. {
  185. if (! isset($this->linkDb[$args['id']])) {
  186. throw new ApiLinkNotFoundException();
  187. }
  188. $link = $this->linkDb[$args['id']];
  189. unset($this->linkDb[(int) $args['id']]);
  190. $this->linkDb->save($this->conf->get('resource.page_cache'));
  191. $this->history->deleteLink($link);
  192. return $response->withStatus(204);
  193. }
  194. }