Passed
Pull Request — develop (#890)
by Tito
03:54
created

RApiHalHal::isOperationAllowed()   F

Complexity

Conditions 30
Paths 796

Size

Total Lines 124
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 61
c 0
b 0
f 0
dl 0
loc 124
rs 0.2833
cc 30
nc 796
nop 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

457
				if (!$this->isAuthorized('', /** @scrutinizer ignore-type */ RBootstrap::getConfig('webservices_default_page_authorization', 0)))
Loading history...
458
				{
459
					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...
460
				}
461
462
				// No webservice name. We display all webservices available
463
				$this->triggerFunction('apiDefaultPage');
464
			}
465
466
			// Set links from resources to the main document
467
			$this->setDataValueToResource($this->hal, $this->resources, $this->data);
468
			$messages = JFactory::getApplication()->getMessageQueue();
469
470
			$executionErrors = ob_get_contents();
471
			ob_end_clean();
472
		}
473
		catch (Exception $e)
474
		{
475
			$executionErrors = ob_get_contents();
0 ignored issues
show
Unused Code introduced by
The assignment to $executionErrors is dead and can be removed.
Loading history...
476
			ob_end_clean();
477
478
			throw $e;
479
		}
480
481
		if (!empty($executionErrors))
482
		{
483
			$messages[] = array('message' => $executionErrors, 'type' => 'notice');
484
		}
485
486
		if (!empty($messages))
487
		{
488
			// If we are not in debug mode we will take out everything except errors
489
			if (RBootstrap::getConfig('debug_webservices', 0) == 0)
490
			{
491
				$warnings = array();
492
493
				foreach ($messages as $key => $message)
494
				{
495
					if ($message['type'] == 'warning')
496
					{
497
						$warnings[] = $message;
498
					}
499
500
					if ($message['type'] != 'error')
501
					{
502
						unset($messages[$key]);
503
					}
504
				}
505
506
				// Showing 'warning' messages only if no 'error' are present
507
				if (!count($messages))
508
				{
509
					$messages = $warnings;
510
				}
511
			}
512
513
			$this->hal->setData('_messages', $messages);
514
		}
515
516
		return $this;
517
	}
518
519
	/**
520
	 * Method to clear the cache and any session state
521
	 * related to API requests
522
	 *
523
	 * @param   string   $group      The cache group
524
	 * @param   integer  $client_id  The ID of the client
525
	 *
526
	 * @throws Exception
527
	 * @return void
528
	 */
529
	private function cleanCache($group = null, $client_id = 0)
530
	{
531
		if ($this->options->get('webservice_stateful', 0) == 1)
532
		{
533
			return;
534
		}
535
536
		$option = $this->options->get('optionName', '');
537
		$option = strpos($option, 'com_') === false ? 'com_' . $option : $option;
538
		$conf   = JFactory::getConfig();
539
540
		$options = array(
541
			'defaultgroup' => ($group) ? $group : $option,
542
			'cachebase' => ($client_id) ? JPATH_ADMINISTRATOR . '/cache' : $conf->get('cache_path', JPATH_SITE . '/cache'));
543
544
		$cache = JCache::getInstance('callback', $options);
545
		$cache->clean();
546
547
		$session  = JFactory::getSession();
548
		$registry = $session->get('registry');
549
		$registry->set($option, null);
550
	}
551
552
	/**
553
	 * Execute the Api Default Page operation.
554
	 *
555
	 * @return  mixed  RApi object with information on success, boolean false on failure.
556
	 *
557
	 * @since   1.2
558
	 */
559
	public function apiDefaultPage()
