API::createLink()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 22
Code Lines 11

Duplication

Lines 3
Ratio 13.64 %

Code Coverage

Tests 10
CRAP Score 3.0067

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 3
loc 22
ccs 10
cts 11
cp 0.9091
rs 9.2
cc 3
eloc 11
nc 4
nop 3
crap 3.0067
1
<?php
2
3
namespace Barracuda\Copy;
4
5
/**
6
 * Copy API class
7
 *
8
 * @package Copy
9
 * @license https://raw.github.com/copy-app/php-client-library/master/LICENSE MIT
10
 */
11
class API
12
{
13
    /**
14
     * API URl
15
     * @var string $api_url
16
     */
17
    protected $api_url = 'https://api.copy.com';
18
19
    /**
20
     * Instance of curl
21
     * @var resource $curl
22
     */
23
    private $curl;
24
25
    /**
26
     * @var array
27
     * User data
28
     */
29
    private $signature;
30
31
    /**
32
     * Constructor
33
     *
34
     * @param string $consumerKey    OAuth consumer key
35
     * @param string $consumerSecret OAuth consumer secret
36
     * @param string $accessToken    OAuth access token
37
     * @param string $tokenSecret    OAuth token secret
38
     */
39 12
    public function __construct($consumerKey, $consumerSecret, $accessToken, $tokenSecret)
40
    {
41
        // oauth setup
42 12
        $this->signature = array(
43 12
            'consumer_key' => $consumerKey,
44 12
            'shared_secret' => $consumerSecret,
45 12
            'oauth_token' => $accessToken,
46
            'oauth_secret' => $tokenSecret
47 12
        );
48
49 12
        $this->__wakeup();
50 12
    }
51
52
    /**
53
     * Wakeup function on unserialize
54
     * 
55
     */
56 12
    private function __wakeup()
57
    {
58
        // curl setup
59 12
        $this->curl = curl_init();
60 12
        if (!$this->curl) {
61
            throw new \Exception("Failed to initialize curl");
62
        }
63
64
        // ca bundle
65 12
        $cacrt = __DIR__ . '/ca.crt';
66 12
        if (!is_file($cacrt)) {
67
            throw new \Exception("Failed to load ca certificate");
68
        }
69
        // check for phar execution
70
        // in case we have to move the .crt file to a temp folder so curl is able to load it
71 12
        if (substr(__FILE__, 0, 7) == 'phar://') {
72
            $cacrt = self::extractPharCacert($cacrt);
73
        }
74 12
        curl_setopt($this->curl, CURLOPT_CAINFO, $cacrt);
75 12
    }
76
77
    /**
78
     * Upload a file from a string
79
     *
80
     * @param string $path full path containing leading slash and file name
81
     * @param string $data binary data
82
     *
83
     * @return object described in createFile()
84
     */
85 1
    public function uploadFromString($path, $data)
86
    {
87
        // create the temporary stream
88 1
        $stream = fopen('php://temp', 'w+');
89
90
        // write the data
91 1
        fwrite($stream, $data);
92
93
        // rewind the pointer
94 1
        rewind($stream);
95
96
        // upload as a stream
97 1
        return $this->uploadFromStream($path, $stream);
98
    }    
99
100
    /**
101
     * Upload a file from a stream resource
102
     *
103
     * @param string $path full path containing leading slash and file name
104
     * @param resource $stream resource to read data from
105
     *
106
     * @return object described in createFile()
107
     */
108 1
    public function uploadFromStream($path, $stream)
109
    {
110
        // send data 1MB at a time
111 1
        $parts = array();
112 1
        $limit = 1048576;
113 1
        $buffer = '';
114 1
        while ($buffer .= fread($stream, $limit)) {
115
            // check $buffer size for remote stream
116
            // ref. http://php.net/manual/function.fread.php
117
            // see Example #3 Remote fread() examples
118 1
            if (!feof($stream) && strlen($buffer) < $limit) {
119
                continue;
120
            }
121 1
            $next = '';
122 1
            if (strlen($buffer) > $limit) {
123
                $next = substr($buffer, $limit);
124
                $buffer = substr($buffer, 0, $limit);
125
            }
126 1
            $parts[] = $this->sendData($buffer);
127 1
            $buffer = $next;
128 1
        }
129
130
        // close the stream
131 1
        fclose($stream);
132
133
        // update the file in the cloud
134 1
        return $this->createFile('/' . $path, $parts);
135
    }
136
137
    /**
138
     * Read a file to a string
139
     *
140
     * @param string $path full path containing leading slash and file name
141
     *
142
     * @return array contains key of contents which contains binary data of the file
143
     */
144 1
    public function readToString($path)
145
    {
146 1
        $object = $this->readToStream($path);
147 1
        $object['contents'] = stream_get_contents($object['stream']);
148 1
        fclose($object['stream']);
149 1
        unset($object['stream']);
150
151 1
        return $object;
152
    }
153
154
    /**
155
     * Read a file to a stream
156
     *
157
     * @param string $path full path containing leading slash and file name
158
     *
159
     * @return array contains key of stream which contains a stream resource
160
     */
161 1
    public function readToStream($path)
162
    {
163
        // create the temporary stream
164 1
        $stream = fopen('php://temp', 'w+');
165
166
        // obtain the list of parts for the file (should be an array of one)
167 1
        $files = $this->listPath('/' . $path, array('include_parts' => true));
168
169 1
        if (is_array($files) === false || sizeof($files) !== 1) {
170
            throw new \Exception("Could not find file at path: '" . $path . "'");
171
        }
172
173
        // found it, verify its a file
174 1
        $file = array_pop($files);
175 1
        if ($file->{"type"} != "file") {
176
            throw new \Exception("Could not find file at path: '" . $path . "'");
177
        }
178
179
        // obtain each part and add it to the stream
180 1
        foreach ($file->{"revisions"}[0]->{"parts"} as $part) {
181 1
            $data = $this->getPart($part->{"fingerprint"}, $part->{"size"}, $file->{"share_id"});
182 1
            fwrite($stream, $data);
183 1
        }
184
185
        // rewind the pointer
186 1
        rewind($stream);
187
188 1
        return compact('stream');
189
    }
190
191
    /**
192
     * Send a request to remove a given file.
193
     *
194
     * @param string $path full path containing leading slash and file name
195
     *
196
     * @return bool true if the file was removed successfully
197
     */
198 2
    public function removeFile($path)
199
    {
200 2
        return $this->removeItem($path, 'file');
201
    }
202
203
    /**
204
     * Send a request to remove a given dir.
205
     *
206
     * @param string $path full path containing leading slash and dir name
207
     *
208
     * @return bool true if the dir was removed successfully
209
     */
210 1
    public function removeDir($path)
211
    {
212 1
        return $this->removeItem($path, 'dir');
213
    }
214
215
    /**
216
     * Send a request to remove a given item.
217
     *
218
     * @param string $path full path containing leading slash and file name
219
     * @param string $type file or dir
220
     *
221
     * @return bool true if the item was removed successfully
222
     */
223 3
    private function removeItem($path, $type)
224
    {
225 3
        $request = array();
226 3
        $request["object_type"] = $type;
227
228 3
        $this->updateObject('remove', $path, $request);
229
230 3
        return true;
231
    }
232
233
    /**
234
     * Rename a file
235
     *
236
     * Object structure:
237
     * {
238
     *  object_id: "4008"
239
     *  path: "/example"
240
     *  type: "dir" || "file"
241
     *  share_id: "0"
242
     *  share_owner: "21956799"
243
     *  company_id: NULL
244
     *  size: filesize in bytes, 0 for folders
245
     *  created_time: unix timestamp, e.g. "1389731126"
246
     *  modified_time: unix timestamp, e.g. "1389731126"
247
     *  date_last_synced: unix timestamp, e.g. "1389731126"
248
     *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
249
     *  mime_type: string
250
     *  revisions: array of revision objects
251
     * }
252
     *
253
     * @param string $source_path full path containing leading slash and file name
254
     * @param string $destination_path full path containing leading slash and file name
255
     *
256
     * @return stdClass using structure as noted above
257
     */
258 1
    public function rename($source_path, $destination_path)
259
    {
260 1
        return $this->updateObject('rename', $source_path, array('new_path' => $destination_path));
261
    }
262
263
    /**
264
     * Copy an item
265
     *
266
     * Object structure:
267
     * {
268
     *  object_id: "4008"
269
     *  path: "/example"
270
     *  type: "dir" || "file"
271
     *  share_id: "0"
272
     *  share_owner: "21956799"
273
     *  company_id: NULL
274
     *  size: filesize in bytes, 0 for folders
275
     *  created_time: unix timestamp, e.g. "1389731126"
276
     *  modified_time: unix timestamp, e.g. "1389731126"
277
     *  date_last_synced: unix timestamp, e.g. "1389731126"
278
     *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
279
     *  mime_type: string
280
     *  revisions: array of revision objects
281
     * }
282
     *
283
     * @param string $source_path full path containing leading slash and file name
284
     * @param string $destination_path full path containing leading slash and file name
285
     *
286
     * @return stdClass using structure as noted above
287
     */
288 1
    public function copy($source_path, $destination_path)
289
    {
290 1
        return $this->updateObject('copy', $source_path, array('new_path' => $destination_path));
291
    }
292
293
    /**
294
     * List objects within a path
295
     *
296
     * Object structure:
297
     * {
298
     *  object_id: "4008"
299
     *  path: "/example"
300
     *  type: "dir" || "file"
301
     *  share_id: "0"
302
     *  share_owner: "21956799"
303
     *  company_id: NULL
304
     *  size: filesize in bytes, 0 for folders
305
     *  created_time: unix timestamp, e.g. "1389731126"
306
     *  modified_time: unix timestamp, e.g. "1389731126"
307
     *  date_last_synced: unix timestamp, e.g. "1389731126"
308
     *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
309
     *  mime_type: string
310
     *  revisions: array of revision objects
311
     * }
312
     *
313
     * @param  string $path              full path with leading slash and optionally a filename
314
     * @param  array  $additionalOptions used for passing options such as include_parts
315
     *
316
     * @return array List of file/folder objects described above.
317
     */
318 3
    public function listPath($path, $additionalOptions = null)
319
    {
320 3
        $list_watermark = false;
321 3
        $return = array();
322
323
        do {
324 3
            $request = array();
325 3
            $request["path"] = $path;
326 3
            $request["max_items"] = 100;
327 3
            $request["list_watermark"] = $list_watermark;
328
329 3
            if ($additionalOptions) {
330 2
                $request = array_merge($request, $additionalOptions);
331 2
            }
332
333 3
            $result = $this->post("list_objects", $this->encodeRequest("list_objects", $request), true);
334
335
            // add the children if we got some, otherwise add the root object itself to the return
336 3
            if (isset($result->result->children) && empty($result->result->children) === false) {
337 1
                $return = array_merge($return, $result->result->children);
338 1
                $list_watermark = $result->result->list_watermark;
339 1
            } else {
340 2
                $return[] = $result->result->object;
341
            }
342 3
        } while (isset($result->result->more_items) && $result->result->more_items == 1);
343
344 3
        return $return;
345
    }
346
347
    /**
348
     * Get directory or file meta data
349
     *
350
     * Object structure:
351
     * {
352
     *  id: "/copy/example"
353
     *  path: "/example"
354
     *  name: "example",
355
     *  type: "dir" || "file"
356
     *  share_id: "0"
357
     *  share_owner: "21956799"
358
     *  company_id: NULL
359
     *  size: filesize in bytes, 0 for folders
360
     *  created_time: unix timestamp, e.g. "1389731126"
361
     *  modified_time: unix timestamp, e.g. "1389731126"
362
     *  date_last_synced: unix timestamp, e.g. "1389731126"
363
     *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
364
     *  mime_type: string
365
     *  revisions: array of revision objects
366
     *  children: array of children objects
367
     * }
368
     *
369
     * @param  string $path  full path with leading slash and optionally a filename
370
     * @param  string $root  Optional, "copy" is the first level of the real filesystem
371
     *
372
     * @return array List of file/folder objects described above.
373
     */
374 1
    public function getMeta($path, $root = "copy")
375
    {
376 1
        $result = $this->get("meta/" . $root . $path);
377
378
        // Decode the json reply
379 1
        $result = json_decode($result);
380
381
        // Check for errors
382 1 View Code Duplication
        if (isset($result->error)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
383
            if ($result->error == 1301) {
384
            	// item not found
385
            	return array();
386
            }
387
            throw new \Exception("Error listing path " . $path . ": (" . $result->error . ") '" . $result->message . "'");
388
        }
389
390 1
        return $result;
391
    }
392
393
    /**
394
     * Create a dir
395
     *
396
     * Object structure:
397
     * {
398
     *  object_id: "4008"
399
     *  path: "/example"
400
     *  type: "dir"
401
     *  share_id: "0"
402
     *  share_owner: "21956799"
403
     *  company_id: NULL
404
     *  size: filesize in bytes, 0 for folders
405
     *  created_time: unix timestamp, e.g. "1389731126"
406
     *  modified_time: unix timestamp, e.g. "1389731126"
407
     *  date_last_synced: unix timestamp, e.g. "1389731126"
408
     *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
409
     * }
410
     *
411
     * @param string $path      full path containing leading slash and dir name
412
     * @param bool   $recursive true to create parent directories
413
     *
414
     * @return object described above.
415
     */
416 1
    public function createDir($path, $recursive = true)
417
    {
418
        $request = array(
419 1
            'object_type' => 'dir',
420 1
            'recurse' => $recursive,
421 1
            );
422
423 1
        return $this->updateObject('create', $path, $request);
424
    }
425
426
    /**
427
     * Create a file with a set of data parts
428
     *
429
     * Object structure:
430
     * {
431
     *  object_id: "4008"
432
     *  path: "/example"
433
     *  type: "file"
434
     *  share_id: "0"
435
     *  share_owner: "21956799"
436
     *  company_id: NULL
437
     *  size: filesize in bytes, 0 for folders
438
     *  created_time: unix timestamp, e.g. "1389731126"
439
     *  modified_time: unix timestamp, e.g. "1389731126"
440
     *  date_last_synced: unix timestamp, e.g. "1389731126"
441
     *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
442
     *  mime_type: string
443
     *  revisions: array of revision objects
444
     * }
445
     *
446
     * @param string $path  full path containing leading slash and file name
447
     * @param array  $parts contains arrays of parts returned by \Barracuda\Copy\API\sendData
448
     *
449
     * @return object described above.
450
     */
451 2
    public function createFile($path, $parts)
452
    {
453 2
        $request = array();
454 2
        $request["object_type"] = "file";
455 2
        $request["parts"] = array();
456
457 2
        $offset = 0;
458 2
        foreach ($parts as $part) {
459
            $partRequest = array(
460 2
                'fingerprint' => $part["fingerprint"],
461 2
                'offset' => $offset,
462 2
                'size' => $part["size"],
463 2
                );
464
465 2
            array_push($request["parts"], $partRequest);
466
467 2
            $offset += $part["size"];
468 2
        }
469
470 2
        $request["size"] = $offset;
471
472 2
        return $this->updateObject('create', $path, $request);
473
    }
474
475
    /**
476
     * Generate the fingerprint for a string of data.
477
     *
478
     * @param string $data Data part to generate the fingerprint for.
479
     *
480
     * @return string Fingerprint for $data.
481
    **/
482 2
    public function fingerprint($data)
483
    {
484 2
        return md5($data) . sha1($data);
485
    }
486
487
    /**
488
     * Send a piece of data
489
     *
490
     * @param  string $data    binary data
491
     * @param  int    $shareId setting this to zero is best, unless share id is known
492
     *
493
     * @return array  contains fingerprint and size, to be used when creating a file
494
     */
495 2
    public function sendData($data, $shareId = 0)
496
    {
497
        // first generate a part hash
498 2
        $fingerprint = $this->fingerprint($data);
499 2
        $part_size = strlen($data);
500
501
        // see if the cloud has this part, and send if needed
502 2
        if(!$this->hasPart($fingerprint, $part_size, $shareId)) {
503 1
            $this->sendPart($fingerprint, $part_size, $data, $shareId);
504 1
        }
505
506
        // return information about this part
507 2
        return array("fingerprint" => $fingerprint, "size" => $part_size);
508
    }
509
510
    /**
511
     * Send a data part
512
     *
513
     * @param string $fingerprint md5 and sha1 concatenated
514
     * @param int    $size        number of bytes
515
     * @param string $data        binary data
516
     * @param int    $shareId     setting this to zero is best, unless share id is known
517
     *
518
     */
519 1
    public function sendPart($fingerprint, $size, $data, $shareId = 0)
520
    {
521
        // They must match
522 1
        if (md5($data) . sha1($data) != $fingerprint) {
523
            throw new \Exception("Failed to validate part hash");
524
        }
525
526
        $request = array(
527
            'parts' => array(
528
                array(
529 1
                    'share_id' => $shareId,
530 1
                    'fingerprint' => $fingerprint,
531 1
                    'size' => $size,
532
                    'data' => 'BinaryData-0-' . $size
533 1
                )
534 1
            )
535 1
        );
536
537 1
        $result = $this->post("send_object_parts_v2", $this->encodeRequest("send_object_parts_v2", $request) . chr(0) . $data, true);
538
539 1
        if ($result->result->has_failed_parts) {
540
            throw new \Exception("Error sending part: " . $result->result->failed_parts[0]->message);
541
        }
542 1
    }
543
544
    /**
545
     * Check to see if a part already exists
546
     *
547
     * @param  string $fingerprint md5 and sha1 concatenated
548
     * @param  int    $size        number of bytes
549
     * @param  int    $shareId     setting this to zero is best, unless share id is known
550
     * @return bool   true if part already exists
551
     */
552 2
    public function hasPart($fingerprint, $size, $shareId = 0)
553
    {
554
        $request = array(
555
            'parts' => array(
556
                array(
557 2
                    'share_id' => $shareId,
558 2
                    'fingerprint' => $fingerprint,
559
                    'size' => $size
560 2
                )
561 2
            )
562 2
        );
563
564 2
        $result = $this->post("has_object_parts_v2", $this->encodeRequest("has_object_parts_v2", $request), true);
565
566 2
        if (empty($result->result->needed_parts)) {
567 1
            return true;
568
        } else {
569 1
            $part = $result->result->needed_parts[0];
570 1
            if (!empty($part->message)) {
571
                throw new \Exception("Error checking for part: " . $part->message);
572
            } else {
573 1
                return false;
574
            }
575
        }
576
    }
577
578
    /**
579
     * Get a part
580
     *
581
     * @param  string $fingerprint md5 and sha1 concatinated
582
     * @param  int    $size        number of bytes
583
     * @param  int    $shareId     setting this to zero is best, unless share id is known
584
     *
585
     * @return string binary data
586
     */
587 2
    public function getPart($fingerprint, $size, $shareId = 0)
588
    {
589
        $request = array(
590
            'parts' => array(
591
                array(
592 2
                    'share_id' => $shareId,
593 2
                    'fingerprint' => $fingerprint,
594
                    'size' => $size
595 2
                )
596 2
            )
597 2
        );
598
599 2
        $result = $this->post("get_object_parts_v2", $this->encodeRequest("get_object_parts_v2", $request));
600
601
        // Find the null byte
602 2
        $null_offset = strpos($result, chr(0));
603
604
        // Grab the binary payload
605 2
        $binary = substr($result, $null_offset + 1, strlen($result) - $null_offset);
606
607 2
        if ($binary === false) {
608
            throw new \Exception("Error getting part data");
609
        }
610
611
        // Grab the json payload
612 2
        $json = isset($binary) ? substr($result, 0, $null_offset) : $result;
613
614 2
        if ($json === false) {
615
            throw new \Exception("Error getting part data");
616
        }
617
618
        // Decode the json reply
619 2
        $result = json_decode($json);
620
621
        // Check for errors
622 2
        if (isset($result->error)) {
623
            throw new \Exception("Error getting part data");
624
        }
625
626 2
        if (isset($result->result->parts[0]->message)) {
627
            throw new \Exception("Error getting part data: " . $result->result->parts[0]->message);
628
        }
629
630
        // Get the part data (since there is only one part the binary payload should just be the data)
631 2
        if (strlen($binary) != $size) {
632
            throw new \Exception("Error getting part data");
633
        }
634
635 2
        return $binary;
636
    }
637
638
    /**
639
     * Create a New Link
640
     * 
641
     * Object structure:
642
     * {
643
     *   id: "MBrss3roGDk4",
644
     *   name: "My Cool Shared Files",
645
     *   public: true,
646
     *   url: "https://copy.com/MBrss3roGDk4",
647
     *   url_short: "https://copy.com/MBrss3roGDk4",
648
     *   creator_id: "1381231",
649
     *   company_id: null,
650
     *   confirmation_required: false,
651
     *   status: "viewed",
652
     *   permissions: "read"
653
     * }
654
     * 
655
     * @param array|string  $paths   target item(s) path
656
     * @param array         $options option attributes, (bool) "public", (string) "name"
657
     * @param string        $root
658
     * 
659
     * @throws \Exception
660
     * 
661
     * @return object described above.
662
     */
663 1
    public function createLink($paths, $options = array(), $root = 'copy')
664
    {
665 1
        if (is_string($paths)) {
666 1
            $paths = array($paths);
667 1
        }
668
        $paths = array_map(function($p) use ($root){return '/' . $root . $p;}, $paths);
669
        
670 1
        $data = array("paths" => $paths);
671 1
        $data = array_merge($data, $options);
672
        
673 1
        $result = $this->post("links", $data);
0 ignored issues
show
Documentation introduced by
$data is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
674
        
675
        // Decode the json reply
676 1
        $result = json_decode($result);
677
        
678
        // Check for errors
679 1 View Code Duplication
        if (isset($result->error)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
680
               throw new \Exception("Error creating link for paths " . implode(', ', $paths) . ": (" . $result->error . ") '" . $result->message . "'");
681
        }
682
        
683 1
        return $result;
684
    }
685
686
    /**
687
     * Update meta object
688
     *
689
     * Object structure:
690
     * {
691
     *  object_id: "4008"
692
     *  path: "/example"
693
     *  type: "dir" || "file"
694
     *  share_id: "0"
695
     *  share_owner: "21956799"
696
     *  company_id: NULL
697
     *  size: filesize in bytes, 0 for folders
698
     *  created_time: unix timestamp, e.g. "1389731126"
699
     *  modified_time: unix timestamp, e.g. "1389731126"
700
     *  date_last_synced: unix timestamp, e.g. "1389731126"
701
     *  removed_time: unix timestamp, e.g. "1389731126" or empty string for non-deleted files/folders
702
     *  mime_type: string
703
     *  revisions: array of revision objects
704
     * }
705
     *
706
     * @param string $action
707
     * @param string $path
708
     * @param array $meta contains action, path, and other attributes of the object to update
709
     *
710
     * @return stdClass using structure as noted above
711
     */
712 8
    private function updateObject($action, $path, $meta)
713
    {
714
        // Add action and path to meta
715 8
        $meta["action"] = $action;
716 8
        $meta["path"] = $path;
717
718 8
        $result = $this->post("update_objects", $this->encodeRequest("update_objects", array("meta" => array($meta))), true);
719
720
        // Return the object
721 8
        return $result->{"result"}[0]->{"object"};
722
    }
723
724
    /**
725
     * Create and execute cURL request to send data.
726
     *
727
     * @param  string  $method         API method
728
     * @param  string  $data           raw request
729
     * @param  boolean $decodeResponse true to decode response
730
     *
731
     * @return mixed  result from curl_exec
732
     */
733 11
    private function post($method, $data, $decodeResponse = false)
734
    {
735 11
        if (is_array($data)) {
736 1
            $data = str_replace('\\/', '/', json_encode($data));
737 1
        }
738
739 11
        curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, "POST");
740 11
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $data);
741 11
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, $this->getHeaders($method));
742 11
        curl_setopt($this->curl, CURLOPT_URL, $this->api_url . "/" . $this->getEndpoint($method));
