Issues (36)

src/Entities/Keyboard.php (1 issue)

Severity
1
<?php
2
3
/**
4
 * This file is part of the TelegramBot package.
5
 *
6
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * Written by Marco Boretto <[email protected]>
12
 */
13
14
namespace Longman\TelegramBot\Entities;
15
16
use Longman\TelegramBot\Exception\TelegramException;
17
18
/**
19
 * Class Keyboard
20
 *
21
 * @link https://core.telegram.org/bots/api#replykeyboardmarkup
22
 *
23
 * @method bool   getIsPersistent()          Optional. Requests clients to always show the keyboard when the regular keyboard is hidden. Defaults to false, in which case the custom keyboard can be hidden and opened with a keyboard icon.
24
 * @method bool   getResizeKeyboard()        Optional. Requests clients to resize the keyboard vertically for optimal fit (e.g., make the keyboard smaller if there are just two rows of buttons). Defaults to false, in which case the custom keyboard is always of the same height as the app's standard keyboard.
25
 * @method bool   getOneTimeKeyboard()       Optional. Requests clients to remove the keyboard as soon as it's been used. The keyboard will still be available, but clients will automatically display the usual letter-keyboard in the chat – the user can press a special button in the input field to see the custom keyboard again. Defaults to false.
26
 * @method string getInputFieldPlaceholder() Optional. The placeholder to be shown in the input field when the keyboard is active; 1-64 characters
27
 * @method bool   getSelective()             Optional. Use this parameter if you want to show the keyboard to specific users only. Targets: 1) users that are @mentioned in the text of the Message object; 2) if the bot's message is a reply (has reply_to_message_id), sender of the original message.
28
 *
29
 * @method $this setIsPersistent(bool $is_persistent)                      Optional. Requests clients to always show the keyboard when the regular keyboard is hidden. Defaults to false, in which case the custom keyboard can be hidden and opened with a keyboard icon.
30
 * @method $this setResizeKeyboard(bool $resize_keyboard)                  Optional. Requests clients to resize the keyboard vertically for optimal fit (e.g., make the keyboard smaller if there are just two rows of buttons). Defaults to false, in which case the custom keyboard is always of the same height as the app's standard keyboard.
31
 * @method $this setOneTimeKeyboard(bool $one_time_keyboard)               Optional. Requests clients to remove the keyboard as soon as it's been used. The keyboard will still be available, but clients will automatically display the usual letter-keyboard in the chat – the user can press a special button in the input field to see the custom keyboard again. Defaults to false.
32
 * @method $this setInputFieldPlaceholder(string $input_field_placeholder) Optional. The placeholder to be shown in the input field when the keyboard is active; 1-64 characters
33
 * @method $this setSelective(bool $selective)                             Optional. Use this parameter if you want to show the keyboard to specific users only. Targets: 1) users that are @mentioned in the text of the Message object; 2) if the bot's message is a reply (has reply_to_message_id), sender of the original message.
34
 */
