Test Failed
Push — master ( a1e735...534e7d )
by Bálint
13:42 queued 13s
created

RequestDescription::getIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

354
            array_pop(/** @scrutinizer ignore-type */ $a_blocks);
Loading history...
355
356
            // loop data blocks
357
            foreach ($a_blocks as $id => $block)
358
            {
359
                if (empty($block)) {
360
                                continue;
361
                }
362
363
                $requestMethod = null;
364
                $requestUrl = null;
365
366
                $reader_state = RequestDescription::STATE_PART_START;
367
                $body = null;
368
                $headers = [];
369
                $part_headers = [];
370
371
                foreach (explode("\n", $block) as $line) {
372
                    if (empty($line)) {
373
                        if ($reader_state == RequestDescription::STATE_PART_START) {
374
                            $reader_state = RequestDescription::STATE_HTTP_STATUS;
375
                        } else if ($reader_state == RequestDescription::STATE_HEADERS) {
376
                            $reader_state = RequestDescription::STATE_BODY;
377
                            $body = '';
378
                        }
379
                        continue;
380
                    }
381
                    $m = array();
382
                    if (in_array($reader_state, [RequestDescription::STATE_HTTP_STATUS, RequestDescription::STATE_HEADERS]) && preg_match('/(?<key>[^:]+):\s*(?<value>.*?)\s*$/', $line, $m)) {
383
                        if ($reader_state == RequestDescription::STATE_HTTP_STATUS) {
384
                            $headers[$m['key']] = $m['value'];
385
                        } else if ($reader_state == RequestDescription::STATE_HEADERS) {
386
                            $part_headers[$m['key']] = $m['value'];
387
                        }
388
                    } else if (preg_match('/^(GET|PUT|POST|PATCH|DELETE)\s+(.*?)(\s+HTTP\/\d\.\d)?\s*$/', $line, $m)) {
389
                        $requestMethod = trim($m[1]);
390
                        $requestUrl = new Url(trim($m[2]), false);
391
                        $reader_state = RequestDescription::STATE_HEADERS;
392
                    } else if ($reader_state = RequestDescription::STATE_BODY) {
393
                        $body .= $line."\n";
394
                    }
395
                }
396
397
                $host = $this->_service->getHost();
398
                $absoluteServiceUri = $host->getAbsoluteServiceUri();
399
400
                $requestUriSegments = array_slice(
401
                    $requestUrl->getSegments(),
402
                    $absoluteServiceUri->getSegmentCount()
403
                );
404
405
                $segments = SegmentParser::parseRequestUriSegments(
406
                    $requestUriSegments,
407
                    $this->_service->getProvidersWrapper(),
408
                    true
409
                );
410
411
                $contentType = $part_headers['Content-Type'] ?? null;
412
413
                // you'll have to var_dump $block to understand this and maybe replace \n or \r with a visibile char
414
                $request = new RequestDescription(
415
                    $segments,
416
                    $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

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