RApiHalHal   F
last analyzed

Complexity

Total Complexity 386

Size/Duplication

Total Lines 2728
Duplicated Lines 0 %

Importance

Changes 7
Bugs 1 Features 0
Metric Value
wmc 386
eloc 997
c 7
b 1
f 0
dl 0
loc 2728
rs 1.603

49 Methods

Rating   Name   Duplication   Size   Complexity  
B setApiOperation() 0 46 11
B apiCreate() 0 64 8
B __construct() 0 66 8
A resetDocumentResources() 0 15 3
B checkRequiredFields() 0 30 7
B apiDefaultPage() 0 56 8
A setUriParams() 0 5 1
A apiDocumentation() 0 36 5
F isOperationAllowed() 0 124 30
A getBody() 0 3 1
A prepareBody() 0 3 1
A cleanCache() 0 21 5
C setOptionsFromHeader() 0 60 12
A setForRenderItem() 0 21 6
A getHelperObject() 0 25 4
A setData() 0 15 4
D execute() 0 106 18
F apiRead() 0 105 24
B loadResourceFromConfiguration() 0 43 10
B setDataValueToResource() 0 42 6
B apiTask() 0 67 9
A setForRenderList() 0 32 5
A getDynamicModelObject() 0 39 4
A sortResourcesByDisplayGroup() 0 10 3
B apiUpdate() 0 64 8
A render() 0 41 4
A setBaseDataValues() 0 21 3
B apiDelete() 0 77 9
A getAllFields() 0 14 3
C validatePostData() 0 99 17
A setWebserviceName() 0 24 5
C isAuthorized() 0 72 17
D processPostData() 0 65 19
A getPrimaryFields() 0 18 4
F assignFiltersList() 0 81 19
B triggerCallFunction() 0 18 7
A triggerFunction() 0 47 5
A loadExtensionLibrary() 0 4 2
A setOptionViewName() 0 26 5
A buildFunctionArgs() 0 42 6
A transformField() 0 13 3
A displayErrors() 0 19 4
B apiFillPrimaryKeys() 0 50 10
C assignValueToResource() 0 79 14
B assignGlobalValueToResource() 0 32 7
A getTransformClass() 0 27 4
A addModelIncludePaths() 0 25 3
A getConfig() 0 14 4
B loadModel() 0 73 11

How to fix   Complexity   

Complex Class

Complex classes like RApiHalHal often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RApiHalHal, and based on these observations, apply Extract Interface, too.

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\CMS\MVC\Model\AdminModel;
0 ignored issues
show
Bug introduced by
The type Joomla\CMS\MVC\Model\AdminModel was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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

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

181
				/** @scrutinizer ignore-type */ $this->webserviceName, $this->webserviceVersion, 'xml', $this->webservicePath, $this->client
Loading history...
182
			);
183
184
			// Set option and view name
185
			$this->setOptionViewName($this->webserviceName, $this->configuration);
186
187
			// Set base data
188
			$this->setBaseDataValues();
189
		}
190
191
		// Init Environment
192
		$this->triggerFunction('setApiOperation');
193
194
		// Set initial status code
195
		$this->setStatusCode($this->statusCode);
196
197
		// Check for defined constants
198
		if (!defined('JSON_UNESCAPED_SLASHES'))
199
		{
200
			define('JSON_UNESCAPED_SLASHES', 64);
201
		}
202
203
		// OAuth2 check
204
		if (RBootstrap::getConfig('webservices_authorization_check', 0) == 0)
205
		{
206
			$this->authorizationCheck = 'oauth2';
207
		}
208
		elseif (RBootstrap::getConfig('webservices_authorization_check', 0) == 1)
209
		{
210
			$this->authorizationCheck = 'joomla';
211
		}
212
213
		// Setting default option for webservices translation fallback
214
		RDatabaseSqlparserSqltranslation::setTranslationFallback(RBootstrap::getConfig('enable_translation_fallback_webservices', '1') == '1');
215
	}
216
217
	/**
218
	 * Set options received from Headers of the request
219
	 *
220
	 * @return  RApi
221
	 *
222
	 * @since   1.7
223
	 */
224
	public function setOptionsFromHeader()
225
	{
226
		$app = JFactory::getApplication();
227
		parent::setOptionsFromHeader();
228
		$headers = self::getHeaderVariablesFromGlobals();
229
230
		if (isset($headers['X_WEBSERVICE_TRANSLATION_FALLBACK']))
231
		{
232
			$fallbackEnabled = $headers['X_WEBSERVICE_TRANSLATION_FALLBACK'] == 'true' ? '1' : '0';
233
234
			// This will force configuration of the redCORE to user defined option
235
			RBootstrap::$config->set('enable_translation_fallback_webservices', $fallbackEnabled);
236
		}
237
238
		if (isset($headers['X_WEBSERVICE_STATEFUL']))
239
		{
240
			$useState = (int) $headers['X_WEBSERVICE_STATEFUL'] == 1 ? 1 : 0;
241
			$this->options->set('webservice_stateful', $useState);
242
			RBootstrap::$config->set('webservice_stateful', $useState);
243
		}
244
245
		if (isset($headers['ACCEPT']))
246
		{
247
			// We are only using header options if the URI does not contain format parameter as it have higher priority
248
			if ($app->input->get('format', '') == '')
249
			{
250
				$outputFormats = explode(',', $headers['ACCEPT']);
251
252
				// We go through all proposed Content Types. First Content Type that is accepted by API is used
253
				foreach ($outputFormats as $outputFormat)
254
				{
255
					$outputFormat = strtolower($outputFormat);
256
					$format       = '';
257
258
					switch ($outputFormat)
259
					{
260
						case 'application/hal+json':
261
							$format = 'json';
262
							break;
263
						case 'application/hal+xml':
264
							$format = 'xml';
265
							break;
266
						case 'application/hal+doc':
267
							$format = 'doc';
268
							break;
269
					}
270
271
					if ($format)
272
					{
273
						// This will force configuration of the redCORE to user defined option
274
						RBootstrap::$config->set('webservices_default_format', $format);
275
						$this->options->set('format', $format);
276
277
						break;
278
					}
279
				}
280
			}
281
		}
282
283
		return $this;
284
	}
285
286
	/**
287
	 * Sets default Base Data Values for resource binding
288
	 *
289
	 * @return  RApi
290
	 *
291
	 * @since   1.4
292
	 */
293
	public function setBaseDataValues()
294
	{
295
		$webserviceUrlPath = '/index.php?option=' . $this->optionName;
296
297
		if (!empty($this->viewName))
298
		{
299
			$webserviceUrlPath .= '&amp;view=' . $this->viewName;
300
		}
301
302
		if (!empty($this->webserviceVersion))
303
		{
304
			$webserviceUrlPath .= '&amp;webserviceVersion=' . $this->webserviceVersion;
305
		}
306
307
		$webserviceUrlPath .= '&amp;webserviceClient=' . $this->client;
308
309
		$this->data['webserviceUrlPath'] = $webserviceUrlPath;
310
		$this->data['webserviceName']    = $this->webserviceName;
311
		$this->data['webserviceVersion'] = $this->webserviceVersion;
312
313
		return $this;
314
	}
315
316
	/**
317
	 * Sets Webservice name according to given options
318
	 *
319
	 * @return  RApi
320
	 *
321
	 * @since   1.2
322
	 */
323
	public function setWebserviceName()
324
	{
325
		$task = $this->options->get('task', '');
326
327
		if (empty($task))
328
		{
329
			$taskSplit = explode(',', $task);
330
331
			if (count($taskSplit) > 1)
332
			{
333
				// We will set name of the webservice as a task controller name
334
				$this->webserviceName = $this->options->get('optionName', '') . '-' . $taskSplit[0];
335
				$task                 = $taskSplit[1];
336
				$this->options->set('task', $task);
337
338
				return $this;
339
			}
340
		}
341
342
		$this->webserviceName  = $this->options->get('optionName', '');
343
		$viewName              = $this->options->get('viewName', '');
344
		$this->webserviceName .= !empty($this->webserviceName) && !empty($viewName) ? '-' . $viewName : '';
345
346
		return $this;
347
	}
348
349
	/**
350
	 * Set Method for Api to be performed
351
	 *
352
	 * @return  RApi
353
	 *
354
	 * @since   1.2
355
	 */
356
	public function setApiOperation()
357
	{
358
		$method = $this->options->get('method', 'GET');
359
		$task   = $this->options->get('task', '');
360
		$format = $this->options->get('format', '');
361
362
		// Set proper operation for given method
363
		switch ((string) $method)
364
		{
365
			case 'PUT':
366
				$method = 'UPDATE';
367
				break;
368
			case 'GET':
369
				$method = !empty($task) ? 'TASK' : 'READ';
370
				break;
371
			case 'POST':
372
				$method = !empty($task) ? 'TASK' : 'CREATE';
373
				break;
374
			case 'DELETE':
375
				$method = 'DELETE';
376
				break;
377
378
			default:
379
				$method = 'READ';
380
				break;
381
		}
382
383
		// If task is pointing to some other operation like apply, update or delete
384
		if (!empty($task) && !empty($this->configuration->operations->task->{$task}['useOperation']))
385
		{
386
			$operation = strtoupper((string) $this->configuration->operations->task->{$task}['useOperation']);
387
388
			if (in_array($operation, array('CREATE', 'READ', 'UPDATE', 'DELETE', 'DOCUMENTATION')))
389
			{
390
				$method = $operation;
391
			}
392
		}
393
394
		if ($format == 'doc')
395
		{
396
			$method = 'documentation';
397
		}
398
399
		$this->operation = strtolower($method);
400
401
		return $this;
402
	}
403
404
	/**
405
	 * Execute the Api operation.
406
	 *
407
	 * @return  mixed  RApi object with information on success, boolean false on failure.
408
	 *
409
	 * @since   1.2
410
	 * @throws  Exception
411
	 */
412
	public function execute()