743 11
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
744 11
        curl_setopt($this->curl, CURLOPT_POST, 1);
745
746 11
        $result = curl_exec($this->curl);
747
748
        // If curl grossly failed, throw
749 11
        if ($result === false) {
750
            throw new \Exception("Curl failed to exec " . curl_error($this->curl));
751
        }
752
753
        // Decode the response if requested to do so
754 11
        if ($decodeResponse) {
755 10
            return $this->decodeResponse($result);
756
        } else {
757 3
            return $result;
758
        }
759
    }
760
761
    /**
762
     * Create and execute cURL request by GET method.
763
     *
764
     * @param  string $method API method
765
     *
766
     * @return mixed  result from curl_exec
767
     */
768 1
    protected function get($method)
769
    {
770 1
    	$method = str_replace("%2F", "/", rawurlencode($method));
771 1
        curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, "GET");
772 1
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, null);
773 1
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, $this->getHeaders($method, "GET"));
774 1
        curl_setopt($this->curl, CURLOPT_URL, $this->api_url . "/" . $this->GetEndpoint($method));
775 1
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
776 1
        curl_setopt($this->curl, CURLOPT_HTTPGET, 1);
777
778 1
        $result = curl_exec($this->curl);
779
780
        // If curl grossly failed, throw
781 1
        if ($result == FALSE) {
782
            throw new \Exception("Curl failed to exec " . curl_error($this->curl));
783
        }