560
	{
561
		// Add standard Joomla namespace as curie.
562
		$documentationCurieAdmin = new RApiHalDocumentLink('/index.php?option={rel}&amp;format=doc&amp;webserviceClient=administrator',
563
			'curies', 'Documentation Admin', 'Admin', null, true
564
		);
565
		$documentationCurieSite  = new RApiHalDocumentLink('/index.php?option={rel}&amp;format=doc&amp;webserviceClient=site',
566
			'curies', 'Documentation Site', 'Site', null, true
567
		);
568
569
		// Add basic hypermedia links.
570
		$this->hal->setLink($documentationCurieAdmin, false, true);
571
		$this->hal->setLink($documentationCurieSite, false, true);
572
		$this->hal->setLink(new RApiHalDocumentLink(JUri::base(), 'base', JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_DOCUMENTATION_DEFAULT_PAGE')));
573
574
		$webservices = RApiHalHelper::getInstalledWebservices();
575
576
		if (!empty($webservices))
577
		{
578
			foreach ($webservices as $webserviceClient => $webserviceNames)
579
			{
580
				foreach ($webserviceNames as $webserviceName => $webserviceVersions)
581
				{
582
					foreach ($webserviceVersions as $webserviceVersion => $webservice)
583
					{
584
						if ($webservice['state'] == 1)
585
						{
586
							$documentation = $webserviceClient == 'site' ? 'Site' : 'Admin';
587
588
							// Set option and view name
589
							$this->setOptionViewName($webservice['name'], $this->configuration);
590
							$webserviceUrlPath = '/index.php?option=' . $this->optionName
591
								. '&amp;webserviceVersion=' . $webserviceVersion;
592
593
							if (!empty($this->viewName))
594
							{
595
								$webserviceUrlPath .= '&view=' . $this->viewName;
596
							}
597
598
							// We will fetch only top level webservice
599
							$this->hal->setLink(
600
								new RApiHalDocumentLink(
601
									$webserviceUrlPath . '&webserviceClient=' . $webserviceClient,
602
									$documentation . ':' . $webservice['name'],
603
									$webservice['title']
604
								)
605
							);
606
607
							break;
608
						}
609
					}
610
				}
611
			}
612
		}
613
614
		return $this;
615
	}
616
617
	/**
618
	 * Execute the Api Documentation operation.
619
	 *
620
	 * @return  mixed  RApi object with information on success, boolean false on failure.
621
	 *
622
	 * @since   1.2
623
	 */
624
	public function apiDocumentation()
625
	{
626
		$currentConfiguration = $this->configuration;
627
		$documentationNone    = false;
628
629
		if ($this->operationConfiguration['source'] == 'url')
0 ignored issues
show
introduced by
The condition $this->operationConfiguration['source'] == 'url' is always false.
Loading history...
630
		{
631
			if (!empty($this->operationConfiguration['url']))
632
			{
633
				JFactory::getApplication()->redirect($this->operationConfiguration['url']);
634
				JFactory::getApplication()->close();
635
			}
636
637
			$documentationNone = true;
638
		}
639
640
		if ($this->operationConfiguration['source'] == 'none' || $documentationNone)
0 ignored issues
show
introduced by
The condition $documentationNone is always false.
Loading history...
641
		{
642
			$currentConfiguration = null;
643
		}
644
645
		$dataGet = $this->options->get('dataGet', array());
646
647
		$this->documentation = RLayoutHelper::render(
648
			'webservice.documentation',
649
			array(
650
				'view' => $this,
651
				'options' => array (
652
					'xml' => $currentConfiguration,
653
					'soapEnabled' => RBootstrap::getConfig('enable_soap', 0),
654
					'print' => isset($dataGet->print)
655
				)
656
			)
657
		);
658
659
		return $this;
660
	}
661
662
	/**
663
	 * Execute the Api Read operation.
664
	 *
665
	 * @return  mixed  RApi object with information on success, boolean false on failure.
666
	 *
667
	 * @since   1.2
668
	 */
669
	public function apiRead()
670
	{
671
		$primaryKeys = array();
672
		$isReadItem  = $this->apiFillPrimaryKeys($primaryKeys);
673
674
		$displayTarget                  = $isReadItem ? 'item' : 'list';
675
		$this->apiDynamicModelClassName = 'RApiHalModel' . ucfirst($displayTarget);
676
		$currentConfiguration           = $this->operationConfiguration->{$displayTarget};
677
		$model                          = $this->triggerFunction('loadModel', $this->elementName, $currentConfiguration);
678
		$this->assignFiltersList($model);
679
680
		if ($displayTarget == 'list')
681
		{
682
			$functionName       = RApiHalHelper::attributeToString($currentConfiguration, 'functionName', 'getItems');
683
			$paginationFunction = RApiHalHelper::attributeToString($currentConfiguration, 'paginationFunction', 'getPagination');
684
			$totalFunction      = RApiHalHelper::attributeToString($currentConfiguration, 'totalFunction', 'getTotal');
685
686
			$items = method_exists($model, $functionName) ? $model->{$functionName}() : array();
687
688
			if (!empty($paginationFunction) && method_exists($model, $paginationFunction))
689
			{
690
				// Get total count to check if we have reached the limit
691
				if (!empty($totalFunction) && method_exists($model, $totalFunction))
692
				{
693
					$totalItems = $model->{$totalFunction}();
694
695
					if ($model->getState('limitstart', 0) >= $totalItems)
696
					{
697
						$customError = $this->triggerFunction('createCustomHttpError', 204, array('No more records found'));
698
						$this->setStatusCode(204, $customError);
699
700
						return $this;
701
					}
702
				}
703
704
				$pagination      = $model->{$paginationFunction}();
705
				$paginationPages = $pagination->getPaginationPages();
706
707
				$this->setData(
708
					'pagination.previous', isset($paginationPages['previous']['data']->base) ? $paginationPages['previous']['data']->base : $pagination->limitstart
709
				);
710
				$this->setData(
711
					'pagination.next', isset($paginationPages['next']['data']->base) ? $paginationPages['next']['data']->base : $pagination->limitstart
712
				);
713
				$this->setData('pagination.limit', $pagination->limit);
714
				$this->setData('pagination.limitstart', $pagination->limitstart);
715
				$this->setData('pagination.totalItems', $pagination->total);
716
				$this->setData('pagination.totalPages', max($pagination->pagesTotal, 1));
717
				$this->setData('pagination.page', max($pagination->pagesCurrent, 1));
718
				$this->setData('pagination.last', ((max($pagination->pagesTotal, 1) - 1) * $pagination->limit));
719
			}
720
721
			$this->triggerFunction('setForRenderList', $items, $currentConfiguration);
722
723
			return $this;
724
		}
725
726
		$primaryKeys = count($primaryKeys) > 1 ? array($primaryKeys) : $primaryKeys;
727
728
		// Getting single item
729
		$functionName   = RApiHalHelper::attributeToString($currentConfiguration, 'functionName', 'getItem');
730
		$messagesBefore = JFactory::getApplication()->getMessageQueue();
731
		$itemObject     = method_exists($model, $functionName) ? call_user_func_array(array(&$model, $functionName), $primaryKeys) : array();
732
		$messagesAfter  = JFactory::getApplication()->getMessageQueue();
733
734
		// Check to see if we have the item or not since it might return default properties
735
		if (count($messagesBefore) != count($messagesAfter))
736
		{
737
			foreach ($messagesAfter as $messageKey => $messageValue)
738
			{
739
				$messageFound = false;
740
741
				foreach ($messagesBefore as $key => $value)
742
				{
743
					if ($messageValue['type'] == $value['type'] && $messageValue['message'] == $value['message'])
744
					{
745
						$messageFound = true;
746
						break;
747
					}
748
				}
749
750
				if (!$messageFound && $messageValue['type'] == 'error')
751
				{
752
					$itemObject = null;
753
					break;
754
				}
755
			}
756
		}
757
758
		if (RApiHalHelper::isAttributeTrue($currentConfiguration, 'enforcePKs', true))
759
		{
760
			// Checking if primary keys are found
761
			foreach ($primaryKeys as $primaryKey => $primaryKeyValue)
762
			{
763
				if (property_exists($itemObject, $primaryKey) && $itemObject->{$primaryKey} != $primaryKeyValue)
764
				{
765
					$itemObject = null;
766
					break;
767
				}
768
			}
769
		}
770
771
		$this->triggerFunction('setForRenderItem', $itemObject, $currentConfiguration);
772
773
		return $this;
774
	}
775
776
	/**
777
	 * Execute the Api Create operation.
778
	 *
779
	 * @return  mixed  RApi object with information on success, boolean false on failure.
780
	 *
781
	 * @since   1.2
782
	 */
783
	public function apiCreate()
784
	{
785
		// Get resource list from configuration
786
		$this->loadResourceFromConfiguration($this->operationConfiguration);
787
788
		$model        = $this->triggerFunction('loadModel', $this->elementName, $this->operationConfiguration);
789
		$functionName = RApiHalHelper::attributeToString($this->operationConfiguration, 'functionName', 'save');
790
791
		$data = $this->triggerFunction('processPostData', $this->options->get('data', array()), $this->operationConfiguration);
792
		$data = $this->triggerFunction('validatePostData', $model, $data, $this->operationConfiguration);
793
794
		if ($data === false)
795
		{
796
			// Not Acceptable
797
			$this->setStatusCode(406);
798
			$this->triggerFunction('displayErrors', $model);
799
			$this->setData('result', $data);
800
801
			return;
802
		}
803
804
		// Prepare parameters for the function
805
		$args   = $this->buildFunctionArgs($this->operationConfiguration, $data);
806
		$result = null;
807
808
		// Checks if that method exists in model class file and executes it
809
		if (method_exists($model, $functionName))
810
		{
811
			$result = $this->triggerCallFunction($model, $functionName, $args);
812
		}
813
		else
814
		{
815
			$customError = $this->triggerFunction('createCustomHttpError', 400, $this->apiErrors);
816
			$this->setStatusCode(400, $customError);
817
		}
818
819
		if (method_exists($model, 'getState'))
820
		{
821
			$this->setData('id', $model->getState($model->getName() . '.id'));
822
		}
823
824
		if (method_exists($model, 'getErrors'))
825
		{
826
			$modelErrors = $model->getErrors();
827
828
			if (!empty($modelErrors))
829
			{
830
				$this->apiErrors = array_merge($this->apiErrors, $modelErrors);
831
			}
832
		}
833
834
		$this->setData('result', $result);
835
		$this->triggerFunction('displayErrors', $model);
836
837
		if ($this->statusCode < 400)
838
		{
839
			if ($result === false)
840
			{
841
				$customError = $this->triggerFunction('createCustomHttpError', 404, $this->apiErrors);
842
				$this->setStatusCode(404, $customError);
843
			}
844
			else
845
			{
846
				$this->setStatusCode(201);
847
			}
848
		}
849
	}
850
851
	/**
852
	 * Execute the Api Delete operation.
853
	 *
854
	 * @return  mixed  RApi object with information on success, boolean false on failure.
855
	 *
856
	 * @since   1.2
857
	 */
858
	public function apiDelete()
859
	{
860
		// Get resource list from configuration
861
		$this->loadResourceFromConfiguration($this->operationConfiguration);
862
863
		// Delete function requires references and not values like we use in call_user_func_array so we use List delete function
864
		$this->apiDynamicModelClassName = 'RApiHalModelList';
865
		$model                          = $this->triggerFunction('loadModel', $this->elementName, $this->operationConfiguration);
866
		$functionName                   = RApiHalHelper::attributeToString($this->operationConfiguration, 'functionName', 'delete');
867
		$data                           = $this->triggerFunction('processPostData', $this->options->get('data', array()), $this->operationConfiguration);
868
869
		$data = $this->triggerFunction('validatePostData', $model, $data, $this->operationConfiguration);
870
871
		if ($data === false)
872
		{
873
			// Not Acceptable
874
			$customError = $this->triggerFunction('createCustomHttpError', 406, $this->apiErrors);
875
			$this->setStatusCode(406, $customError);
876
			$this->triggerFunction('displayErrors', $model);
877
			$this->setData('result', $data);
878
879
			return;
880
		}
881
882
		$result = null;
883
		$args   = $this->buildFunctionArgs($this->operationConfiguration, $data);
884
885
		// Prepare parameters for the function
886
		if (strtolower(RApiHalHelper::attributeToString($this->operationConfiguration, 'dataMode', 'model')) == 'table')
887
		{
888
			$primaryKeys = array();
889
			$this->apiFillPrimaryKeys($primaryKeys, $this->operationConfiguration);
890
891
			if (!empty($primaryKeys))
892
			{
893
				$result = $model->{$functionName}($primaryKeys);
894
			}
895
			else
896
			{
897
				$result = $model->{$functionName}($args);
898
			}
899
		}
900
		else
901
		{
902
			// Checks if that method exists in model class file and executes it
903
			if (method_exists($model, $functionName))
904
			{
905
				$result = $this->triggerCallFunction($model, $functionName, $args);
906
			}
907
			else
908
			{
909
				$customError = $this->triggerFunction('createCustomHttpError', 400, $this->apiErrors);
910
				$this->setStatusCode(400, $customError);
911
			}
912
		}
913
914
		if (method_exists($model, 'getErrors'))
915
		{
916
			$modelErrors = $model->getErrors();
917
918
			if (!empty($modelErrors))
919
			{
920
				$this->apiErrors = array_merge($this->apiErrors, $modelErrors);
921
			}
922
		}
923
924
		$this->setData('result', $result);
925
926
		$this->triggerFunction('displayErrors', $model);
927
928
		if ($this->statusCode < 400)
929
		{
930
			if ($result === false)
931
			{
932
				// If delete failed then we set it to Internal Server Error status code
933
				$customError = $this->triggerFunction('createCustomHttpError', 500, $this->apiErrors);
934
				$this->setStatusCode(500, $customError);
935
			}
936
		}
937
	}
938
939
	/**
940
	 * Execute the Api Update operation.
941
	 *
942
	 * @return  mixed  RApi object with information on success, boolean false on failure.
943
	 *
944
	 * @since   1.2
945
	 */
946
	public function apiUpdate()
947
	{
948
		// Get resource list from configuration
949
		$this->loadResourceFromConfiguration($this->operationConfiguration);
950
		$model        = $this->triggerFunction('loadModel', $this->elementName, $this->operationConfiguration);
951
		$functionName = RApiHalHelper::attributeToString($this->operationConfiguration, 'functionName', 'save');
952
		$data         = $this->triggerFunction('processPostData', $this->options->get('data', array()), $this->operationConfiguration);
953
954
		$data = $this->triggerFunction('validatePostData', $model, $data, $this->operationConfiguration);
955
956
		if ($data === false)
957
		{
958
			// Not Acceptable
959
			$customError = $this->triggerFunction('createCustomHttpError', 406, $this->apiErrors);
960
			$this->setStatusCode(406, $customError);
961
			$this->triggerFunction('displayErrors', $model);
962
			$this->setData('result', $data);
963
964
			return;
965
		}
966
967
		// Prepare parameters for the function
968
		$args   = $this->buildFunctionArgs($this->operationConfiguration, $data);
969
		$result = null;
970
971
		// Checks if that method exists in model class and executes it
972
		if (method_exists($model, $functionName))
973
		{
974
			$result = $this->triggerCallFunction($model, $functionName, $args);
975
		}
976
		else
977
		{
978
			$customError = $this->triggerFunction('createCustomHttpError', 400, $this->apiErrors);
979
			$this->setStatusCode(400, $customError);
980
		}
981
982
		if (method_exists($model, 'getState'))
983
		{
984
			$this->setData('id', $model->getState(strtolower($this->elementName) . '.id'));
985
		}
986
987
		if (method_exists($model, 'getErrors'))
988
		{
989
			$modelErrors = $model->getErrors();
990
991
			if (!empty($modelErrors))
992
			{
993
				$this->apiErrors = array_merge($this->apiErrors, $modelErrors);
994
			}
995
		}
996
997
		$this->setData('result', $result);
998
		$this->triggerFunction('displayErrors', $model);
999
1000
		if ($this->statusCode < 400)
1001
		{
1002
			if ($result === false)
1003
			{
1004
				// If update failed then we set it to Internal Server Error status code
1005
				$customError = $this->triggerFunction('createCustomHttpError', 500, $this->apiErrors);
1006
				$this->setStatusCode(500, $customError);
1007
			}
1008
		}
1009
	}
1010
1011
	/**
1012
	 * Execute the Api Task operation.
1013
	 *
1014
	 * @return  mixed  RApi object with information on success, boolean false on failure.
1015
	 *
1016
	 * @since   1.2
1017
	 */
1018
	public function apiTask()
1019
	{
1020
		$task   = $this->options->get('task', '');
1021
		$result = false;
1022
1023
		if (!empty($task))
1024
		{
1025
			// Load resources directly from task group
1026
			if (!empty($this->operationConfiguration->{$task}->resources))
1027
			{
1028
				$this->loadResourceFromConfiguration($this->operationConfiguration->{$task});
1029
			}
1030
1031
			$taskConfiguration = !empty($this->operationConfiguration->{$task}) ?
1032
				$this->operationConfiguration->{$task} : $this->operationConfiguration;
1033
1034
			$model        = $this->triggerFunction('loadModel', $this->elementName, $taskConfiguration);
1035
			$functionName = RApiHalHelper::attributeToString($taskConfiguration, 'functionName', $task);
1036
			$data         = $this->triggerFunction('processPostData', $this->options->get('data', array()), $taskConfiguration);
1037
1038
			$data = $this->triggerFunction('validatePostData', $model, $data, $taskConfiguration);
1039
1040
			if ($data === false)
1041
			{
1042
				// Not Acceptable
1043
				$customError = $this->triggerFunction('createCustomHttpError', 406, $this->apiErrors);
1044
				$this->setStatusCode(406, $customError);
1045
				$this->triggerFunction('displayErrors', $model);
1046
				$this->setData('result', $data);
1047
1048
				return;
1049
			}
1050
1051
			// Prepare parameters for the function
1052
			$args   = $this->buildFunctionArgs($taskConfiguration, $data);
1053
			$result = null;
1054
1055
			// Checks if that method exists in model class and executes it
1056
			if (method_exists($model, $functionName))
1057
			{
1058
				$result = $this->triggerCallFunction($model, $functionName, $args);
1059
			}
1060
			else
1061
			{
1062
				$customError = $this->triggerFunction('createCustomHttpError', 400, $this->apiErrors);
1063
				$this->setStatusCode(400, $customError);
1064
				$this->triggerFunction('displayErrors', $model);
1065
			}
1066
1067
			if (method_exists($model, 'getErrors'))
1068
			{
1069
				$modelErrors = $model->getErrors();
1070
1071
				if (!empty($modelErrors))
1072
				{
1073
					$this->apiErrors = array_merge($this->apiErrors, $modelErrors);
1074
				}
1075
			}
1076
1077
			if (method_exists($model, 'getState'))
1078
			{
1079
				$this->setData('id', $model->getState(strtolower($this->elementName) . '.id'));
1080
			}
1081
		}
1082
1083
		$this->setData('result', $result);
1084
		$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...
1085
	}
1086
1087
	/**
1088
	 * Set document content for List view
1089
	 *
1090
	 * @param   array             $items          List of items
1091
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1092
	 *
1093
	 * @return void
1094
	 */
1095
	public function setForRenderList($items, $configuration)
1096
	{
1097
		// Get resource list from configuration
1098
		$this->loadResourceFromConfiguration($configuration);
1099
1100
		$listResourcesKeys = array_keys($this->resources['listItem']);
1101
1102
		if (!empty($items))
1103
		{
1104
			// Filter out all fields that are not in resource list and apply appropriate transform rules
1105
			foreach ($items as $itemValue)
1106
			{
1107
				$item = ArrayHelper::fromObject($itemValue);
1108
1109
				foreach ($item as $key => $value)
1110
				{
1111
					if (!in_array($key, $listResourcesKeys))
1112
					{
1113
						unset($item[$key]);
1114
						continue;
1115
					}
1116
					else
1117
					{
1118
						$item[$this->assignGlobalValueToResource($key)] = $this->assignValueToResource(
1119
							$this->resources['listItem'][$key], $item
1120
						);
1121
					}
1122
				}
1123
1124
				$embedItem = new RApiHalDocumentResource('item', $item);
1125
				$embedItem = $this->setDataValueToResource($embedItem, $this->resources, $itemValue, 'listItem');
1126
				$this->hal->setEmbedded('item', $embedItem);
1127
			}
1128
		}
1129
	}
1130
1131
	/**
1132
	 * Loads Resource list from configuration file for specific method or task
1133
	 *
1134
	 * @param   RApiHalDocumentResource  $resourceDocument  Resource document for binding the resource
1135
	 * @param   array                    $resources         Configuration for displaying object
1136
	 * @param   mixed                    $data              Data to bind to the resources
1137
	 * @param   string                   $resourceSpecific  Resource specific string that separates resources
1138
	 *
1139
	 * @return RApiHalDocumentResource
1140
	 */
1141
	public function setDataValueToResource($resourceDocument, $resources, $data, $resourceSpecific = 'rcwsGlobal')
1142
	{
1143
		if (!empty($resources[$resourceSpecific]))
1144
		{
1145
			// Add links from the resource
1146
			foreach ($resources[$resourceSpecific] as $resource)
1147
			{
1148
				if (!empty($resource['displayGroup']))
1149
				{
1150
					if ($resource['displayGroup'] == '_links')
1151
					{
1152
						$linkRel = !empty($resource['linkRel']) ? $resource['linkRel'] : $this->assignGlobalValueToResource($resource['displayName']);
1153
1154
						// We will force curries as link array
1155
						$linkPlural = $linkRel == 'curies';
1156
1157
						$resourceDocument->setLink(
1158
							new RApiHalDocumentLink(
1159
								$this->assignValueToResource($resource, $data),
1160
								$linkRel,
1161
								$resource['linkTitle'],
1162
								$this->assignGlobalValueToResource($resource['linkName']),
1163
								$resource['hrefLang'],
1164
								RApiHalHelper::isAttributeTrue($resource, 'linkTemplated')
1165
							), $linkSingular = false, $linkPlural
1166
						);
1167
					}
1168
					else
1169
					{
1170
						$resourceDocument->setDataGrouped(
1171
							$resource['displayGroup'], $this->assignGlobalValueToResource($resource['displayName']), $this->assignValueToResource($resource, $data)
1172
						);
1173
					}
1174
				}
1175
				else
1176
				{
1177
					$resourceDocument->setData($this->assignGlobalValueToResource($resource['displayName']), $this->assignValueToResource($resource, $data));
1178
				}
1179
			}
1180
		}
1181
1182
		return $resourceDocument;
1183
	}
1184
1185
	/**
1186
	 * Loads Resource list from configuration file for specific method or task
1187
	 *
1188
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1189
	 *
1190
	 * @return array
1191
	 */
1192
	public function loadResourceFromConfiguration($configuration)
1193
	{
1194
		if (isset($configuration->resources->resource))
1195
		{
1196
			foreach ($configuration->resources->resource as $resourceXML)
1197
			{
1198
				$resource = RApiHalHelper::getXMLElementAttributes($resourceXML);
1199
1200
				// Filters out specified displayGroup values
1201
				if ($this->options->get('filterOutResourcesGroups') != ''
1202
					&& in_array($resource['displayGroup'], $this->options->get('filterOutResourcesGroups')))
1203
				{
1204
					continue;
1205
				}
1206
1207
				// Filters out if the optional resourceSpecific filter is not the one defined
1208
				if ($this->options->get('filterResourcesSpecific') != ''
1209
					&& $resource['resourceSpecific'] != $this->options->get('filterResourcesSpecific'))
1210
				{
1211
					continue;
1212
				}
1213
1214
				// Filters out if the optional displayName filter is not the one defined
1215
				if ($this->options->get('filterDisplayName') != ''
1216
					&& $resource['displayName'] != $this->options->get('filterDisplayName'))
1217
				{
1218
					continue;
1219
				}
1220
1221
				if (!empty($resourceXML->description))
1222
				{
1223
					$resource['description'] = $resourceXML->description;
1224
				}
1225
1226
				$resource         = RApiHalDocumentResource::defaultResourceField($resource);
1227
				$resourceName     = $resource['displayName'];
1228
				$resourceSpecific = $resource['resourceSpecific'];
1229
1230
				$this->resources[$resourceSpecific][$resourceName] = $resource;
1231
			}
1232
		}
1233
1234
		return $this->resources;
1235
	}
1236
1237
	/**
1238
	 * Resets specific Resource list or all Resources
1239
	 *
1240
	 * @param   string  $resourceSpecific  Resource specific string that separates resources
1241
	 *
1242
	 * @return RApiHalHal
1243
	 */
1244
	public function resetDocumentResources($resourceSpecific = '')
1245
	{
1246
		if (!empty($resourceSpecific))
1247
		{
1248
			if (isset($this->resources[$resourceSpecific]))
1249
			{
1250
				unset($this->resources[$resourceSpecific]);
1251
			}
1252
1253
			return $this;
1254
		}
1255
1256
		$this->resources = array();
1257
1258
		return $this;
1259
	}
1260
1261
	/**
1262
	 * Used for ordering arrays
1263
	 *
1264
	 * @param   string  $a  Current array
1265
	 * @param   string  $b  Next array
1266
	 *
1267
	 * @return RApiHalHal
1268
	 */
1269
	public function sortResourcesByDisplayGroup($a, $b)
1270
	{
1271
		$sort = strcmp($a["displayGroup"], $b["displayGroup"]);
1272
1273
		if (!$sort)
1274
		{
1275
			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...
1276
		}
1277
1278
		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...
1279
	}
1280
1281
	/**
1282
	 * Set document content for Item view
1283
	 *
1284
	 * @param   object|array      $item           Item content
1285
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1286
	 *
1287
	 * @throws Exception
1288
	 * @return void
1289
	 */
1290
	public function setForRenderItem($item, $configuration)
1291
	{
1292
		// Get resource list from configuration
1293
		$this->loadResourceFromConfiguration($configuration);
1294
1295
		if (!empty($item) && (is_array($item) || is_object($item)))
1296
		{
1297
			// Filter out all fields that are not in resource list and apply appropriate transform rules
1298
			foreach ($item as $key => $value)
1299
			{
1300
				$value = !empty($this->resources['rcwsGlobal'][$key]) ? $this->assignValueToResource($this->resources['rcwsGlobal'][$key], $item) : $value;
1301
				$this->setData($this->assignGlobalValueToResource($key), $value);
1302
			}
1303
		}
1304
		else
1305
		{
1306
			// 404 => 'Not found'
1307
			$customError = $this->triggerFunction('createCustomHttpError', 404, $this->apiErrors);
1308
			$this->setStatusCode(404, $customError);
1309
1310
			throw new Exception(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_ERROR_NO_CONTENT'), 404);
1311
		}
1312
	}
1313
1314
	/**
1315
	 * Method to send the application response to the client.  All headers will be sent prior to the main
1316
	 * application output data.
1317
	 *
1318
	 * @return  void
1319
	 *
1320
	 * @since   1.2
1321
	 */
1322
	public function render()
1323
	{
1324
		// Set token to uri if used in that way
1325
		$token  = $this->options->get('accessToken', '');
1326
		$client = $this->options->get('webserviceClient', '');
1327
		$format = $this->options->get('format', 'json');
1328
1329
		if (!empty($token))
1330
		{
1331
			$this->setUriParams(RBootstrap::getConfig('oauth2_token_param_name', 'access_token'), $token);
1332
		}
1333
1334
		if ($client == 'administrator')
1335
		{
1336
			$this->setUriParams('webserviceClient', $client);
1337
		}
1338
1339
		$this->setUriParams('api', 'Hal');
1340
1341
		if ($format == 'doc')
1342
		{
1343
			// This is already in HTML format
1344
			echo $this->documentation;
1345
		}
1346
		else
1347
		{
1348
			$documentOptions    = array(
1349
				'absoluteHrefs' => $this->options->get('absoluteHrefs', false),
1350
				'documentFormat' => $format,
1351
				'uriParams' => $this->uriParams,
1352
			);
1353
			JFactory::$document = new RApiHalDocumentDocument($documentOptions);
1354
1355
			$body = $this->getBody();
1356
			$body = $this->triggerFunction('prepareBody', $body);
1357
1358
			// Push results into the document.
1359
			JFactory::$document
1360
				->setHal($this)
1361
				->setBuffer($body)
1362
				->render(false);
1363
		}
1364
	}
1365
1366
	/**
1367
	 * Method to fill response with requested data
1368
	 *
1369
	 * @return  string  Api call output
1370
	 *
1371
	 * @since   1.2
1372
	 */
1373
	public function getBody()
1374
	{
1375
		return $this->hal;
1376
	}
1377
1378
	/**
1379
	 * Prepares body for response
1380
	 *
1381
	 * @param   string  $message  The return message
1382
	 *
1383
	 * @return  string	The message prepared
1384
	 *
1385
	 * @since   1.2
1386
	 */
1387
	public function prepareBody($message)
1388
	{
1389
		return $message;
1390
	}
1391
1392
	/**
1393
	 * Sets data for resource binding
1394
	 *
1395
	 * @param   string  $key   Rel element
1396
	 * @param   mixed   $data  Data for the resource
1397
	 *
1398
	 * @return RApiHalHal
1399
	 */
1400
	public function setData($key, $data = null)
1401
	{
1402
		if (is_array($key) && null === $data)
0 ignored issues
show
introduced by
The condition is_array($key) is always false.
Loading history...
1403
		{
1404
			foreach ($key as $k => $v)
1405
			{
1406
				$this->data[$k] = $v;
1407
			}
1408
		}
1409
		else
1410
		{
1411
			$this->data[$key] = $data;
1412
		}
1413
1414
		return $this;
1415
	}
1416
1417
	/**
1418
	 * Set the Uri parameters
1419
	 *
1420
	 * @param   string  $uriKey    Uri Key
1421
	 * @param   string  $uriValue  Uri Value
1422
	 *
1423
	 * @return  RApiHalHal      An instance of itself for chaining
1424
	 */
1425
	public function setUriParams($uriKey, $uriValue)
1426
	{
1427
		$this->uriParams[$uriKey] = $uriValue;
1428
1429
		return $this;
1430
	}
1431
1432
	/**
1433
	 * Process posted data from json or object to array
1434
	 *
1435
	 * @param   array             $data           Raw Posted data
1436
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1437
	 *
1438
	 * @return  mixed  Array with posted data.
1439
	 *
1440
	 * @since   1.2
1441
	 */
1442
	public function processPostData($data, $configuration)
1443
	{
1444
		if (is_object($data))
0 ignored issues
show
introduced by
The condition is_object($data) is always false.
Loading history...
1445
		{
1446
			$data = ArrayHelper::fromObject($data);
1447
		}
1448
1449
		if (!is_array($data))
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
1450
		{
1451
			$data = (array) $data;
1452
		}
1453
1454
		if (!empty($data) && !empty($configuration->fields))
1455
		{
1456
			$dataFields = array();
1457
1458
			foreach ($configuration->fields->field as $field)
1459
			{
1460
				$fieldAttributes                 = RApiHalHelper::getXMLElementAttributes($field);
1461
				$fieldAttributes['transform']    = !is_null($fieldAttributes['transform']) ? $fieldAttributes['transform'] : 'string';
1462
				$fieldAttributes['defaultValue'] = !is_null($fieldAttributes['defaultValue'])
1463
					&& !RApiHalHelper::isAttributeTrue($fieldAttributes, 'isPrimaryField') ? $fieldAttributes['defaultValue'] : '';
1464
1465
				// If field is not sent through Request
1466
				if (!isset($data[$fieldAttributes['name']]))
1467
				{
1468
					// We will populate missing fields with null value
1469
					$data[$fieldAttributes['name']] = null;
1470
1471
					// We will populate value with default value if the field is not set for create operation
1472
					if ($this->operation == 'create')
1473
					{
1474
						$data[$fieldAttributes['name']] = $fieldAttributes['defaultValue'];
1475
					}
1476
				}
1477
1478
				if (!is_null($data[$fieldAttributes['name']]))
1479
				{
1480
					$data[$fieldAttributes['name']] = $this->transformField($fieldAttributes['transform'], $data[$fieldAttributes['name']], false);
1481
				}
1482
1483
				$dataFields[$fieldAttributes['name']] = $data[$fieldAttributes['name']];
1484
			}
1485
1486
			if (RApiHalHelper::isAttributeTrue($configuration, 'strictFields'))
1487
			{
1488
				$data = $dataFields;
1489
			}
1490
		}
1491
1492
		// Common functions are not checking this field so we will
1493
		$data['params']       = isset($data['params']) ? $data['params'] : null;
1494
		$data['associations'] = isset($data['associations']) ? $data['associations'] : array();
1495
1496
		return $data;
1497
	}
1498
1499
	/**
1500
	 * Validates posted data
1501
	 *
1502
	 * @param   object            $model          Model
1503
	 * @param   array             $data           Raw Posted data
1504
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1505
	 *
1506
	 * @return  mixed  Array with posted data or false.
1507
	 *
1508
	 * @since   1.3
1509
	 */
1510
	public function validatePostData($model, $data, $configuration)
1511
	{
1512
		$data = (array) $data;
1513
		$app  = JFactory::getApplication();
1514
1515
		// We are checking required fields set in webservice XMLs
1516
		if (!$this->checkRequiredFields($data, $configuration))
1517
		{
1518
			return false;
1519
		}
1520
1521
		$validateMethod = strtolower(RApiHalHelper::attributeToString($configuration, 'validateData', 'none'));
1522
1523
		if ($validateMethod == 'none')
1524
		{
1525
			return $data;
1526
		}
1527
1528
		if ($validateMethod == 'form')
1529
		{
1530
			if (method_exists($model, 'getForm'))
1531
			{
1532
				// Validate the posted data.
1533
				// Sometimes the form needs some posted data, such as for plugins and modules.
1534
				$form = $model->getForm($data, false);
1535
1536
				if (!$form)
1537
				{
1538
					return $data;
1539
				}
1540
1541
				// Test whether the data is valid.
1542
				$validData = $model->validate($form, $data);
1543
1544
				// Common functions are not checking this field so we will
1545
				$validData['params']       = isset($validData['params']) ? $validData['params'] : null;
1546
				$validData['associations'] = isset($validData['associations']) ? $validData['associations'] : array();
1547
1548
				return $validData;
1549
			}
1550
1551
			$app->enqueueMessage(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_FUNCTION_DONT_EXIST'), 'error');
1552
1553
			return false;
1554
		}
1555
1556
		if ($validateMethod == 'function')
1557
		{
1558
			$validateMethod = strtolower(RApiHalHelper::attributeToString($configuration, 'validateDataFunction', 'validate'));
1559
1560
			if (method_exists($model, $validateMethod))
1561
			{
1562
				$result = $model->{$validateMethod}($data);
1563
1564
				return $result;
1565
			}
1566
1567
			$app->enqueueMessage(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_FUNCTION_DONT_EXIST'), 'error');
1568
1569
			return false;
1570
		}
1571
1572
		return false;
1573
	}
1574
1575
	/**
1576
	 * Validates posted data
1577
	 *
1578
	 * @param   array             $data           Raw Posted data
1579
	 * @param   SimpleXMLElement  $configuration  Configuration for displaying object
1580
	 *
1581
	 * @return  mixed  Array with posted data or false.
1582
	 *
1583
	 * @since   1.3
1584
	 */
1585
	public function checkRequiredFields($data, $configuration)
1586
	{
1587
		$errors = array();
1588
1589
		if (!empty($configuration->fields))
1590
		{
1591
			foreach ($configuration->fields->field as $field)
1592
			{
1593
				if (RApiHalHelper::isAttributeTrue($field, 'isRequiredField'))
1594
				{
1595
					if (is_null($data[(string) $field['name']]) || $data[(string) $field['name']] === '')
1596
					{
1597
						JFactory::getApplication()->enqueueMessage(
1598
							JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_ERROR_REQUIRED_FIELD', (string) $field['name']), 'error'
1599
						);
1600
1601
						$errors[] = JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_ERROR_REQUIRED_FIELD', (string) $field['name']);
1602
					}
1603
				}
1604
			}
1605
		}
1606
1607
		if (!empty($errors))
1608
		{
1609
			$this->apiErrors = array_merge($this->apiErrors, $errors);
1610
1611
			return false;
1612
		}
1613
1614
		return true;
1615
	}
1616
1617
	/**
1618
	 * Checks if operation is allowed from the configuration file
1619
	 *
1620
	 * @return boolean
1621
	 *
1622
	 * @throws  Exception
1623
	 */
1624
	public function isOperationAllowed()
1625
	{
1626
		// Check if webservice is published
1627
		if (!RApiHalHelper::isPublishedWebservice($this->client, $this->webserviceName, $this->webserviceVersion) && !empty($this->webserviceName))
1628
		{
1629
			throw new Exception(JText::sprintf('LIB_REDCORE_API_HAL_WEBSERVICE_IS_UNPUBLISHED', $this->webserviceName));
1630
		}
1631
1632
		// Check for allowed operations
1633
		$allowedOperations = $this->getConfig('operations');
1634
1635
		if (!isset($allowedOperations->{$this->operation}))
1636
		{
1637
			$customError = $this->triggerFunction('createCustomHttpError', 405, $this->apiErrors);
1638
			$this->setStatusCode(405, $customError);
1639
1640
			return false;
1641
		}
1642
1643
		$scope                    = $this->operation;
1644
		$authorizationGroups      = !empty($allowedOperations->{$this->operation}['authorization']) ?
1645
			(string) $allowedOperations->{$this->operation}['authorization'] : '';
1646
		$terminateIfNotAuthorized = true;
1647
1648
		if ($this->operation == 'task')
1649
		{
1650
			$task   = $this->options->get('task', '');
1651
			$scope .= '.' . $task;
1652
1653
			if (!isset($allowedOperations->task->{$task}))
1654
			{
1655
				$customError = $this->triggerFunction('createCustomHttpError', 405, $this->apiErrors);
1656
				$this->setStatusCode(405, $customError);
1657
1658
				return false;
1659
			}
1660
1661
			$authorizationGroups = !empty($allowedOperations->task->{$task}['authorization']) ?
1662
				(string) $allowedOperations->task->{$task}['authorization'] : '';
1663
1664
			if (isset($allowedOperations->task->{$task}['authorizationNeeded'])
1665
				&& strtolower($allowedOperations->task->{$task}['authorizationNeeded']) == 'false')
1666
			{
1667
				$terminateIfNotAuthorized = false;
1668
			}
1669
		}
1670
		elseif ($this->operation == 'read')
1671
		{
1672
			// Disable authorization on operation read level
1673
			if (isset($allowedOperations->{$this->operation}['authorizationNeeded'])
1674
				&& strtolower($allowedOperations->{$this->operation}['authorizationNeeded']) == 'false')
1675
			{
1676
				$terminateIfNotAuthorized = false;
1677
			}
1678
			else
1679
			{
1680
				$primaryKeys = array();
1681
				$isReadItem  = $this->apiFillPrimaryKeys($primaryKeys);
1682
				$readType    = $isReadItem ? 'item' : 'list';
1683
1684
				if (isset($allowedOperations->read->{$readType}['authorizationNeeded'])
1685
					&& strtolower($allowedOperations->read->{$readType}['authorizationNeeded']) == 'false')
1686
				{
1687
					$terminateIfNotAuthorized = false;
1688
				}
1689
			}
1690
		}
1691
		elseif (isset($allowedOperations->{$this->operation}['authorizationNeeded'])
1692
			&& strtolower($allowedOperations->{$this->operation}['authorizationNeeded']) == 'false')
1693
		{
1694
			$terminateIfNotAuthorized = false;
1695
		}
1696
1697
		// Does user have permission
1698
		// OAuth2 check
1699
		if ($this->authorizationCheck == 'oauth2')
1700
		{
1701
			// Use scopes to authorize
1702
			$scope = array(strtolower($this->client . '.' . $this->webserviceName . '.' . $scope));
1703
1704
			// Add in Global scope check
1705
			$scope[] = $this->client . '.' . $this->operation;
1706
1707
			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

1707
			return $this->isAuthorized(/** @scrutinizer ignore-type */ $scope, $terminateIfNotAuthorized) || !$terminateIfNotAuthorized;
Loading history...
1708
		}
1709
		// Joomla check
1710
		elseif ($this->authorizationCheck == 'joomla')
1711
		{
1712
			$isAuthorized = $this->isAuthorized($scope = null, $terminateIfNotAuthorized);
1713
1714
			// Use Joomla to authorize
1715
			if ($isAuthorized && $terminateIfNotAuthorized && !empty($authorizationGroups))
1716
			{
1717
				$authorizationGroups = explode(',', $authorizationGroups);
1718
				$authorized          = false;
1719
				$configAssetName     = !empty($this->configuration->config->authorizationAssetName) ?
1720
					(string) $this->configuration->config->authorizationAssetName : null;
1721
1722
				foreach ($authorizationGroups as $authorizationGroup)
1723
				{
1724
					$authorization = explode(':', trim($authorizationGroup));
1725
					$action        = $authorization[0];
1726
					$assetName     = !empty($authorization[1]) ? $authorization[1] : $configAssetName;
1727
1728
					if (JFactory::getUser()->authorise(trim($action), trim($assetName)))
1729
					{
1730
						$authorized = true;
1731
						break;
1732
					}
1733
				}
1734
1735
				if (!$authorized)
1736
				{
1737
					$customError = $this->triggerFunction('createCustomHttpError', 405, $this->apiErrors);
1738
					$this->setStatusCode(405, $customError);
1739
1740
					return false;
1741
				}
1742
			}
1743
1744
			return $isAuthorized || !$terminateIfNotAuthorized;
1745
		}
1746
1747
		return false;
1748
	}
1749
1750
	/**
1751
	 * Log-in client if successful or terminate api if not authorized
1752
	 *
1753
	 * @param   string  $scope                     Name of the scope to test against
1754
	 * @param   bool    $terminateIfNotAuthorized  Terminate api if client is not authorized
1755
	 *
1756
	 * @throws Exception
1757
	 * @return  boolean
1758
	 *
1759
	 * @since   1.2
1760
	 */
1761
	public function isAuthorized($scope, $terminateIfNotAuthorized)
1762
	{
1763
		$authorized = false;
1764
		JFactory::getApplication()->triggerEvent('RApiHalBeforeIsAuthorizedCheck',
1765
			array($scope, $terminateIfNotAuthorized, $this->options, $this->authorizationCheck, &$authorized)
1766
		);
1767
1768
		if ($authorized)
0 ignored issues
show
introduced by
The condition $authorized is always false.
Loading history...
1769
		{
1770
			return $authorized;
1771
		}
1772
1773
		// OAuth2 check
1774
		if ($this->authorizationCheck == 'oauth2')
1775
		{
1776
			/** @var $response OAuth2\Response */
1777
			$response = RApiOauth2Helper::verifyResourceRequest($scope);
1778
1779
			if ($response instanceof OAuth2\Response)
0 ignored issues
show
introduced by
$response is always a sub-type of OAuth2\Response.
Loading history...
1780
			{
1781
				if (!$response->isSuccessful() && $terminateIfNotAuthorized)
1782
				{
1783
					// OAuth2 Server response is in fact correct output for errors
1784
					$response->send($this->options->get('format', 'json'));
1785
1786
					JFactory::getApplication()->close();
1787
				}
1788
			}
1789
			elseif ($response === false && $terminateIfNotAuthorized)
1790
			{
1791
				throw new Exception(JText::_('LIB_REDCORE_API_OAUTH2_SERVER_IS_NOT_ACTIVE'));
1792
			}
1793
			else
1794
			{
1795
				$response = json_decode($response);
1796
1797
				if (!empty($response->user_id))
1798
				{
1799
					$user = JFactory::getUser($response->user_id);
1800
1801
					// Load the JUser class on application for this client
1802
					JFactory::getApplication()->loadIdentity($user);
1803
					JFactory::getSession()->set('user', $user);
1804
1805
					return true;
1806
				}
1807
1808
				// We will use this only for authorizations with no actual users. This is used for reading of the data.
1809
				if (isset($response->success) && $response->success === true)
1810
				{
1811
					return true;
1812
				}
1813
1814
				$authorized = false || !$terminateIfNotAuthorized;
1815
			}
1816
		}
1817
		// Joomla check through Basic Authentication
1818
		elseif ($this->authorizationCheck == 'joomla')
1819
		{
1820
			// Get username and password from globals
1821
			$credentials = RApiHalHelper::getCredentialsFromGlobals();
1822
1823
			$authorized = RUser::userLogin($credentials) || !$terminateIfNotAuthorized;
1824
		}
1825
1826
		if (!$authorized && $terminateIfNotAuthorized)
1827
		{
1828
			$customError = $this->triggerFunction('createCustomHttpError', 401, $this->apiErrors);
1829
			$this->setStatusCode(401, $customError);
1830
		}
1831
1832
		return $authorized || !$terminateIfNotAuthorized;
1833
	}
1834
1835
	/**
1836
	 * Gets instance of helper object class if exists
1837
	 *
1838
	 * @return  mixed It will return Api helper class or false if it does not exists
1839
	 *
1840
	 * @since   1.2
1841
	 */
1842
	public function getHelperObject()
1843
	{
1844
		if (!empty($this->apiHelperClass))
1845
		{
1846
			return $this->apiHelperClass;
1847
		}
1848
1849
		$helperFile = RApiHalHelper::getWebserviceFile(
1850
			$this->client, strtolower($this->webserviceName), $this->webserviceVersion, 'php', $this->webservicePath
1851
		);
1852
1853
		if (file_exists($helperFile))
1854
		{
1855
			require_once $helperFile;
1856
		}
1857
1858
		$webserviceName  = preg_replace('/[^A-Z0-9_\.]/i', '', $this->webserviceName);
1859
		$helperClassName = 'RApiHalHelper' . ucfirst($this->client) . ucfirst(strtolower($webserviceName));
1860
1861
		if (class_exists($helperClassName))
1862
		{
1863
			$this->apiHelperClass = new $helperClassName;
1864
		}
1865
1866
		return $this->apiHelperClass;
1867
	}
1868
1869
	/**
1870
	 * Gets instance of dynamic model object class (for table bind)
1871
	 *
1872
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
1873
	 *
1874
	 * @return mixed It will return Api dynamic model class
1875
	 *
1876
	 * @throws Exception
1877
	 * @since   1.3
1878
	 */
1879
	public function getDynamicModelObject($configuration)
1880
	{
1881
		if (!empty($this->apiDynamicModelClass))
1882
		{
1883
			return $this->apiDynamicModelClass;
1884
		}
1885
1886
		$tableName = RApiHalHelper::attributeToString($configuration, 'tableName', '');
1887
1888
		if (empty($tableName))
1889
		{
1890
			throw new Exception(JText::_('LIB_REDCORE_API_HAL_WEBSERVICE_TABLE_NAME_NOT_SET'));
1891
		}
1892
1893
		$context = $this->webserviceName . '.' . $this->webserviceVersion;
1894
1895
		// We are not using prefix like str_replace(array('.', '-'), array('_', '_'), $context) . '_';
1896
		$paginationPrefix = '';
1897
		$filterFields     = RApiHalHelper::getFilterFields($configuration);
1898
		$primaryFields    = $this->getPrimaryFields($configuration);
1899
		$fields           = $this->getAllFields($configuration);
1900
1901
		$config = array(
1902
			'tableName' => $tableName,
1903
			'context'   => $context,
1904
			'paginationPrefix' => $paginationPrefix,
1905
			'filterFields' => $filterFields,
1906
			'primaryFields' => $primaryFields,
1907
			'fields' => $fields,
1908
		);
1909
1910
		$apiDynamicModelClassName = $this->apiDynamicModelClassName;
1911
1912
		if (class_exists($apiDynamicModelClassName))
1913
		{
1914
			$this->apiDynamicModelClass = new $apiDynamicModelClassName($config);
1915
		}
1916
1917
		return $this->apiDynamicModelClass;
1918
	}
1919
1920
	/**
1921
	 * Gets list of primary fields from operation configuration
1922
	 *
1923
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
1924
	 *
1925
	 * @return  array
1926
	 *
1927
	 * @since   1.3
1928
	 */
1929
	public function getPrimaryFields($configuration)
1930
	{
1931
		$primaryFields = array();
1932
1933
		if (!empty($configuration->fields))
1934
		{
1935
			foreach ($configuration->fields->field as $field)
1936
			{
1937
				$isPrimaryField = RApiHalHelper::isAttributeTrue($field, 'isPrimaryField');
1938
1939
				if ($isPrimaryField)
1940
				{
1941
					$primaryFields[] = (string) $field["name"];
1942
				}
1943
			}
1944
		}
1945
1946
		return $primaryFields;
1947
	}
1948
1949
	/**
1950
	 * Gets list of all fields from operation configuration
1951
	 *
1952
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
1953
	 *
1954
	 * @return  array
1955
	 *
1956
	 * @since   1.3
1957
	 */
1958
	public function getAllFields($configuration)
1959
	{
1960
		$fields = array();
1961
1962
		if (!empty($configuration->fields))
1963
		{
1964
			foreach ($configuration->fields->field as $field)
1965
			{
1966
				$fieldAttributes = RApiHalHelper::getXMLElementAttributes($field);
1967
				$fields[]        = $fieldAttributes;
1968
			}
1969
		}
1970
1971
		return $fields;
1972
	}
1973
1974
	/**
1975
	 * Load model class for data manipulation
1976
	 *
1977
	 * @param   string            $elementName    Element name
1978
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
1979
	 *
1980
	 * @return  mixed  Model class for data manipulation
1981
	 *
1982
	 * @since   1.2
1983
	 */
1984
	public function loadModel($elementName, $configuration)
1985
	{
1986
		$this->setOptionViewName($elementName, $configuration);
1987
		$isAdmin = RApiHalHelper::isAttributeTrue($configuration, 'isAdminClass');
1988
		$this->addModelIncludePaths($isAdmin, $this->optionName);
1989
		$this->loadExtensionLanguage($this->optionName, $isAdmin ? JPATH_ADMINISTRATOR : JPATH_SITE);
1990
		$this->triggerFunction('loadExtensionLibrary', $this->optionName);
1991
		$dataMode = strtolower(RApiHalHelper::attributeToString($configuration, 'dataMode', 'model'));
1992
1993
		if ($dataMode == 'helper')
1994
		{
1995
			return $this->getHelperObject();
1996
		}
1997
1998
		if ($dataMode == 'table')
1999
		{
2000
			return $this->getDynamicModelObject($configuration);
2001
		}
2002
2003
		if (!empty($configuration['modelClassName']))
2004
		{
2005
			$modelClass = (string) $configuration['modelClassName'];
2006
2007
			if (!empty($configuration['modelClassPath']))
2008
			{
2009
				require_once JPATH_SITE . '/' . $configuration['modelClassPath'];
2010
2011
				if (class_exists($modelClass))
2012
				{
2013
					return new $modelClass;
2014
				}
2015
			}
2016
			else
2017
			{
2018
				$componentName = ucfirst(strtolower(substr($this->optionName, 4)));
2019
				$prefix        = $componentName . 'Model';
2020
2021
				$model = RModel::getInstance($modelClass, $prefix);
2022
2023
				if ($model)
2024
				{
2025
					return $model;
2026
				}
2027
			}
2028
		}
2029
2030
		if (!empty($this->viewName))
2031
		{
2032
			$elementName = $this->viewName;
2033
		}
2034
2035
		if ($isAdmin)
2036
		{
2037
			return RModel::getAdminInstance($elementName, array(), $this->optionName);
2038
		}
2039
2040
		return RModel::getFrontInstance($elementName, array(), $this->optionName);
2041
	}
2042
2043
	/**
2044
	 * Add include paths for model class
2045
	 *
2046
	 * @param   boolean  $isAdmin     Is client admin or site
2047
	 * @param   string   $optionName  Option name
2048
	 *
2049
	 * @return  void
2050
	 *
2051
	 * @since   1.3
2052
	 */
2053
	public function addModelIncludePaths($isAdmin, $optionName)
2054
	{
2055
		if ($isAdmin)
2056
		{
2057
			$this->loadExtensionLanguage($optionName, JPATH_ADMINISTRATOR);
2058
			$path = JPATH_ADMINISTRATOR . '/components/' . $optionName;
2059
			RModel::addIncludePath($path . '/models');
2060
			JTable::addIncludePath($path . '/tables');
2061
			RForm::addFormPath($path . '/models/forms');
2062
			RForm::addFieldPath($path . '/models/fields');
2063
		}
2064
		else
2065
		{
2066
			$this->loadExtensionLanguage($optionName);
2067
			$path = JPATH_SITE . '/components/' . $optionName;
2068
			RModel::addIncludePath($path . '/models');
2069
			JTable::addIncludePath($path . '/tables');
2070
			JTable::addIncludePath(JPATH_ADMINISTRATOR . '/components/' . $optionName . '/tables');
2071
			RForm::addFormPath($path . '/models/forms');
2072
			RForm::addFieldPath($path . '/models/fields');
2073
		}
2074
2075
		if (!defined('JPATH_COMPONENT'))
2076
		{
2077
			define('JPATH_COMPONENT', $path);
2078
		}
2079
	}
2080
2081
	/**
2082
	 * Include library classes
2083
	 *
2084
	 * @param   string  $element  Option name
2085
	 *
2086
	 * @return  void
2087
	 *
2088
	 * @since   1.4
2089
	 */
2090
	public function loadExtensionLibrary($element)
2091
	{
2092
		$element = strpos($element, 'com_') === 0 ? substr($element, 4) : $element;
2093
		JLoader::import(strtolower($element) . '.library');
2094
	}
2095
2096
	/**
2097
	 * Sets option and view name
2098
	 *
2099
	 * @param   string            $elementName    Element name
2100
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
2101
	 *
2102
	 * @return  void
2103
	 *
2104
	 * @since   1.3
2105
	 */
2106
	public function setOptionViewName($elementName, $configuration)
2107
	{
2108
		// Views are separated by dash
2109
		$view        = explode('-', $elementName);
2110
		$elementName = $view[0];
2111
		$viewName    = '';
2112
2113
		if (!empty($view[1]))
2114
		{
2115
			$viewName = $view[1];
2116
		}
2117
2118
		$optionName = !empty($configuration['optionName']) ? $configuration['optionName'] : $elementName;
2119
2120
		// Add com_ to the element name if not exist
2121
		$optionName = (strpos($optionName, 'com_') === 0 ? '' : 'com_') . $optionName;
2122
2123
		$this->optionName = $optionName;
2124
		$this->viewName   = $viewName;
2125
2126
		// We add separate view and option name if they were merged
2127
		if (!empty($viewName))
2128
		{
2129
			$input = JFactory::getApplication()->input;
2130
			$input->set('option', $optionName);
2131
			$input->set('view', $viewName);
2132
		}
2133
	}
2134
2135
	/**
2136
	 * Checks if operation is allowed from the configuration file
2137
	 *
2138
	 * @param   string  $path  Path to the configuration setting
2139
	 *
2140
	 * @return mixed May return single value or array
2141
	 */
2142
	public function getConfig($path = '')
2143
	{
2144
		$path          = explode('.', $path);
2145
		$configuration = $this->configuration;
2146
2147
		foreach ($path as $pathInstance)
2148
		{
2149
			if (isset($configuration->{$pathInstance}))
2150
			{
2151
				$configuration = $configuration->{$pathInstance};
2152
			}
2153
		}
2154
2155
		return is_string($configuration) ? (string) $configuration : $configuration;
2156
	}
2157
2158
	/**
2159
	 * Gets errors from model and places it into Application message queue
2160
	 *
2161
	 * @param   object  $model  Model
2162
	 *
2163
	 * @return void
2164
	 */
2165
	public function displayErrors($model)
2166
	{
2167
		if (method_exists($model, 'getErrors'))
2168
		{
2169
			$app = JFactory::getApplication();
2170
2171
			// Get the validation messages.
2172
			$errors = $model->getErrors();
2173
2174
			// Push up all validation messages out to the user.
2175
			for ($i = 0, $n = count($errors); $i < $n; $i++)
2176
			{
2177
				if ($errors[$i] instanceof Exception)
2178
				{
2179
					$app->enqueueMessage($errors[$i]->getMessage(), 'error');
2180
				}
2181
				else
2182
				{
2183
					$app->enqueueMessage($errors[$i], 'error');
2184
				}
2185
			}
2186
		}
2187
	}
2188
2189
	/**
2190
	 * Assign value to Resource
2191
	 *
2192
	 * @param   array   $resource   Resource list with options
2193
	 * @param   mixed   $value      Data values to set to resource format
2194
	 * @param   string  $attribute  Attribute from array to replace the data
2195
	 *
2196
	 * @return  string
2197
	 *
2198
	 * @since   1.2
2199
	 */
2200
	public function assignValueToResource($resource, $value, $attribute = 'fieldFormat')
2201
	{
2202
		$format    = $resource[$attribute];
2203
		$transform = RApiHalHelper::attributeToString($resource, 'transform', '');
2204
2205
		// Filters out the complex SOAP arrays, to treat them as regular arrays
2206
		if (preg_match('/^array\[(.+)\]$/im', $transform))
2207
		{
2208
			$transform = 'array';
2209
		}
2210
2211
		$stringsToReplace = array();
2212
		preg_match_all('/\{([^}]+)\}/', $format, $stringsToReplace);
2213
2214
		if (!empty($stringsToReplace[1]))
2215
		{
2216
			foreach ($stringsToReplace[1] as $replacementKey)
2217
			{
2218
				if (is_object($value))
2219
				{
2220
					if (property_exists($value, $replacementKey))
2221
					{
2222
						// We are transforming only value
2223
						if ($format == '{' . $replacementKey . '}')
2224
						{
2225
							$format = $this->transformField($transform, $value->{$replacementKey});
2226
						}
2227
						// We are transforming only part of the string
2228
						else
2229
						{
2230
							$format = str_replace('{' . $replacementKey . '}', $this->transformField($transform, $value->{$replacementKey}), $format);
2231
						}
2232
					}
2233
				}
2234
				elseif (is_array($value))
2235
				{
2236
					if (isset($value[$replacementKey]))
2237
					{
2238
						// We are transforming only value
2239
						if ($format == '{' . $replacementKey . '}')
2240
						{
2241
							$format = $this->transformField($transform, $value[$replacementKey]);
2242
						}
2243
						// We are transforming only part of the string
2244
						else
2245
						{
2246
							$format = str_replace('{' . $replacementKey . '}', $this->transformField($transform, $value[$replacementKey]), $format);
2247
						}
2248
					}
2249
				}
2250
				else
2251
				{
2252
					// We are transforming only value
2253
					if ($format == '{' . $replacementKey . '}')
2254
					{
2255
						$format = $this->transformField($transform, $value);
2256
					}
2257
					// We are transforming only part of the string
2258
					else
2259
					{
2260
						$format = str_replace('{' . $replacementKey . '}', $this->transformField($transform, $value), $format);
2261
					}
2262
				}
2263
			}
2264
		}
2265
2266
		// We replace global data as well
2267
		$format = $this->assignGlobalValueToResource($format);
2268
2269
		if (!empty($stringsToReplace[1]))
2270
		{
2271
			// If we did not found data with that resource we will set it to 0, except for linkRel which is a documentation template
2272
			if ($format === $resource[$attribute] && $resource['linkRel'] != 'curies')
2273
			{
2274
				$format = null;
2275
			}
2276
		}
2277
2278
		return $format;
2279
	}
2280
2281
	/**
2282
	 * Assign value to Resource
2283
	 *
2284
	 * @param   string  $format  String to parse
2285
	 *
2286
	 * @return  string
2287
	 *
2288
	 * @since   1.2
2289
	 */
2290
	public function assignGlobalValueToResource($format)
2291
	{
2292
		if (empty($format) || !is_string($format))
2293
		{
2294
			return $format;
2295
		}
2296
2297
		$stringsToReplace = array();
2298
		preg_match_all('/\{([^}]+)\}/', $format, $stringsToReplace);
2299
2300
		if (!empty($stringsToReplace[1]))
2301
		{
2302
			foreach ($stringsToReplace[1] as $replacementKey)
2303
			{
2304
				// Replace from global variables if present
2305
				if (isset($this->data[$replacementKey]))
2306
				{
2307
					// We are transforming only value
2308
					if ($format == '{' . $replacementKey . '}')
2309
					{
2310
						$format = $this->data[$replacementKey];
2311
					}
2312
					// We are transforming only part of the string
2313
					else
2314
					{
2315
						$format = str_replace('{' . $replacementKey . '}', $this->data[$replacementKey], $format);
2316
					}
2317
				}
2318
			}
2319
		}
2320
2321
		return $format;
2322
	}
2323
2324
	/**
2325
	 * Get the name of the transform class for a given field type.
2326
	 *
2327
	 * First looks for the transform class in the /transform directory
2328
	 * in the same directory as the web service file.  Then looks
2329
	 * for it in the /api/transform directory.
2330
	 *
2331
	 * @param   string  $fieldType  Field type.
2332
	 *
2333
	 * @return string  Transform class name.
2334
	 */
2335
	private function getTransformClass($fieldType)
2336
	{
2337
		$fieldType = !empty($fieldType) ? $fieldType : 'string';
2338
2339
		// Cache for the class names.
2340
		static $classNames = array();
2341
2342
		// If we already know the class name, just return it.
2343
		if (isset($classNames[$fieldType]))
2344
		{
2345
			return $classNames[$fieldType];
2346
		}
2347
2348
		// Construct the name of the class to do the transform (default is RApiHalTransformString).
2349
		$className = 'RApiHalTransform' . ucfirst($fieldType);
2350
2351
		if (class_exists($className))
2352
		{
2353
			$classInstance = new $className;
2354
2355
			// Cache it for later.
2356
			$classNames[$fieldType] = $classInstance;
2357
2358
			return $classNames[$fieldType];
2359
		}
2360
2361
		return $this->getTransformClass('string');
2362
	}
2363
2364
	/**
2365
	 * Transform a source field data value.
2366
	 *
2367
	 * Calls the static toExternal method of a transform class.
2368
	 *
2369
	 * @param   string   $fieldType          Field type.
2370
	 * @param   string   $definition         Field definition.
2371
	 * @param   boolean  $directionExternal  Transform direction
2372
	 *
2373
	 * @return mixed Transformed data.
2374
	 */
2375
	public function transformField($fieldType, $definition, $directionExternal = true)
2376
	{
2377
		// Get the transform class name.
2378
		$className = $this->getTransformClass($fieldType);
2379
2380
		// Execute the transform.
2381
		if ($className instanceof RApiHalTransformInterface)
0 ignored issues
show
introduced by
$className is never a sub-type of RApiHalTransformInterface.
Loading history...
2382
		{
2383
			return $directionExternal ? $className::toExternal($definition) : $className::toInternal($definition);
2384
		}
2385
		else
2386
		{
2387
			return $definition;
2388
		}
2389
	}
2390
2391
	/**
2392
	 * Calls method from helper file if exists or method from this class,
2393
	 * Additionally it Triggers plugin call for specific function in a format RApiHalFunctionName
2394
	 *
2395
	 * @param   string  $functionName  Field type.
2396
	 *
2397
	 * @return mixed Result from callback function
2398
	 */
2399
	public function triggerFunction($functionName)
2400
	{
2401
		$apiHelperClass = $this->getHelperObject();
2402
		$args           = func_get_args();
2403
2404
		// Remove function name from arguments
2405
		array_shift($args);
2406
2407
		// PHP 5.3 workaround
2408
		$temp = array();
2409
2410
		foreach ($args as &$arg)
2411
		{
2412
			$temp[] = &$arg;
2413
		}
2414
2415
		// We will add this instance of the object as last argument for manipulation in plugin and helper
2416
		$temp[] = &$this;
2417
		$return = null;
2418
2419
		$result = JFactory::getApplication()->triggerEvent('RApiHalBefore' . $functionName, array($functionName, $temp, &$return));
2420
2421
		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...
2422
		{
2423
			if ($return !== null)
2424
			{
2425
				return $return;
2426
			}
2427
			else
2428
			{
2429
				return $result;
2430
			}
2431
		}
2432
2433
		// Checks if that method exists in helper file and executes it
2434
		if (method_exists($apiHelperClass, $functionName))
2435
		{
2436
			$result = call_user_func_array(array($apiHelperClass, $functionName), $temp);
2437
		}
2438
		else
2439
		{
2440
			$result = call_user_func_array(array($this, $functionName), $temp);
2441
		}
2442
2443
		JFactory::getApplication()->triggerEvent('RApiHal' . $functionName, $temp);
2444
2445
		return $result;
2446
	}
2447
2448
	/**
2449
	 * Calls method from defined object as some Joomla methods require referenced parameters
2450
	 *
2451
	 * @param   object  $object        Object to run function on
2452
	 * @param   string  $functionName  Function name
2453
	 * @param   array   $args          Arguments for the function
2454
	 *
2455
	 * @return mixed Result from callback function
2456
	 */
2457
	public function triggerCallFunction($object, $functionName, $args)
2458
	{
2459
		switch (count($args))
2460
		{
2461
			case 0:
2462
				return $object->{$functionName}();
2463
			case 1:
2464
				return $object->{$functionName}($args[0]);
2465
			case 2:
2466
				return $object->{$functionName}($args[0], $args[1]);
2467
			case 3:
2468
				return $object->{$functionName}($args[0], $args[1], $args[2]);
2469
			case 4:
2470
				return $object->{$functionName}($args[0], $args[1], $args[2], $args[3]);
2471
			case 5:
2472
				return $object->{$functionName}($args[0], $args[1], $args[2], $args[3], $args[4]);
2473
			default:
2474
				return call_user_func_array(array($object, $functionName), $args);
2475
		}
2476
	}
2477
2478
	/**
2479
	 * Get all defined fields and transform them if needed to expected format. Then it puts it into array for function call
2480
	 *
2481
	 * @param   SimpleXMLElement  $configuration  Configuration for current action
2482
	 * @param   array             $data           List of posted data
2483
	 *
2484
	 * @return array List of parameters to pass to the function
2485
	 */
2486
	public function buildFunctionArgs($configuration, $data)
2487
	{
2488
		$args = array();
2489
2490
		if (!empty($configuration['functionArgs']))
2491
		{
2492
			$functionArgs = explode(',', (string) $configuration['functionArgs']);
2493
2494
			foreach ($functionArgs as $functionArg)
2495
			{
2496
				$parameter = explode('{', $functionArg);
2497
2498
				// First field is the name of the data field and second is transformation
2499
				$parameter[0] = trim($parameter[0]);
2500
				$parameter[1] = !empty($parameter[1]) ? strtolower(trim(str_replace('}', '', $parameter[1]))) : 'string';
2501
2502
				// If we set argument to value, then it will not be transformed, instead we will take field name as a value
2503
				if ($parameter[1] == 'value')
2504
				{
2505
					$parameterValue = $parameter[0];
2506
				}
2507
				else
2508
				{
2509
					if (isset($data[$parameter[0]]))
2510
					{
2511
						$parameterValue = $this->transformField($parameter[1], $data[$parameter[0]]);
2512
					}
2513
					else
2514
					{
2515
						$parameterValue = null;
2516
					}
2517
				}
2518
2519
				$args[] = $parameterValue;
2520
			}
2521
		}
2522
		else
2523
		{
2524
			$args[] = $data;
2525
		}
2526
2527
		return $args;
2528
	}
2529
2530
	/**
2531
	 * We set filters and List parameters to the model object
2532
	 *
2533
	 * @param   object  &$model  Model object
2534
	 *
2535
	 * @return  array
2536
	 */
2537
	public function assignFiltersList(&$model)
2538
	{
2539
		if (method_exists($model, 'getState'))
2540
		{
2541
			// To initialize populateState
2542
			$model->getState();
2543
		}
2544
2545
		$dataGet = $this->options->get('dataGet', array());
2546
2547
		if (is_object($dataGet))
2548
		{
2549
			$dataGet = ArrayHelper::fromObject($dataGet);
2550
		}
2551
2552
		$limitField      = 'limit';
2553
		$limitStartField = 'limitstart';
2554
2555
		if (method_exists($model, 'get'))
2556
		{
2557
			// RedCORE limit fields
2558
			$limitField      = $model->get('limitField', $limitField);
2559
			$limitStartField = $model->get('limitstartField', $limitStartField);
2560
		}
2561
2562
		if (isset($dataGet['list']['limit']))
2563
		{
2564
			$dataGet[$limitField] = $dataGet['list']['limit'];
2565
		}
2566
2567
		if (isset($dataGet['list']['limitstart']))
2568
		{
2569
			$dataGet[$limitStartField] = $dataGet['list']['limitstart'];
2570
		}
2571
2572
		// Support for B/C custom limit fields
2573
		if ($limitField != 'limit' && !empty($dataGet['limit']) && !isset($dataGet[$limitField]))
2574
		{
2575
			$dataGet[$limitField] = $dataGet['limit'];
2576
		}
2577
2578
		if ($limitStartField != 'limitstart' && !empty($dataGet['limitstart']) && !isset($dataGet[$limitStartField]))
2579
		{
2580
			$dataGet[$limitStartField] = $dataGet['limitstart'];
2581
		}
2582
2583
		// Set state for Filters and List
2584
		if (method_exists($model, 'setState'))
2585
		{
2586
			if (isset($dataGet['list']))
2587
			{
2588
				foreach ($dataGet['list'] as $key => $value)
2589
				{
2590
					$model->setState('list.' . $key, $value);
2591
				}
2592
			}
2593
2594
			if (isset($dataGet['filter']))
2595
			{
2596
				foreach ($dataGet['filter'] as $key => $value)
2597
				{
2598
					$model->setState('filter.' . $key, $value);
2599
				}
2600
			}
2601
2602
			if (isset($dataGet[$limitField]))
2603
			{
2604
				$model->setState('limit', $dataGet[$limitField]);
2605
				$model->setState('list.limit', $dataGet[$limitField]);
2606
				$model->setState($limitField, $dataGet[$limitField]);
2607
			}
2608
2609
			if (isset($dataGet[$limitStartField]))
2610
			{
2611
				$model->setState('limitstart', $dataGet[$limitStartField]);
2612
				$model->setState('list.start', $dataGet[$limitStartField]);
2613
				$model->setState($limitStartField, $dataGet[$limitStartField]);
2614
			}
2615
		}
2616
2617
		$this->options->set('dataGet', $dataGet);
2618
	}
2619
2620
	/**
2621
	 * Returns if all primary keys have set values
2622
	 * Easily get read type (item or list) for current read operation and fills primary keys
2623
	 *
2624
	 * @param   array             &$primaryKeys   List of primary keys
2625
	 * @param   SimpleXMLElement  $configuration  Configuration group
2626
	 *
2627
	 * @return  bool  Returns true if read type is Item
2628
	 *
2629
	 * @since   1.2
2630
	 */
2631
	public function apiFillPrimaryKeys(&$primaryKeys, $configuration = null)
2632
	{
2633
		if (is_null($configuration))
2634
		{
2635
			$operations = $this->getConfig('operations');
2636
2637
			if (!empty($operations->read->item))
2638
			{
2639
				$configuration = $operations->read->item;
2640
			}
2641
2642
			$data = $this->triggerFunction('processPostData', $this->options->get('dataGet', array()), $configuration);
2643
		}
2644
		else
2645
		{
2646
			$data = $this->triggerFunction('processPostData', $this->options->get('data', array()), $configuration);
2647
		}
2648
2649
		// Checking for primary keys
2650
		if (!empty($configuration))
2651
		{
2652
			$primaryKeysFromFields = RApiHalHelper::getFieldsArray($configuration, true);
2653
2654
			if (!empty($primaryKeysFromFields))
2655
			{
2656
				foreach ($primaryKeysFromFields as $primaryKey => $primaryKeyField)
2657
				{
2658
					if (isset($data[$primaryKey]) && $data[$primaryKey] != '')
2659
					{
2660
						$primaryKeys[$primaryKey] = $this->transformField($primaryKeyField['transform'], $data[$primaryKey], false);
2661
					}
2662
					else
2663
					{
2664
						$primaryKeys[$primaryKey] = null;
2665
					}
2666
				}
2667
2668
				foreach ($primaryKeys as $primaryKey => $primaryKeyField)
2669
				{
2670
					if (is_null($primaryKeyField))
2671
					{
2672
						return false;
2673
					}
2674
				}
2675
			}
2676
2677
			return true;
2678
		}
2679
2680
		return false;
2681
	}
2682
}
2683