Api   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 328
Duplicated Lines 0 %

Test Coverage

Coverage 6.29%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 43
eloc 145
c 1
b 1
f 0
dl 0
loc 328
ccs 9
cts 143
cp 0.0629
rs 8.96

14 Methods

Rating   Name   Duplication   Size   Complexity  
A setSpace() 0 3 1
A __construct() 0 5 1
A getPage() 0 22 4
B handleError() 0 29 7
A getFileSize() 0 11 3
A getAttachment() 0 10 2
A showSourceCode() 0 29 4
A putAttachment() 0 14 3
A uploadAttachment() 0 19 3
A getList() 0 40 5
A createPage() 0 20 3
A deletePage() 0 6 2
A updatePage() 0 28 4
A getClient() 0 8 1

How to fix   Complexity   

Complex Class

Complex classes like Api often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Api, and based on these observations, apply Extract Interface, too.

1
<?php namespace Todaymade\Daux\Format\Confluence;
2
3
use GuzzleHttp\Client;
4
use GuzzleHttp\Exception\BadResponseException;
5
6
class Api
7
{
8
    protected $base_url;
9
    protected $user;
10
    protected $pass;
11
12
    protected $space;
13
14 1
    public function __construct($base_url, $user, $pass)
15
    {
16 1
        $this->base_url = $base_url;
17 1
        $this->user = $user;
18 1
        $this->pass = $pass;
19 1
    }
20
21
    public function setSpace($space_id)
22
    {
23
        $this->space = $space_id;
24
    }
25
26
    /**
27
     * This method is public due to test purposes.
28
     *
29
     * @return Client
30 1
     */
31
    public function getClient()
32
    {
33 1
        $options = [
34 1
            'base_uri' => $this->base_url . 'rest/api/',
35
            'auth' => [$this->user, $this->pass],
36
        ];
37 1
38
        return new Client($options);
39
    }
40
41
    /**
42
     * The standard error message from guzzle is quite poor in informations,
43
     * this will give little bit more sense to it and return it.
44
     *
45
     * @return \Exception
46
     */
47
    protected function handleError(BadResponseException $e)
48
    {
49
        $request = $e->getRequest();
50
        $response = $e->getResponse();
51
52
        $level = floor($response->getStatusCode() / 100);
53
54
        if ($level == '4') {
55
            $label = 'Client error response';
56
        } elseif ($level == '5') {
57
            $label = 'Server error response';
58
        } else {
59
            $label = 'Unsuccessful response';
60
        }
61
62
        $message = $label .
63
            "\n [url] " . $request->getUri() .
64
            "\n [status code] " . $response->getStatusCode() .
65
            "\n [message] ";
66
67
        $body = $response->getBody();
68
        $json = json_decode($body, true);
69
        $message .= ($json != null && array_key_exists('message', $json)) ? $json['message'] : $body;
70
71
        if ($level == '4' && strpos($message, 'page with this title already exists') !== false) {
72
            return new DuplicateTitleException($message, 0, $e->getPrevious());
73
        }
74
75
        return new BadResponseException($message, $request, $response, $e->getPrevious());
76
    }
77
78
    public function getPage($id)
79
    {
80
        $url = "content/$id?expand=ancestors,version,body.storage";
81
82
        try {
83
            $result = json_decode($this->getClient()->get($url)->getBody(), true);
84
        } catch (BadResponseException $e) {
85
            throw $this->handleError($e);
86
        }
87
88
        $ancestor_id = null;
89
        if (array_key_exists('ancestors', $result) && count($result['ancestors'])) {
90
            $ancestor_page = end($result['ancestors']); // We need the direct parent
91
            $ancestor_id = $ancestor_page['id'];
92
        }
93
94
        return [
95
            'id' => $result['id'],
96
            'ancestor_id' => $ancestor_id,
97
            'title' => $result['title'],
98
            'version' => $result['version']['number'],
99
            'content' => $result['body']['storage']['value'],
100
        ];
101
    }
102
103
    /**
104
     * Get a list of pages.
105
     *
106
     * @param int $rootPage
107
     * @param bool $recursive
108
     *
109
     * @return array
110
     */
111
    public function getList($rootPage, $recursive = false)
112
    {
113
        $increment = 15;
114
115
        // We set a limit of 15 as it appears that
116
        // Confluence fails silently when retrieving
117
        // more than 20 entries with "body.storage"
118
        $base_url = $url = "content/$rootPage/child/page?expand=version,body.storage&limit=$increment";
119
        $start = 0;
120
121
        $pages = [];
122
123
        do {
124
            try {
125
                $hierarchy = json_decode($this->getClient()->get($url)->getBody(), true);
126
            } catch (BadResponseException $e) {
127
                throw $this->handleError($e);
128
            }
129
130
            foreach ($hierarchy['results'] as $result) {
131
                $pages[$result['title']] = [
132
                    'id' => $result['id'],
133
                    'title' => $result['title'],
134
                    'version' => $result['version']['number'],
135
                    'content' => $result['body']['storage']['value'],
136
                ];
137
138
                if ($recursive) {
139
                    $pages[$result['title']]['children'] = $this->getList($result['id'], true);
140
                }
141
            }
142
143
            // We don't use _links->next as after ~30 elements
144
            // it doesn't show any new elements. This seems
145
            // to be a bug in Confluence
146
            $start += $increment;
147
            $url = "$base_url&start=$start";
148
        } while (!empty($hierarchy['results']));
149
150
        return $pages;
151
    }
152
153
    /**
154
     * @param int $parent_id
155
     * @param string $title
156
     * @param string $content
157
     *
158
     * @return int
159
     */
160
    public function createPage($parent_id, $title, $content)
161
    {
162
        $body = [
163
            'type' => 'page',
164
            'space' => ['key' => $this->space],
165
            'title' => $title,
166
            'body' => ['storage' => ['value' => $content, 'representation' => 'storage']],
167
        ];
168
169
        if ($parent_id) {
170
            $body['ancestors'] = [['type' => 'page', 'id' => $parent_id]];
171
        }
172
173
        try {
174
            $response = json_decode($this->getClient()->post('content', ['json' => $body])->getBody(), true);
175
        } catch (BadResponseException $e) {
176
            throw $this->handleError($e);
177
        }
178
179
        return $response['id'];
180
    }
181
182
    /**
183
     * @param int $parent_id
184
     * @param int $page_id
185
     * @param int $newVersion
186
     * @param string $title
187
     * @param string $content
188
     */
189
    public function updatePage($parent_id, $page_id, $newVersion, $title, $content)
190
    {
191
        $body = [
192
            'type' => 'page',
193
            'space' => ['key' => $this->space],
194
            'version' => ['number' => $newVersion, 'minorEdit' => true],
195
            'title' => $title,
196
            'body' => ['storage' => ['value' => $content, 'representation' => 'storage']],
197
        ];
198
199
        if ($parent_id) {
200
            $body['ancestors'] = [['type' => 'page', 'id' => $parent_id]];
201
        }
202
203
        try {
204
            $this->getClient()->put("content/$page_id", ['json' => $body]);
205
        } catch (BadResponseException $e) {
206
            $error = $this->handleError($e);
207
208
            $re = '/\[([0-9]*),([0-9]*)\]$/';
209
            preg_match($re, $error->getMessage(), $matches, PREG_OFFSET_CAPTURE, 0);
210
211
            if (count($matches) == 3) {
212
                echo "\nContent: \n";
213
                echo $this->showSourceCode($content, $matches[1][0], $matches[2][0]);
214
            }
215
216
            throw $error;
217
        }
218
    }
219
220
    public function showSourceCode($css, $lineNumber, $column)
221
    {
222
        $lines = preg_split("/\r?\n/", $css);
223
224
        if ($lines === false) {
225
            return $css;
226
        }
227
228
        $start = max($lineNumber - 3, 0);
229
        $end = min($lineNumber + 2, count($lines));
230
231
        $maxWidth = strlen("$end");
232
233
        $filtered = array_slice($lines, $start, $end - $start);
234
235
        $prepared = [];
236
        foreach ($filtered as $index => $line) {
237
            $number = $start + 1 + $index;
238
            $gutter = substr(' ' . (' ' . $number), -$maxWidth) . ' | ';
239
240
            if ($number == $lineNumber) {
241
                $spacing = str_repeat(' ', strlen($gutter) + $column - 2);
242
                $prepared[] = '>' . $gutter . $line . "\n " . $spacing . '^';
243
            } else {
244
                $prepared[] = ' ' . $gutter . $line;
245
            }
246
        }
247
248
        return implode("\n", $prepared);
249
    }
250
251
    /**
252
     * Delete a page.
253
     *
254
     * @param int $page_id
255
     *
256
     * @return mixed
257
     */
258
    public function deletePage($page_id)
259
    {
260
        try {
261
            return json_decode($this->getClient()->delete('content/' . $page_id)->getBody(), true);
262
        } catch (BadResponseException $e) {
263
            throw $this->handleError($e);
264
        }
265
    }
266
267
    private function getAttachment($id, $attachment)
268
    {
269
        // Check if an attachment with
270
        // this name is uploaded
271
        try {
272
            $url = "content/$id/child/attachment?filename=" . urlencode($attachment['filename']);
273
274
            return json_decode($this->getClient()->get($url)->getBody(), true);
275
        } catch (BadResponseException $e) {
276
            throw $this->handleError($e);
277
        }
278
    }
279
280
    private function putAttachment($url, $attachment)
281
    {
282
        $contents = array_key_exists('file', $attachment) ? fopen($attachment['file']->getPath(), 'r') : $attachment['content'];
283
284
        try {
285
            $this->getClient()->post(
286
                $url,
287
                [
288
                    'multipart' => [['name' => 'file', 'contents' => $contents, 'filename' => $attachment['filename']]],
289
                    'headers' => ['X-Atlassian-Token' => 'nocheck'],
290
                ]
291
            );
292
        } catch (BadResponseException $e) {
293
            throw $this->handleError($e);
294
        }
295
    }
296
297
    private function getFileSize($attachment)
298
    {
299
        if (array_key_exists('file', $attachment)) {
300
            return filesize($attachment['file']->getPath());
301
        }
302
303
        if (function_exists('mb_strlen')) {
304
            return mb_strlen($attachment['content']);
305
        }
306
307
        return strlen($attachment['content']);
308
    }
309
310
    /**
311
     * @param int $id
312
     * @param array $attachment
313
     * @param callback $write Write output to the console
314
     */
315
    public function uploadAttachment($id, $attachment, $write)
316
    {
317
        $result = $this->getAttachment($id, $attachment);
318
319
        $url = "content/$id/child/attachment";
320
321
        // If the attachment is already uploaded,
322
        // the update URL is different
323
        if (count($result['results'])) {
324
            if ($this->getFileSize($attachment) == $result['results'][0]['extensions']['fileSize']) {
325
                $write(' ( An attachment of the same size already exists, skipping. )');
326
327
                return;
328
            }
329
330
            $url .= "/{$result['results'][0]['id']}/data";
331
        }
332
333
        $this->putAttachment($url, $attachment);
334
    }
335
}
336