413
	{
414
		// Set initial status code to OK
415
		$this->setStatusCode(200);
416
417
		// Prepare the application state
418
		$this->cleanCache();
419
420
		// We do not want some unwanted text to appear before output
421
		ob_start();
422
423
		try
424
		{
425
			if (!empty($this->webserviceName))
426
			{
427
				if ($this->triggerFunction('isOperationAllowed'))
428
				{
429
					$this->elementName            = ucfirst(strtolower((string) $this->getConfig('config.name')));
430
					$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...
431
432
					switch ($this->operation)
433
					{
434
						case 'create':
435
							$this->triggerFunction('apiCreate');
436
							break;
437
						case 'read':
438
							$this->triggerFunction('apiRead');
439
							break;
440
						case 'update':
441
							$this->triggerFunction('apiUpdate');
442
							break;
443
						case 'delete':
444
							$this->triggerFunction('apiDelete');
445
							break;
446
						case 'task':
447
							$this->triggerFunction('apiTask');
448
							break;
449
						case 'documentation':
450
							$this->triggerFunction('apiDocumentation');
451
							break;
452
					}
453
				}
454
			}
455
			else
456
			{
457
				// If default page needs authorization to access it
458
				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

458
				if (!$this->isAuthorized('', /** @scrutinizer ignore-type */ RBootstrap::getConfig('webservices_default_page_authorization', 0)))
Loading history...
459
				{
460
					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...
461
				}
462
463
				// No webservice name. We display all webservices available
464
				$this->triggerFunction('apiDefaultPage');
465
			}
466
467
			// Set links from resources to the main document
468
			$this->setDataValueToResource($this->hal, $this->resources, $this->data);
469
			$messages = JFactory::getApplication()->getMessageQueue();
470
471
			$executionErrors = ob_get_contents();
472
			ob_end_clean();
473
		}
474
		catch (Exception $e)
475
		{
476
			$executionErrors = ob_get_contents();
0 ignored issues
show
Unused Code introduced by
The assignment to $executionErrors is dead and can be removed.
Loading history...
477
			ob_end_clean();
478
479
			throw $e;
480
		}
481
482
		if (!empty($executionErrors))
483
		{
484
			$messages[] = array('message' => $executionErrors, 'type' => 'notice');
485
		}
486
487
		if (!empty($messages))
488
		{
489
			// If we are not in debug mode we will take out everything except errors
490
			if (RBootstrap::getConfig('debug_webservices', 0) == 0)
491
			{
492
				$warnings = array();
493
494
				foreach ($messages as $key => $message)
495
				{
496
					if ($message['type'] == 'warning')
497
					{
498
						$warnings[] = $message;
499
					}
500
501
					if ($message['type'] != 'error')
502
					{
503
						unset($messages[$key]);
504
					}
505
				}
506
507
				// Showing 'warning' messages only if no 'error' are present
508
				if (!count($messages))
509
				{
510
					$messages = $warnings;
511
				}
512
			}
513
514
			$this->hal->setData('_messages', $messages);
515
		}
516
517
		return $this;
518
	}
519
520
	/**
521
	 * Method to clear the cache and any session state
522
	 * related to API requests
523
	 *
524
	 * @param   string   $group      The cache group
525
	 * @param   integer  $client_id  The ID of the client
526
	 *
527
	 * @throws Exception
528
	 * @return void
529
	 */
530
	private function cleanCache($group = null, $client_id = 0)
531
	{
532
		if ($this->options->get('webservice_stateful', 0) == 1)
533
		{
534
			return;
535
		}
536
537
		$option = $this->options->get('optionName', '');
538
		$option = strpos($option, 'com_') === false ? 'com_' . $option : $option;
539
		$conf   = JFactory::getConfig();
540
541
		$options = array(
542
			'defaultgroup' => ($group) ? $group : $option,
543
			'cachebase' => ($client_id) ? JPATH_ADMINISTRATOR . '/cache' : $conf->get('cache_path', JPATH_SITE . '/cache'));
544
545
		$cache = JCache::getInstance('callback', $options);
546
		$cache->clean();
547
548
		$session  = JFactory::getSession();
549
		$registry = $session->get('registry');
550
		$registry->set($option, null);
551
	}
552
553
	/**
554
	 * Execute the Api Default Page operation.
555
	 *
556
	 * @return  mixed  RApi object with information on success, boolean false on failure.
557
	 *
558
	 * @since   1.2
559
	 */
560
	public function apiDefaultPage()
