Passed
Push — master ( 821580...ac6094 )
by Stéphane
02:23
created

Api   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Test Coverage

Coverage 6.34%

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 145
dl 0
loc 325
ccs 9
cts 142
cp 0.0634
rs 8.96
c 4
b 1
f 0
wmc 43

14 Methods

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

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
     * @return Client
29
     */
30 1
    public function getClient()
31
    {
32
        $options = [
33 1
            'base_uri' => $this->base_url . 'rest/api/',
34 1
            'auth' => [$this->user, $this->pass],
35
        ];
36
37 1
        return new Client($options);
38
    }
39
40
    /**
41
     * The standard error message from guzzle is quite poor in informations,
42
     * this will give little bit more sense to it and return it
43
     *
44
     * @param BadResponseException $e
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
     * @return array
109
     */
110
    public function getList($rootPage, $recursive = false)
111
    {
112
        $increment = 15;
113
114
        // We set a limit of 15 as it appears that
115
        // Confluence fails silently when retrieving
116
        // more than 20 entries with "body.storage"
117
        $base_url = $url = "content/$rootPage/child/page?expand=version,body.storage&limit=$increment";
118
        $start = 0;
119
120
        $pages = [];
121
122
        do {
123
            try {
124
                $hierarchy = json_decode($this->getClient()->get($url)->getBody(), true);
125
            } catch (BadResponseException $e) {
126
                throw $this->handleError($e);
127
            }
128
129
            foreach ($hierarchy['results'] as $result) {
130
                $pages[$result['title']] = [
131
                    'id' => $result['id'],
132
                    'title' => $result['title'],
133
                    'version' => $result['version']['number'],
134
                    'content' => $result['body']['storage']['value'],
135
                ];
136
137
                if ($recursive) {
138
                    $pages[$result['title']]['children'] = $this->getList($result['id'], true);
139
                }
140
            }
141
142
            // We don't use _links->next as after ~30 elements
143
            // it doesn't show any new elements. This seems
144
            // to be a bug in Confluence
145
            $start += $increment;
146
            $url = "$base_url&start=$start";
147
        } while (!empty($hierarchy['results']));
148
149
        return $pages;
150
    }
151
152
    /**
153
     * @param int $parent_id
154
     * @param string $title
155
     * @param string $content
156
     * @return int
157
     */
158
    public function createPage($parent_id, $title, $content)
159
    {
160
        $body = [
161
            'type' => 'page',
162
            'space' => ['key' => $this->space],
163
            'title' => $title,
164
            'body' => ['storage' => ['value' => $content, 'representation' => 'storage']],
165
        ];
166
167
        if ($parent_id) {
168
            $body['ancestors'] = [['type' => 'page', 'id' => $parent_id]];
169
        }
170
171
        try {
172
            $response = json_decode($this->getClient()->post('content', ['json' => $body])->getBody(), true);
173
        } catch (BadResponseException $e) {
174
            throw $this->handleError($e);
175
        }
176
177
        return $response['id'];
178
    }
179
180
    /**
181
     * @param int $parent_id
182
     * @param int $page_id
183
     * @param int $newVersion
184
     * @param string $title
185
     * @param string $content
186
     */
187
    public function updatePage($parent_id, $page_id, $newVersion, $title, $content)
188
    {
189
        $body = [
190
            'type' => 'page',
191
            'space' => ['key' => $this->space],
192
            'version' => ['number' => $newVersion, 'minorEdit' => true],
193
            'title' => $title,
194
            'body' => ['storage' => ['value' => $content, 'representation' => 'storage']],
195
        ];
196
197
        if ($parent_id) {
198
            $body['ancestors'] = [['type' => 'page', 'id' => $parent_id]];
199
        }
200
201
        try {
202
            $this->getClient()->put("content/$page_id", ['json' => $body]);
203
        } catch (BadResponseException $e) {
204
            $error = $this->handleError($e);
205
206
            $re = '/\[([0-9]*),([0-9]*)\]$/';
207
            preg_match($re, $error->getMessage(), $matches, PREG_OFFSET_CAPTURE, 0);
208
209
            if (count($matches) == 3) {
210
                echo "\nContent: \n";
211
                echo $this->showSourceCode($content, $matches[1][0], $matches[2][0]);
212
            }
213
214
            throw $error;
215
        }
216
    }
