Issues (36)

src/Entities/Entity.php (2 issues)

Labels
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
12
namespace Longman\TelegramBot\Entities;
13
14
use Longman\TelegramBot\Entities\InlineQuery\InlineEntity;
15
use Longman\TelegramBot\Entities\InputMedia\InputMedia;
16
17
/**
18
 * Class Entity
19
 *
20
 * This is the base class for all entities.
21
 *
22
 * @link https://core.telegram.org/bots/api#available-types
23
 *
24
 * @method array  getRawData()     Get the raw data passed to this entity
25
 * @method string getBotUsername() Return the bot name passed to this entity
26
 */
27
abstract class Entity implements \JsonSerializable
28
{
29
    public static $fixThumbnailRename = true;
30
31
    public $bot_username = '';
32
    public $raw_data = [];
33
34
    private $fields = [];
35
36
    /**
37
     * Entity constructor.
38
     *
39
     * @todo Get rid of the $bot_username, it shouldn't be here!
40
     *
41
     * @param array  $data
42
     * @param string $bot_username
43
     */
44 76
    public function __construct(array $data, string $bot_username = '')
45
    {
46 76
        $this->bot_username = $bot_username;
47 76
        $this->raw_data     = $data;
48
49 76
        $this->assignMemberVariables($data);
50 76
        $this->validate();
51
    }
52
53
    /**
54
     * Dynamically set a field.
55
     *
56
     * @param string $name
57
     * @param mixed  $value
58
     * @return void
59
     */
60 74
    public function __set(string $name, $value): void
61
    {
62 74
        $this->fields[$name] = $value;
63
    }
64
65
    /**
66
     * Gets a dynamic field.
67
     *
68
     * @param string $name
69
     * @return mixed|null
70
     */
71 67
    public function __get(string $name)
72
    {
73 67
        return $this->fields[$name] ?? null;
74
    }
75
76
    /**
77
     * Return the data that should be serialized for Telegram.
78
     *
79
     * @return array
80
     */
81 2
    public function jsonSerialize(): array
82
    {
83 2
        return $this->fields;
84
    }
85
86
    /**
87
     * Perform to json
88
     *
89
     * @return string
90
     */
91 2
    public function toJson(): string
92
    {
93 2
        return json_encode($this);
94
    }
95
96
    /**
97
     * Perform to string
98
     *
99
     * @return string
100
     */
101
    public function __toString()
102
    {
103
        return $this->toJson();
104
    }
105
106
    /**
107
     * Helper to set member variables
108
     *
109
     * @param array $data
110
     */
111 76
    protected function assignMemberVariables(array $data): void
112
    {
113 76
        foreach ($data as $key => $value) {
114 73
            $key = $this->fixThumbnailRename($key);
115 73
            $this->$key = $value;
116
        }
117
    }
118
119
    /**
120
     * Get the list of the properties that are themselves Entities
121
     *
122
     * @return array
123
     */
124 34
    protected function subEntities(): array
125
    {
126 34
        return [];
127
    }
128
129
    /**
130
     * Perform any special entity validation
131
     */
132 70
    protected function validate(): void
133
    {
134 70
    }
135
136
    /**
137
     * Get a property from the current Entity
138
     *
139
     * @param string $property
140
     * @param mixed  $default
141
     *
142
     * @return mixed
143
     */
144 65
    public function getProperty(string $property, $default = null)
145
    {
146 65
        return $this->$property ?? $default;
147
    }
148
149
    /**
150
     * Return the variable for the called getter or magically set properties dynamically.
151
     *
152
     * @param $method
153
     * @param $args
154
     *
155
     * @return mixed|null
156
     */
157 57
    public function __call($method, $args)
158
    {
159 57
        $method = $this->fixThumbnailRename($method);
160
161
        //Convert method to snake_case (which is the name of the property)
162 57
        $property_name = mb_strtolower(ltrim(preg_replace('/[A-Z]/', '_$0', substr($method, 3)), '_'));
163 57
        $property_name = $this->fixThumbnailRename($property_name);
164
165 57
        $action = substr($method, 0, 3);
166 57
        if ($action === 'get') {
167 56
            $property = $this->getProperty($property_name);
168
169 56
            if ($property !== null) {
170
                //Get all sub-Entities of the current Entity
171 53
                $sub_entities = $this->subEntities();
172
173 53
                if (isset($sub_entities[$property_name])) {
174 13
                    $class = $sub_entities[$property_name];
175
176 13
                    if (is_array($class)) {
177 1
                        return $this->makePrettyObjectArray(reset($class), $property_name);
178
                    }
179
180 12
                    return Factory::resolveEntityClass($class, $property, $this->getProperty('bot_username'));
0 ignored issues
show
It seems like $this->getProperty('bot_username') can also be of type null; however, parameter $bot_username of Longman\TelegramBot\Enti...y::resolveEntityClass() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

180
                    return Factory::resolveEntityClass($class, $property, /** @scrutinizer ignore-type */ $this->getProperty('bot_username'));
Loading history...
181
                }
182
183 54
                return $property;
184
            }
185 2
        } elseif ($action === 'set') {
186
            // Limit setters to specific classes.
187 2
            if ($this instanceof InlineEntity || $this instanceof InputMedia || $this instanceof Keyboard || $this instanceof KeyboardButton) {
188 2
                $this->$property_name = $args[0];
189 2
                $this->raw_data[$property_name] = $args[0];
190
191 2
                return $this;
192
            }
193
        }
194
195 19
        return null;
196
    }
197
198
    /**
199
     * BC for renamed thumb -> thumbnail methods and fields
200
     *
201
     * @todo Remove after a few versions.
202
     *
203
     * @param string $name
204
     * @return string
205
     */
206 73
    protected function fixThumbnailRename(string $name): string
207
    {
208 73
        return self::$fixThumbnailRename ? preg_replace('/([Tt])humb(nail)?/', '$1humbnail', $name, -1, $count) : $name;
209
210
        /*if ($count) {
211
            // Notify user that there are still outdated method calls?
212
        }*/
213
    }
214
215
    /**
216
     * Return an array of nice objects from an array of object arrays
217
     *
218
     * This method is used to generate pretty object arrays
219
     * mainly for PhotoSize and Entities object arrays.
220
     *
221
     * @param string $class
222
     * @param string $property_name
223
     *
224
     * @return array
225
     */
226 1
    protected function makePrettyObjectArray(string $class, string $property_name): array
227
    {
228 1
        $objects      = [];
229 1
        $bot_username = $this->getProperty('bot_username');
230
231 1
        $properties = array_filter($this->getProperty($property_name) ?: []);
232 1
        foreach ($properties as $property) {
233 1
            $objects[] = Factory::resolveEntityClass($class, $property, $bot_username);
0 ignored issues
show
It seems like $bot_username can also be of type null; however, parameter $bot_username of Longman\TelegramBot\Enti...y::resolveEntityClass() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

233
            $objects[] = Factory::resolveEntityClass($class, $property, /** @scrutinizer ignore-type */ $bot_username);
Loading history...
234
        }
235
236 1
        return $objects;
237
    }
238
239
    /**
240
     * Escape markdown (v1) special characters
241
     *
242
     * @see https://core.telegram.org/bots/api#markdown-style
243
     *
244
     * @param string $string
245
     *
246
     * @return string
247
     */
248 2
    public static function escapeMarkdown(string $string): string
249
    {
250 2
        return str_replace(
251 2
            ['[', '`', '*', '_',],
252 2
            ['\[', '\`', '\*', '\_',],
253 2
            $string
254 2
        );
255
    }
256
257
    /**
258
     * Escape markdown (v2) special characters
259
     *
260
     * @see https://core.telegram.org/bots/api#markdownv2-style
261
     *
262
     * @param string $string
263
     *
264
     * @return string
265
     */
266 1
    public static function escapeMarkdownV2(string $string): string
267
    {
268 1
        return str_replace(
269 1
            ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'],
270 1
            ['\_', '\*', '\[', '\]', '\(', '\)', '\~', '\`', '\>', '\#', '\+', '\-', '\=', '\|', '\{', '\}', '\.', '\!'],
271 1
            $string
272 1
        );
273
    }
274
275
    /**
276
     * Try to mention the user
277
     *
278
     * Mention the user with the username otherwise print first and last name
279
     * if the $escape_markdown argument is true special characters are escaped from the output
280
     *
281
     * @todo What about MarkdownV2?
282
     *
283
     * @param bool $escape_markdown
284
     *
285
     * @return string
286
     */
287 3
    public function tryMention($escape_markdown = false): string
288
    {
289
        // TryMention only makes sense for the User and Chat entity.
290 3
        if (!($this instanceof User || $this instanceof Chat)) {
291
            return '';
292
        }
293
294
        //Try with the username first...
295 3
        $name        = $this->getProperty('username');
296 3
        $is_username = $name !== null;
297
298 3
        if ($name === null) {
299
            //...otherwise try with the names.
300 3
            $name      = $this->getProperty('first_name');
301 3
            $last_name = $this->getProperty('last_name');
302 3
            if ($last_name !== null) {
303 3
                $name .= ' ' . $last_name;
304
            }
305
        }
306
307 3
        if ($escape_markdown) {
308 1
            $name = self::escapeMarkdown($name);
309
        }
310
311 3
        return ($is_username ? '@' : '') . $name;
312
    }
313
}
314