RequestDescription::isSingleResult()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 1
c 2
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace POData\UriProcessor;
4
5
use POData\Common\Url;
6
use POData\Common\ODataConstants;
7
use POData\Common\Messages;
8
use POData\Common\MimeTypes;
9
use POData\Common\Version;
10
use POData\Common\ODataException;
11
use POData\IService;
12
use POData\Providers\Metadata\ResourceProperty;
13
use POData\Providers\Metadata\ResourceSetWrapper;
14
use POData\Providers\Metadata\ResourceStreamInfo;
15
use POData\UriProcessor\UriProcessor;
16
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentParser;
17
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetSource;
18
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\TargetKind;
19
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\SegmentDescriptor;
20
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
21
use POData\UriProcessor\QueryProcessor\SkipTokenParser\InternalSkipTokenInfo;
22
use POData\UriProcessor\QueryProcessor\ExpressionParser\FilterInfo;
23
use POData\UriProcessor\QueryProcessor\ExpandProjectionParser\RootProjectionNode;
24
use POData\Providers\Metadata\ResourceType;
25
use POData\Providers\Query\QueryType;
26
/**
27
 * Class RequestDescription
28
 * @package POData\UriProcessor
29
 */
30
class RequestDescription
31
{
32
    /**
33
     * Holds the value of HTTP 'DataServiceVersion' header in the request,
34
     * DataServiceVersion header value states the version of the
35
     * Open Data Protocol used by the client to generate the request.
36
     * Refer http://www.odata.org/developers/protocols/overview#ProtocolVersioning
37
     *
38
     * @var Version
39
     */
40
    private $requestVersion = null;
41
42
    /**
43
     * Holds the value of HTTP 'MaxDataServiceVersion' header in the request,
44
     * MaxDataServiceVersion header value specifies the maximum version number
45
     * the client can accept in a response.
46
     * Refer http://www.odata.org/developers/protocols/overview#ProtocolVersioning
47
     *
48
     * @var Version
49
     */
50
    private $requestMaxVersion = null;
51
52
    /**
53
     * This is the value of 'DataServiceVersion' header to be output in the response. this header
54
     * value states the OData version the server used to generate the response.
55
     * While processing the query and result set this value will be keeps on
56
     * updating, after every update this is compared against the
57
     * 'MaxDataServiceVersion' header in the client request to see whether the
58
     * client can interpret the response or not. The client should use this
59
     * value to determine whether it can correctly interpret the response or not.
60
     * Refer http://www.odata.org/developers/protocols/overview#ProtocolVersioning
61
     *
62
     * @var Version
63
     */
64
    private $requiredMinResponseVersion;
65
66
    /**
67
     * The minimum client version requirement, This value keeps getting updated
68
     * during processing of query, this is compared against the
69
     * DataServiceVersion header in the client request and if the client request
70
     * is less than this value then we fail the request (e.g. $count request
71
     * was sent but client said it was Version 1.0).
72
     *
73
     * @var Version
74
     */
75
    private $requiredMinRequestVersion;
76
77
    /** @var Version */
78
    private $maxServiceVersion;
79
80
    /**
81
     * Collection of known data service versions.
82
     *
83
     * @var Version[]
84
     */
85
    private static $_knownDataServiceVersions = null;
86
87
    /**
88
     *
89
     * @var Url
90
     */
91
    private $requestUrl;
92
93
    /**
94
     * Collection of SegmentDescriptor containing information about
95
     * each segment in the resource path part of the request uri.
96
     *
97
     * @var SegmentDescriptor[]
98
     */
99
    private $segments;
100
101
    /**
102
     * Holds reference to the last segment descriptor.
103
     *
104
     * @var SegmentDescriptor
105
     */
106
    private $lastSegment;
107
108
    /**
109
     * The name of the container for results
110
     *
111
     * @var string|null
112
     */
113
    private $_containerName;
114
115
116
    /**
117
     * The count option specified in the request.
118
     *
119
     * @var string
120
     */
121
    public $queryType;
122
123
    /**
124
     * Number of segments.
125
     *
126
     * @var int
127
     */
128
    private $_segmentCount;
129
130
    /**
131
     * Holds the value of $skip query option, if no $skip option
132
     * found then this parameter will be NULL.
133
     *
134
     * @var int|null
135
     */
136
    private $_skipCount;
137
138
    /**
139
     * Holds the value of take count, this value is depends on
140
     * presence of $top option and configured page size.
141
     *
142
     * @var int|null
143
     */
144
    private $_topCount;
145
146
    /**
147
     * Holds the value of $top query option, if no $top option
148
     * found then this parameter will be NULL.
149
     *
150
     * @var int|null
151
     */
152
    private $_topOptionCount;
153
154
    /**
155
     * Holds the parsed details for sorting, this will
156
     * be set in 3 cases
157
     * (1) if $orderby option is specified in the request uri
158
     * (2) if $skip or $top option is specified in the request uri
159
     * (3) if server side paging is enabled for the resource
160
     *     targeted by the request uri.
161
     *
162
     * @var InternalOrderByInfo|null
163
     */
164
    private $internalOrderByInfo;
165
166
    /**
167
     * Holds the parsed details for $skiptoken option, this will
168
     * be NULL if $skiptoken option is absent.
169
     *
170
     * @var InternalSkipTokenInfo|null
171
     */
172
    private $_internalSkipTokenInfo;
173
174
    /**
175
     * Holds the parsed details for $filter option, this will be NULL if $filter option is absent.
176
     *
177
     * @var FilterInfo|null
178
     */
179
    private $_filterInfo;
180
181
    /**
182
     * Holds reference to the root of the tree describing expand
183
     * and select information, this field will be NULL if no
184
     * $expand or $select specified in the request uri.
185
     *
186
     * @var RootProjectionNode|null
187
     */
188
    private $_rootProjectionNode;
189
190
    /**
191
     * Holds number of entities in the result set, if either $count or
192
     * $inlinecount=allpages is specified, otherwise NULL
193
     *
194
     *
195
     * @var int|null
196
     */
197
    private $_countValue;
198
199
    /**
200
     * Data of request from request body
201
     *
202
     * @var array
203
     */
204
    private $_data;
205
206
    /**
207
     * Flag indicating status of query execution.
208
     *
209
     * @var boolean
210
     */
211
    private $_isExecuted;
212
213
    /**
214
     * Reference to Uri processor.
215
     *
216
     * @var UriProcessor
217
     */
218
    private $_uriProcessor;
219
220
221
    /**
222
     * Reference to the service
223
     * @var IService
224
     */
225
    private $_service;
226
227
    /**
228
     * The parts of a multipart request
229
     * @var array
230
     */
231
    private $_parts;
232
233
    /**
234
     * The content of the Content-ID header of a request part
235
     * @var string
236
     */
237
    private $_contentId;
238
239
    /**
240
     * The HTTP request method of a request part
241
     * @var string
242
     */
243
    private $_requestMethod;
244
245
    /**
246
     * @param SegmentDescriptor[] $segmentDescriptors Description of segments in the resource path.
247
     * @param Url $requestUri
248
     * @param Version $serviceMaxVersion
249
     * @param null|string $requestVersion
250
     * @param null|string $maxRequestVersion
251
     * @param string $dataType
252
     */
253 128
    public function __construct($segmentDescriptors, Url $requestUri, Version $serviceMaxVersion, $requestVersion, $maxRequestVersion, $dataType = null, IService $service = null, $contentId = null, $requestMethod = null)
254
    {
255 128
        $this->segments = $segmentDescriptors;
256 128
        $this->_segmentCount = count($this->segments);
257 128
        $this->requestUrl = $requestUri;
258 128
        $this->lastSegment = $segmentDescriptors[$this->_segmentCount - 1];
259 128
        $this->queryType = QueryType::ENTITIES;
260 128
        $this->_service = $service;
261 128
        $this->_parts = array();
262
263
        //we use this for validation checks down in validateVersions...but maybe we should check that outside of this object...
264 128
        $this->maxServiceVersion = $serviceMaxVersion;
265
266
        //Per OData 1 & 2 spec we must return the smallest size
267
        //We start at 1.0 and move it up as features are requested
268 128
        $this->requiredMinResponseVersion = clone Version::v1();
269 128
        $this->requiredMinRequestVersion = clone Version::v1();
270
271
272
        //see http://www.odata.org/documentation/odata-v2-documentation/overview/#ProtocolVersioning
273
        //if requestVersion isn't there, use Service Max Version
274 128
        $this->requestVersion = is_null($requestVersion) ? $serviceMaxVersion : self::parseVersionHeader($requestVersion, ODataConstants::ODATAVERSIONHEADER);
275
276
        //if max version isn't there, use the request version
277 128
        $this->requestMaxVersion = is_null($maxRequestVersion) ? $this->requestVersion : self::parseVersionHeader($maxRequestVersion, ODataConstants::ODATAMAXVERSIONHEADER);
278
279
        //if it's OData v3..things change a bit
280 128
        if ($this->maxServiceVersion == Version::v3()) {
281 61
            if (is_null($maxRequestVersion))
282
            {
283
                //if max request version isn't specified we use the service max version instead of the request version
284
                //thus we favour newer versions
285 6
                $this->requestMaxVersion = $this->maxServiceVersion;
286
            }
287
288
            //also we change min response version to be the max version, again favoring later things
289
            //note that if the request max version is specified, it is still respected
290 61
            $this->requiredMinResponseVersion = clone $this->requestMaxVersion;
291
        }
292
293
294
295 128
        $this->_containerName = null;
296 128
        $this->_skipCount = null;
297 128
        $this->_topCount = null;
298 128
        $this->_topOptionCount = null;
299 128
        $this->internalOrderByInfo = null;
300 128
        $this->_internalSkipTokenInfo = null;
301
302 128
        $this->_filterInfo = null;
303 128
        $this->_countValue = null;
304 128
        $this->_isExecuted = false;
305 128
        $this->_contentId = $contentId;
306 128
        $this->_requestMethod = $requestMethod;
307
308
        // Define data from request body
309 128
        if ($dataType) {
310
            $this->_readData($dataType);
311
        }
312
    }
313
314 94
    public function getParts() {
315 94
        return $this->_parts;
316
    }
317
318
    public function getContentID() {
319
        return $this->_contentId;
320
    }
321
322
    public function getRequestMethod() {
323
        return $this->_requestMethod;
324
    }
325
326
    const STATE_PART_START = 'STATE_PART_START';
327
    const STATE_HTTP_STATUS = 'STATE_HTTP_STATUS';
328
    const STATE_HEADERS = 'STATE_HEADERS';
329
    const STATE_BODY = 'STATE_BODY';
330
331
    private function _processData(string $string, string $dataType)
332
    {
333
        if ($dataType === MimeTypes::MIME_APPLICATION_XML) {
334
            $data = Xml2Array::createArray($string);
0 ignored issues
show
Bug introduced by
The type POData\UriProcessor\Xml2Array 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...
335
            if (!empty($data['a:entry']['a:content']['m:properties'])) {
336
                $clearData = $data['a:entry']['a:content']['m:properties'];
337
                if (is_array($clearData)) {
338
                    foreach ($clearData as $key => $value) {
339
                        $this->_data[substr($key, 2)] = $value['@value'];
340
                    }
341
                }
342
            }
343
        } elseif ($dataType === MimeTypes::MIME_APPLICATION_JSON) {
344
            $data = json_decode($string, true);
345
            $this->_data = $data;
346
        } elseif (explode(';', $dataType)[0] === MimeTypes::MIME_MULTIPART_MIXED) {
347
            preg_match('/boundary=(.*)$/', $dataType, $matches);
348
            $boundary = $matches[1];
349
350
            // split content by boundary and get rid of last -- element
351
            $a_blocks = preg_split("/-+$boundary/", $string);
352
            array_pop($a_blocks);
353
354
            // loop data blocks
355
            foreach ($a_blocks as $id => $block)
356
            {
357
                if (empty($block)) {
358
                                continue;
359
                }
360
361
                $requestMethod = null;
362
                $requestUrl = null;
363
364
                $reader_state = RequestDescription::STATE_PART_START;
365
                $body = '';
366
                $headers = [];
367
                $part_headers = [];
368
369
                foreach (explode("\n", $block) as $line) {
370
                    if (empty($line)) {
371
                        if ($reader_state == RequestDescription::STATE_PART_START) {
372
                            $reader_state = RequestDescription::STATE_HTTP_STATUS;
373
                        } else if ($reader_state == RequestDescription::STATE_HEADERS) {
374
                            $reader_state = RequestDescription::STATE_BODY;
375
                            $body = '';
376
                        }
377
                        continue;
378
                    }
379
                    $m = array();
380
                    if (in_array($reader_state, [RequestDescription::STATE_HTTP_STATUS, RequestDescription::STATE_HEADERS]) && preg_match('/(?<key>[^:]+):\s*(?<value>.*?)\s*$/', $line, $m)) {
381
                        if ($reader_state == RequestDescription::STATE_HTTP_STATUS) {
382
                            $headers[$m['key']] = $m['value'];
383
                        } else if ($reader_state == RequestDescription::STATE_HEADERS) {
384
                            $part_headers[$m['key']] = $m['value'];
385
                        }
386
                    } else if (preg_match('/^(GET|PUT|POST|PATCH|DELETE)\s+(.*?)(\s+HTTP\/\d\.\d)?\s*$/', $line, $m)) {
387
                        $requestMethod = trim($m[1]);
388
                        $requestUrl = new Url(trim($m[2]), false);
389
                        $reader_state = RequestDescription::STATE_HEADERS;
390
                    } else if ($reader_state = RequestDescription::STATE_BODY) {
391
                        $body .= $line . "\n";
392
                    }
393
                }
394
395
                $host = $this->_service->getHost();
396
                $absoluteServiceUri = $host->getAbsoluteServiceUri();
397
398
                $requestUriSegments = array_slice(
399
                    $requestUrl->getSegments(),
400
                    $absoluteServiceUri->getSegmentCount()
401
                );
402
403
                $segments = SegmentParser::parseRequestUriSegments(
404
                    $requestUriSegments,
405
                    $this->_service->getProvidersWrapper(),
406
                    true
407
                );
408
409
                $contentType = $part_headers['Content-Type'] ?? '';
410
411
                // you'll have to var_dump $block to understand this and maybe replace \n or \r with a visibile char
412
                $request = new RequestDescription(
413
                    $segments,
414
                    $requestUrl,
0 ignored issues
show
Bug introduced by
It seems like $requestUrl can also be of type null; however, parameter $requestUri of POData\UriProcessor\Requ...cription::__construct() does only seem to accept POData\Common\Url, 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

414
                    /** @scrutinizer ignore-type */ $requestUrl,
Loading history...
415
                    $this->maxServiceVersion,
416
                    null, // $this->requestVersion,
417
                    null, // $this->requestMaxVersion,
418
                    $contentType,
419
                    null,
420
                    $headers['Content-ID'] ?? null,
421
                    $requestMethod
422
                );
423
424
                $request->_setBody($body, $contentType);
425
426
                $this->_parts[] = $request;
427
            }
428
        }
429
    }
430
431
    /**
432
     * Define request data from body
433
     * @param string $dataType
434
     */
435
    private function _readData(string $dataType) {
436
        $this->_data = null;
437
        $string = file_get_contents('php://input');
438
        $this->_processData($string, $dataType);
439
    }
440
441
    /**
442
     * Get request data from body
443
     */
444
    private function _setBody(string $string, string $dataType) {
445
        $this->_data = null;
446
        $this->_processData($string, $dataType);
447
    }
448
    /**
449
     * Get request data from body
450
     */
451
    public function getData() {
452
        return $this->_data;
453
    }
454
455
    /**
456
     * Raise the minimum client version requirement for this request and
457
     * perform capability negotiation.
458
     *
459
     * @param int $major The major segment of the version
460
     * @param int $minor The minor segment of the version
461
     *
462
     * @throws ODataException If capability negotiation fails.
463
     */
464 22
    public function raiseMinVersionRequirement($major, $minor) {
465 22
        if ($this->requiredMinRequestVersion->raiseVersion($major, $minor))
466
        {
467 22
            $this->validateVersions();
468
        }
469
    }
470
471
    /**
472
     * Raise the response version for this request and perform capability negotiation.
473
     *
474
     *
475
     * @param int $major The major segment of the version
476
     * @param int $minor The minor segment of the version
477
     *
478
     * @throws ODataException If capability negotiation fails.
479
     */
480 109
    public function raiseResponseVersion($major, $minor) {
481 109
        if ($this->requiredMinResponseVersion->raiseVersion($major, $minor)) {
482 59
            $this->validateVersions();
483
        }
484
485
    }
486
487
    /**
488
     * Gets collection of segment descriptors containing information about
489
     * each segment in the resource path part of the request uri.
490
     *
491
     * @return SegmentDescriptor[]
492
     */
493 94
    public function getSegments()
494
    {
495 94
        return $this->segments;
496
    }
497
498
    /**
499
     * Gets reference to the descriptor of last segment.
500
     *
501
     * @return SegmentDescriptor
502
     */
503 1
    public function getLastSegment()
504
    {
505 1
        return $this->lastSegment;
506
    }
507
508
    /**
509
     * Gets kind of resource targeted by the resource path.
510
     *
511
     * @return int
512
     */
513 94
    public function getTargetKind()
514
    {
515 94
        return $this->lastSegment->getTargetKind();
516
    }
517
518
    /**
519
     * Gets kind of 'source of data' targeted by the resource path.
520
     *
521
     * @return int
522
     */
523 88
    public function getTargetSource()
524
    {
525
        /*
526
        if (!empty($this->_parts)) {
527
            $results = array();
528
            foreach ($this->_parts as &$part) {
529
                $results[] = $part->lastSegment->getTargetSource();
530
            }
531
            return $results;
532
        }
533
        */
534 88
        return $this->lastSegment->getTargetSource();
535
    }
536
537
    /**
538
     * Gets reference to the ResourceSetWrapper instance targeted by
539
     * the resource path, ResourceSetWrapper will present in the
540
     * following cases:
541
     * if the last segment descriptor describes
542
     *      (a) resource set
543
     *          http://server/NW.svc/Customers
544
     *          http://server/NW.svc/Customers('ALFKI')
545
     *          http://server/NW.svc/Customers('ALFKI')/Orders
546
     *          http://server/NW.svc/Customers('ALFKI')/Orders(123)
547
     *          http://server/NW.svc/Customers('ALFKI')/$links/Orders
548
     *      (b) resource set reference
549
     *          http://server/NW.svc/Orders(123)/Customer
550
     *          http://server/NW.svc/Orders(123)/$links/Customer
551
     *      (c) $count
552
     *          http://server/NW.svc/Customers/$count
553
     * ResourceSet wrapper will be absent (NULL) in the following cases:
554
     * if the last segment descriptor describes
555
     *      (a) Primitive
556
     *          http://server/NW.svc/Customers('ALFKI')/Country
557
     *      (b) $value on primitive type
558
     *          http://server/NW.svc/Customers('ALFKI')/Country/$value
559
     *      (c) Complex
560
     *          http://server/NW.svc/Customers('ALFKI')/Address
561
     *      (d) Bag
562
     *          http://server/NW.svc/Employees(123)/Emails
563
     *      (e) MLE
564
     *          http://server/NW.svc/Employees(123)/$value
565
     *      (f) Named Stream
566
     *          http://server/NW.svc/Employees(123)/Thumnail48_48
567
     *      (g) metadata
568
     *          http://server/NW.svc/$metadata
569
     *      (h) service directory
570
     *          http://server/NW.svc
571
     *      (i) $bath
572
     *          http://server/NW.svc/$batch
573
     *
574
     * @return ResourceSetWrapper|null
575
     */
576 88
    public function getTargetResourceSetWrapper()
577
    {
578 88
        return $this->lastSegment->getTargetResourceSetWrapper();
579
    }
580
581
    /**
582
     * Gets reference to the ResourceType instance targeted by
583
     * the resource path, ResourceType will present in the
584
     * following cases:
585
     * if the last segment descriptor describes
586
     *      (a) resource set
587
     *          http://server/NW.svc/Customers
588
     *          http://server/NW.svc/Customers('ALFKI')
589
     *          http://server/NW.svc/Customers('ALFKI')/Orders
590
     *          http://server/NW.svc/Customers('ALFKI')/Orders(123)
591
     *          http://server/NW.svc/Customers('ALFKI')/$links/Orders
592
     *      (b) resource set reference
593
     *          http://server/NW.svc/Orders(123)/Customer
594
     *          http://server/NW.svc/Orders(123)/$links/Customer
595
     *      (c) $count
596
     *          http://server/NW.svc/Customers/$count
597
     *      (d) Primitive
598
     *          http://server/NW.svc/Customers('ALFKI')/Country
599
     *      (e) $value on primitive type
600
     *          http://server/NW.svc/Customers('ALFKI')/Country/$value
601
     *      (f) Complex
602
     *          http://server/NW.svc/Customers('ALFKI')/Address
603
     *      (g) Bag
604
     *          http://server/NW.svc/Employees(123)/Emails
605
     *      (h) MLE
606
     *          http://server/NW.svc/Employees(123)/$value
607
     *      (i) Named Stream
608
     *          http://server/NW.svc/Employees(123)/Thumnail48_48
609
     * ResourceType will be absent (NULL) in the following cases:
610
     * if the last segment descriptor describes
611
     *      (a) metadata
612
     *          http://server/NW.svc/$metadata
613
     *      (b) service directory
614
     *          http://server/NW.svc
615
     *      (c) $bath
616
     *          http://server/NW.svc/$batch
617
     *
618
     * @return ResourceType|null
619
     */
620 88
    public function getTargetResourceType()
621
    {
622 88
        return $this->lastSegment->getTargetResourceType();
623
    }
624
625
    /**
626
     * Gets reference to the ResourceProperty instance targeted by
627
     * the resource path, ResourceProperty will present in the
628
     * following cases:
629
     * if the last segment descriptor describes
630
     *      (a) resource set (after 1 level)
631
     *          http://server/NW.svc/Customers('ALFKI')/Orders
632
     *          http://server/NW.svc/Customers('ALFKI')/Orders(123)
633
     *          http://server/NW.svc/Customers('ALFKI')/$links/Orders
634
     *      (b) resource set reference
635
     *          http://server/NW.svc/Orders(123)/Customer
636
     *          http://server/NW.svc/Orders(123)/$links/Customer
637
     *      (c) $count
638
     *          http://server/NW.svc/Customers/$count
639
     *      (d) Primitive
640
     *          http://server/NW.svc/Customers('ALFKI')/Country
641
     *      (e) $value on primitive type
642
     *          http://server/NW.svc/Customers('ALFKI')/Country/$value
643
     *      (f) Complex
644
     *          http://server/NW.svc/Customers('ALFKI')/Address
645
     *      (g) Bag
646
     *          http://server/NW.svc/Employees(123)/Emails
647
     *      (h) MLE
648
     *          http://server/NW.svc/Employees(123)/$value
649
     *
650
     * ResourceType will be absent (NULL) in the following cases:
651
     * if the last segment descriptor describes
652
     *      (a) If last segment is the only segment pointing to
653
     *          ResourceSet (single or multiple)
654
     *          http://server/NW.svc/Customers
655
     *          http://server/NW.svc/Customers('ALFKI')
656
     *      (b) Named Stream
657
     *          http://server/NW.svc/Employees(123)/Thumnail48_48
658
     *      (c) metadata
659
     *          http://server/NW.svc/$metadata
660
     *      (d) service directory
661
     *          http://server/NW.svc
662
     *      (e) $bath
663
     *          http://server/NW.svc/$batch
664
     *
665
     * @return ResourceProperty|null
666
     */
667 1
    public function getProjectedProperty()
668
    {
669 1
        return  $this->lastSegment->getProjectedProperty();
670
    }
671
672
    /**
673
     * Gets the name of the container for results.
674
     *
675
     * @return string|null
676
     */
677
    public function getContainerName()
678
    {
679
        return $this->_containerName;
680
    }
681
682
    /**
683
     * Sets the name of the container for results.
684
     *
685
     * @param string $containerName The container name.
686
     *
687
     * @return void
688
     */
689 94
    public function setContainerName($containerName)
690
    {
691 94
        $this->_containerName = $containerName;
692
    }
693
694
    /**
695
     * Whether thr request targets a single result or not.
696
     *
697
     * @return boolean
698
     */
699 88
    public function isSingleResult()
700
    {
701 88
        return $this->lastSegment->isSingleResult();
702
    }
703
704
    /**
705
     * Gets the identifier associated with the the resource path.
706
     *
707
     * @return string
708
     */
709 94
    public function getIdentifier()
710
    {
711 94
        return $this->lastSegment->getIdentifier();
712
    }
713
714
    /**
715
     * Gets the request uri.
716
     *
717
     * @return Url
718
     */
719 1
    public function getRequestUrl()
720
    {
721 1
        return $this->requestUrl;
722
    }
723
724
    /**
725
     * Gets the value of $skip query option
726
     *
727
     * @return int|null The value of $skip query option, NULL if $skip is absent.
728
     *
729
     */
730 85
    public function getSkipCount()
731
    {
732 85
        return $this->_skipCount;
733
    }
734
735
    /**
736
     * Sets skip value
737
     *
738
     * @param int $skipCount The value of $skip query option.
739
     *
740
     * @return void
741
     */
742 5
    public function setSkipCount($skipCount)
743
    {
744 5
        $this->_skipCount = $skipCount;
745
    }
746
747
    /**
748
     * Gets the value of take count
749
     *
750
     * @return int|null The value of take, NULL if no take to be applied.
751
     *
752
     */
753 85
    public function getTopCount()
754
    {
755 85
        return $this->_topCount;
756
    }
757
758
    /**
759
     * Sets the value of take count
760
     *
761
     * @param int $topCount The value of take query option
762
     *
763
     * @return void
764
     */
765 57
    public function setTopCount($topCount)
766
    {
767 57
        $this->_topCount = $topCount;
768
    }
769
770
    /**
771
     * Gets the value of $top query option
772
     *
773
     * @return int|null The value of $top query option, NULL if $top is absent.
774
     *
775
     */
776 6
    public function getTopOptionCount()
777
    {
778 6
        return $this->_topOptionCount;
779
    }
780
781
    /**
782
     * Sets top value
783
     *
784
     * @param int $topOptionCount The value of $top query option
785
     *
786
     * @return void
787
     */
788 7
    public function setTopOptionCount($topOptionCount)
789
    {
790 7
        $this->_topOptionCount = $topOptionCount;
791
    }
792
793
    /**
794
     * Gets sorting (orderby) information, this function return
795
     * sorting information in 3 cases:
796
     * (1) if $orderby option is specified in the request uri
797
     * (2) if $skip or $top option is specified in the request uri
798
     * (3) if server side paging is enabled for the resource targeted
799
     *     by the request uri.
800
     *
801
     * @return InternalOrderByInfo|null
802
     */
803 66
    public function getInternalOrderByInfo()
804
    {
805 66
        return $this->internalOrderByInfo;
806
    }
807
808
    /**
809
     * Sets sorting (orderby) information.
810
     *
811
     * @param InternalOrderByInfo &$internalOrderByInfo The sorting information.
812
     *
813
     * @return void
814
     */
815 57
    public function setInternalOrderByInfo(InternalOrderByInfo &$internalOrderByInfo)
816
    {
817 57
        $this->internalOrderByInfo = $internalOrderByInfo;
818
    }
819
820
    /**
821
     * Gets the parsed details for $skiptoken option.
822
     *
823
     * @return InternalSkipTokenInfo|null Returns parsed details of $skiptoken option, NULL if $skiptoken is absent.
824
     *
825
     */
826 10
    public function getInternalSkipTokenInfo()
827
    {
828 10
        return $this->_internalSkipTokenInfo;
829
    }
830
831
    /**
832
     * Sets $skiptoken information.
833
     *
834
     * @param InternalSkipTokenInfo &$internalSkipTokenInfo The paging information.
835
     *
836
     * @return void
837
     */
838 3
    public function setInternalSkipTokenInfo(
839
        InternalSkipTokenInfo &$internalSkipTokenInfo
840
    ) {
841 3
        $this->_internalSkipTokenInfo = $internalSkipTokenInfo;
842
    }
843
844
    /**
845
     *
846
     * @return FilterInfo|null Returns parsed details of $filter option, NULL if $filter is absent.
847
     *
848
     */
849 51
    public function getFilterInfo()
850
    {
851 51
        return $this->_filterInfo;
852
    }
853
854
    /**
855
     *
856
     * @param FilterInfo $filterInfo The filter information.
857
     *
858
     */
859 44
    public function setFilterInfo(FilterInfo $filterInfo)
860
    {
861 44
        $this->_filterInfo = $filterInfo;
862
    }
863
864
    /**
865
     * Sets $expand and $select information.
866
     *
867
     * @param RootProjectionNode &$rootProjectionNode Root of the projection tree.
868
     *
869
     * @return void
870
     */
871 58
    public function setRootProjectionNode(RootProjectionNode &$rootProjectionNode)
872
    {
873 58
        $this->_rootProjectionNode = $rootProjectionNode;
874
    }
875
876
    /**
877
     * Gets the root of the tree describing expand and select options,
878
     *
879
     * @return RootProjectionNode|null Returns parsed details of $expand
880
     *                                 and $select options, NULL if
881
     *                                 $both options are absent.
882
     */
883 12
    public function getRootProjectionNode()
884
    {
885 12
        return $this->_rootProjectionNode;
886
    }
887
888
889
    /**
890
     * Gets the count of result set if $count or $inlinecount=allpages
891
     * has been applied otherwise NULL
892
     *
893
     * @return int|null
894
     */
895 6
    public function getCountValue()
896
    {
897 6
        return $this->_countValue;
898
    }
899
900
    /**
901
     * Sets the count of result set.
902
     *
903
     * @param int $countValue The count value.
904
     *
905
     * @return void
906
     */
907 4
    public function setCountValue($countValue)
908
    {
909 4
        $this->_countValue = $countValue;
910
    }
911
912
    /**
913
     * To set the flag indicating the execution status as true.
914
     *
915
     * @return void
916
     */
917
    public function setExecuted()
918
    {
919
        $this->_isExecuted = true;
920
    }
921
922
    /**
923
     * To check whether to execute the query using IDSQP.
924
     *
925
     * @return boolean True if query need to be executed, False otherwise.
926
     */
927
    public function needExecution()
928
    {
929
        return !$this->_isExecuted
930
            && ($this->lastSegment->getTargetKind() != TargetKind::METADATA)
931
            && ($this->lastSegment->getTargetKind() != TargetKind::SERVICE_DIRECTORY);
932
    }
933
934
    /**
935
     * To check if the resource path is a request for link uri.
936
     *
937
     * @return boolean True if request is for link uri else false.
938
     */
939 84
    public function isLinkUri()
940
    {
941 84
        return (($this->_segmentCount > 2) && ($this->segments[$this->_segmentCount - 2]->getTargetKind() == TargetKind::LINK));
942
    }
943
944
    /**
945
     * To check if the resource path is a request for meida resource
946
     *
947
     * @return boolean True if request is for media resource else false.
948
     */
949 80
    public function isMediaResource()
950
    {
951 80
        return ($this->lastSegment->getTargetKind() == TargetKind::MEDIA_RESOURCE);
952
    }
953
954
    /**
955
     * To check if the resource path is a request for named stream
956
     *
957
     * @return boolean True if request is for named stream else false.
958
     */
959 80
    public function isNamedStream()
960
    {
961 80
        return $this->isMediaResource() && !($this->lastSegment->getIdentifier() === ODataConstants::URI_VALUE_SEGMENT);
962
    }
963
964
    /**
965
     * Get ResourceStreamInfo for the media link entry or named stream request.
966
     *
967
     * @return ResourceStreamInfo|null Instance of ResourceStreamInfo if the
968
     *         current request targets named stream, NULL for MLE
969
     */
970 1
    public function getResourceStreamInfo()
971
    {
972
        //assert($this->isMediaResource)
973 1
        if ($this->isNamedStream()) {
974
            return $this->getTargetResourceType()
975
                ->tryResolveNamedStreamByName(
976
                    $this->lastSegment->getIdentifier()
977
                );
978
        }
979
980 1
        return null;
981
    }
982
983
    /**
984
     * Gets the resource instance targeted by the request uri.
985
     * Note: This value will be populated after query execution only.
986
     *
987
     * @return mixed
988
     */
989 6
    public function getTargetResult()
990
    {
991 6
        if (!empty($this->_parts)) {
992
            $results = array();
993
            foreach ($this->_parts as &$part) {
994
                $results[] = $part->lastSegment->getResult();
995
            }
996
            return $results;
997
        } else {
998 6
            return $this->lastSegment->getResult();
999
        }
1000
    }
1001
1002
    /**
1003
     * Gets the OData version the server used to generate the response.
1004
     *
1005
     * @return Version
1006
     */
1007 34
    public function getResponseVersion()
1008
    {
1009
1010 34
        return $this->requiredMinResponseVersion;
1011
    }
1012
1013
    /**
1014
     * Checks whether etag headers are allowed for this request.
1015
     *
1016
     * @return boolean True if ETag header (If-Match or If-NoneMatch)
1017
     *                 is allowed for the request, False otherwise.
1018
     */
1019
    public function isETagHeaderAllowed()
1020
    {
1021
        return $this->lastSegment->isSingleResult()
1022
            && ($this->queryType != QueryType::COUNT)
1023
            && !$this->isLinkUri()
1024
            && (is_null($this->_rootProjectionNode)
1025
                || !($this->_rootProjectionNode->isExpansionSpecified())
1026
                );
1027
    }
1028
1029
    /**
1030
     * Gets collection of known data service versions, currently 1.0, 2.0 and 3.0.
1031
     *
1032
     * @return Version[]
1033
     */
1034 115
    public static function getKnownDataServiceVersions()
1035
    {
1036 115
        if (is_null(self::$_knownDataServiceVersions)) {
0 ignored issues
show
introduced by
The condition is_null(self::_knownDataServiceVersions) is always false.
Loading history...
1037 1
            self::$_knownDataServiceVersions = array(
1038 1
                new Version(1, 0),
1039 1
                new Version(2, 0),
1040 1
                new Version(3, 0)
1041 1
            );
1042
        }
1043
1044 115
        return self::$_knownDataServiceVersions;
1045
    }
1046
1047
    /**
1048
     * This function is used to perform following checking (validation)
1049
     * for capability negotiation.
1050
     *  (1) Check client request's 'DataServiceVersion' header value is
1051
     *      less than or equal to the minimum version required to intercept
1052
     *      the response
1053
     *  (2) Check client request's 'MaxDataServiceVersion' header value is
1054
     *      less than or equal to the version of protocol required to generate
1055
     *      the response
1056
     *  (3) Check the configured maximum protocol version is less than or equal
1057
     *      to the version of protocol required to generate the response
1058
     *  In addition to these checking, this function is also responsible for
1059
     *  initializing the properties representing 'DataServiceVersion' and
1060
     *  'MaxDataServiceVersion'.
1061
     *
1062
     *
1063
     * @throws ODataException If any of the above 3 check fails.
1064
     */
1065 61
    public function validateVersions() {
1066
1067
        //If the request version is below the minimum version required by supplied request arguments..throw an exception
1068 61
        if ($this->requestVersion->compare($this->requiredMinRequestVersion) < 0) {
1069 4
            throw ODataException::createBadRequestError(
1070 4
                Messages::requestVersionTooLow(
1071 4
                    $this->requestVersion->toString(),
1072 4
                    $this->requiredMinRequestVersion->toString()
1073 4
                )
1074 4
            );
1075
        }
1076
1077
        //If the requested max version is below the version required to fulfill the response...throw an exception
1078 59
        if ($this->requestMaxVersion->compare($this->requiredMinResponseVersion) < 0) {
1079 27
            throw ODataException::createBadRequestError(
1080 27
                Messages::requestVersionTooLow(
1081 27
                    $this->requestMaxVersion->toString(),
1082 27
                    $this->requiredMinResponseVersion->toString()
1083 27
                )
1084 27
            );
1085
        }
1086
1087
        //If the max version supported by the service is below the version required to fulfill the response..throw an exception
1088 38
        if ($this->maxServiceVersion->compare($this->requiredMinResponseVersion) < 0) {
1089 9
            throw ODataException::createBadRequestError(
1090 9
                Messages::requestVersionIsBiggerThanProtocolVersion(
1091 9
                    $this->requiredMinResponseVersion->toString(),
1092 9
                    $this->maxServiceVersion->toString()
1093 9
                )
1094 9
            );
1095
        }
1096
    }
1097
1098
    /**
1099
     * Validates the given version in string format and returns the version as instance of Version
1100
     *
1101
     * @param string $versionHeader The DataServiceVersion or MaxDataServiceVersion header value
1102
     * @param string $headerName    The name of the header
1103
     *
1104
     * @return Version
1105
     *
1106
     * @throws ODataException If the version is malformed or not supported
1107
     */
1108 115
    private static function parseVersionHeader($versionHeader, $headerName)
1109
    {
1110 115
        $libName = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $libName is dead and can be removed.
Loading history...
1111 115
        $versionHeader = trim($versionHeader);
1112 115
        $libNameIndex = strpos($versionHeader, ';');
1113 115
        if ($libNameIndex !== false) {
1114
            $libName = substr($versionHeader, $libNameIndex);
1115
        } else {
1116 115
            $libNameIndex = strlen($versionHeader);
1117
        }
1118
1119 115
        $dotIndex = -1;
1120 115
        for ($i = 0; $i < $libNameIndex; $i++) {
1121 115
            if ($versionHeader[$i] == '.') {
1122
1123
                //Throw an exception if we find more than 1 dot
1124 115
                if ($dotIndex != -1) {
1125
                    throw ODataException::createBadRequestError(
1126
                        Messages::requestDescriptionInvalidVersionHeader(
1127
                            $versionHeader,
1128
                            $headerName
1129
                        )
1130
                    );
1131
                }
1132
1133 115
                $dotIndex = $i;
1134 115
            } else if ($versionHeader[$i] < '0' || $versionHeader[$i] > '9') {
1135
                throw ODataException::createBadRequestError(
1136
                    Messages::requestDescriptionInvalidVersionHeader(
1137
                        $versionHeader,
1138
                        $headerName
1139
                    )
1140
                );
1141
            }
1142
        }
1143
1144
1145 115
        $major = intval(substr($versionHeader, 0, $dotIndex));
1146 115
        $minor = 0;
1147
1148
        //Apparently the . is optional
1149 115
        if ($dotIndex != -1) {
1150 115
            if ($dotIndex == 0) {
1151
                //If it starts with a ., throw an exception
1152
                throw ODataException::createBadRequestError(
1153
                    Messages::requestDescriptionInvalidVersionHeader(
1154
                        $versionHeader,
1155
                        $headerName
1156
                    )
1157
                );
1158
            }
1159 115
            $minor = intval(substr($versionHeader, $dotIndex + 1, $libNameIndex));
1160
        }
1161
1162
1163 115
        $version = new Version($major, $minor);
1164
1165
        //TODO: move this somewhere...
1166 115
        $isSupportedVersion = false;
1167 115
        foreach (self::getKnownDataServiceVersions() as $version1) {
1168 115
            if ($version->compare($version1) == 0) {
1169 115
                $isSupportedVersion = true;
1170 115
                break;
1171
            }
1172
        }
1173
1174 115
        if (!$isSupportedVersion) {
1175
            $availableVersions = null;
1176
            foreach (self::getKnownDataServiceVersions() as $version1) {
1177
                $availableVersions .= $version1->toString() . ', ';
1178
            }
1179
1180
            $availableVersions = rtrim($availableVersions, ', ');
0 ignored issues
show
Bug introduced by
It seems like $availableVersions can also be of type null; however, parameter $string of rtrim() 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

1180
            $availableVersions = rtrim(/** @scrutinizer ignore-type */ $availableVersions, ', ');
Loading history...
1181
            throw ODataException::createBadRequestError(
1182
                Messages::requestDescriptionUnSupportedVersion(
1183
                    $headerName,
1184
                    $versionHeader,
1185
                    $availableVersions
1186
                )
1187
            );
1188
        }
1189
1190 115
        return $version;
1191
    }
1192
1193
    /**
1194
     * Gets reference to the UriProcessor instance.
1195
     *
1196
     * @return UriProcessor
1197
     */
1198
    public function getUriProcessor()
1199
    {
1200
        return $this->_uriProcessor;
1201
    }
1202
1203
    /**
1204
     * Set reference to UriProcessor instance.
1205
     *
1206
     * @param UriProcessor $uriProcessor Reference to the UriProcessor
1207
     *
1208
     * @return void
1209
     */
1210 88
    public function setUriProcessor(UriProcessor $uriProcessor)
1211
    {
1212 88
        $this->_uriProcessor = $uriProcessor;
1213
    }
1214
1215 88
    public function getQueryStringItem(string $item)
1216
    {
1217 88
        return $this->requestUrl->getQueryStringItem($item);
1218
    }
1219
}
1220