217
218
    public function showSourceCode($css, $lineNumber, $column)
219
    {
220
        $lines = preg_split("/\r?\n/", $css);
221
222
        if ($lines === false) {
223
            return $css;
224
        }
225
226
        $start = max($lineNumber - 3, 0);
227
        $end   = min($lineNumber + 2, count($lines));
228
229
        $maxWidth = strlen("$end");
230
231
        $filtered = array_slice($lines, $start, $end - $start);
232
233
        $prepared = [];
234
        foreach ($filtered as $index => $line) {
235
236
            $number = $start + 1 + $index;
237
            $gutter = substr(' ' . (' ' . $number), -$maxWidth) . ' | ';
238
239
            if ($number == $lineNumber) {
240
                $spacing = str_repeat(" ", strlen($gutter) + $column - 2);
241
                $prepared[] = '>' . $gutter . $line . "\n " . $spacing . '^';
242
            } else {
243
                $prepared[] = ' ' . $gutter . $line;
244
            }
245
        }
246
247
        return implode("\n", $prepared);
248
    }
249
250
    /**
251
     * Delete a page
252
     *
253
     * @param int $page_id
254
     * @return mixed
255
     */
256
    public function deletePage($page_id)
257
    {
258
        try {
259
            return json_decode($this->getClient()->delete('content/' . $page_id)->getBody(), true);
260
        } catch (BadResponseException $e) {
261
            throw $this->handleError($e);
262
        }
263
    }
264
265
    private function getAttachment($id, $attachment)
266
    {
267
        // Check if an attachment with
268
        // this name is uploaded
269
        try {
270
            $url = "content/$id/child/attachment?filename=" . urlencode($attachment['filename']);
271
            return json_decode($this->getClient()->get($url)->getBody(), true);
272
        } catch (BadResponseException $e) {
273
            throw $this->handleError($e);
274
        }
275
    }
276
277
    private function putAttachment($url, $attachment)
278
    {
279
        $contents = array_key_exists('file', $attachment) ? fopen($attachment['file']->getPath(), 'r') : $attachment['content'];
280
281
        try {
282
            $this->getClient()->post(
283
                $url,
284
                [
285
                    'multipart' => [['name' => 'file', 'contents' => $contents, 'filename' => $attachment['filename']]],
286
                    'headers' => ['X-Atlassian-Token' => 'nocheck'],
287
                ]
288
            );
289
        } catch (BadResponseException $e) {
290
            throw $this->handleError($e);
291
        }
292
    }
293
294
    private function getFileSize($attachment)
295
    {
296
        if (array_key_exists('file', $attachment)) {
297
            return filesize($attachment['file']->getPath());
298
        }
299
300
        if (function_exists('mb_strlen')) {
301
            return mb_strlen($attachment['content']);
302
        }
303
304
        return strlen($attachment['content']);
305
    }
306
307
    /**
308
     * @param int $id
309
     * @param array $attachment
310
     * @param callback $write Write output to the console
311
     */
312
    public function uploadAttachment($id, $attachment, $write)
313
    {
314
        $result = $this->getAttachment($id, $attachment);
315
316
        $url = "content/$id/child/attachment";
317
318
        // If the attachment is already uploaded,
319
        // the update URL is different
320
        if (count($result['results'])) {
321
322
            if ($this->getFileSize($attachment) == $result['results'][0]['extensions']['fileSize']) {
323
                $write(" ( An attachment of the same size already exists, skipping. )");
324
                return;
325
            }
326
327
            $url .= "/{$result['results'][0]['id']}/data";
328
        }
329
330
        $this->putAttachment($url, $attachment);
331
    }
332
}
333