561
	{
562
		// Add standard Joomla namespace as curie.
563
		$documentationCurieAdmin = new RApiHalDocumentLink('/index.php?option={rel}&amp;format=doc&amp;webserviceClient=administrator',
564
			'curies', 'Documentation Admin', 'Admin', null, true
565
		);
566
		$documentationCurieSite  = new RApiHalDocumentLink('/index.php?option={rel}&amp;format=doc&amp;webserviceClient=site',
567
			'curies', 'Documentation Site', 'Site', null, true
568
		);
569
570
		// Add basic hypermedia links.
571
		$this->hal->setLink($documentationCurieAdmin, false, true);
572
		$this->hal->setLink($documentationCurieSite, false, true);
573
		$this->hal->setLink(new RApiHalDocumentLink(JUri::base(), 'base', JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_DOCUMENTATION_DEFAULT_PAGE')));
574
575
		$webservices = RApiHalHelper::getInstalledWebservices();
576
577
		if (!empty($webservices))
578
		{
579
			foreach ($webservices as $webserviceClient => $webserviceNames)
580
			{
581
				foreach ($webserviceNames as $webserviceName => $webserviceVersions)
582
				{
583
					foreach ($webserviceVersions as $webserviceVersion => $webservice)
584
					{
585
						if ($webservice['state'] == 1)
586
						{
587
							$documentation = $webserviceClient == 'site' ? 'Site' : 'Admin';
588
589
							// Set option and view name
590
							$this->setOptionViewName($webservice['name'], $this->configuration);
591
							$webserviceUrlPath = '/index.php?option=' . $this->optionName
592
								. '&amp;webserviceVersion=' . $webserviceVersion;
593
594
							if (!empty($this->viewName))
595
							{
596
								$webserviceUrlPath .= '&view=' . $this->viewName;
597
							}
598
599
							// We will fetch only top level webservice
600
							$this->hal->setLink(
601
								new RApiHalDocumentLink(
602
									$webserviceUrlPath . '&webserviceClient=' . $webserviceClient,
603
									$documentation . ':' . $webservice['name'],
604
									$webservice['title']
605
								)
606
							);
607
608
							break;
609
						}
610
					}
611
				}
612
			}
613
		}
614
615
		return $this;
616
	}
617
618
	/**
619
	 * Execute the Api Documentation operation.
620
	 *
621
	 * @return  mixed  RApi object with information on success, boolean false on failure.
622
	 *
623
	 * @since   1.2
624
	 */
625
	public function apiDocumentation()
626
	{
627
		$currentConfiguration = $this->configuration;
628
		$documentationNone    = false;
629
630
		if ($this->operationConfiguration['source'] == 'url')
0 ignored issues
show
introduced by
The condition $this->operationConfiguration['source'] == 'url' is always false.
Loading history...
631
		{
632
			if (!empty($this->operationConfiguration['url']))
633
			{
634
				JFactory::getApplication()->redirect($this->operationConfiguration['url']);
635
				JFactory::getApplication()->close();
636
			}
637
638
			$documentationNone = true;
639
		}
640
641
		if ($this->operationConfiguration['source'] == 'none' || $documentationNone)
0 ignored issues
show
introduced by
The condition $documentationNone is always false.
Loading history...
642
		{
643
			$currentConfiguration = null;
644
		}
645
646
		$dataGet = $this->options->get('dataGet', array());
647
648
		$this->documentation = RLayoutHelper::render(
649
			'webservice.documentation',
650
			array(
651
				'view' => $this,
652
				'options' => array (
653
					'xml' => $currentConfiguration,
654
					'soapEnabled' => RBootstrap::getConfig('enable_soap', 0),
655
					'print' => isset($dataGet->print)
656
				)
657
			)
658
		);
659
660
		return $this;
661
	}
662
663
	/**
664
	 * Execute the Api Read operation.
665
	 *
666
	 * @return  mixed  RApi object with information on success, boolean false on failure.
667
	 *
668
	 * @since   1.2
669
	 */
670
	public function apiRead()
671
	{
672
		$primaryKeys = array();
673
		$isReadItem  = $this->apiFillPrimaryKeys($primaryKeys);
674
675
		$displayTarget                  = $isReadItem ? 'item' : 'list';
676
		$this->apiDynamicModelClassName = 'RApiHalModel' . ucfirst($displayTarget);
677
		$currentConfiguration           = $this->operationConfiguration->{$displayTarget};
678
		$model                          = $this->triggerFunction('loadModel', $this->elementName, $currentConfiguration);
679
		$this->assignFiltersList($model);
680
681
		if ($displayTarget == 'list')
682
		{
683
			$functionName       = RApiHalHelper::attributeToString($currentConfiguration, 'functionName', 'getItems');
684
			$paginationFunction = RApiHalHelper::attributeToString($currentConfiguration, 'paginationFunction', 'getPagination');
685
			$totalFunction      = RApiHalHelper::attributeToString($currentConfiguration, 'totalFunction', 'getTotal');
686
687
			$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

687
			$items = method_exists($model, /** @scrutinizer ignore-type */ $functionName) ? $model->{$functionName}() : array();
Loading history...
688
689
			if (!empty($paginationFunction) && method_exists($model, $paginationFunction))
690
			{
691
				// Get total count to check if we have reached the limit
692
				if (!empty($totalFunction) && method_exists($model, $totalFunction))
693
				{
694
					$totalItems = $model->{$totalFunction}();
695
696
					if ($model->getState('limitstart', 0) >= $totalItems)
697
					{
698
						$customError = $this->triggerFunction('createCustomHttpError', 204, array('No more records found'));
699
						$this->setStatusCode(204, $customError);
700
701
						return $this;
702
					}
703
				}
704
705
				$pagination      = $model->{$paginationFunction}();
706
				$paginationPages = $pagination->getPaginationPages();
707
708
				$this->setData(
709
					'pagination.previous', isset($paginationPages['previous']['data']->base) ? $paginationPages['previous']['data']->base : $pagination->limitstart
710
				);
711
				$this->setData(
712
					'pagination.next', isset($paginationPages['next']['data']->base) ? $paginationPages['next']['data']->base : $pagination->limitstart
713
				);
714
				$this->setData('pagination.limit', $pagination->limit);
715
				$this->setData('pagination.limitstart', $pagination->limitstart);
716
				$this->setData('pagination.totalItems', $pagination->total);
717
				$this->setData('pagination.totalPages', max($pagination->pagesTotal, 1));
718
				$this->setData('pagination.page', max($pagination->pagesCurrent, 1));
719
				$this->setData('pagination.last', ((max($pagination->pagesTotal, 1) - 1) * $pagination->limit));
720
			}
721
722
			$this->triggerFunction('setForRenderList', $items, $currentConfiguration);
723
724
			return $this;
725
		}
726
727
		$primaryKeys = count($primaryKeys) > 1 ? array($primaryKeys) : $primaryKeys;
728
729
		// Getting single item
730
		$functionName   = RApiHalHelper::attributeToString($currentConfiguration, 'functionName', 'getItem');
731
		$messagesBefore = JFactory::getApplication()->getMessageQueue();
732
		$itemObject     = method_exists($model, $functionName) ? call_user_func_array(array(&$model, $functionName), $primaryKeys) : array();
733
		$messagesAfter  = JFactory::getApplication()->getMessageQueue();
734
735
		// Check to see if we have the item or not since it might return default properties
736
		if (count($messagesBefore) != count($messagesAfter))
737
		{
738
			foreach ($messagesAfter as $messageKey => $messageValue)
739
			{
740
				$messageFound = false;
741
742
				foreach ($messagesBefore as $key => $value)
743
				{
744
					if ($messageValue['type'] == $value['type'] && $messageValue['message'] == $value['message'])
745
					{
746
						$messageFound = true;
747
						break;
748
					}
749
				}
750
751
				if (!$messageFound && $messageValue['type'] == 'error')
752
				{
753
					$itemObject = null;
754
					break;
755
				}
756
			}
757
		}
758
759
		if (RApiHalHelper::isAttributeTrue($currentConfiguration, 'enforcePKs', true))
760
		{
761
			// Checking if primary keys are found
762
			foreach ($primaryKeys as $primaryKey => $primaryKeyValue)
763
			{
764
				if (property_exists($itemObject, $primaryKey) && $itemObject->{$primaryKey} != $primaryKeyValue)
765
				{
766
					$itemObject = null;
767
					break;
768
				}
769
			}
770
		}
771
772
		$this->triggerFunction('setForRenderItem', $itemObject, $currentConfiguration);
773
774
		return $this;
775
	}
776
777
	/**
778
	 * Execute the Api Create operation.
779
	 *
780
	 * @return  mixed  RApi object with information on success, boolean false on failure.
781
	 *
782
	 * @since   1.2
783
	 */
784
	public function apiCreate()
785
	{
786
		// Get resource list from configuration
787
		$this->loadResourceFromConfiguration($this->operationConfiguration);
788
789
		$model        = $this->triggerFunction('loadModel', $this->elementName, $this->operationConfiguration);
790
		$functionName = RApiHalHelper::attributeToString($this->operationConfiguration, 'functionName', 'save');
791
792
		$data = $this->triggerFunction('processPostData', $this->options->get('data', array()), $this->operationConfiguration);
793
		$data = $this->triggerFunction('validatePostData', $model, $data, $this->operationConfiguration);
794
795
		if ($data === false)
796
		{
797
			// Not Acceptable
798
			$this->setStatusCode(406);
799
			$this->triggerFunction('displayErrors', $model);
800
			$this->setData('result', $data);
801
802
			return;
803
		}
804
805
		// Prepare parameters for the function
806
		$args   = $this->buildFunctionArgs($this->operationConfiguration, $data);
807
		$result = null;
808
809
		// Checks if that method exists in model class file and executes it
810
		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

810
		if (method_exists($model, /** @scrutinizer ignore-type */ $functionName))
Loading history...
811
		{
812
			$result = $this->triggerCallFunction($model, $functionName, $args);
813
		}
814
		else
815
		{
816
			$customError = $this->triggerFunction('createCustomHttpError', 400, $this->apiErrors);
817
			$this->setStatusCode(400, $customError);
818
		}
819
820
		if ($model instanceof AdminModel)
821
		{
822
			$this->setData('id', $model->getState($model->getName() . '.id'));
823
		}
824
825
		if (method_exists($model, 'getErrors'))
826
		{
827
			$modelErrors = $model->getErrors();
828
829
			if (!empty($modelErrors))
830
			{
831
				$this->apiErrors = array_merge($this->apiErrors, $modelErrors);
832
			}
833
		}
834
835
		$this->setData('result', $result);
836
		$this->triggerFunction('displayErrors', $model);
837
838
		if ($this->statusCode < 400)
839
		{
840
			if ($result === false)
841
			{
842
				$customError = $this->triggerFunction('createCustomHttpError', 404, $this->apiErrors);
843
				$this->setStatusCode(404, $customError);
844
			}
845
			else
846
			{
847
				$this->setStatusCode(201);
848
			}
849
		}
850
	}
851
852
	/**
853
	 * Execute the Api Delete operation.
854
	 *
855
	 * @return  mixed  RApi object with information on success, boolean false on failure.
856
	 *
857
	 * @since   1.2
858
	 */
859
	public function apiDelete()
860
	{
861
		// Get resource list from configuration
862
		$this->loadResourceFromConfiguration($this->operationConfiguration);
863
864
		// Delete function requires references and not values like we use in call_user_func_array so we use List delete function
865
		$this->apiDynamicModelClassName = 'RApiHalModelList';
866
		$model                          = $this->triggerFunction('loadModel', $this->elementName, $this->operationConfiguration);
867
		$functionName                   = RApiHalHelper::attributeToString($this->operationConfiguration, 'functionName', 'delete');
868
		$data                           = $this->triggerFunction('processPostData', $this->options->get('data', array()), $this->operationConfiguration);
869
870
		$data = $this->triggerFunction('validatePostData', $model, $data, $this->operationConfiguration);
871
872
		if ($data === false)
873
		{
874
			// Not Acceptable
875
			$customError = $this->triggerFunction('createCustomHttpError', 406, $this->apiErrors);
876
			$this->setStatusCode(406, $customError);
877
			$this->triggerFunction('displayErrors', $model);
878
			$this->setData('result', $data);
879
880
			return;
881
		}
882
883
		$result = null;
884
		$args   = $this->buildFunctionArgs($this->operationConfiguration, $data);
885
886
		// Prepare parameters for the function
887
		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

887
		if (strtolower(/** @scrutinizer ignore-type */ RApiHalHelper::attributeToString($this->operationConfiguration, 'dataMode', 'model')) == 'table')
Loading history...
888
		{
889
			$primaryKeys = array();
890
			$this->apiFillPrimaryKeys($primaryKeys, $this->operationConfiguration);
891
892
			if (!empty($primaryKeys))
893
			{
894
				$result = $model->{$functionName}($primaryKeys);
895
			}
896
			else
897
			{
898
				$result = $model->{$functionName}($args);
899
			}
900
		}
901
		else
902
		{
903
			// Checks if that method exists in model class file and executes it
904
			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

904
			if (method_exists($model, /** @scrutinizer ignore-type */ $functionName))
Loading history...
905
			{
906
				$result = $this->triggerCallFunction($model, $functionName, $args);
907
			}
908
			else
909
			{
910
				$customError = $this->triggerFunction('createCustomHttpError', 400, $this->apiErrors);
911
				$this->setStatusCode(400, $customError);
912
			}
913
		}
914
915
		if (method_exists($model, 'getErrors'))
916
		{
917
			$modelErrors = $model->getErrors();
918
919
			if (!empty($modelErrors))
920
			{
921
				$this->apiErrors = array_merge($this->apiErrors, $modelErrors);
922
			}
923
		}
924
925
		$this->setData('result', $result);
926
927
		$this->triggerFunction('displayErrors', $model);
928
929
		if ($this->statusCode < 400)
930
		{
931
			if ($result === false)
932
			{
933
				// If delete failed then we set it to Internal Server Error status code
934
				$customError = $this->triggerFunction('createCustomHttpError', 500, $this->apiErrors);
935
				$this->setStatusCode(500, $customError);
936
			}
937
		}
938
	}
939
940
	/**
941
	 * Execute the Api Update operation.
942
	 *
943
	 * @return  mixed  RApi object with information on success, boolean false on failure.
944
	 *
945
	 * @since   1.2
946
	 */
947
	public function apiUpdate()
948
	{
949
		// Get resource list from configuration
950
		$this->loadResourceFromConfiguration($this->operationConfiguration);
951
		$model        = $this->triggerFunction('loadModel', $this->elementName, $this->operationConfiguration);
952
		$functionName = RApiHalHelper::attributeToString($this->operationConfiguration, 'functionName', 'save');
953
		$data         = $this->triggerFunction('processPostData', $this->options->get('data', array()), $this->operationConfiguration);
954
955
		$data = $this->triggerFunction('validatePostData', $model, $data, $this->operationConfiguration);
956
957
		if ($data === false)
958
		{
959
			// Not Acceptable
960
			$customError = $this->triggerFunction('createCustomHttpError', 406, $this->apiErrors);
961
			$this->setStatusCode(406, $customError);
962
			$this->triggerFunction('displayErrors', $model);
963
			$this->setData('result', $data);
964
965
			return;
966
		}
967
968
		// Prepare parameters for the function
969
		$args   = $this->buildFunctionArgs($this->operationConfiguration, $data);
970
		$result = null;
971
972
		// Checks if that method exists in model class and executes it
973
		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

973
		if (method_exists($model, /** @scrutinizer ignore-type */ $functionName))
Loading history...
974
		{
975
			$result = $this->triggerCallFunction($model, $functionName, $args);
976
		}
977
		else
978
		{
979
			$customError = $this->triggerFunction('createCustomHttpError', 400, $this->apiErrors);
980
			$this->setStatusCode(400, $customError);
981
		}
982
983
		if ($model instanceof AdminModel)
984
		{
985
			$this->setData(
986
				'id',
987
				$model->getState($model->getName() . '.id')
988
			);
989
		}
990
991
		if (method_exists($model, 'getErrors'))
992
		{
993
			$modelErrors = $model->getErrors();
994
995
			if (!empty($modelErrors))
996
			{
997
				$this->apiErrors = array_merge($this->apiErrors, $modelErrors);
998
			}
999
		}
1000
1001
		$this->setData('result', $result);
1002
		$this->triggerFunction('displayErrors', $model);
1003
1004
		if ($this->statusCode < 400)
1005
		{
1006
			if ($result === false)
1007
			{
1008
				// If update failed then we set it to Internal Server Error status code
1009
				$customError = $this->triggerFunction('createCustomHttpError', 500, $this->apiErrors);
1010
				$this->setStatusCode(500, $customError);
1011
			}
1012
		}
1013
	}
1014
1015
	/**
1016
	 * Execute the Api Task operation.
1017
	 *
1018
	 * @return  mixed  RApi object with information on success, boolean false on failure.
1019
	 *
1020
	 * @since   1.2
1021
	 */
1022
	public function apiTask()
1023
	{
1024
		$task   = $this->options->get('task', '');
1025
		$result = false;
1026
1027
		if (!empty($task))
1028
		{
1029
			// Load resources directly from task group
1030
			if (!empty($this->operationConfiguration->{$task}->resources))
1031
			{
1032
				$this->loadResourceFromConfiguration($this->operationConfiguration->{$task});
1033
			}
1034
1035
			$taskConfiguration = !empty($this->operationConfiguration->{$task}) ?
1036
				$this->operationConfiguration->{$task} : $this->operationConfiguration;
1037
1038
			$model        = $this->triggerFunction('loadModel', $this->elementName, $taskConfiguration);
1039
			$functionName = RApiHalHelper::attributeToString($taskConfiguration, 'functionName', $task);
1040
			$data         = $this->triggerFunction('processPostData', $this->options->get('data', array()), $taskConfiguration);
1041
1042
			$data = $this->triggerFunction('validatePostData', $model, $data, $taskConfiguration);
1043
1044
			if ($data === false)
1045
			{
1046
				// Not Acceptable
1047
				$customError = $this->triggerFunction('createCustomHttpError', 406, $this->apiErrors);
1048
				$this->setStatusCode(406, $customError);
1049
				$this->triggerFunction('displayErrors', $model);
1050
				$this->setData('result', $data);
1051
1052
				return;
1053
			}
1054
1055
			// Prepare parameters for the function
1056
			$args   = $this->buildFunctionArgs($taskConfiguration, $data);
1057
			$result = null;
1058
1059
			// Checks if that method exists in model class and executes it
1060
			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

1060
			if (method_exists($model, /** @scrutinizer ignore-type */ $functionName))
Loading history...
1061
			{
1062
				$result = $this->triggerCallFunction($model, $functionName, $args);
1063
			}
1064
			else
1065
			{
1066
				$customError = $this->triggerFunction('createCustomHttpError', 400, $this->apiErrors);
1067
				$this->setStatusCode(400, $customError);
1068
				$this->triggerFunction('displayErrors', $model);
1069
			}
1070
1071
			if (method_exists($model, 'getErrors'))
1072
			{
1073
				$modelErrors = $model->getErrors();
1074
1075
				if (!empty($modelErrors))
1076
				{
1077
					$this->apiErrors = array_merge($this->apiErrors, $modelErrors);
1078
				}
1079
			}
1080
1081
			if ($model instanceof AdminModel)
1082
			{
1083
				$this->setData('id', $model->getState($model->getName() . '.id'));
1084
			}
1085
		}
1086
1087
		$this->setData('result', $result);
1088
		$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...
1089
	}
1090
1091
	/**
1092
	 * Set document content for List view
1093
	 *
1094
	 * @param   array             $items          List of items
1095
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1096
	 *
1097
	 * @return void
1098
	 */
1099
	public function setForRenderList($items, $configuration)
1100
	{
1101
		// Get resource list from configuration
1102
		$this->loadResourceFromConfiguration($configuration);
1103
1104
		$listResourcesKeys = array_keys($this->resources['listItem']);
1105
1106
		if (!empty($items))
1107
		{
1108
			// Filter out all fields that are not in resource list and apply appropriate transform rules
1109
			foreach ($items as $itemValue)
1110
			{
1111
				$item = ArrayHelper::fromObject($itemValue);
1112
1113
				foreach ($item as $key => $value)
1114
				{
1115
					if (!in_array($key, $listResourcesKeys))
1116
					{
1117
						unset($item[$key]);
1118
						continue;
1119
					}
1120
					else
1121
					{
1122
						$item[$this->assignGlobalValueToResource($key)] = $this->assignValueToResource(
1123
							$this->resources['listItem'][$key], $item
1124
						);
1125
					}
1126
				}
1127
1128
				$embedItem = new RApiHalDocumentResource('item', $item);
1129
				$embedItem = $this->setDataValueToResource($embedItem, $this->resources, $itemValue, 'listItem');
1130
				$this->hal->setEmbedded('item', $embedItem);
1131
			}
1132
		}
1133
	}
1134
1135
	/**
1136
	 * Loads Resource list from configuration file for specific method or task
1137
	 *
1138
	 * @param   RApiHalDocumentResource  $resourceDocument  Resource document for binding the resource
1139
	 * @param   array                    $resources         Configuration for displaying object
1140
	 * @param   mixed                    $data              Data to bind to the resources
1141
	 * @param   string                   $resourceSpecific  Resource specific string that separates resources
1142
	 *
1143
	 * @return RApiHalDocumentResource
1144
	 */
1145
	public function setDataValueToResource($resourceDocument, $resources, $data, $resourceSpecific = 'rcwsGlobal')
1146
	{
1147
		if (!empty($resources[$resourceSpecific]))
1148
		{
1149
			// Add links from the resource
1150
			foreach ($resources[$resourceSpecific] as $resource)
1151
			{
1152
				if (!empty($resource['displayGroup']))
1153
				{
1154
					if ($resource['displayGroup'] == '_links')
1155
					{
1156
						$linkRel = !empty($resource['linkRel']) ? $resource['linkRel'] : $this->assignGlobalValueToResource($resource['displayName']);
1157
1158
						// We will force curries as link array
1159
						$linkPlural = $linkRel == 'curies';
1160
1161
						$resourceDocument->setLink(
1162
							new RApiHalDocumentLink(
1163
								$this->assignValueToResource($resource, $data),
1164
								$linkRel,
1165
								$resource['linkTitle'],
1166
								$this->assignGlobalValueToResource($resource['linkName']),
1167
								$resource['hrefLang'],
1168
								RApiHalHelper::isAttributeTrue($resource, 'linkTemplated')
1169
							), $linkSingular = false, $linkPlural
1170
						);
1171
					}
1172
					else
1173
					{
1174
						$resourceDocument->setDataGrouped(
1175
							$resource['displayGroup'], $this->assignGlobalValueToResource($resource['displayName']), $this->assignValueToResource($resource, $data)
1176
						);
1177
					}
1178
				}
1179
				else
1180
				{
1181
					$resourceDocument->setData($this->assignGlobalValueToResource($resource['displayName']), $this->assignValueToResource($resource, $data));
1182
				}
1183
			}
1184
		}
1185
1186
		return $resourceDocument;
1187
	}
1188
1189
	/**
1190
	 * Loads Resource list from configuration file for specific method or task
1191
	 *
1192
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1193
	 *
1194
	 * @return array
1195
	 */
1196
	public function loadResourceFromConfiguration($configuration)
1197
	{
1198
		if (isset($configuration->resources->resource))
1199
		{
1200
			foreach ($configuration->resources->resource as $resourceXML)
1201
			{
1202
				$resource = RApiHalHelper::getXMLElementAttributes($resourceXML);
1203
1204
				// Filters out specified displayGroup values
1205
				if ($this->options->get('filterOutResourcesGroups') != ''
1206
					&& in_array($resource['displayGroup'], $this->options->get('filterOutResourcesGroups')))
1207
				{
1208
					continue;
1209
				}
1210
1211
				// Filters out if the optional resourceSpecific filter is not the one defined
1212
				if ($this->options->get('filterResourcesSpecific') != ''
1213
					&& $resource['resourceSpecific'] != $this->options->get('filterResourcesSpecific'))
1214
				{
1215
					continue;
1216
				}
1217
1218
				// Filters out if the optional displayName filter is not the one defined
1219
				if ($this->options->get('filterDisplayName') != ''
1220
					&& $resource['displayName'] != $this->options->get('filterDisplayName'))
1221
				{
1222
					continue;
1223
				}
1224
1225
				if (!empty($resourceXML->description))
1226
				{
1227
					$resource['description'] = $resourceXML->description;
1228
				}
1229
1230
				$resource         = RApiHalDocumentResource::defaultResourceField($resource);
1231
				$resourceName     = $resource['displayName'];
1232
				$resourceSpecific = $resource['resourceSpecific'];
1233
1234
				$this->resources[$resourceSpecific][$resourceName] = $resource;
1235
			}
1236
		}
1237
1238
		return $this->resources;
1239
	}
1240
1241
	/**
1242
	 * Resets specific Resource list or all Resources
1243
	 *
1244
	 * @param   string  $resourceSpecific  Resource specific string that separates resources
1245
	 *
1246
	 * @return RApiHalHal
1247
	 */
1248
	public function resetDocumentResources($resourceSpecific = '')
1249
	{
1250
		if (!empty($resourceSpecific))
1251
		{
1252
			if (isset($this->resources[$resourceSpecific]))
1253
			{
1254
				unset($this->resources[$resourceSpecific]);
1255
			}
1256
1257
			return $this;
1258
		}
1259
1260
		$this->resources = array();
1261
1262
		return $this;
1263
	}
1264
1265
	/**
1266
	 * Used for ordering arrays
1267
	 *
1268
	 * @param   string  $a  Current array
1269
	 * @param   string  $b  Next array
1270
	 *
1271
	 * @return RApiHalHal
1272
	 */
1273
	public function sortResourcesByDisplayGroup($a, $b)
1274
	{
1275
		$sort = strcmp($a["displayGroup"], $b["displayGroup"]);
1276
1277
		if (!$sort)
1278
		{
1279
			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...
1280
		}
1281
1282
		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...
1283
	}
1284
1285
	/**
1286
	 * Set document content for Item view
1287
	 *
1288
	 * @param   object|array      $item           Item content
1289
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1290
	 *
1291
	 * @throws Exception
1292
	 * @return void
1293
	 */
1294
	public function setForRenderItem($item, $configuration)
1295
	{
1296
		// Get resource list from configuration
1297
		$this->loadResourceFromConfiguration($configuration);
1298
1299
		if (!empty($item) && (is_array($item) || is_object($item)))
1300
		{
1301
			// Filter out all fields that are not in resource list and apply appropriate transform rules
1302
			foreach ($item as $key => $value)
1303
			{
1304
				$value = !empty($this->resources['rcwsGlobal'][$key]) ? $this->assignValueToResource($this->resources['rcwsGlobal'][$key], $item) : $value;
1305
				$this->setData($this->assignGlobalValueToResource($key), $value);
1306
			}
1307
		}
1308
		else
1309
		{
1310
			// 404 => 'Not found'
1311
			$customError = $this->triggerFunction('createCustomHttpError', 404, $this->apiErrors);
1312
			$this->setStatusCode(404, $customError);
1313
1314
			throw new Exception(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_ERROR_NO_CONTENT'), 404);
1315
		}
1316
	}
1317
1318
	/**
1319
	 * Method to send the application response to the client.  All headers will be sent prior to the main
1320
	 * application output data.
1321
	 *
1322
	 * @return  void
1323
	 *
1324
	 * @since   1.2
1325
	 */
1326
	public function render()
1327
	{
1328
		// Set token to uri if used in that way
1329
		$token  = $this->options->get('accessToken', '');
1330
		$client = $this->options->get('webserviceClient', '');
1331
		$format = $this->options->get('format', 'json');
1332
1333
		if (!empty($token))
1334
		{
1335
			$this->setUriParams(RBootstrap::getConfig('oauth2_token_param_name', 'access_token'), $token);
1336
		}
1337
1338
		if ($client == 'administrator')
1339
		{
1340
			$this->setUriParams('webserviceClient', $client);
1341
		}
1342
1343
		$this->setUriParams('api', 'Hal');
1344
1345
		if ($format == 'doc')
1346
		{
1347
			// This is already in HTML format
1348
			echo $this->documentation;
1349
		}
1350
		else
1351
		{
1352
			$documentOptions    = array(
1353
				'absoluteHrefs' => $this->options->get('absoluteHrefs', false),
1354
				'documentFormat' => $format,
1355
				'uriParams' => $this->uriParams,
1356
			);
1357
			JFactory::$document = new RApiHalDocumentDocument($documentOptions);
1358
1359
			$body = $this->getBody();
1360
			$body = $this->triggerFunction('prepareBody', $body);
1361
1362
			// Push results into the document.
1363
			JFactory::$document
1364
				->setHal($this)
1365
				->setBuffer($body)
1366
				->render(false);
1367
		}
1368
	}
1369
1370
	/**
1371
	 * Method to fill response with requested data
1372
	 *
1373
	 * @return  string  Api call output
1374
	 *
1375
	 * @since   1.2
1376
	 */
1377
	public function getBody()
1378
	{
1379
		return $this->hal;
1380
	}
1381
1382
	/**
1383
	 * Prepares body for response
1384
	 *
1385
	 * @param   string  $message  The return message
1386
	 *
1387
	 * @return  string	The message prepared
1388
	 *
1389
	 * @since   1.2
1390
	 */
1391
	public function prepareBody($message)
1392
	{
1393
		return $message;
1394
	}
1395
1396
	/**
1397
	 * Sets data for resource binding
1398
	 *
1399
	 * @param   string  $key   Rel element
1400
	 * @param   mixed   $data  Data for the resource
1401
	 *
1402
	 * @return RApiHalHal
1403
	 */
1404
	public function setData($key, $data = null)
1405
	{
1406
		if (is_array($key) && null === $data)
0 ignored issues
show
introduced by
The condition is_array($key) is always false.
Loading history...
1407
		{
1408
			foreach ($key as $k => $v)
1409
			{
1410
				$this->data[$k] = $v;
1411
			}
1412
		}
1413
		else
1414
		{
1415
			$this->data[$key] = $data;
1416
		}
1417
1418
		return $this;
1419
	}
1420
1421
	/**
1422
	 * Set the Uri parameters
1423
	 *
1424
	 * @param   string  $uriKey    Uri Key
1425
	 * @param   string  $uriValue  Uri Value
1426
	 *
1427
	 * @return  RApiHalHal      An instance of itself for chaining
1428
	 */
1429
	public function setUriParams($uriKey, $uriValue)
1430
	{
1431
		$this->uriParams[$uriKey] = $uriValue;
1432
1433
		return $this;
1434
	}
1435
1436
	/**
1437
	 * Process posted data from json or object to array
1438
	 *
1439
	 * @param   array             $data           Raw Posted data
1440
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1441
	 *
1442
	 * @return  mixed  Array with posted data.
1443
	 *
1444
	 * @since   1.2
1445
	 */
1446
	public function processPostData($data, $configuration)
1447
	{
1448
		if (is_object($data))
0 ignored issues
show
introduced by
The condition is_object($data) is always false.
Loading history...
1449
		{
1450
			$data = ArrayHelper::fromObject($data);
1451
		}
1452
1453
		if (!is_array($data))
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
1454
		{
1455
			$data = (array) $data;
1456
		}
1457
1458
		if (!empty($data) && !empty($configuration->fields))
1459
		{
1460
			$dataFields = array();
1461
1462
			foreach ($configuration->fields->field as $field)
1463
			{
1464
				$fieldAttributes                 = RApiHalHelper::getXMLElementAttributes($field);
1465
				$fieldAttributes['transform']    = !is_null($fieldAttributes['transform']) ? $fieldAttributes['transform'] : 'string';
1466
				$fieldAttributes['defaultValue'] = !is_null($fieldAttributes['defaultValue'])
1467
					&& !RApiHalHelper::isAttributeTrue($fieldAttributes, 'isPrimaryField') ? $fieldAttributes['defaultValue'] : '';
1468
1469
				// If field is not sent through Request
1470
				if (!array_key_exists($fieldAttributes['name'], $data))
1471
				{
1472
					if (RBootstrap::getConfig('webservice_put_sends_changes_only', 1) != 1)
1473
					{
1474
						// We will populate missing fields with null value
1475
						$data[$fieldAttributes['name']] = null;
1476
					}
1477
1478
					// We will populate value with default value if the field is not set for create operation
1479
					if ($this->operation == 'create')
1480
					{
1481
						$data[$fieldAttributes['name']] = $fieldAttributes['defaultValue'];
1482
					}
1483
				}
1484
1485
				if (isset($data[$fieldAttributes['name']]))
1486
				{
1487
					$data[$fieldAttributes['name']] = $this->transformField($fieldAttributes['transform'], $data[$fieldAttributes['name']], false);
1488
				}
1489
1490
				if (RBootstrap::getConfig('webservice_put_sends_changes_only', 1) == 1
1491
					? array_key_exists($fieldAttributes['name'], $data) : true)
1492
				{
1493
					$dataFields[$fieldAttributes['name']] = $data[$fieldAttributes['name']];
1494
				}
1495
			}
1496
1497
			if (RApiHalHelper::isAttributeTrue($configuration, 'strictFields'))
1498
			{
1499
				$data = $dataFields;
1500
			}
1501
		}
1502
1503
		if (RBootstrap::getConfig('webservice_put_sends_changes_only', 1) != 1)
1504
		{
1505
			// Common functions are not checking this field so we will
1506
			$data['params']       = isset($data['params']) ? $data['params'] : null;
1507
			$data['associations'] = isset($data['associations']) ? $data['associations'] : [];
1508
		}
1509
1510
		return $data;
1511
	}
1512
1513
	/**
1514
	 * Validates posted data
1515
	 *
1516
	 * @param   object            $model          Model
1517
	 * @param   array             $data           Raw Posted data
1518
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1519
	 *
1520
	 * @return  mixed  Array with posted data or false.
1521
	 *
1522
	 * @since   1.3
1523
	 */
1524
	public function validatePostData($model, $data, $configuration)
1525
	{
1526
		$data = (array) $data;
1527
		$app  = JFactory::getApplication();
1528
1529
		// We are checking required fields set in webservice XMLs
1530
		if (!$this->checkRequiredFields($data, $configuration))
1531
		{
1532
			return false;
1533
		}
1534
1535
		$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

1535
		$validateMethod = strtolower(/** @scrutinizer ignore-type */ RApiHalHelper::attributeToString($configuration, 'validateData', 'none'));
Loading history...
1536
1537
		if ($validateMethod == 'none')
1538
		{
1539
			return $data;
1540
		}
1541
1542
		if ($validateMethod == 'form')
1543
		{
1544
			if (method_exists($model, 'getForm'))
1545
			{
1546
				// Validate the posted data.
1547
				// Sometimes the form needs some posted data, such as for plugins and modules.
1548
				$form = $model->getForm($data, false);
1549
1550
				if (!$form)
1551
				{
1552
					return $data;
1553
				}
1554
1555
				$fieldNames = array_keys($data);
1556
1557
				if (RBootstrap::getConfig('webservice_put_sends_changes_only', 1) == 1
1558
					&& $model instanceof AdminModel)
1559
				{
1560
					$primaryKeys = [];
1561
1562
					foreach ($model->getTable()->get('_tbl_keys') as $key)
1563
					{
1564
						$primaryKeys[$key] = $data[$key] ?? null;
1565
					}
1566
1567
					$key  = count($primaryKeys) > 1 ? $primaryKeys : reset($primaryKeys);
1568
					$data = array_merge(
1569
						ArrayHelper::fromObject($model->getItem($key)),
1570
						$data
1571
					);
1572
				}
1573
1574
				// Test whether the data is valid.
1575
				$validData = $model->validate($form, $data);
1576
1577
				if (!is_array($validData))
1578
				{
1579
					return $validData;
1580
				}
1581
1582
				if (RBootstrap::getConfig('webservice_put_sends_changes_only', 1) == 1)
1583
				{
1584
					$returnData = [];
1585
1586
					foreach ($fieldNames as $fieldName)
1587
					{
1588
						$returnData[$fieldName] = $validData[$fieldName];
1589
					}
1590
1591
					return $returnData;
1592
				}
1593
1594
				// Common functions are not checking this field so we will
1595
				$validData['params']       = isset($validData['params']) ? $validData['params'] : null;
1596
				$validData['associations'] = isset($validData['associations']) ? $validData['associations'] : array();
1597
1598
				return $validData;
1599
			}
1600
1601
			$app->enqueueMessage(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_FUNCTION_DONT_EXIST'), 'error');
1602
1603
			return false;
1604
		}
1605
1606
		if ($validateMethod == 'function')
1607
		{
1608
			$validateMethod = strtolower(RApiHalHelper::attributeToString($configuration, 'validateDataFunction', 'validate'));
1609
1610
			if (method_exists($model, $validateMethod))
1611
			{
1612
				$result = $model->{$validateMethod}($data);
1613
1614
				return $result;
1615
			}
1616
1617
			$app->enqueueMessage(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_FUNCTION_DONT_EXIST'), 'error');
1618
1619
			return false;
1620
		}
1621
1622
		return false;
1623
	}
1624
1625
	/**
1626
	 * Validates posted data
1627
	 *
1628
	 * @param   array             $data           Raw Posted data
1629
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1630
	 *
1631
	 * @return  mixed  Array with posted data or false.
1632
	 *
1633
	 * @since   1.3
1634
	 */
1635
	public function checkRequiredFields($data, $configuration)
1636
	{
1637
		$errors = array();
1638
1639
		if (!empty($configuration->fields))
1640
		{
1641
			foreach ($configuration->fields->field as $field)
1642
			{
1643
				if (RApiHalHelper::isAttributeTrue($field, 'isRequiredField'))
1644
				{
1645
					if (is_null($data[(string) $field['name']]) || $data[(string) $field['name']] === '')
1646
					{
1647
						JFactory::getApplication()->enqueueMessage(
1648
							JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_ERROR_REQUIRED_FIELD', (string) $field['name']), 'error'
1649
						);
1650
1651
						$errors[] = JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_ERROR_REQUIRED_FIELD', (string) $field['name']);
1652
					}
1653
				}
1654
			}
1655
		}
1656
1657
		if (!empty($errors))
1658
		{
1659
			$this->apiErrors = array_merge($this->apiErrors, $errors);
1660
1661
			return false;
1662
		}
1663
1664
		return true;
1665
	}
1666
1667
	/**
1668
	 * Checks if operation is allowed from the configuration file
1669
	 *
1670
	 * @return boolean
1671
	 *
1672
	 * @throws  Exception
1673
	 */
1674
	public function isOperationAllowed()
1675
	{
1676
		// Check if webservice is published
1677
		if (!RApiHalHelper::isPublishedWebservice($this->client, $this->webserviceName, $this->webserviceVersion) && !empty($this->webserviceName))
1678
		{
1679
			throw new Exception(JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_IS_UNPUBLISHED', $this->webserviceName));
1680
		}
1681
1682
		// Check for allowed operations
1683
		$allowedOperations = $this->getConfig('operations');
1684
1685
		if (!isset($allowedOperations->{$this->operation}))
1686
		{
1687
			$customError = $this->triggerFunction('createCustomHttpError', 405, $this->apiErrors);
1688
			$this->setStatusCode(405, $customError);
1689
1690
			return false;
1691
		}
1692
1693
		$scope                    = $this->operation;
1694
		$authorizationGroups      = !empty($allowedOperations->{$this->operation}['authorization']) ?
1695
			(string) $allowedOperations->{$this->operation}['authorization'] : '';
1696
		$terminateIfNotAuthorized = true;
1697
1698
		if ($this->operation == 'task')
1699
		{
1700
			$task   = $this->options->get('task', '');
1701
			$scope .= '.' . $task;
1702
1703
			if (!isset($allowedOperations->task->{$task}))
1704
			{
1705
				$customError = $this->triggerFunction('createCustomHttpError', 405, $this->apiErrors);
1706
				$this->setStatusCode(405, $customError);
1707
1708
				return false;
1709
			}
1710
1711
			$authorizationGroups = !empty($allowedOperations->task->{$task}['authorization']) ?
1712
				(string) $allowedOperations->task->{$task}['authorization'] : '';
1713
1714
			if (isset($allowedOperations->task->{$task}['authorizationNeeded'])
1715
				&& strtolower($allowedOperations->task->{$task}['authorizationNeeded']) == 'false')
1716
			{
1717
				$terminateIfNotAuthorized = false;
1718
			}
1719
		}
1720
		elseif ($this->operation == 'read')
1721
		{
1722
			// Disable authorization on operation read level
1723
			if (isset($allowedOperations->{$this->operation}['authorizationNeeded'])
1724
				&& strtolower($allowedOperations->{$this->operation}['authorizationNeeded']) == 'false')
1725
			{
1726
				$terminateIfNotAuthorized = false;
1727
			}
1728
			else
1729
			{
1730
				$primaryKeys = array();
1731
				$isReadItem  = $this->apiFillPrimaryKeys($primaryKeys);
1732
				$readType    = $isReadItem ? 'item' : 'list';
1733
1734
				if (isset($allowedOperations->read->{$readType}['authorizationNeeded'])
1735
					&& strtolower($allowedOperations->read->{$readType}['authorizationNeeded']) == 'false')
1736
				{
1737
					$terminateIfNotAuthorized = false;
1738
				}
1739
			}
1740
		}
1741
		elseif (isset($allowedOperations->{$this->operation}['authorizationNeeded'])
1742
			&& strtolower($allowedOperations->{$this->operation}['authorizationNeeded']) == 'false')
1743
		{
1744
			$terminateIfNotAuthorized = false;
1745
		}
1746
1747
		// Does user have permission
1748
		// OAuth2 check
1749
		if ($this->authorizationCheck == 'oauth2')
1750
		{
1751
			// Use scopes to authorize
1752
			$scope = array(strtolower($this->client . '.' . $this->webserviceName . '.' . $scope));
1753
1754
			// Add in Global scope check
1755
			$scope[] = $this->client . '.' . $this->operation;
1756
1757
			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

1757
			return $this->isAuthorized(/** @scrutinizer ignore-type */ $scope, $terminateIfNotAuthorized) || !$terminateIfNotAuthorized;
Loading history...
1758
		}
1759
		// Joomla check
1760
		elseif ($this->authorizationCheck == 'joomla')
1761
		{
1762
			$isAuthorized = $this->isAuthorized($scope = null, $terminateIfNotAuthorized);
1763
1764
			// Use Joomla to authorize
1765
			if ($isAuthorized && $terminateIfNotAuthorized && !empty($authorizationGroups))
1766
			{
1767
				$authorizationGroups = explode(',', $authorizationGroups);
1768
				$authorized          = false;
1769
				$configAssetName     = !empty($this->configuration->config->authorizationAssetName) ?
1770
					(string) $this->configuration->config->authorizationAssetName : null;
1771
1772
				foreach ($authorizationGroups as $authorizationGroup)
1773
				{
1774
					$authorization = explode(':', trim($authorizationGroup));
1775
					$action        = $authorization[0];
1776
					$assetName     = !empty($authorization[1]) ? $authorization[1] : $configAssetName;
1777
1778
					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

1778
					if (JFactory::getUser()->authorise(trim($action), trim(/** @scrutinizer ignore-type */ $assetName)))
Loading history...
1779
					{
1780
						$authorized = true;
1781
						break;
1782
					}
1783
				}
1784
1785
				if (!$authorized)
1786
				{
1787
					$customError = $this->triggerFunction('createCustomHttpError', 405, $this->apiErrors);
1788
					$this->setStatusCode(405, $customError);
1789
1790
					return false;
1791
				}
1792
			}
1793
1794
			return $isAuthorized || !$terminateIfNotAuthorized;
1795
		}
1796
1797
		return false;
1798
	}
1799
1800
	/**
1801
	 * Log-in client if successful or terminate api if not authorized
1802
	 *
1803
	 * @param   string  $scope                     Name of the scope to test against
1804
	 * @param   bool    $terminateIfNotAuthorized  Terminate api if client is not authorized
1805
	 *
1806
	 * @throws Exception
1807
	 * @return  boolean
1808
	 *
1809
	 * @since   1.2
1810
	 */
1811
	public function isAuthorized($scope, $terminateIfNotAuthorized)
1812
	{
1813
		$authorized = false;
1814
		JFactory::getApplication()->triggerEvent('RApiHalBeforeIsAuthorizedCheck',
1815
			array($scope, $terminateIfNotAuthorized, $this->options, $this->authorizationCheck, &$authorized)
1816
		);
1817
1818
		if ($authorized)
0 ignored issues
show
introduced by
The condition $authorized is always false.
Loading history...
1819
		{
1820
			return $authorized;
1821
		}
1822
1823
		// OAuth2 check
1824
		if ($this->authorizationCheck == 'oauth2')
1825
		{
1826
			/** @var $response OAuth2\Response */
1827
			$response = RApiOauth2Helper::verifyResourceRequest($scope);
1828
1829
			if ($response instanceof OAuth2\Response)
0 ignored issues
show
introduced by
$response is always a sub-type of OAuth2\Response.
Loading history...
1830
			{
1831
				if (!$response->isSuccessful() && $terminateIfNotAuthorized)
1832
				{
1833
					// OAuth2 Server response is in fact correct output for errors
1834
					$response->send($this->options->get('format', 'json'));
1835
1836
					JFactory::getApplication()->close();
1837
				}
1838
			}
1839
			elseif ($response === false && $terminateIfNotAuthorized)
1840
			{
1841
				throw new Exception(JText::_('LIB_REDCORE_API_OAUTH2_SERVER_IS_NOT_ACTIVE'));
1842
			}
1843
			else
1844
			{
1845
				$response = json_decode($response);
1846
1847
				if (!empty($response->user_id))
1848
				{
1849
					$user = JFactory::getUser($response->user_id);
1850
1851
					// Load the JUser class on application for this client
1852
					JFactory::getApplication()->loadIdentity($user);
1853
					JFactory::getSession()->set('user', $user);
1854
1855
					return true;
1856
				}
1857
1858
				// We will use this only for authorizations with no actual users. This is used for reading of the data.
1859
				if (isset($response->success) && $response->success === true)
1860
				{
1861
					return true;
1862
				}
1863
1864
				$authorized = false || !$terminateIfNotAuthorized;
1865
			}
1866
		}
1867
		// Joomla check through Basic Authentication
1868
		elseif ($this->authorizationCheck == 'joomla')
1869
		{
1870
			// Get username and password from globals
1871
			$credentials = RApiHalHelper::getCredentialsFromGlobals();
1872
1873
			$authorized = RUser::userLogin($credentials) || !$terminateIfNotAuthorized;
1874
		}
1875
1876
		if (!$authorized && $terminateIfNotAuthorized)
1877
		{
1878
			$customError = $this->triggerFunction('createCustomHttpError', 401, $this->apiErrors);
1879
			$this->setStatusCode(401, $customError);
1880
		}
1881
1882
		return $authorized || !$terminateIfNotAuthorized;
1883
	}
1884
1885
	/**
1886
	 * Gets instance of helper object class if exists
1887
	 *
1888
	 * @return  mixed It will return Api helper class or false if it does not exists
1889
	 *
1890
	 * @since   1.2
1891
	 */
1892
	public function getHelperObject()
1893
	{
1894
		if (!empty($this->apiHelperClass))
1895
		{
1896
			return $this->apiHelperClass;
1897
		}
1898
1899
		$helperFile = RApiHalHelper::getWebserviceFile(
1900
			$this->client, strtolower($this->webserviceName), $this->webserviceVersion, 'php', $this->webservicePath
1901
		);
1902
1903
		if (file_exists($helperFile))
1904
		{
1905
			require_once $helperFile;
1906
		}
1907
1908
		$webserviceName  = preg_replace('/[^A-Z0-9_\.]/i', '', $this->webserviceName);
1909
		$helperClassName = 'RApiHalHelper' . ucfirst($this->client) . ucfirst(strtolower($webserviceName));
1910
1911
		if (class_exists($helperClassName))
1912
		{
1913
			$this->apiHelperClass = new $helperClassName;
1914
		}
1915
1916
		return $this->apiHelperClass;
1917
	}
1918
1919
	/**
1920
	 * Gets instance of dynamic model object class (for table bind)
1921
	 *
1922
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
1923
	 *
1924
	 * @return mixed It will return Api dynamic model class
1925
	 *
1926
	 * @throws Exception
1927
	 * @since   1.3
1928
	 */
1929
	public function getDynamicModelObject($configuration)
1930
	{
1931
		if (!empty($this->apiDynamicModelClass))
1932
		{
1933
			return $this->apiDynamicModelClass;
1934
		}
1935
1936
		$tableName = RApiHalHelper::attributeToString($configuration, 'tableName', '');
1937
1938
		if (empty($tableName))
1939
		{
1940
			throw new Exception(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_TABLE_NAME_NOT_SET'));
1941
		}
1942
1943
		$context = $this->webserviceName . '.' . $this->webserviceVersion;
1944
1945
		// We are not using prefix like str_replace(array('.', '-'), array('_', '_'), $context) . '_';
1946
		$paginationPrefix = '';
1947
		$filterFields     = RApiHalHelper::getFilterFields($configuration);
1948
		$primaryFields    = $this->getPrimaryFields($configuration);
1949
		$fields           = $this->getAllFields($configuration);
1950
1951
		$config = array(
1952
			'tableName' => $tableName,
1953
			'context'   => $context,
1954
			'paginationPrefix' => $paginationPrefix,
1955
			'filterFields' => $filterFields,
1956
			'primaryFields' => $primaryFields,
1957
			'fields' => $fields,
1958
		);
1959
1960
		$apiDynamicModelClassName = $this->apiDynamicModelClassName;
1961
1962
		if (class_exists($apiDynamicModelClassName))
1963
		{
1964
			$this->apiDynamicModelClass = new $apiDynamicModelClassName($config);
1965
		}
1966
1967
		return $this->apiDynamicModelClass;
1968
	}
1969
1970
	/**
1971
	 * Gets list of primary fields from operation configuration
1972
	 *
1973
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
1974
	 *
1975
	 * @return  array
1976
	 *
1977
	 * @since   1.3
1978
	 */
1979
	public function getPrimaryFields($configuration)
1980
	{
1981
		$primaryFields = array();
1982
1983
		if (!empty($configuration->fields))
1984
		{
1985
			foreach ($configuration->fields->field as $field)
1986
			{
1987
				$isPrimaryField = RApiHalHelper::isAttributeTrue($field, 'isPrimaryField');
1988
1989
				if ($isPrimaryField)
1990
				{
1991
					$primaryFields[] = (string) $field["name"];
1992
				}
1993
			}
1994
		}
1995
1996
		return $primaryFields;
1997
	}
1998
1999
	/**
2000
	 * Gets list of all fields from operation configuration
2001
	 *
2002
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
2003
	 *
2004
	 * @return  array
2005
	 *
2006
	 * @since   1.3
2007
	 */
2008
	public function getAllFields($configuration)
2009
	{
2010
		$fields = array();
2011
2012
		if (!empty($configuration->fields))
2013
		{
2014
			foreach ($configuration->fields->field as $field)
2015
			{
2016
				$fieldAttributes = RApiHalHelper::getXMLElementAttributes($field);
2017
				$fields[]        = $fieldAttributes;
2018
			}
2019
		}
2020
2021
		return $fields;
2022
	}
2023
2024
	/**
2025
	 * Load model class for data manipulation
2026
	 *
2027
	 * @param   string            $elementName    Element name
2028
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
2029
	 *
2030
	 * @return  mixed  Model class for data manipulation
2031
	 *
2032
	 * @since   1.2
2033
	 */
2034
	public function loadModel($elementName, $configuration)
2035
	{
2036
		$this->setOptionViewName($elementName, $configuration);
2037
		$isAdmin = RApiHalHelper::isAttributeTrue($configuration, 'isAdminClass');
2038
		$this->addModelIncludePaths($isAdmin, $this->optionName);
2039
		$this->loadExtensionLanguage($this->optionName, $isAdmin ? JPATH_ADMINISTRATOR : JPATH_SITE);
2040
		$this->triggerFunction('loadExtensionLibrary', $this->optionName);
2041
		$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

2041
		$dataMode = strtolower(/** @scrutinizer ignore-type */ RApiHalHelper::attributeToString($configuration, 'dataMode', 'model'));
Loading history...
2042
2043
		if ($dataMode == 'helper')
2044
		{
2045
			return $this->getHelperObject();
2046
		}
2047
2048
		if ($dataMode == 'table')
2049
		{
2050
			return $this->getDynamicModelObject($configuration);
2051
		}
2052
2053
		$attributes = [
2054
			'webservice_attributes' => [
2055
				'filterFields' => RApiHalHelper::getFilterFields($configuration)
2056
			]
2057
		];
2058
2059
		if (!empty($configuration['modelConstructorArgs']))
2060
		{
2061
			$attributes['webservice_attributes']['constructorArgs'] = array_filter(
2062
				array_map(
2063
					'trim',
2064
					explode(',', $configuration['modelConstructorArgs'])
2065
				)
2066
			);
2067
		}
2068
2069
		if (!empty($configuration['modelClassName']))
2070
		{
2071
			$modelClass = (string) $configuration['modelClassName'];
2072
2073
			if (!empty($configuration['modelClassPath']))
2074
			{
2075
				require_once JPATH_SITE . '/' . $configuration['modelClassPath'];
2076
			}
2077
2078
			if (class_exists($modelClass))
2079
			{
2080
				return new $modelClass($attributes);
2081
			}
2082
			else
2083
			{
2084
				$componentName = ucfirst(strtolower(substr($this->optionName, 4)));
2085
				$prefix        = $componentName . 'Model';
2086
2087
				$model = RModel::getInstance($modelClass, $prefix, $attributes);
2088
2089
				if ($model)
2090
				{
2091
					return $model;
2092
				}
2093
			}
2094
		}
2095
2096
		if (!empty($this->viewName))
2097
		{
2098
			$elementName = $this->viewName;
2099
		}
2100
2101
		if ($isAdmin)
2102
		{
2103
			return RModel::getAdminInstance($elementName, $attributes, $this->optionName);
2104
		}
2105
2106
		return RModel::getFrontInstance($elementName, $attributes, $this->optionName);
2107
	}
2108
2109
	/**
2110
	 * Add include paths for model class
2111
	 *
2112
	 * @param   boolean  $isAdmin     Is client admin or site
2113
	 * @param   string   $optionName  Option name
2114
	 *
2115
	 * @return  void
2116
	 *
2117
	 * @since   1.3
2118
	 */
2119
	public function addModelIncludePaths($isAdmin, $optionName)
2120
	{
2121
		if ($isAdmin)
2122
		{
2123
			$this->loadExtensionLanguage($optionName, JPATH_ADMINISTRATOR);
2124
			$path = JPATH_ADMINISTRATOR . '/components/' . $optionName;
2125
			RModel::addIncludePath($path . '/models');
2126
			JTable::addIncludePath($path . '/tables');
2127
			RForm::addFormPath($path . '/models/forms');
2128
			RForm::addFieldPath($path . '/models/fields');
2129
		}
2130
		else
2131
		{
2132
			$this->loadExtensionLanguage($optionName);
2133
			$path = JPATH_SITE . '/components/' . $optionName;
2134
			RModel::addIncludePath($path . '/models');
2135
			JTable::addIncludePath($path . '/tables');
2136
			JTable::addIncludePath(JPATH_ADMINISTRATOR . '/components/' . $optionName . '/tables');
2137
			RForm::addFormPath($path . '/models/forms');
2138
			RForm::addFieldPath($path . '/models/fields');
2139
		}
2140
2141
		if (!defined('JPATH_COMPONENT'))
2142
		{
2143
			define('JPATH_COMPONENT', $path);
2144
		}
2145
	}
2146
2147
	/**
2148
	 * Include library classes
2149
	 *
2150
	 * @param   string  $element  Option name
2151
	 *
2152
	 * @return  void
2153
	 *
2154
	 * @since   1.4
2155
	 */
2156
	public function loadExtensionLibrary($element)
2157
	{
2158
		$element = strpos($element, 'com_') === 0 ? substr($element, 4) : $element;
2159
		JLoader::import(strtolower($element) . '.library');
2160
	}
2161
2162
	/**
2163
	 * Sets option and view name
2164
	 *
2165
	 * @param   string            $elementName    Element name
2166
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
2167
	 *
2168
	 * @return  void
2169
	 *
2170
	 * @since   1.3
2171
	 */
2172
	public function setOptionViewName($elementName, $configuration)
2173
	{
2174
		// Views are separated by dash
2175
		$view        = explode('-', $elementName);
2176
		$elementName = $view[0];
2177
		$viewName    = '';
2178
2179
		if (!empty($view[1]))
2180
		{
2181
			$viewName = $view[1];
2182
		}
2183
2184
		$optionName = !empty($configuration['optionName']) ? $configuration['optionName'] : $elementName;
2185
2186
		// Add com_ to the element name if not exist
2187
		$optionName = (strpos($optionName, 'com_') === 0 ? '' : 'com_') . $optionName;
2188
2189
		$this->optionName = $optionName;
2190
		$this->viewName   = $viewName;
2191
2192
		// We add separate view and option name if they were merged
2193
		if (!empty($viewName))
2194
		{
2195
			$input = JFactory::getApplication()->input;
2196
			$input->set('option', $optionName);
2197
			$input->set('view', $viewName);
2198
		}
2199
	}
2200
2201
	/**
2202
	 * Checks if operation is allowed from the configuration file
2203
	 *
2204
	 * @param   string  $path  Path to the configuration setting
2205
	 *
2206
	 * @return mixed May return single value or array
2207
	 */
2208
	public function getConfig($path = '')
2209
	{
2210
		$path          = explode('.', $path);
2211
		$configuration = $this->configuration;
2212
2213
		foreach ($path as $pathInstance)
2214
		{
2215
			if (isset($configuration->{$pathInstance}))
2216
			{
2217
				$configuration = $configuration->{$pathInstance};
2218
			}
2219
		}
2220
2221
		return is_string($configuration) ? (string) $configuration : $configuration;
2222
	}
2223
2224
	/**
2225
	 * Gets errors from model and places it into Application message queue
2226
	 *
2227
	 * @param   object  $model  Model
2228
	 *
2229
	 * @return void
2230
	 */
2231
	public function displayErrors($model)
2232
	{
2233
		if (method_exists($model, 'getErrors'))
2234
		{
2235
			$app = JFactory::getApplication();
2236
2237
			// Get the validation messages.
2238
			$errors = $model->getErrors();
2239
2240
			// Push up all validation messages out to the user.
2241
			for ($i = 0, $n = count($errors); $i < $n; $i++)
2242
			{
2243
				if ($errors[$i] instanceof Exception)
2244
				{
2245
					$app->enqueueMessage($errors[$i]->getMessage(), 'error');
2246
				}
2247
				else
2248
				{
2249
					$app->enqueueMessage($errors[$i], 'error');
2250
				}
2251
			}
2252
		}
2253
	}
2254
2255
	/**
2256
	 * Assign value to Resource
2257
	 *
2258
	 * @param   array   $resource   Resource list with options
2259
	 * @param   mixed   $value      Data values to set to resource format
2260
	 * @param   string  $attribute  Attribute from array to replace the data
2261
	 *
2262
	 * @return  string
2263
	 *
2264
	 * @since   1.2
2265
	 */
2266
	public function assignValueToResource($resource, $value, $attribute = 'fieldFormat')
2267
	{
2268
		$format    = $resource[$attribute];
2269
		$transform = RApiHalHelper::attributeToString($resource, 'transform', '');
2270
2271
		// Filters out the complex SOAP arrays, to treat them as regular arrays
2272
		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

2272
		if (preg_match('/^array\[(.+)\]$/im', /** @scrutinizer ignore-type */ $transform))
Loading history...
2273
		{
2274
			$transform = 'array';
2275
		}
2276
2277
		$stringsToReplace = array();
2278
		preg_match_all('/\{([^}]+)\}/', $format, $stringsToReplace);
2279
2280
		if (!empty($stringsToReplace[1]))
2281
		{
2282
			foreach ($stringsToReplace[1] as $replacementKey)
2283
			{
2284
				if (is_object($value))
2285
				{
2286
					if (property_exists($value, $replacementKey))
2287
					{
2288
						// We are transforming only value
2289
						if ($format == '{' . $replacementKey . '}')
2290
						{
2291
							$format = $this->transformField($transform, $value->{$replacementKey});
2292
						}
2293
						// We are transforming only part of the string
2294
						else
2295
						{
2296
							$format = str_replace('{' . $replacementKey . '}', $this->transformField($transform, $value->{$replacementKey}), $format);
2297
						}
2298
					}
2299
				}
2300
				elseif (is_array($value))
2301
				{
2302
					if (isset($value[$replacementKey]))
2303
					{
2304
						// We are transforming only value
2305
						if ($format == '{' . $replacementKey . '}')
2306
						{
2307
							$format = $this->transformField($transform, $value[$replacementKey]);
2308
						}
2309
						// We are transforming only part of the string
2310
						else
2311
						{
2312
							$format = str_replace('{' . $replacementKey . '}', $this->transformField($transform, $value[$replacementKey]), $format);
2313
						}
2314
					}
2315
				}
2316
				else
2317
				{
2318
					// We are transforming only value
2319
					if ($format == '{' . $replacementKey . '}')
2320
					{
2321
						$format = $this->transformField($transform, $value);
2322
					}
2323
					// We are transforming only part of the string
2324
					else
2325
					{
2326
						$format = str_replace('{' . $replacementKey . '}', $this->transformField($transform, $value), $format);
2327
					}
2328
				}
2329
			}
2330
		}
2331
2332
		// We replace global data as well
2333
		$format = $this->assignGlobalValueToResource($format);
2334
2335
		if (!empty($stringsToReplace[1]))
2336
		{
2337
			// If we did not found data with that resource we will set it to 0, except for linkRel which is a documentation template
2338
			if ($format === $resource[$attribute] && $resource['linkRel'] != 'curies')
2339
			{
2340
				$format = null;
2341
			}
2342
		}
2343
2344
		return $format;
2345
	}
2346
2347
	/**
2348
	 * Assign value to Resource
2349
	 *
2350
	 * @param   string  $format  String to parse
2351
	 *
2352
	 * @return  string
2353
	 *
2354
	 * @since   1.2
2355
	 */
2356
	public function assignGlobalValueToResource($format)
2357
	{
2358
		if (empty($format) || !is_string($format))
2359
		{
2360
			return $format;
2361
		}
2362
2363
		$stringsToReplace = array();
2364
		preg_match_all('/\{([^}]+)\}/', $format, $stringsToReplace);
2365
2366
		if (!empty($stringsToReplace[1]))
2367
		{
2368
			foreach ($stringsToReplace[1] as $replacementKey)
2369
			{
2370
				// Replace from global variables if present
2371
				if (isset($this->data[$replacementKey]))
2372
				{
2373
					// We are transforming only value
2374
					if ($format == '{' . $replacementKey . '}')
2375
					{
2376
						$format = $this->data[$replacementKey];
2377
					}
2378
					// We are transforming only part of the string
2379
					else
2380
					{
2381
						$format = str_replace('{' . $replacementKey . '}', $this->data[$replacementKey], $format);
2382
					}
2383
				}
2384
			}
2385
		}
2386
2387
		return $format;
2388
	}
2389
2390
	/**
2391
	 * Get the name of the transform class for a given field type.
2392
	 *
2393
	 * First looks for the transform class in the /transform directory
2394
	 * in the same directory as the web service file.  Then looks
2395
	 * for it in the /api/transform directory.
2396
	 *
2397
	 * @param   string  $fieldType  Field type.
2398
	 *
2399
	 * @return string  Transform class name.
2400
	 */
2401
	private function getTransformClass($fieldType)
2402
	{
2403
		$fieldType = !empty($fieldType) ? $fieldType : 'string';
2404
2405
		// Cache for the class names.
2406
		static $classNames = array();
2407
2408
		// If we already know the class name, just return it.
2409
		if (isset($classNames[$fieldType]))
2410
		{
2411
			return $classNames[$fieldType];
2412
		}
2413
2414
		// Construct the name of the class to do the transform (default is RApiHalTransformString).
2415
		$className = 'RApiHalTransform' . ucfirst($fieldType);
2416
2417
		if (class_exists($className))
2418
		{
2419
			$classInstance = new $className;
2420
2421
			// Cache it for later.
2422
			$classNames[$fieldType] = $classInstance;
2423
2424
			return $classNames[$fieldType];
2425
		}
2426
2427
		return $this->getTransformClass('string');
2428
	}
2429
2430
	/**
2431
	 * Transform a source field data value.
2432
	 *
2433
	 * Calls the static toExternal method of a transform class.
2434
	 *
2435
	 * @param   string   $fieldType          Field type.
2436
	 * @param   string   $definition         Field definition.
2437
	 * @param   boolean  $directionExternal  Transform direction
2438
	 *
2439
	 * @return mixed Transformed data.
2440
	 */
2441
	public function transformField($fieldType, $definition, $directionExternal = true)
2442
	{
2443
		// Get the transform class name.
2444
		$className = $this->getTransformClass($fieldType);
2445
2446
		// Execute the transform.
2447
		if ($className instanceof RApiHalTransformInterface)
0 ignored issues
show
introduced by
$className is never a sub-type of RApiHalTransformInterface.
Loading history...
2448
		{
2449
			return $directionExternal ? $className::toExternal($definition) : $className::toInternal($definition);
2450
		}
2451
		else
2452
		{
2453
			return $definition;
2454
		}
2455
	}
2456
2457
	/**
2458
	 * Calls method from helper file if exists or method from this class,
2459
	 * Additionally it Triggers plugin call for specific function in a format RApiHalFunctionName
2460
	 *
2461
	 * @param   string  $functionName  Field type.
2462
	 *
2463
	 * @return mixed Result from callback function
2464
	 */
2465
	public function triggerFunction($functionName)
2466
	{
2467
		$apiHelperClass = $this->getHelperObject();
2468
		$args           = func_get_args();
2469
2470
		// Remove function name from arguments
2471
		array_shift($args);
2472
2473
		// PHP 5.3 workaround
2474
		$temp = array();
2475
2476
		foreach ($args as &$arg)
2477
		{
2478
			$temp[] = &$arg;
2479
		}
2480
2481
		// We will add this instance of the object as last argument for manipulation in plugin and helper
2482
		$temp[] = &$this;
2483
		$return = null;
2484
2485
		$result = JFactory::getApplication()->triggerEvent('RApiHalBefore' . $functionName, array($functionName, $temp, &$return));
2486
2487
		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...
2488
		{
2489
			if ($return !== null)
2490
			{
2491
				return $return;
2492
			}
2493
			else
2494
			{
2495
				return $result;
2496
			}
2497
		}
2498
2499
		// Checks if that method exists in helper file and executes it
2500
		if (method_exists($apiHelperClass, $functionName))
2501
		{
2502
			$result = call_user_func_array(array($apiHelperClass, $functionName), $temp);
2503
		}
2504
		else
2505
		{
2506
			$result = call_user_func_array(array($this, $functionName), $temp);
2507
		}
2508
2509
		JFactory::getApplication()->triggerEvent('RApiHal' . $functionName, $temp);
2510
2511
		return $result;
2512
	}
2513
2514
	/**
2515
	 * Calls method from defined object as some Joomla methods require referenced parameters
2516
	 *
2517
	 * @param   object  $object        Object to run function on
2518
	 * @param   string  $functionName  Function name
2519
	 * @param   array   $args          Arguments for the function
2520
	 *
2521
	 * @return mixed Result from callback function
2522
	 */
2523
	public function triggerCallFunction($object, $functionName, $args)
2524
	{
2525
		switch (count($args))
2526
		{
2527
			case 0:
2528
				return $object->{$functionName}();
2529
			case 1:
2530
				return $object->{$functionName}($args[0]);
2531
			case 2:
2532
				return $object->{$functionName}($args[0], $args[1]);
2533
			case 3:
2534
				return $object->{$functionName}($args[0], $args[1], $args[2]);
2535
			case 4:
2536
				return $object->{$functionName}($args[0], $args[1], $args[2], $args[3]);
2537
			case 5:
2538
				return $object->{$functionName}($args[0], $args[1], $args[2], $args[3], $args[4]);
2539
			default:
2540
				return call_user_func_array(array($object, $functionName), $args);
2541
		}
2542
	}
2543
2544
	/**
2545
	 * Get all defined fields and transform them if needed to expected format. Then it puts it into array for function call
2546
	 *
2547
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
2548
	 * @param   array             $data           List of posted data
2549
	 *
2550
	 * @return array List of parameters to pass to the function
2551
	 */
2552
	public function buildFunctionArgs($configuration, $data)
2553
	{
2554
		$args = array();
2555
2556
		if (!empty($configuration['functionArgs']))
2557
		{
2558
			$functionArgs = explode(',', (string) $configuration['functionArgs']);
2559
2560
			foreach ($functionArgs as $functionArg)
2561
			{
2562
				$parameter = explode('{', $functionArg);
2563
2564
				// First field is the name of the data field and second is transformation
2565
				$parameter[0] = trim($parameter[0]);
2566
				$parameter[1] = !empty($parameter[1]) ? strtolower(trim(str_replace('}', '', $parameter[1]))) : 'string';
2567
2568
				// If we set argument to value, then it will not be transformed, instead we will take field name as a value
2569
				if ($parameter[1] == 'value')
2570
				{
2571
					$parameterValue = $parameter[0];
2572
				}
2573
				else
2574
				{
2575
					if (isset($data[$parameter[0]]))
2576
					{
2577
						$parameterValue = $this->transformField($parameter[1], $data[$parameter[0]]);
2578
					}
2579
					else
2580
					{
2581
						$parameterValue = null;
2582
					}
2583
				}
2584
2585
				$args[] = $parameterValue;
2586
			}
2587
		}
2588
		else
2589
		{
2590
			$args[] = $data;
2591
		}
2592
2593
		return $args;
2594
	}
2595
2596
	/**
2597
	 * We set filters and List parameters to the model object
2598
	 *
2599
	 * @param   object  &$model  Model object
2600
	 *
2601
	 * @return  array
2602
	 */
2603
	public function assignFiltersList(&$model)
2604
	{
2605
		if (method_exists($model, 'getState'))
2606
		{
2607
			// To initialize populateState
2608
			$model->getState();
2609
		}
2610
2611
		$dataGet = $this->options->get('dataGet', array());
2612
2613
		if (is_object($dataGet))
2614
		{
2615
			$dataGet = ArrayHelper::fromObject($dataGet);
2616
		}
2617
2618
		$limitField      = 'limit';
2619
		$limitStartField = 'limitstart';
2620
2621
		if (method_exists($model, 'get'))
2622
		{
2623
			// RedCORE limit fields
2624
			$limitField      = $model->get('limitField', $limitField);
2625
			$limitStartField = $model->get('limitstartField', $limitStartField);
2626
		}
2627
2628
		if (isset($dataGet['list']['limit']))
2629
		{
2630
			$dataGet[$limitField] = $dataGet['list']['limit'];
2631
		}
2632
2633
		if (isset($dataGet['list']['limitstart']))
2634
		{
2635
			$dataGet[$limitStartField] = $dataGet['list']['limitstart'];
2636
		}
2637
2638
		// Support for B/C custom limit fields
2639
		if ($limitField != 'limit' && !empty($dataGet['limit']) && !isset($dataGet[$limitField]))
2640
		{
2641
			$dataGet[$limitField] = $dataGet['limit'];
2642
		}
2643
2644
		if ($limitStartField != 'limitstart' && !empty($dataGet['limitstart']) && !isset($dataGet[$limitStartField]))
2645
		{
2646
			$dataGet[$limitStartField] = $dataGet['limitstart'];
2647
		}
2648
2649
		// Set state for Filters and List
2650
		if (method_exists($model, 'setState'))
2651
		{
2652
			if (isset($dataGet['list']))
2653
			{
2654
				foreach ($dataGet['list'] as $key => $value)
2655
				{
2656
					$model->setState('list.' . $key, $value);
2657
				}
2658
			}
2659
2660
			if (isset($dataGet['filter']))
2661
			{
2662
				foreach ($dataGet['filter'] as $key => $value)
2663
				{
2664
					$model->setState('filter.' . $key, $value);
2665
				}
2666
			}
2667
2668
			if (isset($dataGet[$limitField]))
2669
			{
2670
				$model->setState('limit', $dataGet[$limitField]);
2671
				$model->setState('list.limit', $dataGet[$limitField]);
2672
				$model->setState($limitField, $dataGet[$limitField]);
2673
			}
2674
2675
			if (isset($dataGet[$limitStartField]))
2676
			{
2677
				$model->setState('limitstart', $dataGet[$limitStartField]);
2678
				$model->setState('list.start', $dataGet[$limitStartField]);
2679
				$model->setState($limitStartField, $dataGet[$limitStartField]);
2680
			}
2681
		}
2682
2683
		$this->options->set('dataGet', $dataGet);
2684
	}
2685
2686
	/**
2687
	 * Returns if all primary keys have set values
2688
	 * Easily get read type (item or list) for current read operation and fills primary keys
2689
	 *
2690
	 * @param   array             &$primaryKeys   List of primary keys
2691
	 * @param   SimpleXMLElement  $configuration  Configuration group
2692
	 *
2693
	 * @return  bool  Returns true if read type is Item
2694
	 *
2695
	 * @since   1.2
2696
	 */
2697
	public function apiFillPrimaryKeys(&$primaryKeys, $configuration = null)
2698
	{
2699
		if (is_null($configuration))
2700
		{
2701
			$operations = $this->getConfig('operations');
2702
2703
			if (!empty($operations->read->item))
2704
			{
2705
				$configuration = $operations->read->item;
2706
			}
2707
2708
			$data = $this->triggerFunction('processPostData', $this->options->get('dataGet', array()), $configuration);
2709
		}
2710
		else
2711
		{
2712
			$data = $this->triggerFunction('processPostData', $this->options->get('data', array()), $configuration);
2713
		}
2714
2715
		// Checking for primary keys
2716
		if (!empty($configuration))
2717
		{
2718
			$primaryKeysFromFields = RApiHalHelper::getFieldsArray($configuration, true);
2719
2720
			if (!empty($primaryKeysFromFields))
2721
			{
2722
				foreach ($primaryKeysFromFields as $primaryKey => $primaryKeyField)
2723
				{
2724
					if (isset($data[$primaryKey]) && $data[$primaryKey] != '')
2725
					{
2726
						$primaryKeys[$primaryKey] = $this->transformField($primaryKeyField['transform'], $data[$primaryKey], false);
2727
					}
2728
					else
2729
					{
2730
						$primaryKeys[$primaryKey] = null;
2731
					}
2732
				}
2733
2734
				foreach ($primaryKeys as $primaryKey => $primaryKeyField)
2735
				{
2736
					if (is_null($primaryKeyField))
2737
					{
2738
						return false;
2739
					}
2740
				}
2741
			}
2742
2743
			return true;
2744
		}
2745
2746
		return false;
2747
	}
2748
}
2749