Passed
Push — master ( d28b46...1bf0cd )
by Stefano
02:05
created

ApiFormatterComponent::embedIncluded()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 15
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 27
rs 9.4555
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * BEdita, API-first content management framework
6
 * Copyright 2021 Atlas Srl, ChannelWeb Srl, Chialab Srl
7
 *
8
 * This file is part of BEdita: you can redistribute it and/or modify
9
 * it under the terms of the GNU Lesser General Public License as published
10
 * by the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
14
 */
15
16
namespace BEdita\WebTools\Controller\Component;
17
18
use Cake\Collection\Collection;
19
use Cake\Controller\Component;
20
use Cake\Utility\Hash;
21
22
/**
23
 * Component class to format API response data.
24
 */
25
class ApiFormatterComponent extends Component
26
{
27
    /**
28
     * Embed included data into relationships.
29
     *
30
     * @param array $response The response from API
31
     * @return array
32
     */
33
    public function embedIncluded(array $response): array
34
    {
35
        $data = (array)Hash::get($response, 'data');
36
        if (empty($data)) {
37
            return $response;
38
        }
39
40
        $included = (array)Hash::get($response, 'included');
41
        if (empty($included)) {
42
            return $response;
43
        }
44
45
        $included = collection($included);
46
        if (!Hash::numeric(array_keys($data))) {
47
            $response['data'] = $this->addIncluded($data, $included);
48
49
            return $response;
50
        }
51
52
        foreach ($data as &$d) {
53
            $d = $this->addIncluded($d, $included);
54
        }
55
        unset($d);
56
57
        $response['data'] = $data;
58
59
        return $response;
60
    }
61
62
    /**
63
     * Add included data to main resource.
64
     *
65
     * @param array $resource The resource.
66
     * @param \Cake\Collection\Collection $included The included collection.
67
     * @return array
68
     */
69
    protected function addIncluded(array $resource, Collection $included): array
70
    {
71
        foreach ($resource['relationships'] as &$relation) {
72
            if (empty($relation['data'])) {
73
                continue;
74
            }
75
76
            $relation['data'] = $this->extractFromIncluded($included, (array)$relation['data']);
77
        }
78
        unset($relation);
79
80
        return $resource;
81
    }
82
83
    /**
84
     * Extract items from included starting from $relationship data.
85
     *
86
     * @param \Cake\Collection\Collection $included The included collection
87
     * @param array $relationshipData Array of relationship data.
88
     *                                Every item must contain 'type' and 'id'.
89
     * @return array
90
     */
91
    protected function extractFromIncluded(Collection $included, array $relationshipData): array
92
    {
93
        // case is 1-1 relationship - object relation in translations is a special case
94
        if (array_key_exists('id', $relationshipData)) {
95
            return (array)$included->firstMatch([
96
                'type' => $relationshipData['type'],
97
                'id' => $relationshipData['id'],
98
            ]);
99
        }
100
        foreach ($relationshipData as &$data) {
101
            $data = (array)$included->firstMatch([
102
                'type' => $data['type'],
103
                'id' => $data['id'],
104
            ]);
105
        }
106
        unset($data);
107
108
        return $relationshipData;
109
    }
110
111
    /**
112
     * Replace a translation in main objects.
113
     * It must be used after `included` data have been embedded using `self::embedIncluded()`.
114
     *
115
     * @param array $response The response API array
116
     * @param string $lang The lang to search in translations
117
     * @return array
118
     */
119
    public function replaceWithTranslation(array $response, string $lang): array
120
    {
121
        if (empty($response['data'])) {
122
            return $response;
123
        }
124
125
        $data = (array)Hash::get($response, 'data');
126
127
        if (!Hash::numeric(array_keys($data))) {
128
            $response['data']['attributes'] = array_merge(
129
                $response['data']['attributes'],
130
                $this->extractTranslatedFields($data, $lang)
131
            );
132
133
            return $response;
134
        }
135
136
        foreach ($response['data'] as &$d) {
137
            $d['attributes'] = array_merge($d['attributes'], $this->extractTranslatedFields($d, $lang));
138
        }
139
140
        return $response;
141
    }
142
143
    /**
144
     * Extract translated fields for a language.
145
     *
146
     * @param array $data The object data
147
     * @param string $lang The lang to extract
148
     * @return array
149
     */
150
    protected function extractTranslatedFields(array $data, string $lang): array
151
    {
152
        $path = sprintf('relationships.translations.data.{n}.attributes[lang=%s].translated_fields', $lang);
153
        if ($lang === Hash::get($data, 'attributes.lang')) {
154
            return [];
155
        }
156
157
        return array_filter(current(Hash::extract($data, $path)));
158
    }
159
}
160