Issues (1358)

modules/OAuth2/OAuth2.php (24 issues)

1
<?php
2
/**
3
 * @package  OAuth2
4
 * @category modules
5
 * @author   Nazar Mokrynskyi <[email protected]>
6
 * @license  0BSD
7
 */
8
namespace cs\modules\OAuth2;
9
use
10
	cs\Cache\Prefix,
11
	cs\Config,
12
	cs\Request,
13
	cs\Session,
14
	cs\User,
15
	cs\DB\Accessor,
16
	cs\Singleton;
17
18
/**
19
 * @todo Use CRUD trait
20
 *
21
 * @method static $this instance($check = false)
22
 */
23
class OAuth2 {
24
	use
25
		Accessor,
26
		Singleton;
27
	/**
28
	 * @var bool
29
	 */
30
	protected $automatic_prolongation;
31
	/**
32
	 * @var int
33
	 */
34
	protected $expiration;
35
	/**
36
	 * @var Prefix
37
	 */
38
	protected $cache;
39
	public function construct () {
40
		$this->cache                  = new Prefix('OAuth2');
41
		$module_data                  = Config::instance()->module('OAuth2');
42
		$this->automatic_prolongation = $module_data->automatic_prolongation;
0 ignored issues
show
Bug Best Practice introduced by
The property automatic_prolongation does not exist on cs\Config\Module_Properties. Since you implemented __get, consider adding a @property annotation.
Loading history...
43
		$this->expiration             = $module_data->expiration;
0 ignored issues
show
Bug Best Practice introduced by
The property expiration does not exist on cs\Config\Module_Properties. Since you implemented __get, consider adding a @property annotation.
Loading history...
Documentation Bug introduced by
It seems like $module_data->expiration can also be of type false. However, the property $expiration is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
44
	}
45
	/**
46
	 * Returns database index
47
	 *
48
	 * @return int
49
	 */
50
	protected function cdb () {
51
		return Config::instance()->module('OAuth2')->db('oauth2');
52
	}
53
	/**
54
	 * Add new client
55
	 *
56
	 * @param string $name
57
	 * @param string $domain
58
	 * @param int    $active
59
	 *
60
	 * @return false|string            <i>false</i> on failure, id of created client otherwise
61
	 */
62
	public function add_client ($name, $domain, $active) {
63
		if (
64
			!$domain ||
65
			strpos($domain, '/') !== false
66
		) {
67
			return false;
68
		}
69
		/**
70
		 * Generate hash in cycle, to obtain unique value
71
		 */
72
		/** @noinspection LoopWhichDoesNotLoopInspection */
73
		while (true) {
74
			$id = md5(random_bytes(1000));
75
			if ($this->db_prime()->qf(
76
				"SELECT `id`
0 ignored issues
show
'SELECT `id` FROM `[... ''.$id.'' LIMIT 1' of type string is incompatible with the type string[] expected by parameter $query of cs\DB\_Abstract::qf(). ( Ignorable by Annotation )

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

76
				/** @scrutinizer ignore-type */ "SELECT `id`
Loading history...
77
				FROM `[prefix]oauth2_clients`
78
				WHERE `id` = '$id'
79
				LIMIT 1"
80
			)
81
			) {
82
				continue;
83
			}
84
			$this->db_prime()->q(
85
				"INSERT INTO `[prefix]oauth2_clients`
86
					(
87
						`id`,
88
						`secret`,
89
						`name`,
90
						`domain`,
91
						`active`
92
					) VALUES (
93
						'%s',
94
						'%s',
95
						'%s',
96
						'%s',
97
						'%s'
98
					)",
99
				$id,
0 ignored issues
show
$id of type string is incompatible with the type array expected by parameter $parameters of cs\DB\_Abstract::q(). ( Ignorable by Annotation )

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

99
				/** @scrutinizer ignore-type */ $id,
Loading history...
100
				md5(random_bytes(1000)),
101
				xap($name),
102
				xap($domain),
103
				(int)(bool)$active
0 ignored issues
show
(int)(bool)$active of type integer is incompatible with the type array expected by parameter $parameters of cs\DB\_Abstract::q(). ( Ignorable by Annotation )

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

103
				/** @scrutinizer ignore-type */ (int)(bool)$active
Loading history...
104
			);
105
			unset($this->cache->$id);
106
			return $id;
107
		}
108
	}
109
	/**
110
	 * Get client data
111
	 *
112
	 * @param string $id
113
	 *
114
	 * @return array|false
115
	 */
116
	public function get_client ($id) {
117
		if (!is_md5($id)) {
118
			return false;
119
		}
120
		return $this->cache->get(
121
			$id,
122
			function () use ($id) {
123
				return $this->db()->qf(
124
					"SELECT *
0 ignored issues
show
'SELECT * FROM `[pr...d` = '%s' LIMIT 1' of type string is incompatible with the type string[] expected by parameter $query of cs\DB\_Abstract::qf(). ( Ignorable by Annotation )

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

124
					/** @scrutinizer ignore-type */ "SELECT *
Loading history...
125
					FROM `[prefix]oauth2_clients`
126
					WHERE `id`	= '%s'
127
					LIMIT 1",
128
					$id
129
				);
130
			}
131
		);
132
	}
133
	/**
134
	 * Set client data
135
	 *
136
	 * @param string $id
137
	 * @param string $secret
138
	 * @param string $name
139
	 * @param string $domain
140
	 * @param int    $active
141
	 *
142
	 * @return bool
143
	 */
144
	public function set_client ($id, $secret, $name, $domain, $active) {
145
		if (
146
			!is_md5($id) ||
147
			!is_md5($secret) ||
148
			!$domain ||
149
			strpos($domain, '/') !== false
150
		) {
151
			return false;
152
		}
153
		$result = $this->db_prime()->q(
154
			"UPDATE `[prefix]oauth2_clients`
155
			SET
156
				`secret`		= '%s',
157
				`name`			= '%s',
158
				`domain`		= '%s',
159
				`active`		= '%s'
160
			WHERE `id` = '%s'",
161
			$secret,
0 ignored issues
show
$secret of type string is incompatible with the type array expected by parameter $parameters of cs\DB\_Abstract::q(). ( Ignorable by Annotation )

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

161
			/** @scrutinizer ignore-type */ $secret,
Loading history...
162
			xap($name),
163
			xap($domain),
164
			(int)(bool)$active,
0 ignored issues
show
(int)(bool)$active of type integer is incompatible with the type array expected by parameter $parameters of cs\DB\_Abstract::q(). ( Ignorable by Annotation )

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

164
			/** @scrutinizer ignore-type */ (int)(bool)$active,
Loading history...
165
			$id
166
		);
167
		unset($this->cache->$id);
168
		return !!$result;
169
	}
170
	/**
171
	 * Delete client
172
	 *
173
	 * @param string $id
174
	 *
175
	 * @return bool
176
	 */
177
	public function del_client ($id) {
178
		$result = $this->db_prime()->q(
179
			[
180
				"DELETE FROM `[prefix]oauth2_clients`
181
				WHERE `id` = '%s'",
182
				"DELETE FROM `[prefix]oauth2_clients_grant_access`
183
				WHERE `id`	= '%s'",
184
				"DELETE FROM `[prefix]oauth2_clients_sessions`
185
				WHERE `id`	= '%s'"
186
			],
187
			$id
0 ignored issues
show
$id of type string is incompatible with the type array expected by parameter $parameters of cs\DB\_Abstract::q(). ( Ignorable by Annotation )

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

187
			/** @scrutinizer ignore-type */ $id
Loading history...
188
		);
189
		unset($this->cache->{'/'});
190
		return !!$result;
191
	}
192
	/**
193
	 * Get clients list in form of associative array
194
	 *
195
	 * @return array|false
196
	 */
197
	public function clients_list () {
198
		return $this->db()->qfa(
199
			'SELECT *
0 ignored issues
show
'SELECT * FROM `[prefix]oauth2_clients`' of type string is incompatible with the type string[] expected by parameter $query of cs\DB\_Abstract::qfa(). ( Ignorable by Annotation )

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

199
			/** @scrutinizer ignore-type */ 'SELECT *
Loading history...
200
			FROM `[prefix]oauth2_clients`'
201
		);
202
	}
203
	/**
204
	 * Grant access for specified client
205
	 *
206
	 * @param string $client
207
	 *
208
	 * @return bool
209
	 */
210
	public function add_access ($client) {
211
		$User = User::instance();
212
		if (!$User->user() || !$this->get_client($client)) {
213
			return false;
214
		}
215
		$result = $this->db_prime()->q(
216
			"INSERT IGNORE INTO `[prefix]oauth2_clients_grant_access`
217
				(
218
					`id`,
219
					`user`
220
				) VALUES (
221
					'%s',
222
					'%s'
223
				)",
224
			$client,
0 ignored issues
show
$client of type string is incompatible with the type array expected by parameter $parameters of cs\DB\_Abstract::q(). ( Ignorable by Annotation )

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

224
			/** @scrutinizer ignore-type */ $client,
Loading history...
225
			$User->id
0 ignored issues
show
$User->id of type integer is incompatible with the type array expected by parameter $parameters of cs\DB\_Abstract::q(). ( Ignorable by Annotation )

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

225
			/** @scrutinizer ignore-type */ $User->id
Loading history...
226
		);
227
		unset($this->cache->{"grant_access/$User->id"});
228
		return $result;
229
	}
230
	/**
231
	 * Check granted access for specified client
232
	 *
233
	 * @param string   $client
234
	 * @param bool|int $user If not specified - current user assumed
235
	 *
236
	 * @return bool
237
	 */
238
	public function get_access ($client, $user = false) {
239
		$user = (int)$user ?: User::instance()->id;
240
		if ($user == User::GUEST_ID) {
241
			return false;
242
		}
243
		$clients = $this->cache->get(
244
			"grant_access/$user",
245
			function () use ($user) {
246
				return $this->db()->qfas(
247
					"SELECT `id`
0 ignored issues
show
'SELECT `id` FROM `... WHERE `user` = '%s'' of type string is incompatible with the type string[] expected by parameter $query of cs\DB\_Abstract::qfas(). ( Ignorable by Annotation )

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

247
					/** @scrutinizer ignore-type */ "SELECT `id`
Loading history...
248
					FROM `[prefix]oauth2_clients_grant_access`
249
					WHERE `user`	= '%s'",
250
					$user
0 ignored issues
show
$user of type integer is incompatible with the type string[] expected by parameter $query of cs\DB\_Abstract::qfas(). ( Ignorable by Annotation )

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

250
					/** @scrutinizer ignore-type */ $user
Loading history...
251
				);
252
			}
253
		);
254
		return $clients ? in_array($client, $clients) : false;
255
	}
256
	/**
257
	 * Deny access for specified client/
258
	 *
259
	 * @param string   $client If '' - access for all clients will be denied
260
	 * @param bool|int $user   If not specified - current user assumed
261
	 *
262
	 * @return bool
263
	 */
264
	public function del_access ($client = '', $user = false) {
265
		$user = (int)$user ?: User::instance()->id;
266
		if ($user == User::GUEST_ID) {
267
			return false;
268
		}
269
		$result = $client ? $this->db_prime()->q(
270
			[
271
				"DELETE FROM `[prefix]oauth2_clients_grant_access`
272
				WHERE
273
					`user`	= $user AND
274
					`id`	= '%s'",
275
				"DELETE FROM `[prefix]oauth2_clients_sessions`
276
				WHERE
277
					`user`	= $user AND
278
					`id`	= '%s'"
279
			],
280
			$client
0 ignored issues
show
$client of type string is incompatible with the type array expected by parameter $parameters of cs\DB\_Abstract::q(). ( Ignorable by Annotation )

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

280
			/** @scrutinizer ignore-type */ $client
Loading history...
281
		) : $this->db_prime()->q(
282
			[
283
				"DELETE FROM `[prefix]oauth2_clients_grant_access`
284
				WHERE
285
					`user`	= $user",
286
				"DELETE FROM `[prefix]oauth2_clients_sessions`
287
				WHERE
288
					`user`	= $user"
289
			]
290
		);
291
		unset($this->cache->{"grant_access/$user"});
292
		return $result;
293
	}
294
	/**
295
	 * Adds new code for specified client, code is used to obtain token
296
	 *
297
	 * @param string $client
298
	 * @param string $response_type 'code' or 'token'
299
	 * @param string $redirect_uri
300
	 *
301
	 * @return false|string                    <i>false</i> on failure or code for token access otherwise
302
	 */
303
	public function add_code ($client, $response_type, $redirect_uri = '') {
304
		$Session = Session::instance();
305
		$client  = $this->get_client($client);
306
		if (
307
			!$client ||
0 ignored issues
show
The condition $client is always false.
Loading history...
308
			!$Session->user() ||
309
			!$this->get_access($client['id'])
310
		) {
311
			return false;
312
		}
313
		$Request                        = Request::instance();
314
		$user_agent                     = $Request->header('user-agent');
315
		$current_session                = $Session->get_id();
316
		$Request->headers['user-agent'] = "OAuth2-$client[name]-$client[id]";
317
		$Session->add($Session->get_user(), false);
318
		$new_session                    = $Session->get_id();
319
		$Request->headers['user-agent'] = $user_agent;
320
		$Session->load($current_session);
321
		unset($user_agent, $current_session);
322
		/** @noinspection LoopWhichDoesNotLoopInspection */
323
		while (true) {
324
			$access_token  = md5(random_bytes(1000));
325
			$refresh_token = md5($access_token.random_bytes(1000));
326
			$code          = md5($refresh_token.random_bytes(1000));
327
			if ($this->db_prime()->qf(
328
				"SELECT `id`
329
				FROM `[prefix]oauth2_clients_sessions`
330
				WHERE
331
					`access_token`	= '$access_token' OR
332
					`refresh_token`	= '$refresh_token' OR
333
					`code`			= '$code'
334
				LIMIT 1"
335
			)
336
			) {
337
				continue;
338
			}
339
			$result = $this->db_prime()->q(
340
				"INSERT INTO `[prefix]oauth2_clients_sessions`
341
					(
342
						`id`,
343
						`user`,
344
						`session`,
345
						`created`,
346
						`expire`,
347
						`access_token`,
348
						`refresh_token`,
349
						`code`,
350
						`type`,
351
						`redirect_uri`
352
					) VALUES (
353
						'%s',
354
						'%s',
355
						'%s',
356
						'%s',
357
						'%s',
358
						'%s',
359
						'%s',
360
						'%s',
361
						'%s',
362
						'%s'
363
					)",
364
				$client['id'],
365
				$Session->get_user(),
366
				$new_session,
367
				time(),
368
				time() + $this->expiration,
369
				$access_token,
370
				$refresh_token,
371
				$code,
372
				$response_type,
373
				md5($redirect_uri)
374
			);
375
			return $result ? $code : false;
376
		}
377
		return false;
378
	}
379
	/**
380
	 * Get code data (tokens)
381
	 *
382
	 * @param string $code
383
	 * @param string $client Client id
384
	 * @param string $secret Client secret
385
	 * @param string $redirect_uri
386
	 *
387
	 * @return array|false                    <i>false</i> on failure, otherwise array
388
	 *                                        ['access_token' => md5, 'refresh_token' => md5, 'expires_in' => seconds, 'token_type' => 'bearer']<br>
389
	 *                                        <i>expires_in</i> may be negative
390
	 */
391
	public function get_code ($code, $client, $secret, $redirect_uri = '') {
392
		$client = $this->get_client($client);
393
		if (!is_md5($code) || !$client || $client['secret'] != $secret) {
0 ignored issues
show
The condition $client is always false.
Loading history...
394
			return false;
395
		}
396
		$data = $this->db_prime()->qf(
397
			"SELECT
398
				`access_token`,
399
				`refresh_token`,
400
				`expire`,
401
				`user`
402
			FROM `[prefix]oauth2_clients_sessions`
403
			WHERE
404
				`id`			= '%s' AND
405
				`code`			= '%s' AND
406
				`redirect_uri`	= '%s'
407
			LIMIT 1",
408
			$client['id'],
409
			$code,
410
			md5($redirect_uri)
411
		);
412
		if (!$data) {
413
			return false;
414
		}
415
		$this->db_prime()->q(
416
			"UPDATE `[prefix]oauth2_clients_sessions`
417
			SET `code` = ''
418
			WHERE
419
				`id`			= '%s' AND
420
				`code`			= '%s' AND
421
				`redirect_uri`	= '%s'",
422
			$client['id'],
423
			$code,
424
			md5($redirect_uri)
425
		);
426
		return [
427
			'access_token'  => $data['access_token'],
428
			'refresh_token' => $data['refresh_token'],
429
			'expires_in'    => $data['expire'] - time(),
430
			'token_type'    => 'bearer',
431
			'user_id'       => $data['user']
432
		];
433
	}
434
	/**
435
	 * Get token data
436
	 *
437
	 * @param string $access_token
438
	 *
439
	 * @return array|false                    <i>false</i> on failure, array ['user' => id, 'session' => id, 'expire' => unix time, 'type' => 'code'|'token']
440
	 */
441
	public function get_token ($access_token) {
442
		if (!is_md5($access_token)) {
443
			return false;
444
		}
445
		$Cache = $this->cache;
446
		$data  = $Cache->get(
447
			"tokens/$access_token",
448
			function () use ($access_token) {
449
				return $this->db()->qf(
450
					"SELECT
0 ignored issues
show
'SELECT `id` AS `c...n` = '%s' LIMIT 1' of type string is incompatible with the type string[] expected by parameter $query of cs\DB\_Abstract::qf(). ( Ignorable by Annotation )

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

450
					/** @scrutinizer ignore-type */ "SELECT
Loading history...
451
						`id` AS `client_id`,
452
						`user`,
453
						`session`,
454
						`expire`,
455
						`type`
456
					FROM `[prefix]oauth2_clients_sessions`
457
					WHERE
458
						`access_token`	= '%s'
459
					LIMIT 1",
460
					$access_token
461
				);
462
			}
463
		);
464
		if ($data) {
465
			if ($data['expire'] < time()) {
466
				return false;
467
			}
468
			if (!$this->get_access($data['client_id'], $data['user'])) {
469
				$this->db_prime()->q(
470
					"DELETE FROM `[prefix]oauth2_clients_sessions`
471
					WHERE `access_token` = '%s'",
472
					$access_token
0 ignored issues
show
$access_token of type string is incompatible with the type array expected by parameter $parameters of cs\DB\_Abstract::q(). ( Ignorable by Annotation )

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

472
					/** @scrutinizer ignore-type */ $access_token
Loading history...
473
				);
474
				unset($Cache->{"tokens/$access_token"});
475
				$data = false;
476
				/**
477
				 * Automatic prolongation of tokens' expiration time if configured
478
				 */
479
			} elseif ($this->automatic_prolongation && $data['expire'] < time() - $this->expiration * Config::instance()->core['update_ratio'] / 100) {
480
				$data['expire'] = time() + $this->expiration;
481
				$this->db_prime()->q(
482
					"UPDATE `[prefix]oauth2_clients_sessions`
483
					SET `expire` = '%s'
484
					WHERE `access_token`	= '%s'",
485
					$data['expire'],
486
					$access_token
487
				);
488
				$Cache->{"tokens/$access_token"} = $data;
489
			}
490
		}
491
		return $data;
492
	}
493
	/**
494
	 * Del token data (invalidate token)
495
	 *
496
	 * @param string $access_token
497
	 *
498
	 * @return bool
499
	 */
500
	public function del_token ($access_token) {
501
		if (!is_md5($access_token)) {
502
			return false;
503
		}
504
		$session = $this->db_prime()->qfs(
505
			"SELECT `session`
0 ignored issues
show
'SELECT `session` FRO...ken` = '%s' LIMIT 1' of type string is incompatible with the type string[] expected by parameter $query of cs\DB\_Abstract::qfs(). ( Ignorable by Annotation )

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

505
			/** @scrutinizer ignore-type */ "SELECT `session`
Loading history...
506
			FROM `[prefix]oauth2_clients_sessions`
507
			WHERE
508
				`access_token`	= '%s'
509
			LIMIT 1",
510
			$access_token
511
		);
512
		if ($this->db_prime()->q(
513
			"DELETE FROM `[prefix]oauth2_clients_sessions`
514
			WHERE `access_token`	= '%s'",
515
			$access_token
0 ignored issues
show
$access_token of type string is incompatible with the type array expected by parameter $parameters of cs\DB\_Abstract::q(). ( Ignorable by Annotation )

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

515
			/** @scrutinizer ignore-type */ $access_token
Loading history...
516
		)
517
		) {
518
			unset($this->cache->{"tokens/$access_token"});
519
			Session::instance()->del($session);
0 ignored issues
show
It seems like $session can also be of type false; however, parameter $session_id of cs\Session::del() does only seem to accept null|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

519
			Session::instance()->del(/** @scrutinizer ignore-type */ $session);
Loading history...
520
			return true;
521
		}
522
		return false;
523
	}
524
	/**
525
	 * Get new access_token with refresh_token
526
	 *
527
	 * @param string $refresh_token
528
	 * @param string $client Client id
529
	 * @param string $secret Client secret
530
	 *
531
	 * @return array|false <i>false</i> on failure, otherwise array ['access_token' => md5, 'refresh_token' => md5, 'expires_in' => seconds, 'token_type' =>
532
	 *                     'bearer']
533
	 */
534
	public function refresh_token ($refresh_token, $client, $secret) {
535
		$client = $this->get_client($client);
536
		if (!is_md5($refresh_token) || !$client || $client['secret'] != $secret) {
0 ignored issues
show
The condition $client is always false.
Loading history...
537
			return false;
538
		}
539
		$data = $this->db_prime()->qf(
540
			"SELECT
541
				`user`,
542
				`access_token`,
543
				`session`
544
			FROM `[prefix]oauth2_clients_sessions`
545
			WHERE
546
				`id`			= '%s' AND
547
				`refresh_token`	= '%s'
548
			LIMIT 1",
549
			$client['id'],
550
			$refresh_token
551
		);
552
		if (!$data) {
553
			return false;
554
		}
555
		$this->db_prime()->q(
556
			"DELETE FROM `[prefix]oauth2_clients_sessions`
557
			WHERE
558
				`id`			= '%s' AND
559
				`refresh_token`	= '%s'",
560
			$client['id'],
561
			$refresh_token
562
		);
563
		unset($this->cache->{"tokens/$data[access_token]"});
564
		$Session = Session::instance();
565
		$id      = $Session->load($data['session']);
566
		if ($id != $data['user']) {
567
			return false;
568
		}
569
		$Session->add($id);
570
		$code = $this->add_code($client['id'], 'code');
571
		if (!$code) {
572
			return false;
573
		}
574
		$result = $this->get_code($code, $client['id'], $client['secret']);
575
		$Session->del();
576
		return $result;
577
	}
578
}
579