784
785 1
        return $result;
786
    }
787
788
    /**
789
     * Return which cloud API end point to use for a given method.
790
     *
791
     * @param  string $method API method
792
     *
793
     * @return string uri of endpoint without leading slash
794
     */
795 12
    private function getEndpoint($method)
796
    {
797 12
        if ($method == "has_object_parts_v2" || $method == "send_object_parts_v2" || $method == "get_object_parts_v2") {
798 4
	        return "jsonrpc_binary";
799 12
        } else if ($method == "update_objects" || $method == "list_objects") {
800 10
            return "jsonrpc";
801
        } else {
802 2
        	return "rest/" . $method;
803
        }
804
    }
805
806
    /**
807
     * Generate the HTTP headers need for a given Cloud API method.
808
     *
809
     * @param  string $method      API method
810
     * @param  string $http_method Optional, HTTP request method
811
     *
812
     * @return array  contains headers to use for HTTP requests
813
     */
814 12
    public function getHeaders($method, $http_method = "POST")
815
    {
816 12
        $headers = array();
817
818 12
        $consumer = new \Eher\OAuth\Consumer($this->signature['consumer_key'], $this->signature['shared_secret']);
819 12
        $signatureMethod = new \Eher\OAuth\HmacSha1();
820 12
        $token = new \Eher\OAuth\Token($this->signature['oauth_token'], $this->signature['oauth_secret']);
821 12
        $request = \Eher\OAuth\Request::from_consumer_and_token(
822 12
            $consumer,
823 12
            $token,
824 12
            $http_method,
825 12
            $this->api_url . "/" . $this->GetEndpoint($method),
826 12
            array()
827 12
        );
828 12
        $request->sign_request($signatureMethod, $consumer, $token);
829
830 12
        if ($method == "has_object_parts_v2" || $method == "send_object_parts_v2" || $method == "get_object_parts_v2") {
831 4
            array_push($headers, "Content-Type: application/octet-stream");
832 4
        }
833
834 12
        array_push($headers, "X-Api-Version: 1.0");
835 12
        array_push($headers, "X-Client-Type: api");
836 12
        array_push($headers, "X-Client-Time: " . time());
837 12
        array_push($headers, $request->to_header());
838
839 12
        return $headers;
840
    }
