Passed
Pull Request — develop (#922)
by Tito
07:09
created

RApiHalHal::loadResourceFromConfiguration()   B

Complexity

Conditions 10
Paths 2

Size

Total Lines 43
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 19
c 0
b 0
f 0
dl 0
loc 43
rs 7.6666
cc 10
nc 2
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @package     Redcore
4
 * @subpackage  Api
5
 *
6
 * @copyright   Copyright (C) 2008 - 2021 redWEB.dk. All rights reserved.
7
 * @license     GNU General Public License version 2 or later, see LICENSE.
8
 */
9
defined('JPATH_BASE') or die;
10
11
use Joomla\Utilities\ArrayHelper;
12
13
/**
14
 * Class to represent a HAL standard object.
15
 *
16
 * @since  1.2
17
 */
18
class RApiHalHal extends RApi
19
{
20
	/**
21
	 * Webservice element name
22
	 * @var string
23
	 */
24
	public $elementName = null;
25
26
	/**
27
	 * @var    string  Name of the Client
28
	 * @since  1.2
29
	 */
30
	public $client = '';
31
32
	/**
33
	 * @var    string  Name of the Webservice
34
	 * @since  1.2
35
	 */
36
	public $webserviceName = '';
37
38
	/**
39
	 * @var    string  Version of the Webservice
40
	 * @since  1.2
41
	 */
42
	public $webserviceVersion = '';
43
44
	/**
45
	 * @var    string  Folder path of the webservice
46
	 * @since  1.2
47
	 */
48
	public $webservicePath = '';
49
50
	/**
51
	 * @var    array  Installed webservice options
52
	 * @since  1.2
53
	 */
54
	public $webservice = '';
55
56
	/**
57
	 * For easier access of current configuration parameters
58
	 * @var SimpleXMLElement
59
	 */
60
	public $operationConfiguration = null;
61
62
	/**
63
	 * Main HAL resource object
64
	 * @var RApiHalDocumentResource
65
	 */
66
	public $hal = null;
67
68
	/**
69
	 * Resource container that will be outputted
70
	 * @var array
71
	 */
72
	public $resources = array();
73
74
	/**
75
	 * Data container that will be used for resource binding
76
	 * @var array
77
	 */
78
	public $data = array();
79
80
	/**
81
	 * Uri parameters that will be added to each link
82
	 * @var array
83
	 */
84
	public $uriParams = array();
85
86
	/**
87
	 * @var    SimpleXMLElement  Api Configuration
88
	 * @since  1.2
89
	 */
90
	public $configuration = null;
91
92
	/**
93
	 * @var    object  Helper class object
94
	 * @since  1.2
95
	 */
96
	public $apiHelperClass = null;
97
98
	/**
99
	 * @var    object  Dynamic model class object
100
	 * @since  1.3
101
	 */
102
	public $apiDynamicModelClass = null;
103
104
	/**
105
	 * @var    string  Dynamic model name used if dataMode="table"
106
	 * @since  1.3
107
	 */
108
	public $apiDynamicModelClassName = 'RApiHalModelItem';
109
110
	/**
111
	 * @var    string  Rendered Documentation
112
	 * @since  1.2
113
	 */
114
	public $documentation = '';
115
116
	/**
117
	 * @var    string  Option name (optional)
118
	 * @since  1.3
119
	 */
120
	public $optionName = '';
121
122
	/**
123
	 * @var    string  View name (optional)
124
	 * @since  1.3
125
	 */
126
	public $viewName = '';
127
128
	/**
129
	 * @var    string  Authorization check method
130
	 * @since  1.4
131
	 */
132
	public $authorizationCheck = 'oauth2';
133
134
	/**
135
	 * @var    array  Array for storing operation errors
136
	 * @since  1.6
137
	 */
138
	public $apiErrors = array();
139
140
	/**
141
	 * @var    object
142
	 * @since  1.11
143
	 */
144
	public $historyLog = null;
145
146
	/**
147
	 * Method to instantiate the file-based api call.
148
	 *
149
	 * @param   mixed  $options  Optional custom options to load. JRegistry or array format
150
	 *
151
	 * @throws Exception
152
	 * @since   1.2
153
	 */
154
	public function __construct($options = null)
155
	{
156
		parent::__construct($options);
157
158
		JPluginHelper::importPlugin('redcore');
159
160
		$this->setWebserviceName();
161
		$this->client            = $this->options->get('webserviceClient', 'site');
162
		$this->webserviceVersion = $this->options->get('webserviceVersion', '');
163
		$this->hal               = new RApiHalDocumentResource('');
164
165
		if (empty($this->webserviceName))
166
		{
167
			throw new Exception(JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_NOT_INSTALLED', $this->webserviceName, $this->webserviceVersion));
168
		}
169
170
		if (empty($this->webserviceVersion))
171
		{
172
			$this->webserviceVersion = RApiHalHelper::getNewestWebserviceVersion($this->client, $this->webserviceName);
0 ignored issues
show
Documentation Bug introduced by
It seems like RApiHalHelper::getNewest... $this->webserviceName) of type array is incompatible with the declared type string of property $webserviceVersion.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
173
		}
174
175
		$this->webservice = RApiHalHelper::getInstalledWebservice($this->client, $this->webserviceName, $this->webserviceVersion);
176
177
		if (empty($this->webservice))
178
		{
179
			throw new Exception(JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_NOT_INSTALLED', $this->webserviceName, $this->webserviceVersion));
180
		}
181
182
		if (empty($this->webservice['state']))
183
		{
184
			throw new Exception(JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_UNPUBLISHED', $this->webserviceName, $this->webserviceVersion));
185
		}
186
187
		$this->webservicePath = $this->webservice['path'];
188
		$this->configuration  = RApiHalHelper::loadWebserviceConfiguration(
189
			$this->webserviceName, $this->webserviceVersion, 'xml', $this->webservicePath, $this->client
190
		);
191
192
		// Set option and view name
193
		$this->setOptionViewName($this->webserviceName, $this->configuration);
194
195
		// Set base data
196
		$this->setBaseDataValues();
197
198
		// Init Environment
199
		$this->triggerFunction('setApiOperation');
200
201
		// Set initial status code
202
		$this->setStatusCode($this->statusCode);
203
204
		// Start Webservice History Log
205
		$this->startHistoryLog();
206
207
		// Check for defined constants
208
		if (!defined('JSON_UNESCAPED_SLASHES'))
209
		{
210
			define('JSON_UNESCAPED_SLASHES', 64);
211
		}
212
213
		// OAuth2 check
214
		if (RBootstrap::getConfig('webservices_authorization_check', 0) == 0)
215
		{
216
			$this->authorizationCheck = 'oauth2';
217
		}
218
		elseif (RBootstrap::getConfig('webservices_authorization_check', 0) == 1)
219
		{
220
			$this->authorizationCheck = 'joomla';
221
		}
222
223
		// Setting default option for webservices translation fallback
224
		RDatabaseSqlparserSqltranslation::setTranslationFallback(RBootstrap::getConfig('enable_translation_fallback_webservices', '1') == '1');
225
	}
226
227
	/**
228
	 * Start History Log
229
	 *
230
	 * @return  void
231
	 *
232
	 * @since   1.11
233
	 */
234
	public function startHistoryLog()
235
	{
236
		try
237
		{
238
			if (RBootstrap::getConfig('enable_webservice_history_log', 0) == 1)
239
			{
240
				if (!$this->historyLog)
241
				{
242
					$allowedOperations = (array) RBootstrap::getConfig('webservice_history_log_allowed_operations', 'all');
243
					$operation         = $this->operation;
244
245
					// Set operation to its full name
246
					if ($operation == 'read')
247
					{
248
						$primaryKeys = array();
249
						$isReadItem  = $this->apiFillPrimaryKeys($primaryKeys);
250
						$operation  .= $isReadItem ? ' Item' : ' List';
251
					}
252
253
					if (!in_array('all', $allowedOperations) && !in_array($operation, $allowedOperations))
254
					{
255
						return;
256
					}
257
258
					if ($operation == 'task')
259
					{
260
						$operation .= ' ' . $this->options->get('task', '');
261
					}
262
263
					$this->historyLog = RTable::getAdminInstance('Webservice_History_Log', array('ignore_request' => true), 'com_redcore');
264
					$basicRequestData = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $basicRequestData is dead and can be removed.
Loading history...
265
					$url              = JUri::getInstance();
266
267
					// Filter URL token data
268
					if ($url->hasVar('access_token'))
269
					{
270
						$url->setVar('access_token', 'XX-HIDDEN-XX');
271
					}
272
273
					$row = array(
274
						'webservice_name'    => $this->webserviceName,
275
						'webservice_version' => $this->webserviceVersion,
276
						'webservice_client'  => $this->client,
277
						'url'                => $url->toString(),
278
						'operation'          => $operation,
279
						'method'             => $this->options->get('method', 'GET'),
280
						'using_soap'         => (int) $this->options->get('usingSoap', 0),
281
						'status'             => JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_HISTORY_LOG_STARTED'),
282
					);
283
284
					$this->historyLog->save($row);
285
286
					$dir = trim(RBootstrap::getConfig('webservice_history_log_path', 'logs/com_redcore/webservice_history_log'), '/');
287
288
					if (!is_dir(JPATH_ROOT . '/' . $dir))
289
					{
290
						JFolder::create(JPATH_ROOT . '/' . $dir);
291
						$data = "<html>\n<body bgcolor=\"#FFFFFF\">\n</body>\n</html>";
292
						JFile::write(JPATH_ROOT . '/' . $dir . "/index.html", $data);
293
					}
294
295
					$fileName = $dir
296
						. '/' . $this->historyLog->id . '_' . date('Y_m_d_H_i_s', strtotime($this->historyLog->created_date))
297
						. '_' . $this->webserviceName . '_' . $this->operation . '_' . RFilesystemFile::getUniqueName(microtime()) . '.php';
298
299
					$data = sprintf(
300
						"<?php defined('_JEXEC') or die;?>%s %s %s\r\n\r\n---- Request HTTP headers: ----\r\n",
301
						$_SERVER['REQUEST_METHOD'],
302
						$url->getPath() . '?' . $url->getQuery(),
303
						$_SERVER['SERVER_PROTOCOL']
304
					);
305
306
					foreach ($_SERVER as $name => $value)
307
					{
308
						if (preg_match('/^HTTP_/', $name))
309
						{
310
							// Convert HTTP_HEADER_NAME to Header-Name
311
							$name = strtr(substr($name, 5), '_', ' ');
312
							$name = ucwords(strtolower($name));
313
							$name = strtr($name, ' ', '-');
314
315
							$data .= $name . ': ' . $value . "\r\n";
316
						}
317
					}
318
319
					$data      .= "\r\n---- Request body: ----\r\n";
320
					$postValues = $this->options->get('data', array());
321
322
					if (is_object($postValues))
323
					{
324
						// We are dealing with soap request, simplexml_load_string has problems with namespaces so we have to workaround that
325
						if ($postValues->{'<?xml_version'})
326
						{
327
							$xml = preg_replace("/(<\/?)(\w+):([^>]*>)/", "$1$2$3", file_get_contents("php://input"));
328
							$xml = simplexml_load_string($xml);
329
330
							foreach ($xml as $xmlKey => $xmlBody)
331
							{
332
								if (strpos($xmlKey, 'Body') !== false)
333
								{
334
									foreach ($xmlBody as $xmlContent)
335
									{
336
										$json       = json_encode((array) $xmlContent);
337
										$postValues = json_decode($json, true);
338
										break 2;
339
									}
340
								}
341
							}
342
						}
343
						else
344
						{
345
							$postValues = ArrayHelper::fromObject($postValues);
346
						}
347
					}
348
349
					if (!is_array($postValues))
350
					{
351
						$postValues = (array) $postValues;
352
					}
353
354
					if (isset($postValues['access_token']))
355
					{
356
						$postValues['access_token'] = 'XX-HIDDEN-XX';
357
					}
358
359
					file_put_contents(
360
						JPATH_ROOT . '/' . $fileName,
361
						$data . json_encode($postValues, JSON_PRETTY_PRINT) . "\r\n"
362
					);
363
364
					$row = array('file_name' => $fileName);
365
					$this->historyLog->save($row);
366
				}
367
			}
368
		}
369
		catch (Exception $e)
370
		{
371
			JFactory::getApplication()->enqueueMessage(JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_HISTORY_LOG_ERROR', $e->getMessage()));
372
		}
373
	}
374
375
	/**
376
	 * End History Log
377
	 *
378
	 * @param   string   $status  Status message
379
	 *
380
	 * @return  void
381
	 *
382
	 * @since   1.11
383
	 */
384
	public function endHistoryLog($status)
385
	{
386
		try
387
		{
388
			if (RBootstrap::getConfig('enable_webservice_history_log', 0) == 1)
389
			{
390
				if ($this->historyLog)
391
				{
392
					$allowedOperations = (array) RBootstrap::getConfig('webservice_history_log_allowed_operations', 'all');
393
394
					if (!in_array('all', $allowedOperations) && !in_array($this->historyLog->operation, $allowedOperations))
395
					{
396
						return;
397
					}
398
399
					$messages = $this->hal->getData('_messages');
400
401
					if ($status == JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_HISTORY_LOG_SUCCESS'))
402
					{
403
						foreach ($messages as $key => $message)
404
						{
405
							if ($message['type'] == 'error')
406
							{
407
								$status = JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_HISTORY_LOG_FAILED');
408
								break;
409
							}
410
						}
411
					}
412
					elseif ($status != JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_HISTORY_LOG_FAILED'))
413
					{
414
						$messageFound = false;
415
416
						foreach ($messages as $key => $message)
417
						{
418
							if ($message['message'] == $status)
419
							{
420
								$messageFound = true;
421
								break;
422
							}
423
						}
424
425
						if (!$messageFound)
426
						{
427
							$message    = array(
428
								'type'    => 'error',
429
								'message' => $status
430
							);
431
							$messages[] = $message;
432
						}
433
434
						$status = JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_HISTORY_LOG_FAILED');
435
					}
436
437
					$row = array(
438
						'execution_time'      => microtime(true) - $this->startTime,
439
						'execution_memory'    => memory_get_peak_usage(),
440
						'status'              => $status,
441
						'messages'            => json_encode($messages),
442
						'authentication'      => $this->authorizationCheck,
443
						'authentication_user' => JFactory::getUser()->name,
444
					);
445
446
					$this->historyLog->save($row);
447
448
					$data         = "\r\n---- Response HTTP headers: ----\r\n" . implode("\r\n", headers_list()) . "\r\n";
449
					$responseData = '';
450
451
					if (RBootstrap::getConfig('webservice_history_log_store_read_response', 1) == 1 || $this->operation != 'read')
452
					{
453
						$responseData = $this->hal->toArray();
454
					}
455
456
					file_put_contents(
457
						JPATH_ROOT . '/' . $this->historyLog->file_name,
458
						$data . "\r\n---- Response body: ----\r\n" . json_encode($responseData, JSON_PRETTY_PRINT) . "\r\n",
459
						FILE_APPEND
460
					);
461
				}
462
			}
463
		}
464
		catch (Exception $e)
465
		{
466
			JFactory::getApplication()->enqueueMessage(JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_HISTORY_LOG_ERROR', $e->getMessage()));
467
		}
468
	}
469
470
	/**
471
	 * Set options received from Headers of the request
472
	 *
473
	 * @return  RApi
474
	 *
475
	 * @since   1.7
476
	 */
477
	public function setOptionsFromHeader()
478
	{
479
		$app = JFactory::getApplication();
480
		parent::setOptionsFromHeader();
481
		$headers = self::getHeaderVariablesFromGlobals();
482
483
		if (isset($headers['X_WEBSERVICE_TRANSLATION_FALLBACK']))
484
		{
485
			$fallbackEnabled = $headers['X_WEBSERVICE_TRANSLATION_FALLBACK'] == 'true' ? '1' : '0';
486
487
			// This will force configuration of the redCORE to user defined option
488
			RBootstrap::$config->set('enable_translation_fallback_webservices', $fallbackEnabled);
489
		}
490
491
		if (isset($headers['X_WEBSERVICE_STATEFUL']))
492
		{
493
			$useState = (int) $headers['X_WEBSERVICE_STATEFUL'] == 1 ? 1 : 0;
494
			$this->options->set('webservice_stateful', $useState);
495
			RBootstrap::$config->set('webservice_stateful', $useState);
496
		}
497
498
		if (isset($headers['ACCEPT']))
499
		{
500
			// We are only using header options if the URI does not contain format parameter as it have higher priority
501
			if ($app->input->get('format', '') == '')
502
			{
503
				$outputFormats = explode(',', $headers['ACCEPT']);
504
505
				// We go through all proposed Content Types. First Content Type that is accepted by API is used
506
				foreach ($outputFormats as $outputFormat)
507
				{
508
					$outputFormat = strtolower($outputFormat);
509
					$format       = '';
510
511
					switch ($outputFormat)
512
					{
513
						case 'application/hal+json':
514
							$format = 'json';
515
							break;
516
						case 'application/hal+xml':
517
							$format = 'xml';
518
							break;
519
						case 'application/hal+doc':
520
							$format = 'doc';
521
							break;
522
					}
523
524
					if ($format)
525
					{
526
						// This will force configuration of the redCORE to user defined option
527
						RBootstrap::$config->set('webservices_default_format', $format);
528
						$this->options->set('format', $format);
529
530
						break;
531
					}
532
				}
533
			}
534
		}
535
536
		return $this;
537
	}
538
539
	/**
540
	 * Sets default Base Data Values for resource binding
541
	 *
542
	 * @return  RApi
543
	 *
544
	 * @since   1.4
545
	 */
546
	public function setBaseDataValues()
547
	{
548
		$webserviceUrlPath = '/index.php?option=' . $this->optionName;
549
550
		if (!empty($this->viewName))
551
		{
552
			$webserviceUrlPath .= '&amp;view=' . $this->viewName;
553
		}
554
555
		if (!empty($this->webserviceVersion))
556
		{
557
			$webserviceUrlPath .= '&amp;webserviceVersion=' . $this->webserviceVersion;
558
		}
559
560
		$webserviceUrlPath .= '&amp;webserviceClient=' . $this->client;
561
562
		$this->data['webserviceUrlPath'] = $webserviceUrlPath;
563
		$this->data['webserviceName']    = $this->webserviceName;
564
		$this->data['webserviceVersion'] = $this->webserviceVersion;
565
566
		return $this;
567
	}
568
569
	/**
570
	 * Sets Webservice name according to given options
571
	 *
572
	 * @return  RApi
573
	 *
574
	 * @since   1.2
575
	 */
576
	public function setWebserviceName()
577
	{
578
		$task = $this->options->get('task', '');
579
580
		if (empty($task))
581
		{
582
			$taskSplit = explode(',', $task);
583
584
			if (count($taskSplit) > 1)
585
			{
586
				// We will set name of the webservice as a task controller name
587
				$this->webserviceName = $this->options->get('optionName', '') . '-' . $taskSplit[0];
588
				$task                 = $taskSplit[1];
589
				$this->options->set('task', $task);
590
591
				return $this;
592
			}
593
		}
594
595
		$this->webserviceName  = $this->options->get('optionName', '');
596
		$viewName              = $this->options->get('viewName', '');
597
		$this->webserviceName .= !empty($this->webserviceName) && !empty($viewName) ? '-' . $viewName : '';
598
599
		return $this;
600
	}
601
602
	/**
603
	 * Set Method for Api to be performed
604
	 *
605
	 * @return  RApi
606
	 *
607
	 * @since   1.2
608
	 */
609
	public function setApiOperation()
610
	{
611
		$method = $this->options->get('method', 'GET');
612
		$task   = $this->options->get('task', '');
613
		$format = $this->options->get('format', '');
614
615
		// Set proper operation for given method
616
		switch ((string) $method)
617
		{
618
			case 'PUT':
619
				$method = 'UPDATE';
620
				break;
621
			case 'GET':
622
				$method = !empty($task) ? 'TASK' : 'READ';
623
				break;
624
			case 'POST':
625
				$method = !empty($task) ? 'TASK' : 'CREATE';
626
				break;
627
			case 'DELETE':
628
				$method = 'DELETE';
629
				break;
630
631
			default:
632
				$method = 'READ';
633
				break;
634
		}
635
636
		// If task is pointing to some other operation like apply, update or delete
637
		if (!empty($task) && !empty($this->configuration->operations->task->{$task}['useOperation']))
638
		{
639
			$operation = strtoupper((string) $this->configuration->operations->task->{$task}['useOperation']);
640
641
			if (in_array($operation, array('CREATE', 'READ', 'UPDATE', 'DELETE', 'DOCUMENTATION')))
642
			{
643
				$method = $operation;
644
			}
645
		}
646
647
		if ($format == 'doc')
648
		{
649
			$method = 'documentation';
650
		}
651
652
		$this->operation = strtolower($method);
653
654
		return $this;
655
	}
656
657
	/**
658
	 * Execute the Api operation.
659
	 *
660
	 * @return  mixed  RApi object with information on success, boolean false on failure.
661
	 *
662
	 * @since   1.2
663
	 * @throws  Exception
664
	 */
665
	public function execute()
666
	{
667
		// Set initial status code to OK
668
		$this->setStatusCode(200);
669
670
		// Prepare the application state
671
		$this->cleanCache();
672
673
		// We do not want some unwanted text to appear before output
674
		ob_start();
675
		$executionException = null;
676
677
		try
678
		{
679
			if (!empty($this->webserviceName))
680
			{
681
				if ($this->triggerFunction('isOperationAllowed'))
682
				{
683
					$this->elementName            = ucfirst(strtolower((string) $this->getConfig('config.name')));
684
					$this->operationConfiguration = $this->getConfig('operations.' . strtolower($this->operation));
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getConfig('operat...ower($this->operation)) can also be of type string. However, the property $operationConfiguration is declared as type SimpleXMLElement. 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...
685
686
					switch ($this->operation)
687
					{
688
						case 'create':
689
							$this->triggerFunction('apiCreate');
690
							break;
691
						case 'read':
692
							$this->triggerFunction('apiRead');
693
							break;
694
						case 'update':
695
							$this->triggerFunction('apiUpdate');
696
							break;
697
						case 'delete':
698
							$this->triggerFunction('apiDelete');
699
							break;
700
						case 'task':
701
							$this->triggerFunction('apiTask');
702
							break;
703
						case 'documentation':
704
							$this->triggerFunction('apiDocumentation');
705
							break;
706
					}
707
				}
708
			}
709
			else
710
			{
711
				// If default page needs authorization to access it
712
				if (!$this->isAuthorized('', RBootstrap::getConfig('webservices_default_page_authorization', 0)))
0 ignored issues
show
Bug introduced by
It seems like RBootstrap::getConfig('w...page_authorization', 0) can also be of type integer; however, parameter $terminateIfNotAuthorized of RApiHalHal::isAuthorized() does only seem to accept boolean, 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

712
				if (!$this->isAuthorized('', /** @scrutinizer ignore-type */ RBootstrap::getConfig('webservices_default_page_authorization', 0)))
Loading history...
713
				{
714
					return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by RApiInterface::execute() of RApi.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
715
				}
716
717
				// No webservice name. We display all webservices available
718
				$this->triggerFunction('apiDefaultPage');
719
			}
720
721
			// Set links from resources to the main document
722
			$this->setDataValueToResource($this->hal, $this->resources, $this->data);
723
724
			$executionErrors = ob_get_contents();
725
			ob_end_clean();
726
		}
727
		catch (Exception $e)
728
		{
729
			$executionException = $e;
730
			$executionErrors    = ob_get_contents();
731
			ob_end_clean();
732
		}
733
734
		$messages = JFactory::getApplication()->getMessageQueue();
735
736
		if (!empty($executionErrors))
737
		{
738
			$messages[] = array('message' => $executionErrors, 'type' => 'notice');
739
		}
740
741
		if (!empty($messages))
742
		{
743
			// If we are not in debug mode we will take out everything except errors
744
			if (RBootstrap::getConfig('debug_webservices', 0) == 0)
745
			{
746
				$warnings = array();
747
748
				foreach ($messages as $key => $message)
749
				{
750
					if ($message['type'] == 'warning')
751
					{
752
						$warnings[] = $message;
753
					}
754
755
					if ($message['type'] != 'error')
756
					{
757
						unset($messages[$key]);
758
					}
759
				}
760
761
				// Showing 'warning' messages only if no 'error' are present
762
				if (!count($messages))
763
				{
764
					$messages = $warnings;
765
				}
766
			}
767
768
			$this->hal->setData('_messages', $messages);
769
		}
770
771
		if ($executionException)
772
		{
773
			throw $executionException;
774
		}
775
776
		return $this;
777
	}
778
779
	/**
780
	 * Method to clear the cache and any session state
781
	 * related to API requests
782
	 *
783
	 * @param   string   $group     The cache group
784
	 * @param   integer  $clientId  The ID of the client
785
	 *
786
	 * @throws Exception
787
	 * @return void
788
	 */
789
	private function cleanCache($group = null, $clientId = 0)
790
	{
791
		if ($this->options->get('webservice_stateful', 0) == 1)
792
		{
793
			return;
794
		}
795
796
		$option = $this->options->get('optionName', '');
797
		$option = strpos($option, 'com_') === false ? 'com_' . $option : $option;
798
		$conf   = JFactory::getConfig();
799
800
		$options = array(
801
			'defaultgroup' => ($group) ? $group : $option,
802
			'cachebase' => ($clientId) ? JPATH_ADMINISTRATOR . '/cache' : $conf->get('cache_path', JPATH_SITE . '/cache'));
803
804
		$cache = JCache::getInstance('callback', $options);
805
		$cache->clean();
806
807
		$session  = JFactory::getSession();
808
		$registry = $session->get('registry');
809
		$registry->set($option, null);
810
	}
811
812
	/**
813
	 * Execute the Api Default Page operation.
814
	 *
815
	 * @return  mixed  RApi object with information on success, boolean false on failure.
816
	 *
817
	 * @since   1.2
818
	 */
819
	public function apiDefaultPage()
820
	{
821
		// Add standard Joomla namespace as curie.
822
		$documentationCurieAdmin = new RApiHalDocumentLink('/index.php?option={rel}&amp;format=doc&amp;webserviceClient=administrator',
823
			'curies', 'Documentation Admin', 'Admin', null, true
824
		);
825
		$documentationCurieSite  = new RApiHalDocumentLink('/index.php?option={rel}&amp;format=doc&amp;webserviceClient=site',
826
			'curies', 'Documentation Site', 'Site', null, true
827
		);
828
829
		// Add basic hypermedia links.
830
		$this->hal->setLink($documentationCurieAdmin, false, true);
831
		$this->hal->setLink($documentationCurieSite, false, true);
832
		$this->hal->setLink(new RApiHalDocumentLink(JUri::base(), 'base', JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_DOCUMENTATION_DEFAULT_PAGE')));
833
834
		$webservices = RApiHalHelper::getInstalledWebservices();
835
836
		if (!empty($webservices))
837
		{
838
			foreach ($webservices as $webserviceClient => $webserviceNames)
839
			{
840
				foreach ($webserviceNames as $webserviceName => $webserviceVersions)
841
				{
842
					foreach ($webserviceVersions as $webserviceVersion => $webservice)
843
					{
844
						if ($webservice['state'] == 1)
845
						{
846
							$documentation = $webserviceClient == 'site' ? 'Site' : 'Admin';
847
848
							// Set option and view name
849
							$this->setOptionViewName($webservice['name'], $this->configuration);
850
							$webserviceUrlPath = '/index.php?option=' . $this->optionName
851
								. '&amp;webserviceVersion=' . $webserviceVersion;
852
853
							if (!empty($this->viewName))
854
							{
855
								$webserviceUrlPath .= '&view=' . $this->viewName;
856
							}
857
858
							// We will fetch only top level webservice
859
							$this->hal->setLink(
860
								new RApiHalDocumentLink(
861
									$webserviceUrlPath . '&webserviceClient=' . $webserviceClient,
862
									$documentation . ':' . $webservice['name'],
863
									$webservice['title']
864
								)
865
							);
866
867
							break;
868
						}
869
					}
870
				}
871
			}
872
		}
873
874
		return $this;
875
	}
876
877
	/**
878
	 * Execute the Api Documentation operation.
879
	 *
880
	 * @return  mixed  RApi object with information on success, boolean false on failure.
881
	 *
882
	 * @since   1.2
883
	 */
884
	public function apiDocumentation()
885
	{
886
		$currentConfiguration = $this->configuration;
887
		$documentationNone    = false;
888
889
		if ($this->operationConfiguration['source'] == 'url')
0 ignored issues
show
introduced by
The condition $this->operationConfiguration['source'] == 'url' is always false.
Loading history...
890
		{
891
			if (!empty($this->operationConfiguration['url']))
892
			{
893
				JFactory::getApplication()->redirect($this->operationConfiguration['url']);
894
				$this->endHistoryLog(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_HISTORY_LOG_SUCCESS'));
895
				JFactory::getApplication()->close();
896
			}
897
898
			$documentationNone = true;
899
		}
900
901
		if ($this->operationConfiguration['source'] == 'none' || $documentationNone)
0 ignored issues
show
introduced by
The condition $documentationNone is always false.
Loading history...
902
		{
903
			$currentConfiguration = null;
904
		}
905
906
		$dataGet = $this->options->get('dataGet', array());
907
908
		$this->documentation = RLayoutHelper::render(
909
			'webservice.documentation',
910
			array(
911
				'view' => $this,
912
				'options' => array (
913
					'xml' => $currentConfiguration,
914
					'soapEnabled' => RBootstrap::getConfig('enable_soap', 0),
915
					'print' => isset($dataGet->print)
916
				)
917
			)
918
		);
919
920
		return $this;
921
	}
922
923
	/**
924
	 * Execute the Api Read operation.
925
	 *
926
	 * @return  mixed  RApi object with information on success, boolean false on failure.
927
	 *
928
	 * @since   1.2
929
	 */
930
	public function apiRead()
931
	{
932
		$primaryKeys = array();
933
		$isReadItem  = $this->apiFillPrimaryKeys($primaryKeys);
934
935
		$displayTarget                  = $isReadItem ? 'item' : 'list';
936
		$this->apiDynamicModelClassName = 'RApiHalModel' . ucfirst($displayTarget);
937
		$currentConfiguration           = $this->operationConfiguration->{$displayTarget};
938
		$model                          = $this->triggerFunction('loadModel', $this->elementName, $currentConfiguration);
939
		$this->assignFiltersList($model);
940
941
		if ($displayTarget == 'list')
942
		{
943
			$functionName       = RApiHalHelper::attributeToString($currentConfiguration, 'functionName', 'getItems');
944
			$paginationFunction = RApiHalHelper::attributeToString($currentConfiguration, 'paginationFunction', 'getPagination');
945
			$totalFunction      = RApiHalHelper::attributeToString($currentConfiguration, 'totalFunction', 'getTotal');
946
947
			$items = method_exists($model, $functionName) ? $model->{$functionName}() : array();
0 ignored issues
show
Bug introduced by
$functionName of type boolean is incompatible with the type string expected by parameter $method of method_exists(). ( Ignorable by Annotation )

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

947
			$items = method_exists($model, /** @scrutinizer ignore-type */ $functionName) ? $model->{$functionName}() : array();
Loading history...
948
949
			if (!empty($paginationFunction) && method_exists($model, $paginationFunction))
950
			{
951
				// Get total count to check if we have reached the limit
952
				if (!empty($totalFunction) && method_exists($model, $totalFunction))
953
				{
954
					$totalItems = $model->{$totalFunction}();
955
956
					if ($model->getState('limitstart', 0) >= $totalItems)
957
					{
958
						$customError = $this->triggerFunction('createCustomHttpError', 204, array('No more records found'));
959
						$this->setStatusCode(204, $customError);
960
961
						return $this;
962
					}
963
				}
964
965
				$pagination      = $model->{$paginationFunction}();
966
				$paginationPages = $pagination->getPaginationPages();
967
968
				$this->setData(
969
					'pagination.previous', isset($paginationPages['previous']['data']->base) ? $paginationPages['previous']['data']->base : $pagination->limitstart
970
				);
971
				$this->setData(
972
					'pagination.next', isset($paginationPages['next']['data']->base) ? $paginationPages['next']['data']->base : $pagination->limitstart
973
				);
974
				$this->setData('pagination.limit', $pagination->limit);
975
				$this->setData('pagination.limitstart', $pagination->limitstart);
976
				$this->setData('pagination.totalItems', $pagination->total);
977
				$this->setData('pagination.totalPages', max($pagination->pagesTotal, 1));
978
				$this->setData('pagination.page', max($pagination->pagesCurrent, 1));
979
				$this->setData('pagination.last', ((max($pagination->pagesTotal, 1) - 1) * $pagination->limit));
980
			}
981
982
			$this->triggerFunction('setForRenderList', $items, $currentConfiguration);
983
984
			return $this;
985
		}
986
987
		$primaryKeys = count($primaryKeys) > 1 ? array($primaryKeys) : $primaryKeys;
988
989
		// Getting single item
990
		$functionName   = RApiHalHelper::attributeToString($currentConfiguration, 'functionName', 'getItem');
991
		$messagesBefore = JFactory::getApplication()->getMessageQueue();
992
		$itemObject     = method_exists($model, $functionName) ? call_user_func_array(array(&$model, $functionName), $primaryKeys) : array();
993
		$messagesAfter  = JFactory::getApplication()->getMessageQueue();
994
995
		// Check to see if we have the item or not since it might return default properties
996
		if (count($messagesBefore) != count($messagesAfter))
997
		{
998
			foreach ($messagesAfter as $messageKey => $messageValue)
999
			{
1000
				$messageFound = false;
1001
1002
				foreach ($messagesBefore as $key => $value)
1003
				{
1004
					if ($messageValue['type'] == $value['type'] && $messageValue['message'] == $value['message'])
1005
					{
1006
						$messageFound = true;
1007
						break;
1008
					}
1009
				}
1010
1011
				if (!$messageFound && $messageValue['type'] == 'error')
1012
				{
1013
					$itemObject = null;
1014
					break;
1015
				}
1016
			}
1017
		}
1018
1019
		if (RApiHalHelper::isAttributeTrue($currentConfiguration, 'enforcePKs', true))
1020
		{
1021
			// Checking if primary keys are found
1022
			foreach ($primaryKeys as $primaryKey => $primaryKeyValue)
1023
			{
1024
				if (property_exists($itemObject, $primaryKey) && $itemObject->{$primaryKey} != $primaryKeyValue)
1025
				{
1026
					$itemObject = null;
1027
					break;
1028
				}
1029
			}
1030
		}
1031
1032
		$this->triggerFunction('setForRenderItem', $itemObject, $currentConfiguration);
1033
1034
		return $this;
1035
	}
1036
1037
	/**
1038
	 * Execute the Api Create operation.
1039
	 *
1040
	 * @return  mixed  RApi object with information on success, boolean false on failure.
1041
	 *
1042
	 * @since   1.2
1043
	 */
1044
	public function apiCreate()
1045
	{
1046
		// Get resource list from configuration
1047
		$this->loadResourceFromConfiguration($this->operationConfiguration);
1048
1049
		$model        = $this->triggerFunction('loadModel', $this->elementName, $this->operationConfiguration);
1050
		$functionName = RApiHalHelper::attributeToString($this->operationConfiguration, 'functionName', 'save');
1051
1052
		$data = $this->triggerFunction('processPostData', $this->options->get('data', array()), $this->operationConfiguration);
1053
		$data = $this->triggerFunction('validatePostData', $model, $data, $this->operationConfiguration);
1054
1055
		if ($data === false)
1056
		{
1057
			// Not Acceptable
1058
			$this->setStatusCode(406);
1059
			$this->triggerFunction('displayErrors', $model);
1060
			$this->setData('result', $data);
1061
1062
			return;
1063
		}
1064
1065
		// Prepare parameters for the function
1066
		$args   = $this->buildFunctionArgs($this->operationConfiguration, $data);
1067
		$result = null;
1068
1069
		// Checks if that method exists in model class file and executes it
1070
		if (method_exists($model, $functionName))
0 ignored issues
show
Bug introduced by
$functionName of type boolean is incompatible with the type string expected by parameter $method of method_exists(). ( Ignorable by Annotation )

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

1070
		if (method_exists($model, /** @scrutinizer ignore-type */ $functionName))
Loading history...
1071
		{
1072
			$result = $this->triggerCallFunction($model, $functionName, $args);
1073
		}
1074
		else
1075
		{
1076
			$customError = $this->triggerFunction('createCustomHttpError', 400, $this->apiErrors);
1077
			$this->setStatusCode(400, $customError);
1078
		}
1079
1080
		if (method_exists($model, 'getState'))
1081
		{
1082
			$this->setData('id', $model->getState($model->getName() . '.id'));
1083
		}
1084
1085
		if (method_exists($model, 'getErrors'))
1086
		{
1087
			$modelErrors = $model->getErrors();
1088
1089
			if (!empty($modelErrors))
1090
			{
1091
				$this->apiErrors = array_merge($this->apiErrors, $modelErrors);
1092
			}
1093
		}
1094
1095
		$this->setData('result', $result);
1096
		$this->triggerFunction('displayErrors', $model);
1097
1098
		if ($this->statusCode < 400)
1099
		{
1100
			if ($result === false)
1101
			{
1102
				$customError = $this->triggerFunction('createCustomHttpError', 404, $this->apiErrors);
1103
				$this->setStatusCode(404, $customError);
1104
			}
1105
			else
1106
			{
1107
				$this->setStatusCode(201);
1108
			}
1109
		}
1110
	}
1111
1112
	/**
1113
	 * Execute the Api Delete operation.
1114
	 *
1115
	 * @return  mixed  RApi object with information on success, boolean false on failure.
1116
	 *
1117
	 * @since   1.2
1118
	 */
1119
	public function apiDelete()
1120
	{
1121
		// Get resource list from configuration
1122
		$this->loadResourceFromConfiguration($this->operationConfiguration);
1123
1124
		// Delete function requires references and not values like we use in call_user_func_array so we use List delete function
1125
		$this->apiDynamicModelClassName = 'RApiHalModelList';
1126
		$model                          = $this->triggerFunction('loadModel', $this->elementName, $this->operationConfiguration);
1127
		$functionName                   = RApiHalHelper::attributeToString($this->operationConfiguration, 'functionName', 'delete');
1128
		$data                           = $this->triggerFunction('processPostData', $this->options->get('data', array()), $this->operationConfiguration);
1129
1130
		$data = $this->triggerFunction('validatePostData', $model, $data, $this->operationConfiguration);
1131
1132
		if ($data === false)
1133
		{
1134
			// Not Acceptable
1135
			$customError = $this->triggerFunction('createCustomHttpError', 406, $this->apiErrors);
1136
			$this->setStatusCode(406, $customError);
1137
			$this->triggerFunction('displayErrors', $model);
1138
			$this->setData('result', $data);
1139
1140
			return;
1141
		}
1142
1143
		$result = null;
1144
		$args   = $this->buildFunctionArgs($this->operationConfiguration, $data);
1145
1146
		// Prepare parameters for the function
1147
		if (strtolower(RApiHalHelper::attributeToString($this->operationConfiguration, 'dataMode', 'model')) == 'table')
0 ignored issues
show
Bug introduced by
RApiHalHelper::attribute...n, 'dataMode', 'model') of type boolean is incompatible with the type string expected by parameter $string of strtolower(). ( Ignorable by Annotation )

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

1147
		if (strtolower(/** @scrutinizer ignore-type */ RApiHalHelper::attributeToString($this->operationConfiguration, 'dataMode', 'model')) == 'table')
Loading history...
1148
		{
1149
			$primaryKeys = array();
1150
			$this->apiFillPrimaryKeys($primaryKeys, $this->operationConfiguration);
1151
1152
			if (!empty($primaryKeys))
1153
			{
1154
				$result = $model->{$functionName}($primaryKeys);
1155
			}
1156
			else
1157
			{
1158
				$result = $model->{$functionName}($args);
1159
			}
1160
		}
1161
		else
1162
		{
1163
			// Checks if that method exists in model class file and executes it
1164
			if (method_exists($model, $functionName))
0 ignored issues
show
Bug introduced by
$functionName of type boolean is incompatible with the type string expected by parameter $method of method_exists(). ( Ignorable by Annotation )

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

1164
			if (method_exists($model, /** @scrutinizer ignore-type */ $functionName))
Loading history...
1165
			{
1166
				$result = $this->triggerCallFunction($model, $functionName, $args);
1167
			}
1168
			else
1169
			{
1170
				$customError = $this->triggerFunction('createCustomHttpError', 400, $this->apiErrors);
1171
				$this->setStatusCode(400, $customError);
1172
			}
1173
		}
1174
1175
		if (method_exists($model, 'getErrors'))
1176
		{
1177
			$modelErrors = $model->getErrors();
1178
1179
			if (!empty($modelErrors))
1180
			{
1181
				$this->apiErrors = array_merge($this->apiErrors, $modelErrors);
1182
			}
1183
		}
1184
1185
		$this->setData('result', $result);
1186
1187
		$this->triggerFunction('displayErrors', $model);
1188
1189
		if ($this->statusCode < 400)
1190
		{
1191
			if ($result === false)
1192
			{
1193
				// If delete failed then we set it to Internal Server Error status code
1194
				$customError = $this->triggerFunction('createCustomHttpError', 500, $this->apiErrors);
1195
				$this->setStatusCode(500, $customError);
1196
			}
1197
		}
1198
	}
1199
1200
	/**
1201
	 * Execute the Api Update operation.
1202
	 *
1203
	 * @return  mixed  RApi object with information on success, boolean false on failure.
1204
	 *
1205
	 * @since   1.2
1206
	 */
1207
	public function apiUpdate()
1208
	{
1209
		// Get resource list from configuration
1210
		$this->loadResourceFromConfiguration($this->operationConfiguration);
1211
		$model        = $this->triggerFunction('loadModel', $this->elementName, $this->operationConfiguration);
1212
		$functionName = RApiHalHelper::attributeToString($this->operationConfiguration, 'functionName', 'save');
1213
		$data         = $this->triggerFunction('processPostData', $this->options->get('data', array()), $this->operationConfiguration);
1214
1215
		$data = $this->triggerFunction('validatePostData', $model, $data, $this->operationConfiguration);
1216
1217
		if ($data === false)
1218
		{
1219
			// Not Acceptable
1220
			$customError = $this->triggerFunction('createCustomHttpError', 406, $this->apiErrors);
1221
			$this->setStatusCode(406, $customError);
1222
			$this->triggerFunction('displayErrors', $model);
1223
			$this->setData('result', $data);
1224
1225
			return;
1226
		}
1227
1228
		// Prepare parameters for the function
1229
		$args   = $this->buildFunctionArgs($this->operationConfiguration, $data);
1230
		$result = null;
1231
1232
		// Checks if that method exists in model class and executes it
1233
		if (method_exists($model, $functionName))
0 ignored issues
show
Bug introduced by
$functionName of type boolean is incompatible with the type string expected by parameter $method of method_exists(). ( Ignorable by Annotation )

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

1233
		if (method_exists($model, /** @scrutinizer ignore-type */ $functionName))
Loading history...
1234
		{
1235
			$result = $this->triggerCallFunction($model, $functionName, $args);
1236
		}
1237
		else
1238
		{
1239
			$customError = $this->triggerFunction('createCustomHttpError', 400, $this->apiErrors);
1240
			$this->setStatusCode(400, $customError);
1241
		}
1242
1243
		if (method_exists($model, 'getState'))
1244
		{
1245
			$this->setData('id', $model->getState(strtolower($this->elementName) . '.id'));
1246
		}
1247
1248
		if (method_exists($model, 'getErrors'))
1249
		{
1250
			$modelErrors = $model->getErrors();
1251
1252
			if (!empty($modelErrors))
1253
			{
1254
				$this->apiErrors = array_merge($this->apiErrors, $modelErrors);
1255
			}
1256
		}
1257
1258
		$this->setData('result', $result);
1259
		$this->triggerFunction('displayErrors', $model);
1260
1261
		if ($this->statusCode < 400)
1262
		{
1263
			if ($result === false)
1264
			{
1265
				// If update failed then we set it to Internal Server Error status code
1266
				$customError = $this->triggerFunction('createCustomHttpError', 500, $this->apiErrors);
1267
				$this->setStatusCode(500, $customError);
1268
			}
1269
		}
1270
	}
1271
1272
	/**
1273
	 * Execute the Api Task operation.
1274
	 *
1275
	 * @return  mixed  RApi object with information on success, boolean false on failure.
1276
	 *
1277
	 * @since   1.2
1278
	 */
1279
	public function apiTask()
1280
	{
1281
		$task   = $this->options->get('task', '');
1282
		$result = false;
1283
1284
		if (!empty($task))
1285
		{
1286
			// Load resources directly from task group
1287
			if (!empty($this->operationConfiguration->{$task}->resources))
1288
			{
1289
				$this->loadResourceFromConfiguration($this->operationConfiguration->{$task});
1290
			}
1291
1292
			$taskConfiguration = !empty($this->operationConfiguration->{$task}) ?
1293
				$this->operationConfiguration->{$task} : $this->operationConfiguration;
1294
1295
			$model        = $this->triggerFunction('loadModel', $this->elementName, $taskConfiguration);
1296
			$functionName = RApiHalHelper::attributeToString($taskConfiguration, 'functionName', $task);
1297
			$data         = $this->triggerFunction('processPostData', $this->options->get('data', array()), $taskConfiguration);
1298
1299
			$data = $this->triggerFunction('validatePostData', $model, $data, $taskConfiguration);
1300
1301
			if ($data === false)
1302
			{
1303
				// Not Acceptable
1304
				$customError = $this->triggerFunction('createCustomHttpError', 406, $this->apiErrors);
1305
				$this->setStatusCode(406, $customError);
1306
				$this->triggerFunction('displayErrors', $model);
1307
				$this->setData('result', $data);
1308
1309
				return;
1310
			}
1311
1312
			// Prepare parameters for the function
1313
			$args   = $this->buildFunctionArgs($taskConfiguration, $data);
1314
			$result = null;
1315
1316
			// Checks if that method exists in model class and executes it
1317
			if (method_exists($model, $functionName))
0 ignored issues
show
Bug introduced by
$functionName of type boolean is incompatible with the type string expected by parameter $method of method_exists(). ( Ignorable by Annotation )

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

1317
			if (method_exists($model, /** @scrutinizer ignore-type */ $functionName))
Loading history...
1318
			{
1319
				$result = $this->triggerCallFunction($model, $functionName, $args);
1320
			}
1321
			else
1322
			{
1323
				$customError = $this->triggerFunction('createCustomHttpError', 400, $this->apiErrors);
1324
				$this->setStatusCode(400, $customError);
1325
				$this->triggerFunction('displayErrors', $model);
1326
			}
1327
1328
			if (method_exists($model, 'getErrors'))
1329
			{
1330
				$modelErrors = $model->getErrors();
1331
1332
				if (!empty($modelErrors))
1333
				{
1334
					$this->apiErrors = array_merge($this->apiErrors, $modelErrors);
1335
				}
1336
			}
1337
1338
			if (method_exists($model, 'getState'))
1339
			{
1340
				$this->setData('id', $model->getState(strtolower($this->elementName) . '.id'));
1341
			}
1342
		}
1343
1344
		$this->setData('result', $result);
1345
		$this->triggerFunction('displayErrors', $model);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $model does not seem to be defined for all execution paths leading up to this point.
Loading history...
1346
	}
1347
1348
	/**
1349
	 * Set document content for List view
1350
	 *
1351
	 * @param   array             $items          List of items
1352
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1353
	 *
1354
	 * @return void
1355
	 */
1356
	public function setForRenderList($items, $configuration)
1357
	{
1358
		// Get resource list from configuration
1359
		$this->loadResourceFromConfiguration($configuration);
1360
1361
		$listResourcesKeys = array_keys($this->resources['listItem']);
1362
1363
		if (!empty($items))
1364
		{
1365
			// Filter out all fields that are not in resource list and apply appropriate transform rules
1366
			foreach ($items as $itemValue)
1367
			{
1368
				$item = ArrayHelper::fromObject($itemValue);
1369
1370
				foreach ($item as $key => $value)
1371
				{
1372
					if (!in_array($key, $listResourcesKeys))
1373
					{
1374
						unset($item[$key]);
1375
						continue;
1376
					}
1377
					else
1378
					{
1379
						$item[$this->assignGlobalValueToResource($key)] = $this->assignValueToResource(
1380
							$this->resources['listItem'][$key], $item
1381
						);
1382
					}
1383
				}
1384
1385
				$embedItem = new RApiHalDocumentResource('item', $item);
1386
				$embedItem = $this->setDataValueToResource($embedItem, $this->resources, $itemValue, 'listItem');
1387
				$this->hal->setEmbedded('item', $embedItem);
1388
			}
1389
		}
1390
	}
1391
1392
	/**
1393
	 * Loads Resource list from configuration file for specific method or task
1394
	 *
1395
	 * @param   RApiHalDocumentResource  $resourceDocument  Resource document for binding the resource
1396
	 * @param   array                    $resources         Configuration for displaying object
1397
	 * @param   mixed                    $data              Data to bind to the resources
1398
	 * @param   string                   $resourceSpecific  Resource specific string that separates resources
1399
	 *
1400
	 * @return RApiHalDocumentResource
1401
	 */
1402
	public function setDataValueToResource($resourceDocument, $resources, $data, $resourceSpecific = 'rcwsGlobal')
1403
	{
1404
		if (!empty($resources[$resourceSpecific]))
1405
		{
1406
			// Add links from the resource
1407
			foreach ($resources[$resourceSpecific] as $resource)
1408
			{
1409
				if (!empty($resource['displayGroup']))
1410
				{
1411
					if ($resource['displayGroup'] == '_links')
1412
					{
1413
						$linkRel = !empty($resource['linkRel']) ? $resource['linkRel'] : $this->assignGlobalValueToResource($resource['displayName']);
1414
1415
						// We will force curries as link array
1416
						$linkPlural = $linkRel == 'curies';
1417
1418
						$resourceDocument->setLink(
1419
							new RApiHalDocumentLink(
1420
								$this->assignValueToResource($resource, $data),
1421
								$linkRel,
1422
								$resource['linkTitle'],
1423
								$this->assignGlobalValueToResource($resource['linkName']),
1424
								$resource['hrefLang'],
1425
								RApiHalHelper::isAttributeTrue($resource, 'linkTemplated')
1426
							), $linkSingular = false, $linkPlural
1427
						);
1428
					}
1429
					else
1430
					{
1431
						$resourceDocument->setDataGrouped(
1432
							$resource['displayGroup'], $this->assignGlobalValueToResource($resource['displayName']), $this->assignValueToResource($resource, $data)
1433
						);
1434
					}
1435
				}
1436
				else
1437
				{
1438
					$resourceDocument->setData($this->assignGlobalValueToResource($resource['displayName']), $this->assignValueToResource($resource, $data));
1439
				}
1440
			}
1441
		}
1442
1443
		return $resourceDocument;
1444
	}
1445
1446
	/**
1447
	 * Loads Resource list from configuration file for specific method or task
1448
	 *
1449
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1450
	 *
1451
	 * @return array
1452
	 */
1453
	public function loadResourceFromConfiguration($configuration)
1454
	{
1455
		if (isset($configuration->resources->resource))
1456
		{
1457
			foreach ($configuration->resources->resource as $resourceXML)
1458
			{
1459
				$resource = RApiHalHelper::getXMLElementAttributes($resourceXML);
1460
1461
				// Filters out specified displayGroup values
1462
				if ($this->options->get('filterOutResourcesGroups') != ''
1463
					&& in_array($resource['displayGroup'], $this->options->get('filterOutResourcesGroups')))
1464
				{
1465
					continue;
1466
				}
1467
1468
				// Filters out if the optional resourceSpecific filter is not the one defined
1469
				if ($this->options->get('filterResourcesSpecific') != ''
1470
					&& $resource['resourceSpecific'] != $this->options->get('filterResourcesSpecific'))
1471
				{
1472
					continue;
1473
				}
1474
1475
				// Filters out if the optional displayName filter is not the one defined
1476
				if ($this->options->get('filterDisplayName') != ''
1477
					&& $resource['displayName'] != $this->options->get('filterDisplayName'))
1478
				{
1479
					continue;
1480
				}
1481
1482
				if (!empty($resourceXML->description))
1483
				{
1484
					$resource['description'] = $resourceXML->description;
1485
				}
1486
1487
				$resource         = RApiHalDocumentResource::defaultResourceField($resource);
1488
				$resourceName     = $resource['displayName'];
1489
				$resourceSpecific = $resource['resourceSpecific'];
1490
1491
				$this->resources[$resourceSpecific][$resourceName] = $resource;
1492
			}
1493
		}
1494
1495
		return $this->resources;
1496
	}
1497
1498
	/**
1499
	 * Resets specific Resource list or all Resources
1500
	 *
1501
	 * @param   string  $resourceSpecific  Resource specific string that separates resources
1502
	 *
1503
	 * @return RApiHalHal
1504
	 */
1505
	public function resetDocumentResources($resourceSpecific = '')
1506
	{
1507
		if (!empty($resourceSpecific))
1508
		{
1509
			if (isset($this->resources[$resourceSpecific]))
1510
			{
1511
				unset($this->resources[$resourceSpecific]);
1512
			}
1513
1514
			return $this;
1515
		}
1516
1517
		$this->resources = array();
1518
1519
		return $this;
1520
	}
1521
1522
	/**
1523
	 * Used for ordering arrays
1524
	 *
1525
	 * @param   string  $a  Current array
1526
	 * @param   string  $b  Next array
1527
	 *
1528
	 * @return RApiHalHal
1529
	 */
1530
	public function sortResourcesByDisplayGroup($a, $b)
1531
	{
1532
		$sort = strcmp($a["displayGroup"], $b["displayGroup"]);
1533
1534
		if (!$sort)
1535
		{
1536
			return ($a['original_order'] < $b['original_order'] ? -1 : 1);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $a['original_orde...iginal_order'] ? -1 : 1 returns the type integer which is incompatible with the documented return type RApiHalHal.
Loading history...
1537
		}
1538
1539
		return $sort;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $sort returns the type integer which is incompatible with the documented return type RApiHalHal.
Loading history...
1540
	}
1541
1542
	/**
1543
	 * Set document content for Item view
1544
	 *
1545
	 * @param   object|array      $item           Item content
1546
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1547
	 *
1548
	 * @throws Exception
1549
	 * @return void
1550
	 */
1551
	public function setForRenderItem($item, $configuration)
1552
	{
1553
		// Get resource list from configuration
1554
		$this->loadResourceFromConfiguration($configuration);
1555
1556
		if (!empty($item) && (is_array($item) || is_object($item)))
1557
		{
1558
			// Filter out all fields that are not in resource list and apply appropriate transform rules
1559
			foreach ($item as $key => $value)
1560
			{
1561
				$value = !empty($this->resources['rcwsGlobal'][$key]) ? $this->assignValueToResource($this->resources['rcwsGlobal'][$key], $item) : $value;
1562
				$this->setData($this->assignGlobalValueToResource($key), $value);
1563
			}
1564
		}
1565
		else
1566
		{
1567
			// 404 => 'Not found'
1568
			$customError = $this->triggerFunction('createCustomHttpError', 404, $this->apiErrors);
1569
			$this->setStatusCode(404, $customError);
1570
1571
			throw new Exception(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_ERROR_NO_CONTENT'), 404);
1572
		}
1573
	}
1574
1575
	/**
1576
	 * Method to send the application response to the client.  All headers will be sent prior to the main
1577
	 * application output data.
1578
	 *
1579
	 * @return  void
1580
	 *
1581
	 * @since   1.2
1582
	 */
1583
	public function render()
1584
	{
1585
		// Set token to uri if used in that way
1586
		$token  = $this->options->get('accessToken', '');
1587
		$client = $this->options->get('webserviceClient', '');
1588
		$format = $this->options->get('format', 'json');
1589
1590
		if (!empty($token))
1591
		{
1592
			$this->setUriParams(RBootstrap::getConfig('oauth2_token_param_name', 'access_token'), $token);
1593
		}
1594
1595
		if ($client == 'administrator')
1596
		{
1597
			$this->setUriParams('webserviceClient', $client);
1598
		}
1599
1600
		$this->setUriParams('api', 'Hal');
1601
1602
		if ($format == 'doc')
1603
		{
1604
			// This is already in HTML format
1605
			echo $this->documentation;
1606
		}
1607
		else
1608
		{
1609
			$documentOptions    = array(
1610
				'absoluteHrefs' => $this->options->get('absoluteHrefs', false),
1611
				'documentFormat' => $format,
1612
				'uriParams' => $this->uriParams,
1613
			);
1614
			JFactory::$document = new RApiHalDocumentDocument($documentOptions);
1615
1616
			$body = $this->getBody();
1617
			$body = $this->triggerFunction('prepareBody', $body);
1618
1619
			// Push results into the document.
1620
			JFactory::$document
1621
				->setHal($this)
1622
				->setBuffer($body)
1623
				->render(false);
1624
		}
1625
	}
1626
1627
	/**
1628
	 * Method to fill response with requested data
1629
	 *
1630
	 * @return  string  Api call output
1631
	 *
1632
	 * @since   1.2
1633
	 */
1634
	public function getBody()
1635
	{
1636
		return $this->hal;
1637
	}
1638
1639
	/**
1640
	 * Prepares body for response
1641
	 *
1642
	 * @param   string  $message  The return message
1643
	 *
1644
	 * @return  string	The message prepared
1645
	 *
1646
	 * @since   1.2
1647
	 */
1648
	public function prepareBody($message)
1649
	{
1650
		return $message;
1651
	}
1652
1653
	/**
1654
	 * Sets data for resource binding
1655
	 *
1656
	 * @param   string  $key   Rel element
1657
	 * @param   mixed   $data  Data for the resource
1658
	 *
1659
	 * @return RApiHalHal
1660
	 */
1661
	public function setData($key, $data = null)
1662
	{
1663
		if (is_array($key) && null === $data)
0 ignored issues
show
introduced by
The condition is_array($key) is always false.
Loading history...
1664
		{
1665
			foreach ($key as $k => $v)
1666
			{
1667
				$this->data[$k] = $v;
1668
			}
1669
		}
1670
		else
1671
		{
1672
			$this->data[$key] = $data;
1673
		}
1674
1675
		return $this;
1676
	}
1677
1678
	/**
1679
	 * Set the Uri parameters
1680
	 *
1681
	 * @param   string  $uriKey    Uri Key
1682
	 * @param   string  $uriValue  Uri Value
1683
	 *
1684
	 * @return  RApiHalHal      An instance of itself for chaining
1685
	 */
1686
	public function setUriParams($uriKey, $uriValue)
1687
	{
1688
		$this->uriParams[$uriKey] = $uriValue;
1689
1690
		return $this;
1691
	}
1692
1693
	/**
1694
	 * Process posted data from json or object to array
1695
	 *
1696
	 * @param   array             $data           Raw Posted data
1697
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1698
	 *
1699
	 * @return  mixed  Array with posted data.
1700
	 *
1701
	 * @since   1.2
1702
	 */
1703
	public function processPostData($data, $configuration)
1704
	{
1705
		if (is_object($data))
0 ignored issues
show
introduced by
The condition is_object($data) is always false.
Loading history...
1706
		{
1707
			$data = ArrayHelper::fromObject($data);
1708
		}
1709
1710
		if (!is_array($data))
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
1711
		{
1712
			$data = (array) $data;
1713
		}
1714
1715
		if (!empty($data) && !empty($configuration->fields))
1716
		{
1717
			$dataFields = array();
1718
1719
			foreach ($configuration->fields->field as $field)
1720
			{
1721
				$fieldAttributes                 = RApiHalHelper::getXMLElementAttributes($field);
1722
				$fieldAttributes['transform']    = !is_null($fieldAttributes['transform']) ? $fieldAttributes['transform'] : 'string';
1723
				$fieldAttributes['defaultValue'] = !is_null($fieldAttributes['defaultValue'])
1724
					&& !RApiHalHelper::isAttributeTrue($fieldAttributes, 'isPrimaryField') ? $fieldAttributes['defaultValue'] : '';
1725
1726
				// If field is not sent through Request
1727
				if (!isset($data[$fieldAttributes['name']]))
1728
				{
1729
					// We will populate missing fields with null value
1730
					$data[$fieldAttributes['name']] = null;
1731
1732
					// We will populate value with default value if the field is not set for create operation
1733
					if ($this->operation == 'create')
1734
					{
1735
						$data[$fieldAttributes['name']] = $fieldAttributes['defaultValue'];
1736
					}
1737
				}
1738
1739
				if (!is_null($data[$fieldAttributes['name']]))
1740
				{
1741
					$data[$fieldAttributes['name']] = $this->transformField($fieldAttributes['transform'], $data[$fieldAttributes['name']], false);
1742
				}
1743
1744
				$dataFields[$fieldAttributes['name']] = $data[$fieldAttributes['name']];
1745
			}
1746
1747
			if (RApiHalHelper::isAttributeTrue($configuration, 'strictFields'))
1748
			{
1749
				$data = $dataFields;
1750
			}
1751
		}
1752
1753
		// Common functions are not checking this field so we will
1754
		$data['params']       = isset($data['params']) ? $data['params'] : null;
1755
		$data['associations'] = isset($data['associations']) ? $data['associations'] : array();
1756
1757
		return $data;
1758
	}
1759
1760
	/**
1761
	 * Validates posted data
1762
	 *
1763
	 * @param   object            $model          Model
1764
	 * @param   array             $data           Raw Posted data
1765
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1766
	 *
1767
	 * @return  mixed  Array with posted data or false.
1768
	 *
1769
	 * @since   1.3
1770
	 */
1771
	public function validatePostData($model, $data, $configuration)
1772
	{
1773
		$data = (array) $data;
1774
		$app  = JFactory::getApplication();
1775
1776
		// We are checking required fields set in webservice XMLs
1777
		if (!$this->checkRequiredFields($data, $configuration))
1778
		{
1779
			return false;
1780
		}
1781
1782
		$validateMethod = strtolower(RApiHalHelper::attributeToString($configuration, 'validateData', 'none'));
0 ignored issues
show
Bug introduced by
RApiHalHelper::attribute...'validateData', 'none') of type boolean is incompatible with the type string expected by parameter $string of strtolower(). ( Ignorable by Annotation )

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

1782
		$validateMethod = strtolower(/** @scrutinizer ignore-type */ RApiHalHelper::attributeToString($configuration, 'validateData', 'none'));
Loading history...
1783
1784
		if ($validateMethod == 'none')
1785
		{
1786
			return $data;
1787
		}
1788
1789
		if ($validateMethod == 'form')
1790
		{
1791
			if (method_exists($model, 'getForm'))
1792
			{
1793
				// Validate the posted data.
1794
				// Sometimes the form needs some posted data, such as for plugins and modules.
1795
				$form = $model->getForm($data, false);
1796
1797
				if (!$form)
1798
				{
1799
					return $data;
1800
				}
1801
1802
				// Test whether the data is valid.
1803
				$validData = $model->validate($form, $data);
1804
1805
				// Common functions are not checking this field so we will
1806
				$validData['params']       = isset($validData['params']) ? $validData['params'] : null;
1807
				$validData['associations'] = isset($validData['associations']) ? $validData['associations'] : array();
1808
1809
				return $validData;
1810
			}
1811
1812
			$app->enqueueMessage(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_FUNCTION_DONT_EXIST'), 'error');
1813
1814
			return false;
1815
		}
1816
1817
		if ($validateMethod == 'function')
1818
		{
1819
			$validateMethod = strtolower(RApiHalHelper::attributeToString($configuration, 'validateDataFunction', 'validate'));
1820
1821
			if (method_exists($model, $validateMethod))
1822
			{
1823
				$result = $model->{$validateMethod}($data);
1824
1825
				return $result;
1826
			}
1827
1828
			$app->enqueueMessage(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_FUNCTION_DONT_EXIST'), 'error');
1829
1830
			return false;
1831
		}
1832
1833
		return false;
1834
	}
1835
1836
	/**
1837
	 * Validates posted data
1838
	 *
1839
	 * @param   array             $data           Raw Posted data
1840
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1841
	 *
1842
	 * @return  mixed  Array with posted data or false.
1843
	 *
1844
	 * @since   1.3
1845
	 */
1846
	public function checkRequiredFields($data, $configuration)
1847
	{
1848
		$errors = array();
1849
1850
		if (!empty($configuration->fields))
1851
		{
1852
			foreach ($configuration->fields->field as $field)
1853
			{
1854
				if (RApiHalHelper::isAttributeTrue($field, 'isRequiredField'))
1855
				{
1856
					if (is_null($data[(string) $field['name']]) || $data[(string) $field['name']] === '')
1857
					{
1858
						JFactory::getApplication()->enqueueMessage(
1859
							JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_ERROR_REQUIRED_FIELD', (string) $field['name']), 'error'
1860
						);
1861
1862
						$errors[] = JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_ERROR_REQUIRED_FIELD', (string) $field['name']);
1863
					}
1864
				}
1865
			}
1866
		}
1867
1868
		if (!empty($errors))
1869
		{
1870
			$this->apiErrors = array_merge($this->apiErrors, $errors);
1871
1872
			return false;
1873
		}
1874
1875
		return true;
1876
	}
1877
1878
	/**
1879
	 * Checks if operation is allowed from the configuration file
1880
	 *
1881
	 * @return boolean
1882
	 *
1883
	 * @throws  Exception
1884
	 */
1885
	public function isOperationAllowed()
1886
	{
1887
		// Check if webservice is published
1888
		if (!RApiHalHelper::isPublishedWebservice($this->client, $this->webserviceName, $this->webserviceVersion) && !empty($this->webserviceName))
1889
		{
1890
			throw new Exception(JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_IS_UNPUBLISHED', $this->webserviceName));
1891
		}
1892
1893
		// Check for allowed operations
1894
		$allowedOperations = $this->getConfig('operations');
1895
1896
		if (!isset($allowedOperations->{$this->operation}))
1897
		{
1898
			$customError = $this->triggerFunction('createCustomHttpError', 405, $this->apiErrors);
1899
			$this->setStatusCode(405, $customError);
1900
1901
			return false;
1902
		}
1903
1904
		$scope                    = $this->operation;
1905
		$authorizationGroups      = !empty($allowedOperations->{$this->operation}['authorization']) ?
1906
			(string) $allowedOperations->{$this->operation}['authorization'] : '';
1907
		$terminateIfNotAuthorized = true;
1908
1909
		if ($this->operation == 'task')
1910
		{
1911
			$task   = $this->options->get('task', '');
1912
			$scope .= '.' . $task;
1913
1914
			if (!isset($allowedOperations->task->{$task}))
1915
			{
1916
				$customError = $this->triggerFunction('createCustomHttpError', 405, $this->apiErrors);
1917
				$this->setStatusCode(405, $customError);
1918
1919
				return false;
1920
			}
1921
1922
			$authorizationGroups = !empty($allowedOperations->task->{$task}['authorization']) ?
1923
				(string) $allowedOperations->task->{$task}['authorization'] : '';
1924
1925
			if (isset($allowedOperations->task->{$task}['authorizationNeeded'])
1926
				&& strtolower($allowedOperations->task->{$task}['authorizationNeeded']) == 'false')
1927
			{
1928
				$terminateIfNotAuthorized = false;
1929
			}
1930
		}
1931
		elseif ($this->operation == 'read')
1932
		{
1933
			// Disable authorization on operation read level
1934
			if (isset($allowedOperations->{$this->operation}['authorizationNeeded'])
1935
				&& strtolower($allowedOperations->{$this->operation}['authorizationNeeded']) == 'false')
1936
			{
1937
				$terminateIfNotAuthorized = false;
1938
			}
1939
			else
1940
			{
1941
				$primaryKeys = array();
1942
				$isReadItem  = $this->apiFillPrimaryKeys($primaryKeys);
1943
				$readType    = $isReadItem ? 'item' : 'list';
1944
1945
				if (isset($allowedOperations->read->{$readType}['authorizationNeeded'])
1946
					&& strtolower($allowedOperations->read->{$readType}['authorizationNeeded']) == 'false')
1947
				{
1948
					$terminateIfNotAuthorized = false;
1949
				}
1950
			}
1951
		}
1952
		elseif (isset($allowedOperations->{$this->operation}['authorizationNeeded'])
1953
			&& strtolower($allowedOperations->{$this->operation}['authorizationNeeded']) == 'false')
1954
		{
1955
			$terminateIfNotAuthorized = false;
1956
		}
1957
1958
		// Does user have permission
1959
		// OAuth2 check
1960
		if ($this->authorizationCheck == 'oauth2')
1961
		{
1962
			// Use scopes to authorize
1963
			$scope = array(strtolower($this->client . '.' . $this->webserviceName . '.' . $scope));
1964
1965
			// Add in Global scope check
1966
			$scope[] = $this->client . '.' . $this->operation;
1967
1968
			return $this->isAuthorized($scope, $terminateIfNotAuthorized) || !$terminateIfNotAuthorized;
0 ignored issues
show
Bug introduced by
$scope of type array<integer,string> is incompatible with the type string expected by parameter $scope of RApiHalHal::isAuthorized(). ( Ignorable by Annotation )

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

1968
			return $this->isAuthorized(/** @scrutinizer ignore-type */ $scope, $terminateIfNotAuthorized) || !$terminateIfNotAuthorized;
Loading history...
1969
		}
1970
		// Joomla check
1971
		elseif ($this->authorizationCheck == 'joomla')
1972
		{
1973
			$isAuthorized = $this->isAuthorized(null, $terminateIfNotAuthorized);
1974
1975
			// Use Joomla to authorize
1976
			if ($isAuthorized && $terminateIfNotAuthorized && !empty($authorizationGroups))
1977
			{
1978
				$authorizationGroups = explode(',', $authorizationGroups);
1979
				$authorized          = false;
1980
				$configAssetName     = !empty($this->configuration->config->authorizationAssetName) ?
1981
					(string) $this->configuration->config->authorizationAssetName : null;
1982
1983
				foreach ($authorizationGroups as $authorizationGroup)
1984
				{
1985
					$authorization = explode(':', trim($authorizationGroup));
1986
					$action        = $authorization[0];
1987
					$assetName     = !empty($authorization[1]) ? $authorization[1] : $configAssetName;
1988
1989
					if (JFactory::getUser()->authorise(trim($action), trim($assetName)))
0 ignored issues
show
Bug introduced by
It seems like $assetName can also be of type null; however, parameter $string of trim() 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

1989
					if (JFactory::getUser()->authorise(trim($action), trim(/** @scrutinizer ignore-type */ $assetName)))
Loading history...
1990
					{
1991
						$authorized = true;
1992
						break;
1993
					}
1994
				}
1995
1996
				if (!$authorized)
1997
				{
1998
					$customError = $this->triggerFunction('createCustomHttpError', 405, $this->apiErrors);
1999
					$this->setStatusCode(405, $customError);
2000
2001
					return false;
2002
				}
2003
			}
2004
2005
			return $isAuthorized || !$terminateIfNotAuthorized;
2006
		}
2007
2008
		return false;
2009
	}
2010
2011
	/**
2012
	 * Log-in client if successful or terminate api if not authorized
2013
	 *
2014
	 * @param   string  $scope                     Name of the scope to test against
2015
	 * @param   bool    $terminateIfNotAuthorized  Terminate api if client is not authorized
2016
	 *
2017
	 * @throws Exception
2018
	 * @return  boolean
2019
	 *
2020
	 * @since   1.2
2021
	 */
2022
	public function isAuthorized($scope, $terminateIfNotAuthorized)
2023
	{
2024
		$authorized = false;
2025
		JFactory::getApplication()->triggerEvent('RApiHalBeforeIsAuthorizedCheck',
2026
			array($scope, $terminateIfNotAuthorized, $this->options, $this->authorizationCheck, &$authorized)
2027
		);
2028
2029
		if ($authorized)
0 ignored issues
show
introduced by
The condition $authorized is always false.
Loading history...
2030
		{
2031
			return $authorized;
2032
		}
2033
2034
		// OAuth2 check
2035
		if ($this->authorizationCheck == 'oauth2')
2036
		{
2037
			/** @var $response OAuth2\Response */
2038
			$response = RApiOauth2Helper::verifyResourceRequest($scope);
2039
2040
			if ($response instanceof OAuth2\Response)
0 ignored issues
show
introduced by
$response is always a sub-type of OAuth2\Response.
Loading history...
2041
			{
2042
				if (!$response->isSuccessful() && $terminateIfNotAuthorized)
2043
				{
2044
					// OAuth2 Server response is in fact correct output for errors
2045
					$response->send($this->options->get('format', 'json'));
2046
					$error = json_decode($response->getResponseBody());
2047
					$this->hal->setData((array) $error);
2048
					$this->endHistoryLog($error->error . ' - ' . $error->error_description);
2049
2050
					JFactory::getApplication()->close();
2051
				}
2052
			}
2053
			elseif ($response === false && $terminateIfNotAuthorized)
2054
			{
2055
				throw new Exception(JText::_('LIB_REDCORE_API_OAUTH2_SERVER_IS_NOT_ACTIVE'));
2056
			}
2057
			else
2058
			{
2059
				$response = json_decode($response);
2060
2061
				if (!empty($response->user_id))
2062
				{
2063
					$user = JFactory::getUser($response->user_id);
2064
2065
					// Load the JUser class on application for this client
2066
					JFactory::getApplication()->loadIdentity($user);
2067
					JFactory::getSession()->set('user', $user);
2068
2069
					return true;
2070
				}
2071
2072
				// We will use this only for authorizations with no actual users. This is used for reading of the data.
2073
				if (isset($response->success) && $response->success === true)
2074
				{
2075
					return true;
2076
				}
2077
2078
				$authorized = false || !$terminateIfNotAuthorized;
2079
			}
2080
		}
2081
		// Joomla check through Basic Authentication
2082
		elseif ($this->authorizationCheck == 'joomla')
2083
		{
2084
			// Get username and password from globals
2085
			$credentials = RApiHalHelper::getCredentialsFromGlobals();
2086
2087
			$authorized = RUser::userLogin($credentials) || !$terminateIfNotAuthorized;
2088
		}
2089
2090
		if (!$authorized && $terminateIfNotAuthorized)
2091
		{
2092
			$customError = $this->triggerFunction('createCustomHttpError', 401, $this->apiErrors);
2093
			$this->setStatusCode(401, $customError);
2094
		}
2095
2096
		return $authorized || !$terminateIfNotAuthorized;
2097
	}
2098
2099
	/**
2100
	 * Gets instance of helper object class if exists
2101
	 *
2102
	 * @return  mixed It will return Api helper class or false if it does not exists
2103
	 *
2104
	 * @since   1.2
2105
	 */
2106
	public function getHelperObject()
2107
	{
2108
		if (!empty($this->apiHelperClass))
2109
		{
2110
			return $this->apiHelperClass;
2111
		}
2112
2113
		$helperFile = RApiHalHelper::getWebserviceFile(
2114
			$this->client, strtolower($this->webserviceName), $this->webserviceVersion, 'php', $this->webservicePath
2115
		);
2116
2117
		if (file_exists($helperFile))
2118
		{
2119
			require_once $helperFile;
2120
		}
2121
2122
		$webserviceName  = preg_replace('/[^A-Z0-9_\.]/i', '', $this->webserviceName);
2123
		$helperClassName = 'RApiHalHelper' . ucfirst($this->client) . ucfirst(strtolower($webserviceName));
2124
2125
		if (class_exists($helperClassName))
2126
		{
2127
			$this->apiHelperClass = new $helperClassName;
2128
		}
2129
2130
		return $this->apiHelperClass;
2131
	}
2132
2133
	/**
2134
	 * Gets instance of dynamic model object class (for table bind)
2135
	 *
2136
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
2137
	 *
2138
	 * @return mixed It will return Api dynamic model class
2139
	 *
2140
	 * @throws Exception
2141
	 * @since   1.3
2142
	 */
2143
	public function getDynamicModelObject($configuration)
2144
	{
2145
		if (!empty($this->apiDynamicModelClass))
2146
		{
2147
			return $this->apiDynamicModelClass;
2148
		}
2149
2150
		$tableName = RApiHalHelper::attributeToString($configuration, 'tableName', '');
2151
2152
		if (empty($tableName))
2153
		{
2154
			throw new Exception(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_TABLE_NAME_NOT_SET'));
2155
		}
2156
2157
		$context = $this->webserviceName . '.' . $this->webserviceVersion;
2158
2159
		// We are not using prefix like str_replace(array('.', '-'), array('_', '_'), $context) . '_';
2160
		$paginationPrefix = '';
2161
		$filterFields     = RApiHalHelper::getFilterFields($configuration);
2162
		$primaryFields    = $this->getPrimaryFields($configuration);
2163
		$fields           = $this->getAllFields($configuration);
2164
2165
		$config = array(
2166
			'tableName' => $tableName,
2167
			'context'   => $context,
2168
			'paginationPrefix' => $paginationPrefix,
2169
			'filterFields' => $filterFields,
2170
			'primaryFields' => $primaryFields,
2171
			'fields' => $fields,
2172
		);
2173
2174
		$apiDynamicModelClassName = $this->apiDynamicModelClassName;
2175
2176
		if (class_exists($apiDynamicModelClassName))
2177
		{
2178
			$this->apiDynamicModelClass = new $apiDynamicModelClassName($config);
2179
		}
2180
2181
		return $this->apiDynamicModelClass;
2182
	}
2183
2184
	/**
2185
	 * Gets list of primary fields from operation configuration
2186
	 *
2187
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
2188
	 *
2189
	 * @return  array
2190
	 *
2191
	 * @since   1.3
2192
	 */
2193
	public function getPrimaryFields($configuration)
2194
	{
2195
		$primaryFields = array();
2196
2197
		if (!empty($configuration->fields))
2198
		{
2199
			foreach ($configuration->fields->field as $field)
2200
			{
2201
				$isPrimaryField = RApiHalHelper::isAttributeTrue($field, 'isPrimaryField');
2202
2203
				if ($isPrimaryField)
2204
				{
2205
					$primaryFields[] = (string) $field["name"];
2206
				}
2207
			}
2208
		}
2209
2210
		return $primaryFields;
2211
	}
2212
2213
	/**
2214
	 * Gets list of all fields from operation configuration
2215
	 *
2216
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
2217
	 *
2218
	 * @return  array
2219
	 *
2220
	 * @since   1.3
2221
	 */
2222
	public function getAllFields($configuration)
2223
	{
2224
		$fields = array();
2225
2226
		if (!empty($configuration->fields))
2227
		{
2228
			foreach ($configuration->fields->field as $field)
2229
			{
2230
				$fieldAttributes = RApiHalHelper::getXMLElementAttributes($field);
2231
				$fields[]        = $fieldAttributes;
2232
			}
2233
		}
2234
2235
		return $fields;
2236
	}
2237
2238
	/**
2239
	 * Load model class for data manipulation
2240
	 *
2241
	 * @param   string            $elementName    Element name
2242
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
2243
	 *
2244
	 * @return  mixed  Model class for data manipulation
2245
	 *
2246
	 * @since   1.2
2247
	 */
2248
	public function loadModel($elementName, $configuration)
2249
	{
2250
		$this->setOptionViewName($elementName, $configuration);
2251
		$isAdmin = RApiHalHelper::isAttributeTrue($configuration, 'isAdminClass');
2252
		$this->addModelIncludePaths($isAdmin, $this->optionName);
2253
		$this->loadExtensionLanguage($this->optionName, $isAdmin ? JPATH_ADMINISTRATOR : JPATH_SITE);
2254
		$this->triggerFunction('loadExtensionLibrary', $this->optionName);
2255
		$dataMode = strtolower(RApiHalHelper::attributeToString($configuration, 'dataMode', 'model'));
0 ignored issues
show
Bug introduced by
RApiHalHelper::attribute...n, 'dataMode', 'model') of type boolean is incompatible with the type string expected by parameter $string of strtolower(). ( Ignorable by Annotation )

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

2255
		$dataMode = strtolower(/** @scrutinizer ignore-type */ RApiHalHelper::attributeToString($configuration, 'dataMode', 'model'));
Loading history...
2256
2257
		if ($dataMode == 'helper')
2258
		{
2259
			return $this->getHelperObject();
2260
		}
2261
2262
		if ($dataMode == 'table')
2263
		{
2264
			return $this->getDynamicModelObject($configuration);
2265
		}
2266
2267
		if (!empty($configuration['modelClassName']))
2268
		{
2269
			$modelClass = (string) $configuration['modelClassName'];
2270
2271
			if (!empty($configuration['modelClassPath']))
2272
			{
2273
				require_once JPATH_SITE . '/' . $configuration['modelClassPath'];
2274
2275
				if (class_exists($modelClass))
2276
				{
2277
					return new $modelClass;
2278
				}
2279
			}
2280
			else
2281
			{
2282
				$componentName = ucfirst(strtolower(substr($this->optionName, 4)));
2283
				$prefix        = $componentName . 'Model';
2284
2285
				$model = RModel::getInstance($modelClass, $prefix);
2286
2287
				if ($model)
2288
				{
2289
					return $model;
2290
				}
2291
			}
2292
		}
2293
2294
		if (!empty($this->viewName))
2295
		{
2296
			$elementName = $this->viewName;
2297
		}
2298
2299
		if ($isAdmin)
2300
		{
2301
			return RModel::getAdminInstance($elementName, array(), $this->optionName);
2302
		}
2303
2304
		return RModel::getFrontInstance($elementName, array(), $this->optionName);
2305
	}
2306
2307
	/**
2308
	 * Add include paths for model class
2309
	 *
2310
	 * @param   boolean  $isAdmin     Is client admin or site
2311
	 * @param   string   $optionName  Option name
2312
	 *
2313
	 * @return  void
2314
	 *
2315
	 * @since   1.3
2316
	 */
2317
	public function addModelIncludePaths($isAdmin, $optionName)
2318
	{
2319
		if ($isAdmin)
2320
		{
2321
			$this->loadExtensionLanguage($optionName, JPATH_ADMINISTRATOR);
2322
			$path = JPATH_ADMINISTRATOR . '/components/' . $optionName;
2323
			RModel::addIncludePath($path . '/models');
2324
			JTable::addIncludePath($path . '/tables');
2325
			RForm::addFormPath($path . '/models/forms');
2326
			RForm::addFieldPath($path . '/models/fields');
2327
		}
2328
		else
2329
		{
2330
			$this->loadExtensionLanguage($optionName);
2331
			$path = JPATH_SITE . '/components/' . $optionName;
2332
			RModel::addIncludePath($path . '/models');
2333
			JTable::addIncludePath($path . '/tables');
2334
			JTable::addIncludePath(JPATH_ADMINISTRATOR . '/components/' . $optionName . '/tables');
2335
			RForm::addFormPath($path . '/models/forms');
2336
			RForm::addFieldPath($path . '/models/fields');
2337
		}
2338
2339
		if (!defined('JPATH_COMPONENT'))
2340
		{
2341
			define('JPATH_COMPONENT', $path);
2342
		}
2343
	}
2344
2345
	/**
2346
	 * Include library classes
2347
	 *
2348
	 * @param   string  $element  Option name
2349
	 *
2350
	 * @return  void
2351
	 *
2352
	 * @since   1.4
2353
	 */
2354
	public function loadExtensionLibrary($element)
2355
	{
2356
		$element = strpos($element, 'com_') === 0 ? substr($element, 4) : $element;
2357
		JLoader::import(strtolower($element) . '.library');
2358
	}
2359
2360
	/**
2361
	 * Sets option and view name
2362
	 *
2363
	 * @param   string            $elementName    Element name
2364
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
2365
	 *
2366
	 * @return  void
2367
	 *
2368
	 * @since   1.3
2369
	 */
2370
	public function setOptionViewName($elementName, $configuration)
2371
	{
2372
		// Views are separated by dash
2373
		$view        = explode('-', $elementName);
2374
		$elementName = $view[0];
2375
		$viewName    = '';
2376
2377
		if (!empty($view[1]))
2378
		{
2379
			$viewName = $view[1];
2380
		}
2381
2382
		$optionName = !empty($configuration['optionName']) ? $configuration['optionName'] : $elementName;
2383
2384
		// Add com_ to the element name if not exist
2385
		$optionName = (strpos($optionName, 'com_') === 0 ? '' : 'com_') . $optionName;
2386
2387
		$this->optionName = $optionName;
2388
		$this->viewName   = $viewName;
2389
2390
		// We add separate view and option name if they were merged
2391
		if (!empty($viewName))
2392
		{
2393
			$input = JFactory::getApplication()->input;
2394
			$input->set('option', $optionName);
2395
			$input->set('view', $viewName);
2396
		}
2397
	}
2398
2399
	/**
2400
	 * Checks if operation is allowed from the configuration file
2401
	 *
2402
	 * @param   string  $path  Path to the configuration setting
2403
	 *
2404
	 * @return mixed May return single value or array
2405
	 */
2406
	public function getConfig($path = '')
2407
	{
2408
		$path          = explode('.', $path);
2409
		$configuration = $this->configuration;
2410
2411
		foreach ($path as $pathInstance)
2412
		{
2413
			if (isset($configuration->{$pathInstance}))
2414
			{
2415
				$configuration = $configuration->{$pathInstance};
2416
			}
2417
		}
2418
2419
		return is_string($configuration) ? (string) $configuration : $configuration;
2420
	}
2421
2422
	/**
2423
	 * Gets errors from model and places it into Application message queue
2424
	 *
2425
	 * @param   object  $model  Model
2426
	 *
2427
	 * @return void
2428
	 */
2429
	public function displayErrors($model)
2430
	{
2431
		if (method_exists($model, 'getErrors'))
2432
		{
2433
			$app = JFactory::getApplication();
2434
2435
			// Get the validation messages.
2436
			$errors = $model->getErrors();
2437
2438
			// Push up all validation messages out to the user.
2439
			for ($i = 0, $n = count($errors); $i < $n; $i++)
2440
			{
2441
				if ($errors[$i] instanceof Exception)
2442
				{
2443
					$app->enqueueMessage($errors[$i]->getMessage(), 'error');
2444
				}
2445
				else
2446
				{
2447
					$app->enqueueMessage($errors[$i], 'error');
2448
				}
2449
			}
2450
		}
2451
	}
2452
2453
	/**
2454
	 * Assign value to Resource
2455
	 *
2456
	 * @param   array   $resource   Resource list with options
2457
	 * @param   mixed   $value      Data values to set to resource format
2458
	 * @param   string  $attribute  Attribute from array to replace the data
2459
	 *
2460
	 * @return  string
2461
	 *
2462
	 * @since   1.2
2463
	 */
2464
	public function assignValueToResource($resource, $value, $attribute = 'fieldFormat')
2465
	{
2466
		$format    = $resource[$attribute];
2467
		$transform = RApiHalHelper::attributeToString($resource, 'transform', '');
2468
2469
		// Filters out the complex SOAP arrays, to treat them as regular arrays
2470
		if (preg_match('/^array\[(.+)\]$/im', $transform))
0 ignored issues
show
Bug introduced by
$transform of type boolean is incompatible with the type string expected by parameter $subject of preg_match(). ( Ignorable by Annotation )

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

2470
		if (preg_match('/^array\[(.+)\]$/im', /** @scrutinizer ignore-type */ $transform))
Loading history...
2471
		{
2472
			$transform = 'array';
2473
		}
2474
2475
		$stringsToReplace = array();
2476
		preg_match_all('/\{([^}]+)\}/', $format, $stringsToReplace);
2477
2478
		if (!empty($stringsToReplace[1]))
2479
		{
2480
			foreach ($stringsToReplace[1] as $replacementKey)
2481
			{
2482
				if (is_object($value))
2483
				{
2484
					if (property_exists($value, $replacementKey))
2485
					{
2486
						// We are transforming only value
2487
						if ($format == '{' . $replacementKey . '}')
2488
						{
2489
							$format = $this->transformField($transform, $value->{$replacementKey});
2490
						}
2491
						// We are transforming only part of the string
2492
						else
2493
						{
2494
							$format = str_replace('{' . $replacementKey . '}', $this->transformField($transform, $value->{$replacementKey}), $format);
2495
						}
2496
					}
2497
				}
2498
				elseif (is_array($value))
2499
				{
2500
					if (isset($value[$replacementKey]))
2501
					{
2502
						// We are transforming only value
2503
						if ($format == '{' . $replacementKey . '}')
2504
						{
2505
							$format = $this->transformField($transform, $value[$replacementKey]);
2506
						}
2507
						// We are transforming only part of the string
2508
						else
2509
						{
2510
							$format = str_replace('{' . $replacementKey . '}', $this->transformField($transform, $value[$replacementKey]), $format);
2511
						}
2512
					}
2513
				}
2514
				else
2515
				{
2516
					// We are transforming only value
2517
					if ($format == '{' . $replacementKey . '}')
2518
					{
2519
						$format = $this->transformField($transform, $value);
2520
					}
2521
					// We are transforming only part of the string
2522
					else
2523
					{
2524
						$format = str_replace('{' . $replacementKey . '}', $this->transformField($transform, $value), $format);
2525
					}
2526
				}
2527
			}
2528
		}
2529
2530
		// We replace global data as well
2531
		$format = $this->assignGlobalValueToResource($format);
2532
2533
		if (!empty($stringsToReplace[1]))
2534
		{
2535
			// If we did not found data with that resource we will set it to 0, except for linkRel which is a documentation template
2536
			if ($format === $resource[$attribute] && $resource['linkRel'] != 'curies')
2537
			{
2538
				$format = null;
2539
			}
2540
		}
2541
2542
		return $format;
2543
	}
2544
2545
	/**
2546
	 * Assign value to Resource
2547
	 *
2548
	 * @param   string  $format  String to parse
2549
	 *
2550
	 * @return  string
2551
	 *
2552
	 * @since   1.2
2553
	 */
2554
	public function assignGlobalValueToResource($format)
2555
	{
2556
		if (empty($format) || !is_string($format))
2557
		{
2558
			return $format;
2559
		}
2560
2561
		$stringsToReplace = array();
2562
		preg_match_all('/\{([^}]+)\}/', $format, $stringsToReplace);
2563
2564
		if (!empty($stringsToReplace[1]))
2565
		{
2566
			foreach ($stringsToReplace[1] as $replacementKey)
2567
			{
2568
				// Replace from global variables if present
2569
				if (isset($this->data[$replacementKey]))
2570
				{
2571
					// We are transforming only value
2572
					if ($format == '{' . $replacementKey . '}')
2573
					{
2574
						$format = $this->data[$replacementKey];
2575
					}
2576
					// We are transforming only part of the string
2577
					else
2578
					{
2579
						$format = str_replace('{' . $replacementKey . '}', $this->data[$replacementKey], $format);
2580
					}
2581
				}
2582
			}
2583
		}
2584
2585
		return $format;
2586
	}
2587
2588
	/**
2589
	 * Get the name of the transform class for a given field type.
2590
	 *
2591
	 * First looks for the transform class in the /transform directory
2592
	 * in the same directory as the web service file.  Then looks
2593
	 * for it in the /api/transform directory.
2594
	 *
2595
	 * @param   string  $fieldType  Field type.
2596
	 *
2597
	 * @return string  Transform class name.
2598
	 */
2599
	private function getTransformClass($fieldType)
2600
	{
2601
		$fieldType = !empty($fieldType) ? $fieldType : 'string';
2602
2603
		// Cache for the class names.
2604
		static $classNames = array();
2605
2606
		// If we already know the class name, just return it.
2607
		if (isset($classNames[$fieldType]))
2608
		{
2609
			return $classNames[$fieldType];
2610
		}
2611
2612
		// Construct the name of the class to do the transform (default is RApiHalTransformString).
2613
		$className = 'RApiHalTransform' . ucfirst($fieldType);
2614
2615
		if (class_exists($className))
2616
		{
2617
			$classInstance = new $className;
2618
2619
			// Cache it for later.
2620
			$classNames[$fieldType] = $classInstance;
2621
2622
			return $classNames[$fieldType];
2623
		}
2624
2625
		return $this->getTransformClass('string');
2626
	}
2627
2628
	/**
2629
	 * Transform a source field data value.
2630
	 *
2631
	 * Calls the static toExternal method of a transform class.
2632
	 *
2633
	 * @param   string   $fieldType          Field type.
2634
	 * @param   string   $definition         Field definition.
2635
	 * @param   boolean  $directionExternal  Transform direction
2636
	 *
2637
	 * @return mixed Transformed data.
2638
	 */
2639
	public function transformField($fieldType, $definition, $directionExternal = true)
2640
	{
2641
		// Get the transform class name.
2642
		$className = $this->getTransformClass($fieldType);
2643
2644
		// Execute the transform.
2645
		if ($className instanceof RApiHalTransformInterface)
0 ignored issues
show
introduced by
$className is never a sub-type of RApiHalTransformInterface.
Loading history...
2646
		{
2647
			return $directionExternal ? $className::toExternal($definition) : $className::toInternal($definition);
2648
		}
2649
		else
2650
		{
2651
			return $definition;
2652
		}
2653
	}
2654
2655
	/**
2656
	 * Calls method from helper file if exists or method from this class,
2657
	 * Additionally it Triggers plugin call for specific function in a format RApiHalFunctionName
2658
	 *
2659
	 * @param   string  $functionName  Field type.
2660
	 *
2661
	 * @return mixed Result from callback function
2662
	 */
2663
	public function triggerFunction($functionName)
2664
	{
2665
		$apiHelperClass = $this->getHelperObject();
2666
		$args           = func_get_args();
2667
2668
		// Remove function name from arguments
2669
		array_shift($args);
2670
2671
		// PHP 5.3 workaround
2672
		$temp = array();
2673
2674
		foreach ($args as &$arg)
2675
		{
2676
			$temp[] = &$arg;
2677
		}
2678
2679
		// We will add this instance of the object as last argument for manipulation in plugin and helper
2680
		$temp[] = &$this;
2681
		$return = null;
2682
2683
		$result = JFactory::getApplication()->triggerEvent('RApiHalBefore' . $functionName, array($functionName, $temp, &$return));
2684
2685
		if ($result)
0 ignored issues
show
Bug Best Practice introduced by
The expression $result of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
introduced by
$result is an empty array, thus is always false.
Loading history...
2686
		{
2687
			if ($return !== null)
2688
			{
2689
				return $return;
2690
			}
2691
			else
2692
			{
2693
				return $result;
2694
			}
2695
		}
2696
2697
		// Checks if that method exists in helper file and executes it
2698
		if (method_exists($apiHelperClass, $functionName))
2699
		{
2700
			$result = call_user_func_array(array($apiHelperClass, $functionName), $temp);
2701
		}
2702
		else
2703
		{
2704
			$result = call_user_func_array(array($this, $functionName), $temp);
2705
		}
2706
2707
		JFactory::getApplication()->triggerEvent('RApiHal' . $functionName, $temp);
2708
2709
		return $result;
2710
	}
2711
2712
	/**
2713
	 * Calls method from defined object as some Joomla methods require referenced parameters
2714
	 *
2715
	 * @param   object  $object        Object to run function on
2716
	 * @param   string  $functionName  Function name
2717
	 * @param   array   $args          Arguments for the function
2718
	 *
2719
	 * @return mixed Result from callback function
2720
	 */
2721
	public function triggerCallFunction($object, $functionName, $args)
2722
	{
2723
		switch (count($args))
2724
		{
2725
			case 0:
2726
				return $object->{$functionName}();
2727
			case 1:
2728
				return $object->{$functionName}($args[0]);
2729
			case 2:
2730
				return $object->{$functionName}($args[0], $args[1]);
2731
			case 3:
2732
				return $object->{$functionName}($args[0], $args[1], $args[2]);
2733
			case 4:
2734
				return $object->{$functionName}($args[0], $args[1], $args[2], $args[3]);
2735
			case 5:
2736
				return $object->{$functionName}($args[0], $args[1], $args[2], $args[3], $args[4]);
2737
			default:
2738
				return call_user_func_array(array($object, $functionName), $args);
2739
		}
2740
	}
2741
2742
	/**
2743
	 * Get all defined fields and transform them if needed to expected format. Then it puts it into array for function call
2744
	 *
2745
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
2746
	 * @param   array             $data           List of posted data
2747
	 *
2748
	 * @return array List of parameters to pass to the function
2749
	 */
2750
	public function buildFunctionArgs($configuration, $data)
2751
	{
2752
		$args = array();
2753
2754
		if (!empty($configuration['functionArgs']))
2755
		{
2756
			$functionArgs = explode(',', (string) $configuration['functionArgs']);
2757
2758
			foreach ($functionArgs as $functionArg)
2759
			{
2760
				$parameter = explode('{', $functionArg);
2761
2762
				// First field is the name of the data field and second is transformation
2763
				$parameter[0] = trim($parameter[0]);
2764
				$parameter[1] = !empty($parameter[1]) ? strtolower(trim(str_replace('}', '', $parameter[1]))) : 'string';
2765
2766
				// If we set argument to value, then it will not be transformed, instead we will take field name as a value
2767
				if ($parameter[1] == 'value')
2768
				{
2769
					$parameterValue = $parameter[0];
2770
				}
2771
				else
2772
				{
2773
					if (isset($data[$parameter[0]]))
2774
					{
2775
						$parameterValue = $this->transformField($parameter[1], $data[$parameter[0]]);
2776
					}
2777
					else
2778
					{
2779
						$parameterValue = null;
2780
					}
2781
				}
2782
2783
				$args[] = $parameterValue;
2784
			}
2785
		}
2786
		else
2787
		{
2788
			$args[] = $data;
2789
		}
2790
2791
		return $args;
2792
	}
2793
2794
	/**
2795
	 * We set filters and List parameters to the model object
2796
	 *
2797
	 * @param   object  &$model  Model object
2798
	 *
2799
	 * @return  array
2800
	 */
2801
	public function assignFiltersList(&$model)
2802
	{
2803
		if (method_exists($model, 'getState'))
2804
		{
2805
			// To initialize populateState
2806
			$model->getState();
2807
		}
2808
2809
		$dataGet = $this->options->get('dataGet', array());
2810
2811
		if (is_object($dataGet))
2812
		{
2813
			$dataGet = ArrayHelper::fromObject($dataGet);
2814
		}
2815
2816
		$limitField      = 'limit';
2817
		$limitStartField = 'limitstart';
2818
2819
		if (method_exists($model, 'get'))
2820
		{
2821
			// RedCORE limit fields
2822
			$limitField      = $model->get('limitField', $limitField);
2823
			$limitStartField = $model->get('limitstartField', $limitStartField);
2824
		}
2825
2826
		if (isset($dataGet['list']['limit']))
2827
		{
2828
			$dataGet[$limitField] = $dataGet['list']['limit'];
2829
		}
2830
2831
		if (isset($dataGet['list']['limitstart']))
2832
		{
2833
			$dataGet[$limitStartField] = $dataGet['list']['limitstart'];
2834
		}
2835
2836
		// Support for B/C custom limit fields
2837
		if ($limitField != 'limit' && !empty($dataGet['limit']) && !isset($dataGet[$limitField]))
2838
		{
2839
			$dataGet[$limitField] = $dataGet['limit'];
2840
		}
2841
2842
		if ($limitStartField != 'limitstart' && !empty($dataGet['limitstart']) && !isset($dataGet[$limitStartField]))
2843
		{
2844
			$dataGet[$limitStartField] = $dataGet['limitstart'];
2845
		}
2846
2847
		// Set state for Filters and List
2848
		if (method_exists($model, 'setState'))
2849
		{
2850
			if (isset($dataGet['list']))
2851
			{
2852
				foreach ($dataGet['list'] as $key => $value)
2853
				{
2854
					$model->setState('list.' . $key, $value);
2855
				}
2856
			}
2857
2858
			if (isset($dataGet['filter']))
2859
			{
2860
				foreach ($dataGet['filter'] as $key => $value)
2861
				{
2862
					$model->setState('filter.' . $key, $value);
2863
				}
2864
			}
2865
2866
			if (isset($dataGet[$limitField]))
2867
			{
2868
				$model->setState('limit', $dataGet[$limitField]);
2869
				$model->setState('list.limit', $dataGet[$limitField]);
2870
				$model->setState($limitField, $dataGet[$limitField]);
2871
			}
2872
2873
			if (isset($dataGet[$limitStartField]))
2874
			{
2875
				$model->setState('limitstart', $dataGet[$limitStartField]);
2876
				$model->setState('list.start', $dataGet[$limitStartField]);
2877
				$model->setState($limitStartField, $dataGet[$limitStartField]);
2878
			}
2879
		}
2880
2881
		$this->options->set('dataGet', $dataGet);
2882
	}
2883
2884
	/**
2885
	 * Returns if all primary keys have set values
2886
	 * Easily get read type (item or list) for current read operation and fills primary keys
2887
	 *
2888
	 * @param   array             &$primaryKeys   List of primary keys
2889
	 * @param   SimpleXMLElement  $configuration  Configuration group
2890
	 *
2891
	 * @return  bool  Returns true if read type is Item
2892
	 *
2893
	 * @since   1.2
2894
	 */
2895
	public function apiFillPrimaryKeys(&$primaryKeys, $configuration = null)
2896
	{
2897
		if (is_null($configuration))
2898
		{
2899
			$operations = $this->getConfig('operations');
2900
2901
			if (!empty($operations->read->item))
2902
			{
2903
				$configuration = $operations->read->item;
2904
			}
2905
2906
			$data = $this->triggerFunction('processPostData', $this->options->get('dataGet', array()), $configuration);
2907
		}
2908
		else
2909
		{
2910
			$data = $this->triggerFunction('processPostData', $this->options->get('data', array()), $configuration);
2911
		}
2912
2913
		// Checking for primary keys
2914
		if (!empty($configuration))
2915
		{
2916
			$primaryKeysFromFields = RApiHalHelper::getFieldsArray($configuration, true);
2917
2918
			if (!empty($primaryKeysFromFields))
2919
			{
2920
				foreach ($primaryKeysFromFields as $primaryKey => $primaryKeyField)
2921
				{
2922
					if (isset($data[$primaryKey]) && $data[$primaryKey] != '')
2923
					{
2924
						$primaryKeys[$primaryKey] = $this->transformField($primaryKeyField['transform'], $data[$primaryKey], false);
2925
					}
2926
					else
2927
					{
2928
						$primaryKeys[$primaryKey] = null;
2929
					}
2930
				}
2931
2932
				foreach ($primaryKeys as $primaryKey => $primaryKeyField)
2933
				{
2934
					if (is_null($primaryKeyField))
2935
					{
2936
						return false;
2937
					}
2938
				}
2939
			}
2940
2941
			return true;
2942
		}
2943
2944
		return false;
2945
	}
2946
}
2947