Completed
Branch master (274637)
by Armando
01:30
created

InlineKeyboardPagination::getItemsPerPage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace TelegramBot\InlineKeyboardPagination;
4
5
use TelegramBot\InlineKeyboardPagination\Exceptions\InlineKeyboardPaginationException;
6
7
/**
8
 * Class InlineKeyboardPagination
9
 *
10
 * @package TelegramBot\InlineKeyboardPagination
11
 */
12
class InlineKeyboardPagination implements InlineKeyboardPaginator
13
{
14
    /**
15
     * @var integer
16
     */
17
    private $items_per_page;
18
19
    /**
20
     * @var integer
21
     */
22
    private $max_buttons = 5;
23
24
    /**
25
     * @var integer
26
     */
27
    private $selected_page;
28
29
    /**
30
     * @var array
31
     */
32
    private $items;
33
34
    /**
35
     * @var string
36
     */
37
    private $command;
38
39
    /**
40
     * @var array
41
     */
42
    private $labels = [
43
        'default'  => '%d',
44
        'first'    => '« %d',
45
        'previous' => '‹ %d',
46
        'current'  => '· %d ·',
47
        'next'     => '%d ›',
48
        'last'     => '%d »',
49
    ];
50
51
    /**
52
     * @inheritdoc
53
     * @throws InlineKeyboardPaginationException
54
     */
55
    public function setMaxButtons(int $max_buttons = 5): InlineKeyboardPagination
56
    {
57
        if ($max_buttons < 5 || $max_buttons > 8) {
58
            throw new InlineKeyboardPaginationException('Invalid max buttons, must be between 5 and 8.');
59
        }
60
        $this->max_buttons = $max_buttons;
61
62
        return $this;
63
    }
64
65
    /**
66
     * Return list of keyboard button labels.
67
     *
68
     * @return array
69
     */
70
    public function getLabels(): array
71
    {
72
        return $this->labels;
73
    }
74
75
    /**
76
     * Set the keyboard button labels.
77
     *
78
     * @param array $labels
79
     *
80
     * @return InlineKeyboardPagination
81
     */
82
    public function setLabels($labels): InlineKeyboardPagination
83
    {
84
        $this->labels = $labels;
85
86
        return $this;
87
    }
88
89
    /**
90
     * @inheritdoc
91
     */
92
    public function setCommand(string $command = 'pagination'): InlineKeyboardPagination
93
    {
94
        $this->command = $command;
95
96
        return $this;
97
    }
98
99
    /**
100
     * @inheritdoc
101
     * @throws InlineKeyboardPaginationException
102
     */
103
    public function setSelectedPage(int $selected_page): InlineKeyboardPagination
104
    {
105
        $number_of_pages = $this->getNumberOfPages();
106
        if ($selected_page < 1 || $selected_page > $number_of_pages) {
107
            throw new InlineKeyboardPaginationException('Invalid selected page, must be between 1 and ' . $number_of_pages);
108
        }
109
        $this->selected_page = $selected_page;
110
111
        return $this;
112
    }
113
114
    /**
115
     * @return int
116
     */
117
    public function getItemsPerPage(): int
118
    {
119
        return $this->items_per_page;
120
    }
121
122
    /**
123
     * @param int $items_per_page
124
     *
125
     * @return InlineKeyboardPagination
126
     * @throws InlineKeyboardPaginationException
127
     */
128
    public function setItemsPerPage($items_per_page): InlineKeyboardPagination
129
    {
130
        if ($items_per_page <= 0) {
131
            throw new InlineKeyboardPaginationException('Invalid number of items per page, must be at least 1');
132
        }
133
        $this->items_per_page = $items_per_page;
134
135
        return $this;
136
    }
137
138
    /**
139
     * @param array $items
140
     *
141
     * @return InlineKeyboardPagination
142
     * @throws InlineKeyboardPaginationException
143
     */
144
    public function setItems(array $items): InlineKeyboardPagination
145
    {
146
        if (empty($items)) {
147
            throw new InlineKeyboardPaginationException('Items list empty.');
148
        }
149
        $this->items = $items;
150
151
        return $this;
152
    }
153
154
    /**
155
     * Calculate and return the number of pages.
156
     *
157
     * @return int
158
     */
159
    public function getNumberOfPages(): int
160
    {
161
        return (int) ceil(count($this->items) / $this->items_per_page);
162
    }
163
164
    /**
165
     * TelegramBotPagination constructor.
166
     *
167
     * @inheritdoc
168
     * @throws InlineKeyboardPaginationException
169
     */
170
    public function __construct(array $items, string $command = 'pagination', int $selected_page = 1, int $items_per_page = 5)
171
    {
172
        $this->setCommand($command);
173
        $this->setItemsPerPage($items_per_page);
174
        $this->setItems($items);
175
        $this->setSelectedPage($selected_page);
176
    }
177
178
    /**
179
     * @inheritdoc
180
     * @throws InlineKeyboardPaginationException
181
     */
182
    public function getPagination(int $selected_page = null): array
183
    {
184
        if ($selected_page !== null) {
185
            $this->setSelectedPage($selected_page);
186
        }
187
188
        return [
189
            'items'    => $this->getPreparedItems(),
190
            'keyboard' => [$this->generateKeyboard()],
191
        ];
192
    }
193
194
    /**
195
     * Generate the keyboard with the correctly labelled buttons.
196
     *
197
     * @return array
198
     */
199
    protected function generateKeyboard(): array
200
    {
201
        $buttons         = [];
202
        $number_of_pages = $this->getNumberOfPages();
203
204
        if ($number_of_pages > $this->max_buttons) {
205
            $buttons[1] = $this->generateButton(1);
206
207
            $range = $this->generateRange();
208
            for ($i = $range['from']; $i < $range['to']; $i++) {
209
                $buttons[$i] = $this->generateButton($i);
210
            }
211
212
            $buttons[$number_of_pages] = $this->generateButton($number_of_pages);
213
        } else {
214
            for ($i = 1; $i <= $number_of_pages; $i++) {
215
                $buttons[$i] = $this->generateButton($i);
216
            }
217
        }
218
219
        // Set the correct labels.
220
        foreach ($buttons as $page => &$button) {
221
            $in_first_block = $this->selected_page <= 3 && $page <= 3;
222
            $in_last_block  = $this->selected_page >= $number_of_pages - 2 && $page >= $number_of_pages - 2;
223
224
            $label_key = 'next';
225
            if ($page === $this->selected_page) {
226
                $label_key = 'current';
227
            } elseif ($in_first_block || $in_last_block) {
228
                $label_key = 'default';
229
            } elseif ($page === 1) {
230
                $label_key = 'first';
231
            } elseif ($page === $number_of_pages) {
232
                $label_key = 'last';
233
            } elseif ($page < $this->selected_page) {
234
                $label_key = 'previous';
235
            }
236
237
            $label = $this->labels[$label_key] ?? '';
238
239
            if ($label === '') {
240
                $button = null;
241
                continue;
242
            }
243
244
            $button['text'] = sprintf($label, $page);
245
        }
246
247
        return array_filter($buttons);
248
    }
249
250
    /**
251
     * Get the range of intermediate buttons for the keyboard.
252
     *
253
     * @return array
254
     */
255
    protected function generateRange(): array
256
    {
257
        $number_of_intermediate_buttons = $this->max_buttons - 2;
258
        $number_of_pages                = $this->getNumberOfPages();
259
260
        if ($this->selected_page === 1) {
261
            $from = 2;
262
            $to   = $from + $number_of_intermediate_buttons;
263
        } elseif ($this->selected_page === $number_of_pages) {
264
            $from = $number_of_pages - $number_of_intermediate_buttons;
265
            $to   = $number_of_pages;
266
        } else {
267
            if (($this->selected_page + $number_of_intermediate_buttons) > $number_of_pages) {
268
                $from = $number_of_pages - $number_of_intermediate_buttons;
269
                $to   = $number_of_pages;
270
            } elseif (($this->selected_page - 2) < 1) {
271
                $from = $this->selected_page;
272
                $to   = $this->selected_page + $number_of_intermediate_buttons;
273
            } else {
274
                $from = $this->selected_page - 1;
275
                $to   = $this->selected_page + 2;
276
            }
277
        }
278
279
        return compact('from', 'to');
280
    }
281
282
    /**
283
     * Generate the button for the passed page.
284
     *
285
     * @param int $page
286
     *
287
     * @return array
288
     */
289
    protected function generateButton(int $page): array
290
    {
291
        return [
292
            'text'          => (string) $page,
293
            'callback_data' => $this->generateCallbackData($page),
294
        ];
295
    }
296
297
    /**
298
     * Generate the callback data for the passed page.
299
     *
300
     * @param int $page
301
     *
302
     * @return string
303
     */
304
    protected function generateCallbackData(int $page): string
305
    {
306
        return "{$this->command}?currentPage={$this->selected_page}&nextPage={$page}";
307
    }
308
309
    /**
310
     * Get the prepared items for the selected page.
311
     *
312
     * @return array
313
     */
314
    protected function getPreparedItems(): array
315
    {
316
        return array_slice($this->items, $this->getOffset(), $this->items_per_page);
317
    }
318
319
    /**
320
     * @return int
321
     */
322
    protected function getOffset(): int
323
    {
324
        return $this->items_per_page * ($this->selected_page - 1);
325
    }
326
}
327