ListShortUrlsCommand.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. <?php
  2. declare(strict_types=1);
  3. namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
  4. use Cake\Chronos\Chronos;
  5. use Laminas\Paginator\Paginator;
  6. use Shlinkio\Shlink\CLI\Command\Util\AbstractWithDateRangeCommand;
  7. use Shlinkio\Shlink\CLI\Util\ExitCodes;
  8. use Shlinkio\Shlink\CLI\Util\ShlinkTable;
  9. use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
  10. use Shlinkio\Shlink\Common\Util\DateRange;
  11. use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
  12. use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
  13. use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
  14. use Symfony\Component\Console\Input\InputInterface;
  15. use Symfony\Component\Console\Input\InputOption;
  16. use Symfony\Component\Console\Output\OutputInterface;
  17. use Symfony\Component\Console\Style\SymfonyStyle;
  18. use function array_flip;
  19. use function array_intersect_key;
  20. use function array_values;
  21. use function count;
  22. use function explode;
  23. use function implode;
  24. use function sprintf;
  25. class ListShortUrlsCommand extends AbstractWithDateRangeCommand
  26. {
  27. use PaginatorUtilsTrait;
  28. public const NAME = 'short-url:list';
  29. private const COLUMNS_WHITELIST = [
  30. 'shortCode',
  31. 'shortUrl',
  32. 'longUrl',
  33. 'dateCreated',
  34. 'visitsCount',
  35. 'tags',
  36. ];
  37. private ShortUrlServiceInterface $shortUrlService;
  38. private ShortUrlDataTransformer $transformer;
  39. public function __construct(ShortUrlServiceInterface $shortUrlService, array $domainConfig)
  40. {
  41. parent::__construct();
  42. $this->shortUrlService = $shortUrlService;
  43. $this->transformer = new ShortUrlDataTransformer($domainConfig);
  44. }
  45. protected function doConfigure(): void
  46. {
  47. $this
  48. ->setName(self::NAME)
  49. ->setDescription('List all short URLs')
  50. ->addOption(
  51. 'page',
  52. 'p',
  53. InputOption::VALUE_REQUIRED,
  54. sprintf('The first page to list (%s items per page)', ShortUrlRepositoryAdapter::ITEMS_PER_PAGE),
  55. '1',
  56. )
  57. ->addOption(
  58. 'searchTerm',
  59. 'st',
  60. InputOption::VALUE_REQUIRED,
  61. 'A query used to filter results by searching for it on the longUrl and shortCode fields',
  62. )
  63. ->addOption(
  64. 'tags',
  65. 't',
  66. InputOption::VALUE_REQUIRED,
  67. 'A comma-separated list of tags to filter results',
  68. )
  69. ->addOption(
  70. 'orderBy',
  71. 'o',
  72. InputOption::VALUE_REQUIRED,
  73. 'The field from which we want to order by. Pass ASC or DESC separated by a comma',
  74. )
  75. ->addOption('showTags', null, InputOption::VALUE_NONE, 'Whether to display the tags or not');
  76. }
  77. protected function getStartDateDesc(): string
  78. {
  79. return 'Allows to filter short URLs, returning only those created after "startDate"';
  80. }
  81. protected function getEndDateDesc(): string
  82. {
  83. return 'Allows to filter short URLs, returning only those created before "endDate"';
  84. }
  85. protected function execute(InputInterface $input, OutputInterface $output): ?int
  86. {
  87. $io = new SymfonyStyle($input, $output);
  88. $page = (int) $input->getOption('page');
  89. $searchTerm = $input->getOption('searchTerm');
  90. $tags = $input->getOption('tags');
  91. $tags = ! empty($tags) ? explode(',', $tags) : [];
  92. $showTags = (bool) $input->getOption('showTags');
  93. $startDate = $this->getDateOption($input, $output, 'startDate');
  94. $endDate = $this->getDateOption($input, $output, 'endDate');
  95. $orderBy = $this->processOrderBy($input);
  96. do {
  97. $result = $this->renderPage($output, $page, $searchTerm, $tags, $showTags, $startDate, $endDate, $orderBy);
  98. $page++;
  99. $continue = $this->isLastPage($result)
  100. ? false
  101. : $io->confirm(sprintf('Continue with page <options=bold>%s</>?', $page), false);
  102. } while ($continue);
  103. $io->newLine();
  104. $io->success('Short URLs properly listed');
  105. return ExitCodes::EXIT_SUCCESS;
  106. }
  107. /**
  108. * @param string|array|null $orderBy
  109. */
  110. private function renderPage(
  111. OutputInterface $output,
  112. int $page,
  113. ?string $searchTerm,
  114. array $tags,
  115. bool $showTags,
  116. ?Chronos $startDate,
  117. ?Chronos $endDate,
  118. $orderBy
  119. ): Paginator {
  120. $result = $this->shortUrlService->listShortUrls(
  121. $page,
  122. $searchTerm,
  123. $tags,
  124. $orderBy,
  125. new DateRange($startDate, $endDate),
  126. );
  127. $headers = ['Short code', 'Short URL', 'Long URL', 'Date created', 'Visits count'];
  128. if ($showTags) {
  129. $headers[] = 'Tags';
  130. }
  131. $rows = [];
  132. foreach ($result as $row) {
  133. $shortUrl = $this->transformer->transform($row);
  134. if ($showTags) {
  135. $shortUrl['tags'] = implode(', ', $shortUrl['tags']);
  136. } else {
  137. unset($shortUrl['tags']);
  138. }
  139. $rows[] = array_values(array_intersect_key($shortUrl, array_flip(self::COLUMNS_WHITELIST)));
  140. }
  141. ShlinkTable::fromOutput($output)->render($headers, $rows, $this->formatCurrentPageMessage(
  142. $result,
  143. 'Page %s of %s',
  144. ));
  145. return $result;
  146. }
  147. /**
  148. * @return array|string|null
  149. */
  150. private function processOrderBy(InputInterface $input)
  151. {
  152. $orderBy = $input->getOption('orderBy');
  153. if (empty($orderBy)) {
  154. return null;
  155. }
  156. $orderBy = explode(',', $orderBy);
  157. return count($orderBy) === 1 ? $orderBy[0] : [$orderBy[0] => $orderBy[1]];
  158. }
  159. }