XMLDataFormatter::convertArrayWithoutHeader()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 4
nop 1
dl 0
loc 19
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\RestfulServer\DataFormatter;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Core\Convert;
7
use SilverStripe\Dev\Debug;
8
use SilverStripe\RestfulServer\DataFormatter;
9
use SilverStripe\ORM\DataObject;
10
use SilverStripe\ORM\DataObjectInterface;
11
use SilverStripe\Control\Director;
12
use SilverStripe\ORM\SS_List;
13
use SilverStripe\RestfulServer\RestfulServer;
14
15
/**
16
 * Formats a DataObject's member fields into an XML string
17
 */
18
class XMLDataFormatter extends DataFormatter
19
{
20
21
    /**
22
     * @config
23
     * @todo pass this from the API to the data formatter somehow
24
     */
25
    private static $api_base = "api/v1/";
26
27
    protected $outputContentType = 'text/xml';
28
29
    /**
30
     * @return array
31
     */
32
    public function supportedExtensions()
33
    {
34
        return array(
35
            'xml'
36
        );
37
    }
38
39
    /**
40
     * @return array
41
     */
42
    public function supportedMimeTypes()
43
    {
44
        return array(
45
            'text/xml',
46
            'application/xml',
47
        );
48
    }
49
50
    /**
51
     * @param $array
52
     * @return string
53
     * @throws \Exception
54
     */
55
    public function convertArray($array)
56
    {
57
        $response = Controller::curr()->getResponse();
58
        if ($response) {
0 ignored issues
show
introduced by
$response is of type SilverStripe\Control\HTTPResponse, thus it always evaluated to true.
Loading history...
59
            $response->addHeader("Content-Type", "text/xml");
60
        }
61
62
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n
63
            <response>{$this->convertArrayWithoutHeader($array)}</response>";
64
    }
65
66
    /**
67
     * @param $array
68
     * @return string
69
     * @throws \Exception
70
     */
71
    public function convertArrayWithoutHeader($array)
72
    {
73
        $xml = '';
74
75
        foreach ($array as $fieldName => $fieldValue) {
76
            if (is_array($fieldValue)) {
77
                if (is_numeric($fieldName)) {
78
                    $fieldName = 'Item';
79
                }
80
81
                $xml .= "<{$fieldName}>\n";
82
                $xml .= $this->convertArrayWithoutHeader($fieldValue);
83
                $xml .= "</{$fieldName}>\n";
84
            } else {
85
                $xml .= "<$fieldName>$fieldValue</$fieldName>\n";
86
            }
87
        }
88
89
        return $xml;
90
    }
91
92
    /**
93
     * Generate an XML representation of the given {@link DataObject}.
94
     *
95
     * @param DataObject $obj
96
     * @param $includeHeader Include <?xml ...?> header (Default: true)
0 ignored issues
show
Bug introduced by
The type SilverStripe\RestfulServer\DataFormatter\Include 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...
97
     * @return String XML
98
     */
99
    public function convertDataObject(DataObjectInterface $obj, $fields = null)
100
    {
101
        $response = Controller::curr()->getResponse();
102
        if ($response) {
0 ignored issues
show
introduced by
$response is of type SilverStripe\Control\HTTPResponse, thus it always evaluated to true.
Loading history...
103
            $response->addHeader("Content-Type", "text/xml");
104
        }
105
106
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . $this->convertDataObjectWithoutHeader($obj, $fields);
107
    }
108
109
    /**
110
     * @param DataObject $obj
111
     * @param null $fields
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $fields is correct as it would always require null to be passed?
Loading history...
112
     * @param null $relations
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $relations is correct as it would always require null to be passed?
Loading history...
113
     * @return string
114
     */
115
    public function convertDataObjectWithoutHeader(DataObject $obj, $fields = null, $relations = null)
116
    {
117
        $className = $this->sanitiseClassName(get_class($obj));
118
        $id = $obj->ID;
119
        $objHref = Director::absoluteURL($this->config()->api_base . "$className/$obj->ID");
120
121
        $xml = "<$className href=\"$objHref.xml\">\n";
122
        foreach ($this->getFieldsForObj($obj) as $fieldName => $fieldType) {
123
            // Field filtering
124
            if ($fields && !in_array($fieldName, $fields)) {
0 ignored issues
show
Bug introduced by
$fields of type void is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

124
            if ($fields && !in_array($fieldName, /** @scrutinizer ignore-type */ $fields)) {
Loading history...
125
                continue;
126
            }
127
            $fieldValue = $obj->obj($fieldName)->forTemplate();
128
            if (!mb_check_encoding($fieldValue, 'utf-8')) {
129
                $fieldValue = "(data is badly encoded)";
130
            }
131
132
            if (is_object($fieldValue) && is_subclass_of($fieldValue, 'Object') && $fieldValue->hasMethod('toXML')) {
133
                $xml .= $fieldValue->toXML();
134
            } else {
135
                if ('HTMLText' == $fieldType) {
136
                    // Escape HTML values using CDATA
137
                    $fieldValue = sprintf('<![CDATA[%s]]>', str_replace(']]>', ']]]]><![CDATA[>', $fieldValue));
138
                } else {
139
                    $fieldValue = Convert::raw2xml($fieldValue);
140
                }
141
                $mappedFieldName = $this->getFieldAlias(get_class($obj), $fieldName);
142
                $xml .= "<$mappedFieldName>$fieldValue</$mappedFieldName>\n";
143
            }
144
        }
145
146
        if ($this->relationDepth > 0) {
147
            foreach ($obj->hasOne() as $relName => $relClass) {
148
                if (!singleton($relClass)->stat('api_access')) {
149
                    continue;
150
                }
151
152
                // Field filtering
153
                if ($fields && !in_array($relName, $fields)) {
154
                    continue;
155
                }
156
                if ($this->customRelations && !in_array($relName, $this->customRelations)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customRelations of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
157
                    continue;
158
                }
159
160
                $fieldName = $relName . 'ID';
161
                if ($obj->$fieldName) {
162
                    $href = Director::absoluteURL($this->config()->api_base . "$relClass/" . $obj->$fieldName);
163
                } else {
164
                    $href = Director::absoluteURL($this->config()->api_base . "$className/$id/$relName");
165
                }
166
                $xml .= "<$relName linktype=\"has_one\" href=\"$href.xml\" id=\"" . $obj->$fieldName
167
                    . "\"></$relName>\n";
168
            }
169
170
            foreach ($obj->hasMany() as $relName => $relClass) {
171
                //remove dot notation from relation names
172
                $parts = explode('.', $relClass);
173
                $relClass = array_shift($parts);
174
                if (!singleton($relClass)->stat('api_access')) {
175
                    continue;
176
                }
177
                // backslashes in FQCNs kills both URIs and XML
178
                $relClass = $this->sanitiseClassName($relClass);
179
180
                // Field filtering
181
                if ($fields && !in_array($relName, $fields)) {
182
                    continue;
183
                }
184
                if ($this->customRelations && !in_array($relName, $this->customRelations)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customRelations of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
185
                    continue;
186
                }
187
188
                $xml .= "<$relName linktype=\"has_many\" href=\"$objHref/$relName.xml\">\n";
189
                $items = $obj->$relName();
190
                if ($items) {
191
                    foreach ($items as $item) {
192
                        $href = Director::absoluteURL($this->config()->api_base . "$relClass/$item->ID");
193
                        $xml .= "<$relClass href=\"$href.xml\" id=\"{$item->ID}\"></$relClass>\n";
194
                    }
195
                }
196
                $xml .= "</$relName>\n";
197
            }
198
199
            foreach ($obj->manyMany() as $relName => $relClass) {
200
                $relClass = RestfulServer::parseRelationClass($relClass);
201
202
                //remove dot notation from relation names
203
                $parts = explode('.', $relClass);
0 ignored issues
show
Bug introduced by
It seems like $relClass can also be of type array; however, parameter $string of explode() 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

203
                $parts = explode('.', /** @scrutinizer ignore-type */ $relClass);
Loading history...
204
                $relClass = array_shift($parts);
205
                if (!singleton($relClass)->stat('api_access')) {
206
                    continue;
207
                }
208
                // backslashes in FQCNs kills both URIs and XML
209
                $relClass = $this->sanitiseClassName($relClass);
210
211
                // Field filtering
212
                if ($fields && !in_array($relName, $fields)) {
213
                    continue;
214
                }
215
                if ($this->customRelations && !in_array($relName, $this->customRelations)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customRelations of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
216
                    continue;
217
                }
218
219
                $xml .= "<$relName linktype=\"many_many\" href=\"$objHref/$relName.xml\">\n";
220
                $items = $obj->$relName();
221
                if ($items) {
222
                    foreach ($items as $item) {
223
                        $href = Director::absoluteURL($this->config()->api_base . "$relClass/$item->ID");
224
                        $xml .= "<$relClass href=\"$href.xml\" id=\"{$item->ID}\"></$relClass>\n";
225
                    }
226
                }
227
                $xml .= "</$relName>\n";
228
            }
229
        }
230
231
        $xml .= "</$className>";
232
233
        return $xml;
234
    }
235
236
    /**
237
     * Generate an XML representation of the given {@link SS_List}.
238
     *
239
     * @param SS_List $set
240
     * @return String XML
241
     */
242
    public function convertDataObjectSet(SS_List $set, $fields = null)
243
    {
244
        Controller::curr()->getResponse()->addHeader("Content-Type", "text/xml");
245
        $className = $this->sanitiseClassName(get_class($set));
246
247
        $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
248
        $xml .= (is_numeric($this->totalSize)) ? "<$className totalSize=\"{$this->totalSize}\">\n" : "<$className>\n";
0 ignored issues
show
introduced by
The condition is_numeric($this->totalSize) is always true.
Loading history...
249
        foreach ($set as $item) {
250
            $xml .= $this->convertDataObjectWithoutHeader($item, $fields);
251
        }
252
        $xml .= "</$className>";
253
254
        return $xml;
255
    }
256
257
    /**
258
     * @param string $strData
259
     * @return array|void
260
     * @throws \Exception
261
     */
262
    public function convertStringToArray($strData)
263
    {
264
        return Convert::xml2array($strData);
265
    }
266
}
267