diff --git a/README.md b/README.md index ab00909..e8e381e 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,14 @@ $callback_data_format = 'command={COMMAND}&oldPage={OLD_PAGE}&newPage={NEW_PAGE} // Define inline keyboard pagination. $ikp = new InlineKeyboardPagination($items, $command); $ikp->setMaxButtons(7, true); // Second parameter set to always show 7 buttons if possible. +$ikp->setRangeOffset(1); //optional: if you change offsets of selected page, you can use this method. e.g if selected page is 6 and Range offset set as 1, you will have 5 & 7 in pagination $ikp->setLabels($labels); + +/* + *optional: But recommended. if you want that max_page will set according to labels you defined, + * please call this method. if you remove $label elements and then call this method, max_page will be defined according to labels + */ +$ikp->setMaxButtonsBasedOnLabels(); $ikp->setCallbackDataFormat($callback_data_format); // Get pagination. diff --git a/src/InlineKeyboardPagination.php b/src/InlineKeyboardPagination.php index 926d695..350ebaf 100644 --- a/src/InlineKeyboardPagination.php +++ b/src/InlineKeyboardPagination.php @@ -14,42 +14,47 @@ class InlineKeyboardPagination implements InlineKeyboardPaginator /** * @var integer */ - private $items_per_page; + protected $items_per_page; /** * @var integer */ - private $max_buttons = 5; + protected $max_buttons = 5; /** * @var bool */ - private $force_button_count = false; + protected $force_button_count = false; /** * @var integer */ - private $selected_page; + protected $selected_page; /** * @var array */ - private $items; + protected $items; + + /** + * @var integer + */ + protected $range_offset = 1; /** * @var string */ - private $command; + protected $command; /** * @var string */ - private $callback_data_format = 'command={COMMAND}&oldPage={OLD_PAGE}&newPage={NEW_PAGE}'; + protected $callback_data_format = 'command={COMMAND}&oldPage={OLD_PAGE}&newPage={NEW_PAGE}'; /** * @var array */ - private $labels = [ + protected $labels = [ 'default' => '%d', 'first' => '« %d', 'previous' => '‹ %d', @@ -64,10 +69,10 @@ class InlineKeyboardPagination implements InlineKeyboardPaginator */ public function setMaxButtons(int $max_buttons = 5, bool $force_button_count = false): InlineKeyboardPagination { - if ($max_buttons < 5 || $max_buttons > 8) { - throw new InlineKeyboardPaginationException('Invalid max buttons, must be between 5 and 8.'); + if ($max_buttons < 3 || $max_buttons > 8) { + throw new InlineKeyboardPaginationException('Invalid max buttons, must be between 3 and 8.'); } - $this->max_buttons = $max_buttons; + $this->max_buttons = $max_buttons; $this->force_button_count = $force_button_count; return $this; @@ -138,8 +143,19 @@ public function setCommand(string $command = 'pagination'): InlineKeyboardPagina public function setSelectedPage(int $selected_page): InlineKeyboardPagination { $number_of_pages = $this->getNumberOfPages(); - if ($selected_page < 1 || $selected_page > $number_of_pages) { - throw new InlineKeyboardPaginationException('Invalid selected page, must be between 1 and ' . $number_of_pages); + /*if ($selected_page < 1 || $selected_page > $number_of_pages) { + throw new CustomInlineKeyboardPaginationException('Invalid selected page, must be between 1 and ' . $number_of_pages); + }*/ + + // if current page is greater than total pages... + if ($selected_page > $number_of_pages) { + // set current page to last page + $selected_page = $number_of_pages; + } + // if current page is less than first page... + if ($selected_page < 1) { + // set current page to first page + $selected_page = 1; } $this->selected_page = $selected_page; @@ -192,6 +208,63 @@ public function setItems(array $items): InlineKeyboardPagination return $this; } + /** + * Set max number of pages based on labels which user defined + * + * @return InlineKeyboardPagination + * @throws InlineKeyboardPaginationException + */ + public function setMaxPageBasedOnLabels(): InlineKeyboardPagination + { + $max_buttons = 0; + $count = count($this->labels); + if ($count < 2) { + throw new InlineKeyboardPaginationException('Invalid number of labels was passed to paginator'); + } + + if (isset($this->labels['current'])) { + $max_buttons++; + } + + if (isset($this->labels['first'])) { + $max_buttons++; + } + + if (isset($this->labels['last'])) { + $max_buttons++; + } + + if (isset($this->labels['previous'])) { + $max_buttons++; + } + + if (isset($this->labels['next'])) { + $max_buttons++; + } + $max_buttons += $this->range_offset * 2; + + $this->max_buttons = $max_buttons; + + return $this; + } + + /** + * Set offset of range + * + * @return InlineKeyboardPagination + * @throws InlineKeyboardPaginationException + */ + public function setRangeOffset($offset): InlineKeyboardPagination + { + if ($offset < 0 || !is_numeric($offset)) { + throw new InlineKeyboardPaginationException('Invalid offset for range'); + } + + $this->range_offset = $offset; + + return $this; + } + /** * Calculate and return the number of pages. * @@ -199,7 +272,7 @@ public function setItems(array $items): InlineKeyboardPagination */ public function getNumberOfPages(): int { - return (int) ceil(count($this->items) / $this->items_per_page); + return (int)ceil(count($this->items) / $this->items_per_page); } /** @@ -208,8 +281,12 @@ public function getNumberOfPages(): int * @inheritdoc * @throws InlineKeyboardPaginationException */ - public function __construct(array $items, string $command = 'pagination', int $selected_page = 1, int $items_per_page = 5) - { + public function __construct( + array $items, + string $command = 'pagination', + int $selected_page = 1, + int $items_per_page = 5 + ) { $this->setCommand($command); $this->setItemsPerPage($items_per_page); $this->setItems($items); @@ -239,41 +316,56 @@ public function getPagination(int $selected_page = null): array */ protected function generateKeyboard(): array { - $buttons = []; + $buttons = []; $number_of_pages = $this->getNumberOfPages(); + if ($number_of_pages === 1) { + return $buttons; + } + if ($number_of_pages > $this->max_buttons) { - $buttons[1] = $this->generateButton(1); + if ($this->selected_page > 1) { + // get previous page num + $buttons[] = $this->generateButton($this->selected_page - 1, 'previous'); + } + // for first pages + if ($this->selected_page > $this->range_offset + 1 && $number_of_pages >= $this->max_buttons) { + $buttons[] = $this->generateButton(1, 'first'); + } - $range = $this->generateRange(); - for ($i = $range['from']; $i < $range['to']; $i++) { - $buttons[$i] = $this->generateButton($i); + $range_offsets = $this->generateRange(); + // loop to show links to range of pages around current page + for ($i = $range_offsets['from']; $i < $range_offsets['to']; $i++) { + // if it's a valid page number... + if ($i == $this->selected_page) { + $buttons[] = $this->generateButton($this->selected_page, 'current'); + } elseif (($i > 0) && ($i <= $number_of_pages)) { + $buttons[] = $this->generateButton($i, 'default'); + } } - $buttons[$number_of_pages] = $this->generateButton($number_of_pages); + // if not on last page, show forward and last page links + if ($this->selected_page + $this->range_offset < $number_of_pages && $number_of_pages >= $this->max_buttons) { + $buttons[] = $this->generateButton($number_of_pages, 'last'); + } + if ($this->selected_page != $number_of_pages && $number_of_pages > 1) { + $buttons[] = $this->generateButton($this->selected_page + 1, 'next'); + } } else { for ($i = 1; $i <= $number_of_pages; $i++) { - $buttons[$i] = $this->generateButton($i); + // if it's a valid page number... + if ($i == $this->selected_page) { + $buttons[] = $this->generateButton($this->selected_page, 'current'); + } elseif (($i > 0) && ($i <= $number_of_pages)) { + $buttons[] = $this->generateButton($i, 'default'); + } } } // Set the correct labels. foreach ($buttons as $page => &$button) { - $in_first_block = $this->selected_page <= 3 && $page <= 3; - $in_last_block = $this->selected_page >= $number_of_pages - 2 && $page >= $number_of_pages - 2; - - $label_key = 'next'; - if ($page === $this->selected_page) { - $label_key = 'current'; - } elseif ($in_first_block || $in_last_block) { - $label_key = 'default'; - } elseif ($page === 1) { - $label_key = 'first'; - } elseif ($page === $number_of_pages) { - $label_key = 'last'; - } elseif ($page < $this->selected_page) { - $label_key = 'previous'; - } + + $label_key = $button['label']; $label = $this->labels[$label_key] ?? ''; @@ -282,7 +374,7 @@ protected function generateKeyboard(): array continue; } - $button['text'] = sprintf($label, $page); + $button['text'] = sprintf($label, $button['text']); } return array_values(array_filter($buttons)); @@ -295,32 +387,16 @@ protected function generateKeyboard(): array */ protected function generateRange(): array { - $number_of_intermediate_buttons = $this->max_buttons - 2; - $number_of_pages = $this->getNumberOfPages(); - - if ($this->selected_page === 1) { - $from = 2; - $to = $this->max_buttons; - } elseif ($this->selected_page === $number_of_pages) { - $from = $number_of_pages - $number_of_intermediate_buttons; - $to = $number_of_pages; - } else { - if ($this->selected_page < 3) { - $from = $this->selected_page; - $to = $this->selected_page + $number_of_intermediate_buttons; - } elseif (($number_of_pages - $this->selected_page) < 3) { - $from = $number_of_pages - $number_of_intermediate_buttons; - $to = $number_of_pages; - } else { - // @todo: Find a nicer solution for page 3 - if ($this->force_button_count) { - $from = $this->selected_page - floor($number_of_intermediate_buttons / 2); - $to = $this->selected_page + ceil($number_of_intermediate_buttons / 2) + ($this->selected_page === 3 && $this->max_buttons > 5); - } else { - $from = $this->selected_page - 1; - $to = $this->selected_page + ($this->selected_page === 3 ? $number_of_intermediate_buttons - 1 : 2); - } - } + $number_of_pages = $this->getNumberOfPages(); + + $from = $this->selected_page - $this->range_offset; + $to = (($this->selected_page + $this->range_offset) + 1); + $last = $number_of_pages - $this->selected_page; + if ($number_of_pages - $this->selected_page <= $this->range_offset) { + $from -= ($this->range_offset) - $last; + } + if ($this->selected_page < $this->range_offset + 1) { + $to += ($this->range_offset + 1) - $this->selected_page; } return compact('from', 'to'); @@ -330,14 +406,16 @@ protected function generateRange(): array * Generate the button for the passed page. * * @param int $page + * @param string $label * * @return array */ - protected function generateButton(int $page): array + protected function generateButton(int $page, string $label): array { return [ - 'text' => (string) $page, + 'text' => (string)$page, 'callback_data' => $this->generateCallbackData($page), + 'label' => $label, ]; } @@ -380,7 +458,7 @@ protected function getOffset(): int /** * Get the parameters from the callback query. * - * @todo Possibly make it work for custon formats too? + * @todo Possibly make it work for custom formats too? * * @param string $data * diff --git a/tests/InlineKeyboardPaginationTest.php b/tests/InlineKeyboardPaginationTest.php index 18f576d..2feb4cf 100644 --- a/tests/InlineKeyboardPaginationTest.php +++ b/tests/InlineKeyboardPaginationTest.php @@ -7,334 +7,469 @@ /** * Class InlineKeyboardPaginationTest */ -final class InlineKeyboardPaginationTest extends \PHPUnit\Framework\TestCase + +/** + * Class InlineKeyboardPagination + * Based on https://github.com/php-telegram-bot/inline-keyboard-pagination + * + * @package MehrdadKhoddami\TelegramBot\InlineKeyboardPagination + */ +class InlineKeyboardPagination implements InlineKeyboardPaginator { /** - * @var int + * @var integer + */ + protected $items_per_page; + + /** + * @var integer + */ + protected $max_buttons = 5; + + /** + * @var bool + */ + protected $force_button_count = false; + + /** + * @var integer + */ + protected $selected_page; + + /** + * @var array */ - private $items_per_page = 5; + protected $items; /** - * @var int + * @var integer + */ + protected $range_offset = 1; + + /** + * @var string */ - private $selected_page; + protected $command; /** * @var string */ - private $command; + protected $callback_data_format = 'command={COMMAND}&oldPage={OLD_PAGE}&newPage={NEW_PAGE}'; /** * @var array */ - private $items; + protected $labels = [ + 'default' => '%d', + 'first' => '« %d', + 'previous' => '‹ %d', + 'current' => '· %d ·', + 'next' => '%d ›', + 'last' => '%d »', + ]; /** - * InlineKeyboardPaginationTest constructor. + * @inheritdoc + * @throws InlineKeyboardPaginationException */ - public function __construct() + public function setMaxButtons(int $max_buttons = 5, bool $force_button_count = false): InlineKeyboardPagination { - parent::__construct(); + if ($max_buttons < 2 || $max_buttons > 8) { + throw new InlineKeyboardPaginationException('Invalid max buttons, must be between 2 and 8.'); + } + $this->max_buttons = $max_buttons; + $this->force_button_count = $force_button_count; - $this->items = range(1, 100); - $this->command = 'testCommand'; - $this->selected_page = random_int(1, 15); + return $this; } - public function testValidConstructor() + /** + * Set max number of buttons based on labels which user defined & range of selected page + * + * @return InlineKeyboardPagination + * @throws InlineKeyboardPaginationException + */ + public function setMaxButtonsBasedOnLabels(): InlineKeyboardPagination { - $ikp = new InlineKeyboardPagination($this->items, $this->command, $this->selected_page, $this->items_per_page); + $max_buttons = 0; + $count = count($this->labels); + if ($count < 2) { + throw new InlineKeyboardPaginationException('Invalid number of labels was passed to paginator'); + } + + if (isset($this->labels['current'])) { + $max_buttons++; + } - $data = $ikp->getPagination(); + if (isset($this->labels['first'])) { + $max_buttons++; + } + + if (isset($this->labels['last'])) { + $max_buttons++; + } + + if (isset($this->labels['previous'])) { + $max_buttons++; + } + + if (isset($this->labels['next'])) { + $max_buttons++; + } + $max_buttons += $this->range_offset * 2; - $this->assertArrayHasKey('items', $data); - $this->assertCount($this->items_per_page, $data['items']); - $this->assertArrayHasKey('keyboard', $data); - $this->assertArrayHasKey(0, $data['keyboard']); - $this->assertArrayHasKey('text', $data['keyboard'][0]); - $this->assertStringStartsWith("command={$this->command}", $data['keyboard'][0]['callback_data']); + $this->max_buttons = $max_buttons; + + return $this; } /** - * @expectedException \TelegramBot\InlineKeyboardPagination\Exceptions\InlineKeyboardPaginationException - * @expectedExceptionMessage Invalid selected page, must be between 1 and 20 + * Get the current callback format. + * + * @return string */ - public function testInvalidConstructor() + public function getCallbackDataFormat(): string { - $ikp = new InlineKeyboardPagination($this->items, $this->command, 10000, $this->items_per_page); - $ikp->getPagination(); + return $this->callback_data_format; } /** - * @expectedException \TelegramBot\InlineKeyboardPagination\Exceptions\InlineKeyboardPaginationException - * @expectedExceptionMessage Items list empty. + * Set the callback_data format. + * + * @param string $callback_data_format + * + * @return InlineKeyboardPagination */ - public function testEmptyItemsConstructor() + public function setCallbackDataFormat(string $callback_data_format): InlineKeyboardPagination { - $ikp = new InlineKeyboardPagination([]); - $ikp->getPagination(); + $this->callback_data_format = $callback_data_format; + + return $this; } - public function testCallbackDataFormat() + /** + * Return list of keyboard button labels. + * + * @return array + */ + public function getLabels(): array { - $ikp = new InlineKeyboardPagination(range(1, 10), 'cmd', 2, 5); - - self::assertAllButtonPropertiesEqual([ - [ - 'command=cmd&oldPage=2&newPage=1', - 'command=cmd&oldPage=2&newPage=2', - ], - ], 'callback_data', [$ikp->getPagination()['keyboard']]); - - $ikp->setCallbackDataFormat('{COMMAND};{OLD_PAGE};{NEW_PAGE}'); - - self::assertAllButtonPropertiesEqual([ - [ - 'cmd;2;1', - 'cmd;2;2', - ], - ], 'callback_data', [$ikp->getPagination()['keyboard']]); + return $this->labels; } - public function testCallbackDataParser() + /** + * Set the keyboard button labels. + * + * @param array $labels + * + * @return InlineKeyboardPagination + */ + public function setLabels($labels): InlineKeyboardPagination { - $ikp = new InlineKeyboardPagination($this->items, $this->command, $this->selected_page, $this->items_per_page); - $data = $ikp->getPagination(); + $this->labels = $labels; - $callback_data = $ikp::getParametersFromCallbackData($data['keyboard'][0]['callback_data']); + return $this; + } - self::assertSame([ - 'command' => $this->command, - 'oldPage' => "$this->selected_page", - 'newPage' => '1', // because we're getting the button at position 0, which is page 1 - ], $callback_data); + /** + * @inheritdoc + */ + public function setCommand(string $command = 'pagination'): InlineKeyboardPagination + { + $this->command = $command; + + return $this; } - public function testValidPagination() + /** + * @inheritdoc + * @throws InlineKeyboardPaginationException + */ + public function setSelectedPage(int $selected_page): InlineKeyboardPagination { - $ikp = new InlineKeyboardPagination($this->items, $this->command, $this->selected_page, $this->items_per_page); + $number_of_pages = $this->getNumberOfPages(); - $length = (int) ceil(count($this->items) / $this->items_per_page); + // if current page is greater than total pages... + if ($selected_page > $number_of_pages) { + // set current page to last page + $selected_page = $number_of_pages; + } + // if current page is less than first page... + if ($selected_page < 1) { + // set current page to first page + $selected_page = 1; + } + $this->selected_page = $selected_page; + + return $this; + } + + /** + * Get the number of items shown per page. + * + * @return int + */ + public function getItemsPerPage(): int + { + return $this->items_per_page; + } - for ($i = 1; $i < $length; $i++) { - $ikp->getPagination($i); + /** + * Set how many items should be shown per page. + * + * @param int $items_per_page + * + * @return InlineKeyboardPagination + * @throws InlineKeyboardPaginationException + */ + public function setItemsPerPage($items_per_page): InlineKeyboardPagination + { + if ($items_per_page <= 0) { + throw new InlineKeyboardPaginationException('Invalid number of items per page, must be at least 1'); } + $this->items_per_page = $items_per_page; - $this->assertTrue(true); + return $this; } /** - * @expectedException \TelegramBot\InlineKeyboardPagination\Exceptions\InlineKeyboardPaginationException - * @expectedExceptionMessage Invalid selected page, must be between 1 and 20 + * Set the items for the pagination. + * + * @param array $items + * + * @return InlineKeyboardPagination + * @throws InlineKeyboardPaginationException */ - public function testInvalidPagination() + public function setItems(array $items): InlineKeyboardPagination { - $ikp = new InlineKeyboardPagination($this->items, $this->command, $this->selected_page, $this->items_per_page); - $ikp->getPagination($ikp->getNumberOfPages() + 1); + if (empty($items)) { + throw new InlineKeyboardPaginationException('Items list empty.'); + } + $this->items = $items; + + return $this; } - public function testSetMaxButtons() + /** + * Set offset of range + * + * @return InlineKeyboardPagination + * @throws InlineKeyboardPaginationException + */ + public function setRangeOffset($offset): InlineKeyboardPagination { - $ikp = new InlineKeyboardPagination($this->items, $this->command, $this->selected_page, $this->items_per_page); - $ikp->setMaxButtons(6); - self::assertTrue(true); + if ($offset < 0 || !is_numeric($offset)) { + throw new InlineKeyboardPaginationException('Invalid offset for range'); + } + + $this->range_offset = $offset; + + return $this; } - public function testForceButtonsCount() + /** + * Calculate and return the number of pages. + * + * @return int + */ + public function getNumberOfPages(): int { - $ikp = new InlineKeyboardPagination(range(1, 10), 'cbdata', 1, 1); + return (int)ceil(count($this->items) / $this->items_per_page); + } - // testing with 8 flexible buttons - $ikp->setMaxButtons(8, false); + /** + * TelegramBotPagination constructor. + * + * @inheritdoc + * @throws InlineKeyboardPaginationException + */ + public function __construct( + array $items, + string $command = 'pagination', + int $selected_page = 1, + int $items_per_page = 5 + ) { + $this->setCommand($command); + $this->setItemsPerPage($items_per_page); + $this->setItems($items); + $this->setSelectedPage($selected_page); + } - self::assertAllButtonPropertiesEqual([ - ['· 1 ·', '2', '3', '4 ›', '5 ›', '6 ›', '7 ›', '10 »'], - ], 'text', [$ikp->getPagination(1)['keyboard']]); + /** + * @inheritdoc + * @throws InlineKeyboardPaginationException + */ + public function getPagination(int $selected_page = null): array + { + if ($selected_page !== null) { + $this->setSelectedPage($selected_page); + } + + return [ + 'items' => $this->getPreparedItems(), + 'keyboard' => $this->generateKeyboard(), + ]; + } - self::assertAllButtonPropertiesEqual([ - ['« 1', '‹ 4', '· 5 ·', '6 ›', '10 »'], - ], 'text', [$ikp->getPagination(5)['keyboard']]); + /** + * Generate the keyboard with the correctly labelled buttons. + * + * @return array + */ + protected function generateKeyboard(): array + { + $buttons = []; + $number_of_pages = $this->getNumberOfPages(); - // testing with 8 fixed buttons - $ikp->setMaxButtons(8, true); + if ($number_of_pages === 1) { + return $buttons; + } - self::assertAllButtonPropertiesEqual([ - ['· 1 ·', '2', '3', '4 ›', '5 ›', '6 ›', '7 ›', '10 »'], - ], 'text', [$ikp->getPagination(1)['keyboard']]); + if ($number_of_pages > $this->max_buttons) { + if ($this->selected_page > 1) { + // get previous page num + $buttons[] = $this->generateButton($this->selected_page - 1, 'previous'); + } + // for first pages + if ($this->selected_page > $this->range_offset + 1 && $number_of_pages >= $this->max_buttons) { + $buttons[] = $this->generateButton(1, 'first'); + } + + $range_offsets = $this->generateRange(); + // loop to show links to range of pages around current page + for ($i = $range_offsets['from']; $i < $range_offsets['to']; $i++) { + // if it's a valid page number... + if ($i == $this->selected_page) { + $buttons[] = $this->generateButton($this->selected_page, 'current'); + } elseif (($i > 0) && ($i <= $number_of_pages)) { + $buttons[] = $this->generateButton($i, 'default'); + } + } + + // if not on last page, show forward and last page links + if ($this->selected_page + $this->range_offset < $number_of_pages && $number_of_pages >= $this->max_buttons) { + $buttons[] = $this->generateButton($number_of_pages, 'last'); + } + if ($this->selected_page != $number_of_pages && $number_of_pages > 1) { + $buttons[] = $this->generateButton($this->selected_page + 1, 'next'); + } + } else { + for ($i = 1; $i <= $number_of_pages; $i++) { + // if it's a valid page number... + if ($i == $this->selected_page) { + $buttons[] = $this->generateButton($this->selected_page, 'current'); + } elseif (($i > 0) && ($i <= $number_of_pages)) { + $buttons[] = $this->generateButton($i, 'default'); + } + } + } - self::assertAllButtonPropertiesEqual([ - ['· 1 ·', '2', '3', '4 ›', '5 ›', '6 ›', '7 ›', '10 »'], - ], 'text', [$ikp->getPagination(1)['keyboard']]); + // Set the correct labels. + foreach ($buttons as $page => &$button) { - self::assertAllButtonPropertiesEqual([ - ['« 1', '‹ 2', '‹ 3', '‹ 4', '· 5 ·', '6 ›', '7 ›', '10 »'], - ], 'text', [$ikp->getPagination(5)['keyboard']]); + $label_key = $button['label']; - // testing with 7 fixed buttons - $ikp->setMaxButtons(7, true); + $label = $this->labels[$label_key] ?? ''; - self::assertAllButtonPropertiesEqual([ - ['· 1 ·', '2', '3', '4 ›', '5 ›', '6 ›', '10 »'], - ], 'text', [$ikp->getPagination(1)['keyboard']]); + if ($label === '') { + $button = null; + continue; + } - self::assertAllButtonPropertiesEqual([ - ['« 1', '‹ 3', '‹ 4', '· 5 ·', '6 ›', '7 ›', '10 »'], - ], 'text', [$ikp->getPagination(5)['keyboard']]); + $button['text'] = sprintf($label, $button['text']); + } - self::assertAllButtonPropertiesEqual([ - ['« 1', '‹ 5', '‹ 6', '‹ 7', '8', '9', '· 10 ·'], - ], 'text', [$ikp->getPagination(10)['keyboard']]); + return array_values(array_filter($buttons)); } /** - * @expectedException \TelegramBot\InlineKeyboardPagination\Exceptions\InlineKeyboardPaginationException - * @expectedExceptionMessage Invalid max buttons, must be between 5 and 8. + * Get the range of intermediate buttons for the keyboard. + * + * @return array */ - public function testInvalidMaxButtons() + protected function generateRange(): array { - $ikp = new InlineKeyboardPagination($this->items, $this->command, $this->selected_page, $this->items_per_page); - $ikp->setMaxButtons(2); - $ikp->getPagination(); + $number_of_pages = $this->getNumberOfPages(); + + $from = $this->selected_page - $this->range_offset; + $to = (($this->selected_page + $this->range_offset) + 1); + $last = $number_of_pages - $this->selected_page; + if ($number_of_pages - $this->selected_page <= $this->range_offset) { + $from -= ($this->range_offset) - $last; + } + if ($this->selected_page < $this->range_offset + 1) { + $to += ($this->range_offset + 1) - $this->selected_page; + } + + return compact('from', 'to'); } /** - * @expectedException \TelegramBot\InlineKeyboardPagination\Exceptions\InlineKeyboardPaginationException - * @expectedExceptionMessage Invalid selected page, must be between 1 and 20 + * Generate the button for the passed page. + * + * @param int $page + * @param string $label + * + * @return array */ - public function testInvalidSelectedPage() - { - $ikp = new InlineKeyboardPagination($this->items, $this->command, $this->selected_page, $this->items_per_page); - $ikp->setSelectedPage(-5); - $ikp->getPagination(); - } - - public function testGetItemsPerPage() + protected function generateButton(int $page, string $label): array { - $ikp = new InlineKeyboardPagination($this->items, $this->command, $this->selected_page, 4); - self::assertEquals(4, $ikp->getItemsPerPage()); + return [ + 'text' => (string)$page, + 'callback_data' => $this->generateCallbackData($page), + 'label' => $label, + ]; } /** - * @expectedException \TelegramBot\InlineKeyboardPagination\Exceptions\InlineKeyboardPaginationException - * @expectedExceptionMessage Invalid number of items per page, must be at least 1 + * Generate the callback data for the passed page. + * + * @param int $page + * + * @return string */ - public function testInvalidItemsPerPage() + protected function generateCallbackData(int $page): string { - $ikp = new InlineKeyboardPagination($this->items, $this->command, $this->selected_page, 0); - $ikp->getPagination(); + return str_replace( + ['{COMMAND}', '{OLD_PAGE}', '{NEW_PAGE}'], + [$this->command, $this->selected_page, $page], + $this->callback_data_format + ); } - public function testButtonLabels() + /** + * Get the prepared items for the selected page. + * + * @return array + */ + protected function getPreparedItems(): array { - $cbdata = 'command=%s&oldPage=%d&newPage=%d'; - $command = 'cbdata'; - $ikp1 = new InlineKeyboardPagination(range(1, 1), $command, 1, $this->items_per_page); - $ikp10 = new InlineKeyboardPagination(range(1, $this->items_per_page * 10), $command, 1, $this->items_per_page); - - // current - $keyboard = [$ikp1->getPagination(1)['keyboard']]; - self::assertAllButtonPropertiesEqual([ - ['· 1 ·'], - ], 'text', $keyboard); - self::assertAllButtonPropertiesEqual([ - [ - sprintf($cbdata, $command, 1, 1), - ], - ], 'callback_data', $keyboard); - - // first, previous, current, next, last - $keyboard = [$ikp10->getPagination(5)['keyboard']]; - self::assertAllButtonPropertiesEqual([ - ['« 1', '‹ 4', '· 5 ·', '6 ›', '10 »'], - ], 'text', $keyboard); - self::assertAllButtonPropertiesEqual([ - [ - sprintf($cbdata, $command, 5, 1), - sprintf($cbdata, $command, 5, 4), - sprintf($cbdata, $command, 5, 5), - sprintf($cbdata, $command, 5, 6), - sprintf($cbdata, $command, 5, 10), - ], - ], 'callback_data', $keyboard); - - // first, previous, current, last - $keyboard = [$ikp10->getPagination(9)['keyboard']]; - self::assertAllButtonPropertiesEqual([ - ['« 1', '‹ 7', '8', '· 9 ·', '10'], - ], 'text', $keyboard); - self::assertAllButtonPropertiesEqual([ - [ - sprintf($cbdata, $command, 9, 1), - sprintf($cbdata, $command, 9, 7), - sprintf($cbdata, $command, 9, 8), - sprintf($cbdata, $command, 9, 9), - sprintf($cbdata, $command, 9, 10), - ], - ], 'callback_data', $keyboard); - - // first, previous, current - $keyboard = [$ikp10->getPagination(10)['keyboard']]; - self::assertAllButtonPropertiesEqual([ - ['« 1', '‹ 7', '8', '9', '· 10 ·'], - ], 'text', $keyboard); - self::assertAllButtonPropertiesEqual([ - [ - sprintf($cbdata, $command, 10, 1), - sprintf($cbdata, $command, 10, 7), - sprintf($cbdata, $command, 10, 8), - sprintf($cbdata, $command, 10, 9), - sprintf($cbdata, $command, 10, 10), - ], - ], 'callback_data', $keyboard); - - // custom labels, skipping some buttons - // first, previous, current, next, last - $labels = [ - 'first' => '', - 'previous' => 'previous %d', - 'current' => null, - 'next' => '%d next', - 'last' => 'last', - ]; - $ikp10->setLabels($labels); - self::assertEquals($labels, $ikp10->getLabels()); - - $keyboard = [$ikp10->getPagination(5)['keyboard']]; - self::assertAllButtonPropertiesEqual([ - ['previous 4', '6 next', 'last'], - ], 'text', $keyboard); - self::assertAllButtonPropertiesEqual([ - [ - sprintf($cbdata, $command, 5, 4), - sprintf($cbdata, $command, 5, 6), - sprintf($cbdata, $command, 5, 10), - ], - ], 'callback_data', $keyboard); + return array_slice($this->items, $this->getOffset(), $this->items_per_page); } - public static function assertButtonPropertiesEqual($value, $property, $keyboard, $row, $column, $message = '') + /** + * Get the items offset for the selected page. + * + * @return int + */ + protected function getOffset(): int { - $row_raw = array_values($keyboard)[$row]; - $column_raw = array_values($row_raw)[$column]; - - self::assertSame($value, $column_raw[$property], $message); + return $this->items_per_page * ($this->selected_page - 1); } - public static function assertRowButtonPropertiesEqual(array $values, $property, $keyboard, $row, $message = '') + /** + * Get the parameters from the callback query. + * + * @todo Possibly make it work for custom formats too? + * + * @param string $data + * + * @return array + */ + public static function getParametersFromCallbackData($data): array { - $column = 0; - foreach ($values as $value) { - self::assertButtonPropertiesEqual($value, $property, $keyboard, $row, $column++, $message); - } - self::assertCount(count(array_values($keyboard)[$row]), $values); - } + parse_str($data, $params); - public static function assertAllButtonPropertiesEqual(array $all_values, $property, $keyboard, $message = '') - { - $row = 0; - foreach ($all_values as $values) { - self::assertRowButtonPropertiesEqual($values, $property, $keyboard, $row++, $message); - } - self::assertCount(count($keyboard), $all_values); + return $params; } -} +} \ No newline at end of file