35
class Keyboard extends Entity
36
{
37 19
    public function __construct()
38
    {
39 19
        $data = $this->createFromParams(...func_get_args());
40 19
        parent::__construct($data);
41
42
        // Remove any empty buttons.
43 15
        $this->{$this->getKeyboardType()} = array_filter($this->{$this->getKeyboardType()});
44
    }
45
46
    /**
47
     * If this keyboard is an inline keyboard.
48
     *
49
     * @return bool
50
     */
51 19
    public function isInlineKeyboard(): bool
52
    {
53 19
        return $this instanceof InlineKeyboard;
54
    }
55
56
    /**
57
     * Get the proper keyboard button class for this keyboard.
58
     *
59
     * @return string
60
     */
61 13
    public function getKeyboardButtonClass(): string
62
    {
63 13
        return $this->isInlineKeyboard() ? InlineKeyboardButton::class : KeyboardButton::class;
64
    }
65
66
    /**
67
     * Get the type of keyboard, either "inline_keyboard" or "keyboard".
68
     *
69
     * @return string
70
     */
71 19
    public function getKeyboardType(): string
72
    {
73 19
        return $this->isInlineKeyboard() ? 'inline_keyboard' : 'keyboard';
74
    }
75
76
    /**
77
     * If no explicit keyboard is passed, try to create one from the parameters.
78
     *
79
     * @return array
80
     */
81 19
    protected function createFromParams(): array
82
    {
83 19
        $keyboard_type = $this->getKeyboardType();
84
85 19
        $args = func_get_args();
86
87
        // Force button parameters into individual rows.
88 19
        foreach ($args as &$arg) {
89 19
            !is_array($arg) && $arg = [$arg];
90
        }
91 19
        unset($arg);
92
93 19
        $data = reset($args);
94
95 19
        if ($from_data = array_key_exists($keyboard_type, (array) $data)) {
96 6
            $args = $data[$keyboard_type];
97
98
            // Make sure we're working with a proper row.
99 6
            if (!is_array($args)) {
100 2
                $args = [];
101
            }
102
        }
103
104 19
        $new_keyboard = [];
105 19
        foreach ($args as $row) {
106 16
            $new_keyboard[] = $this->parseRow($row);
107
        }
108
109 19
        if (!empty($new_keyboard)) {
110 16
            if (!$from_data) {
111 13
                $data = [];
112
            }
113 16
            $data[$keyboard_type] = $new_keyboard;
114
        }
115
116
        // If $args was empty, $data still contains `false`
117 19
        return $data ?: [];
118
    }
119
120
    /**
121
     * Create a new row in keyboard and add buttons.
122
     *
123
     * @return Keyboard
124
     */
125 2
    public function addRow(): Keyboard
126
    {
127 2
        if (($new_row = $this->parseRow(func_get_args())) !== null) {
0 ignored issues
show
The condition $new_row = $this->parseR...nc_get_args()) !== null is always true.
Loading history...
128
            // Workaround for "Indirect modification of overloaded property has no effect" notice in PHP 8.2.
129
            // https://stackoverflow.com/a/19749730/3757422
130 2
            $keyboard                         = $this->{$this->getKeyboardType()};
131 2
            $keyboard[]                       = $new_row;
132 2
            $this->{$this->getKeyboardType()} = $keyboard;
133
        }
134
135 2
        return $this;
136
    }
137
138
    /**
139
     * Parse a given row to the correct array format.
140
     *
141
     * @param array|string $row
142
     *
143
     * @return array|null
144
     */
145 16
    protected function parseRow($row): ?array
146
    {
147 16
        if (!is_array($row)) {
148 2
            return null;
149
        }
150
151 14
        $new_row = [];
152 14
        foreach ($row as $button) {
153 13
            if (($new_button = $this->parseButton($button)) !== null) {
154 13
                $new_row[] = $new_button;
155
            }
156
        }
157
158 14
        return $new_row;
159
    }
160
161
    /**
162
     * Parse a given button to the correct KeyboardButton object type.
163
     *
164
     * @param array|string|KeyboardButton $button
165
     *
166
     * @return KeyboardButton|null
167
     */
168 13
    protected function parseButton($button): ?KeyboardButton
169
    {
170 13
        $button_class = $this->getKeyboardButtonClass();
171
172 13
        if ($button instanceof $button_class) {
173 6
            return $button;
174
        }
175
176 7
        if (!$this->isInlineKeyboard() || call_user_func([$button_class, 'couldBe'], $button)) {
177 7
            return new $button_class($button);
178
        }
179
180
        return null;
181
    }
182
183
    /**
184
     * {@inheritdoc}
185
     */
186 19
    protected function validate(): void
187
    {
188 19
        $keyboard_type = $this->getKeyboardType();
189 19
        $keyboard      = $this->getProperty($keyboard_type);
190
191 19
        if ($keyboard !== null) {
192 19
            if (!is_array($keyboard)) {
193 2
                throw new TelegramException($keyboard_type . ' field is not an array!');
194
            }
195
196 17
            foreach ($keyboard as $item) {
197 16
                if (!is_array($item)) {
198 2
                    throw new TelegramException($keyboard_type . ' subfield is not an array!');
199
                }
200
            }
201
        }
202
    }
203
204
    /**
205
     * Remove the current custom keyboard and display the default letter-keyboard.
206
     *
207
     * @link https://core.telegram.org/bots/api/#replykeyboardremove
208
     *
209
     * @param array $data
210
     *
211
     * @return Keyboard
212
     */
213 1
    public static function remove(array $data = []): Keyboard
214
    {
215 1
        return new static(array_merge(['keyboard' => [], 'remove_keyboard' => true, 'selective' => false], $data));
216
    }
217
218
    /**
219
     * Display a reply interface to the user (act as if the user has selected the bot's message and tapped 'Reply').
220
     *
221
     * @link https://core.telegram.org/bots/api#forcereply
222
     *
223
     * @param array $data
224
     *
225
     * @return Keyboard
226
     */
227 1
    public static function forceReply(array $data = []): Keyboard
228
    {
229 1
        return new static(array_merge(['keyboard' => [], 'force_reply' => true, 'selective' => false], $data));
230
    }
231
}
232