Completed
Push — master ( b59d95...c30b72 )
by Robbie
10s
created

XMLDataFormatter::convertArray()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 9
rs 9.6666
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
14
/**
15
 * Formats a DataObject's member fields into an XML string
16
 */
17
class XMLDataFormatter extends DataFormatter
18
{
19
20
    /**
21
     * @config
22
     * @todo pass this from the API to the data formatter somehow
23
     */
24
    private static $api_base = "api/v1/";
25
26
    protected $outputContentType = 'text/xml';
27
28
    /**
29
     * @return array
30
     */
31
    public function supportedExtensions()
32
    {
33
        return array(
34
            'xml'
35
        );
36
    }
37
38
    /**
39
     * @return array
40
     */
41
    public function supportedMimeTypes()
42
    {
43
        return array(
44
            'text/xml',
45
            'application/xml',
46
        );
47
    }
48
49
    /**
50
     * @param $array
51
     * @return string
52
     * @throws \Exception
53
     */
54
    public function convertArray($array)
55
    {
56
        $response = Controller::curr()->getResponse();
57
        if ($response) {
58
            $response->addHeader("Content-Type", "text/xml");
59
        }
60
61
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n
62
            <response>{$this->convertArrayWithoutHeader($array)}</response>";
63
    }
64
65
    /**
66
     * @param $array
67
     * @return string
68
     * @throws \Exception
69
     */
70
    public function convertArrayWithoutHeader($array)
71
    {
72
        $xml = '';
73
74
        foreach ($array as $fieldName => $fieldValue) {
75
            if (is_array($fieldValue)) {
76
                if (is_numeric($fieldName)) {
77
                    $fieldName = 'Item';
78
                }
79
80
                $xml .= "<{$fieldName}>\n";
81
                $xml .= $this->convertArrayWithoutHeader($fieldValue);
82
                $xml .= "</{$fieldName}>\n";
83
            } else {
84
                $xml .= "<$fieldName>$fieldValue</$fieldName>\n";
85
            }
86
        }
87
88
        return $xml;
89
    }
90
91
    /**
92
     * Generate an XML representation of the given {@link DataObject}.
93
     *
94
     * @param DataObject $obj
95
     * @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...
96
     * @return String XML
97
     */
98
    public function convertDataObject(DataObjectInterface $obj, $fields = null)
99
    {
100
        $response = Controller::curr()->getResponse();
101
        if ($response) {
102
            $response->addHeader("Content-Type", "text/xml");
103
        }
104
105
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . $this->convertDataObjectWithoutHeader($obj, $fields);
106
    }
107
108
    /**
109
     * @param DataObject $obj
110
     * @param null $fields
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...
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...
111
     * @param null $relations
112
     * @return string
113
     */
114
    public function convertDataObjectWithoutHeader(DataObject $obj, $fields = null, $relations = null)
115
    {
116
        $className = $this->sanitiseClassName(get_class($obj));
117
        $id = $obj->ID;
118
        $objHref = Director::absoluteURL($this->config()->api_base . "$className/$obj->ID");
119
120
        $xml = "<$className href=\"$objHref.xml\">\n";
121
        foreach ($this->getFieldsForObj($obj) as $fieldName => $fieldType) {
122
            // Field filtering
123
            if ($fields && !in_array($fieldName, $fields)) {
124
                continue;
125
            }
126
            $fieldValue = $obj->obj($fieldName)->forTemplate();
127
            if (!mb_check_encoding($fieldValue, 'utf-8')) {
128
                $fieldValue = "(data is badly encoded)";
129
            }
130
131
            if (is_object($fieldValue) && is_subclass_of($fieldValue, 'Object') && $fieldValue->hasMethod('toXML')) {
132
                $xml .= $fieldValue->toXML();
133
            } else {
134
                if ('HTMLText' == $fieldType) {
135
                    // Escape HTML values using CDATA
136
                    $fieldValue = sprintf('<![CDATA[%s]]>', str_replace(']]>', ']]]]><![CDATA[>', $fieldValue));
137
                } else {
138
                    $fieldValue = Convert::raw2xml($fieldValue);
139
                }
140
                $xml .= "<$fieldName>$fieldValue</$fieldName>\n";
141
            }
142
        }
143
144
        if ($this->relationDepth > 0) {
145
            foreach ($obj->hasOne() as $relName => $relClass) {
146
                if (!singleton($relClass)->stat('api_access')) {
147
                    continue;
148
                }
149
150
                // Field filtering
151
                if ($fields && !in_array($relName, $fields)) {
152
                    continue;
153
                }
154
                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...
155
                    continue;
156
                }
157
158
                $fieldName = $relName . 'ID';
159
                if ($obj->$fieldName) {
160
                    $href = Director::absoluteURL($this->config()->api_base . "$relClass/" . $obj->$fieldName);
161
                } else {
162
                    $href = Director::absoluteURL($this->config()->api_base . "$className/$id/$relName");
163
                }
164
                $xml .= "<$relName linktype=\"has_one\" href=\"$href.xml\" id=\"" . $obj->$fieldName
165
                    . "\"></$relName>\n";
166
            }
167
168
            foreach ($obj->hasMany() as $relName => $relClass) {
169
                //remove dot notation from relation names
170
                $parts = explode('.', $relClass);
171
                $relClass = array_shift($parts);
172
                if (!singleton($relClass)->stat('api_access')) {
173
                    continue;
174
                }
175
                // backslashes in FQCNs kills both URIs and XML
176
                $relClass = $this->sanitiseClassName($relClass);
177
178
                // Field filtering
179
                if ($fields && !in_array($relName, $fields)) {
180
                    continue;
181
                }
182
                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...
183
                    continue;
184
                }
185
186
                $xml .= "<$relName linktype=\"has_many\" href=\"$objHref/$relName.xml\">\n";
187
                $items = $obj->$relName();
188
                if ($items) {
189
                    foreach ($items as $item) {
190
                        $href = Director::absoluteURL($this->config()->api_base . "$relClass/$item->ID");
191
                        $xml .= "<$relClass href=\"$href.xml\" id=\"{$item->ID}\"></$relClass>\n";
192
                    }
193
                }
194
                $xml .= "</$relName>\n";
195
            }
196
197
            foreach ($obj->manyMany() as $relName => $relClass) {
198
                //remove dot notation from relation names
199
                $parts = explode('.', $relClass);
200
                $relClass = array_shift($parts);
201
                if (!singleton($relClass)->stat('api_access')) {
202
                    continue;
203
                }
204
                // backslashes in FQCNs kills both URIs and XML
205
                $relClass = $this->sanitiseClassName($relClass);
206
207
                // Field filtering
208
                if ($fields && !in_array($relName, $fields)) {
209
                    continue;
210
                }
211
                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...
212
                    continue;
213
                }
214
215
                $xml .= "<$relName linktype=\"many_many\" href=\"$objHref/$relName.xml\">\n";
216
                $items = $obj->$relName();
217
                if ($items) {
218
                    foreach ($items as $item) {
219
                        $href = Director::absoluteURL($this->config()->api_base . "$relClass/$item->ID");
220
                        $xml .= "<$relClass href=\"$href.xml\" id=\"{$item->ID}\"></$relClass>\n";
221
                    }
222
                }
223
                $xml .= "</$relName>\n";
224
            }
225
        }
226
227
        $xml .= "</$className>";
228
229
        return $xml;
230
    }
231
232
    /**
233
     * Generate an XML representation of the given {@link SS_List}.
234
     *
235
     * @param SS_List $set
236
     * @return String XML
237
     */
238
    public function convertDataObjectSet(SS_List $set, $fields = null)
239
    {
240
        Controller::curr()->getResponse()->addHeader("Content-Type", "text/xml");
241
        $className = $this->sanitiseClassName(get_class($set));
242
243
        $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
244
        $xml .= (is_numeric($this->totalSize)) ? "<$className totalSize=\"{$this->totalSize}\">\n" : "<$className>\n";
0 ignored issues
show
introduced by
The condition is_numeric($this->totalSize) can never be false.
Loading history...
245
        foreach ($set as $item) {
246
            $xml .= $this->convertDataObjectWithoutHeader($item, $fields);
247
        }
248
        $xml .= "</$className>";
249
250
        return $xml;
251
    }
252
253
    /**
254
     * @param string $strData
255
     * @return array|void
256
     * @throws \Exception
257
     */
258
    public function convertStringToArray($strData)
259
    {
260
        return Convert::xml2array($strData);
261
    }
262
}
263