Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 106
0.00% covered (danger)
0.00%
0 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
tao_actions_CommonRestModule
0.00% covered (danger)
0.00%
0 / 106
0.00% covered (danger)
0.00%
0 / 15
2550
0.00% covered (danger)
0.00%
0 / 1
 index
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
90
 getCrudService
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 delete
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 post
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 put
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 getPostData
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getBodyData
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
30
 getRequestData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getParameters
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
42
 getParametersRequirements
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getParametersAliases
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 reverseSearchAlias
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isRequiredParameter
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 getUriFromRequest
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2016 (original work) Open Assessment Technologies SA;
19 *
20 */
21
22use oat\generis\model\OntologyRdf;
23use oat\generis\model\OntologyRdfs;
24
25/**
26 * Class tao_actions_CommonRestModule
27 *
28 * @OA\Info(title="TAO Rest API", version="1.0")
29 */
30abstract class tao_actions_CommonRestModule extends tao_actions_RestController
31{
32    /** @var tao_models_classes_CrudService */
33    protected $service;
34
35    /**
36     * Entry point of API
37     * If uri parameters is provided, it must be a valid uri
38     * Depending on HTTP method, request is routed to crud function
39     *
40     * @throws common_exception_NotImplemented
41     */
42    public function index()
43    {
44        try {
45            $uri = $this->getUriFromRequest();
46            $request = $this->getPsrRequest();
47
48            switch ($request->getMethod()) {
49                case 'GET':
50                    $response = $this->get($uri);
51                    break;
52                case 'PUT':
53                    $response = $this->put($uri);
54                    break;
55                case 'POST':
56                    $response = $this->post();
57                    break;
58                case 'DELETE':
59                    $response = $this->delete($uri);
60                    break;
61                default:
62                    throw new common_exception_BadRequest($request->getUri()->getPath());
63            }
64
65            $this->returnSuccess($response);
66        } catch (Exception $e) {
67            if (
68                $e instanceof \common_exception_ValidationFailed &&
69                $alias = $this->reverseSearchAlias($e->getField())
70            ) {
71                $e = new \common_exception_ValidationFailed($alias, null, $e->getCode());
72            }
73
74            $this->returnFailure($e);
75        }
76    }
77
78    /**
79     * Return crud service
80     *
81     * @throws common_Exception
82     *
83     * @return tao_models_classes_CrudService
84     */
85    protected function getCrudService()
86    {
87        if (!$this->service) {
88            throw new common_Exception('Crud service is not set.');
89        }
90
91        return $this->service;
92    }
93
94    /**
95     * Method to wrap fetching to service:
96     * - get() if uri is not null
97     * - getAll() if uri is null
98     *
99     * @param string|null $uri
100     *
101     * @throws common_Exception
102     * @throws common_Exception_NoContent
103     * @throws common_exception_InvalidArgumentType
104     * @throws common_exception_PreConditionFailure
105     *
106     * @return object|stdClass
107     */
108    protected function get($uri = null)
109    {
110        if ($uri !== null) {
111            if ($this->getCrudService()->isInScope($uri) === false) {
112                throw new common_exception_PreConditionFailure(
113                    'The URI must be a valid resource under the root Class'
114                );
115            }
116
117            return $this->getCrudService()->get($uri);
118        } else {
119            return $this->getCrudService()->getAll();
120        }
121    }
122
123    /**
124     * Method to wrap deleting to service if uri is not null
125     *
126     * @param string|null $uri
127     *
128     * @throws common_Exception
129     * @throws common_exception_BadRequest
130     * @throws common_exception_InvalidArgumentType
131     * @throws common_exception_NoContent
132     * @throws common_exception_PreConditionFailure
133     */
134    protected function delete($uri = null)
135    {
136        if ($uri === null) {
137            throw new common_exception_BadRequest('Delete method requires an uri parameter');
138        } elseif ($this->getCrudService()->isInScope($uri) === false) {
139            throw new common_exception_PreConditionFailure(
140                'The URI must be a valid resource under the root Class'
141            );
142        }
143
144        return $this->getCrudService()->delete($uri);
145    }
146
147    /**
148     * Method to wrap creating to service
149     *
150     * @OA\Schema(
151     *     schema="tao.CommonRestModule.CreatedResource",
152     *     description="Created resource data",
153     *     @OA\Property(
154     *         property="uriResource",
155     *         type="string",
156     *         example="http://sample/first.rdf#i1536680377163171"
157     *     ),
158     *     @OA\Property(
159     *         property="label",
160     *         type="string"
161     *     ),
162     *     @OA\Property(
163     *         property="comment",
164     *         type="string"
165     *     )
166     * )
167     * @OA\Schema(
168     *     schema="tao.CommonRestModule.CreatedResourceResponse",
169     *     description="Created resource data",
170     *     allOf={
171     *         @OA\Schema(ref="#/components/schemas/tao.RestTrait.BaseResponse")
172     *     },
173     *     @OA\Property(
174     *         property="data",
175     *         ref="#/components/schemas/tao.CommonRestModule.CreatedResource"
176     *     )
177     * )
178     *
179     * @throws common_Exception
180     * @throws common_exception_RestApi
181     *
182     * @return mixed
183     */
184    protected function post()
185    {
186        try {
187            return $this->getCrudService()->createFromArray($this->getParameters());
188        } catch (common_exception_PreConditionFailure $e) {
189            throw new common_exception_RestApi($e->getMessage());
190        }
191    }
192
193    /**
194     * Method to wrap to updating to service if uri is not null
195     *
196     * @param string|null $uri
197     *
198     * @throws common_Exception
199     * @throws common_exception_BadRequest
200     * @throws common_exception_InvalidArgumentType
201     * @throws common_exception_NoContent
202     * @throws common_exception_PreConditionFailure
203     * @throws common_exception_RestApi
204     *
205     * @return mixed
206     */
207    protected function put($uri)
208    {
209        if ($uri === null) {
210            throw new common_exception_BadRequest('Update method requires an uri parameter');
211        } elseif ($this->getCrudService()->isInScope($uri) === false) {
212            throw new common_exception_PreConditionFailure(
213                'The URI must be a valid resource under the root Class'
214            );
215        }
216
217        try {
218            return $this->getCrudService()->update($uri, $this->getParameters());
219        } catch (common_exception_PreConditionFailure $e) {
220            throw new common_exception_RestApi($e->getMessage());
221        }
222    }
223
224    private function getPostData(): array
225    {
226        if (!is_array($parameters = $this->getPsrRequest()->getParsedBody())) {
227            $parameters = [];
228        }
229
230        return $parameters;
231    }
232
233    private function getBodyData(): array
234    {
235        $data = $this->getPsrRequest()->getBody()->getContents();
236        if ($data && $this->hasHeader('Accept') && current($this->getHeader('Accept')) === 'application/json') {
237            $data = @json_decode($data, true);
238        }
239
240        return is_array($data) ? $data : [];
241    }
242
243    private function getRequestData(): array
244    {
245        return array_merge($this->getPostData(), $this->getBodyData());
246    }
247
248    /**
249     * Returns all parameters that are URIs or Aliased with values
250     *
251     * @throws \common_exception_RestApi If a mandatory parameter is not found
252     *
253     * @return array
254     */
255    protected function getParameters()
256    {
257        $effectiveParameters = [];
258        $missedAliases = [];
259
260        $parameters = $this->getRequestData();
261
262        foreach ($this->getParametersAliases() as $checkParameterShort => $checkParameterUri) {
263            if (array_key_exists($checkParameterUri, $parameters)) {
264                $effectiveParameters[$checkParameterUri] = $parameters[$checkParameterUri];
265            } elseif (array_key_exists($checkParameterShort, $parameters)) {
266                $effectiveParameters[$checkParameterUri] = $parameters[$checkParameterShort];
267            } elseif ($this->isRequiredParameter($checkParameterShort)) {
268                $missedAliases[] = $checkParameterShort;
269            }
270        }
271
272        if (!empty($missedAliases)) {
273            throw new \common_exception_RestApi(
274                'Missed required parameters: ' . implode(', ', $missedAliases)
275            );
276        }
277
278        return $effectiveParameters;
279    }
280
281    /**
282     * Return required parameters by method
283     * Should return an array with key as HTTP method and value as array of parameters
284     *
285     * @return array
286     */
287    protected function getParametersRequirements()
288    {
289        return [
290            'put' => ['uri'],
291            'delete' => ['uri'],
292        ];
293    }
294
295    /**
296     * Default parameters aliases,
297     * Map from get parameter name to class uri
298     *
299     * @return array
300     */
301    protected function getParametersAliases()
302    {
303        return [
304            'label' => OntologyRdfs::RDFS_LABEL,
305            'comment' => OntologyRdfs::RDFS_COMMENT,
306            'type' => OntologyRdf::RDF_TYPE,
307        ];
308    }
309
310    /**
311     * @param string $paramName
312     *
313     * @return false|int|string
314     */
315    protected function reverseSearchAlias($paramName)
316    {
317        return array_search($paramName, $this->getParametersAliases(), true);
318    }
319
320    /**
321     * Defines if the parameter is mandatory according:
322     * - getParametersRequirements array
323     * - HTTP method
324     *
325     * @param string $parameter The alias name or uri of a parameter
326     *
327     * @return bool
328     */
329    protected function isRequiredParameter($parameter)
330    {
331        $requirements = array_change_key_case($this->getParametersRequirements(), CASE_LOWER);
332        $method = strtolower($this->getPsrRequest()->getMethod());
333
334        if (!isset($requirements[$method])) {
335            return false;
336        } elseif (in_array($parameter, $requirements[$method], true)) {
337            return true;
338        }
339
340        $isRequired = false;
341
342        // The requirements may have been declared using URIs, look up for the URI
343        $aliases = $this->getParametersAliases();
344
345        if (isset($aliases[$parameter])) {
346            $isRequired = in_array($aliases[$parameter], $requirements[$method], true);
347        }
348
349        return $isRequired;
350    }
351
352    /**
353     * @throws common_exception_InvalidArgumentType
354     *
355     * @return string|null
356     */
357    protected function getUriFromRequest()
358    {
359        $uri = null;
360
361        $request = $this->getPsrRequest();
362        $parsedBody = $request->getParsedBody();
363        $queryParams = $request->getQueryParams();
364
365        if (is_array($parsedBody) && array_key_exists('uri', $parsedBody)) {
366            $uri = $parsedBody['uri'];
367        } elseif (array_key_exists('uri', $queryParams)) {
368            $uri = $queryParams['uri'];
369        }
370
371        if ($uri !== null && common_Utils::isUri($uri) === false) {
372            throw new common_exception_InvalidArgumentType();
373        }
374
375        return $uri;
376    }
377}