841
842
    /**
843
     * JSON encode request data.
844
     *
845
     * @param  string $method Cloud API method
846
     * @param  array  $json   contains data to be encoded
847
     *
848
     * @return string JSON formatted request body
849
     */
850 10
    private function encodeRequest($method, $json)
851
    {
852
        $request = array(
853 10
            'jsonrpc' => '2.0',
854 10
            'id' => '0',
855 10
            'method' => $method,
856 10
            'params' => $json,
857 10
            );
858
859 10
        return str_replace('\\/', '/', json_encode($request));
860
    }
861
862
    /**
863
     * Decode a JSON response.
864
     *
865
     * @param string $response JSON response
866
     *
867
     * @return array JSON decoded string
868
     */
869 10
    private function decodeResponse($response)
870
    {
871
        // Decode the json reply
872 10
        $result = json_decode($response);
873
874
        // Check for errors
875 10
        if (isset($result->error)) {
876
            throw new \Exception("Error: '" . $result->error->message . "'");
877
        }
878
879 10
        return $result;
880
    }
881
882
    /**
883
     * Copies the phar cacert from a phar into the temp directory.
884
     *
885
     * @param  string $pharCacertPath Path to the phar cacert.
886
     *
887
     * @return string Returns the path to the extracted cacert file.
888
     */
889
    public static function extractPharCacert($pharCacertPath)
890
    {
891
        $certFile = sys_get_temp_dir() . '/barracuda-copycom-cacert.crt';
892
893
        if (!file_exists($pharCacertPath)) {
894
            throw new \Exception("Could not find " . $pharCacertPath);
895
        }
896
897
        // Copy the cacert file from the phar if it is not in the temp folder.
898
        if (!file_exists($certFile) || filesize($certFile) != filesize($pharCacertPath)) {
899
            if (!copy($pharCacertPath, $certFile)) {
900
                throw new \Exception(
901
                    "Could not copy " . $pharCacertPath . " to " . $certFile . ": "
902
                    . var_export(error_get_last(), true)
903
                );
904
            }
905
        }
906
907
        return $certFile;
908
    }
909
}
910