Overview

Namespaces

  • None
  • OpenCloud
    • Autoscale
      • Resource
    • CloudMonitoring
      • Exception
      • Resource
    • Common
      • Exceptions
      • Log
      • Request
        • Response
    • Compute
    • Database
    • DNS
    • LoadBalancer
      • Resources
    • ObjectStore
      • Resource
    • Orchestration
    • Volume
  • PHP

Classes

  • AbstractStorageObject
  • CDNContainer
  • Container
  • DataObject
  • Overview
  • Namespace
  • Class
  • Tree
  • Download
  1: <?php
  2: /**
  3:  * PHP OpenCloud library.
  4:  * 
  5:  * @copyright Copyright 2013 Rackspace US, Inc. See COPYING for licensing information.
  6:  * @license   https://www.apache.org/licenses/LICENSE-2.0 Apache 2.0
  7:  * @version   1.6.0
  8:  * @author    Glen Campbell <glen.campbell@rackspace.com>
  9:  * @author    Jamie Hannaford <jamie.hannaford@rackspace.com>
 10:  */
 11: 
 12: namespace OpenCloud\ObjectStore\Resource;
 13: 
 14: use finfo as FileInfo;
 15: use OpenCloud\Common\Lang;
 16: use OpenCloud\Common\Exceptions;
 17: use OpenCloud\ObjectStore\AbstractService;
 18: use OpenCloud\Common\Request\Response\Http;
 19: 
 20: /**
 21:  * Objects are the basic storage entities in Cloud Files. They represent the 
 22:  * files and their optional metadata you upload to the system. When you upload 
 23:  * objects to Cloud Files, the data is stored as-is (without compression or 
 24:  * encryption) and consists of a location (container), the object's name, and 
 25:  * any metadata you assign consisting of key/value pairs.
 26:  */
 27: class DataObject extends AbstractStorageObject
 28: {
 29:     /**
 30:      * Object name. The only restriction on object names is that they must be 
 31:      * less than 1024 bytes in length after URL encoding.
 32:      * 
 33:      * @var string 
 34:      */
 35:     public $name;
 36:     
 37:     /**
 38:      * Hash value of the object.
 39:      * 
 40:      * @var string 
 41:      */
 42:     public $hash;
 43:     
 44:     /**
 45:      * Size of object in bytes.
 46:      * 
 47:      * @var string 
 48:      */
 49:     public $bytes;
 50:     
 51:     /**
 52:      * Date of last modification.
 53:      * 
 54:      * @var string 
 55:      */
 56:     public $last_modified;
 57:     
 58:     /**
 59:      * Object's content type.
 60:      * 
 61:      * @var string 
 62:      */
 63:     public $content_type;
 64:     
 65:     /**
 66:      * Object's content length.
 67:      * 
 68:      * @var string
 69:      */
 70:     public $content_length;
 71:     
 72:     /**
 73:      * Other headers set for this object (e.g. Access-Control-Allow-Origin)
 74:      * 
 75:      * @var array 
 76:      */
 77:     public $extra_headers = array();
 78:     
 79:     /**
 80:      * Whether or not to calculate and send an ETag on create.
 81:      * 
 82:      * @var bool 
 83:      */
 84:     public $send_etag = true;
 85: 
 86:     /**
 87:      * The data contained by the object.
 88:      * 
 89:      * @var string 
 90:      */
 91:     private $data;
 92:     
 93:     /**
 94:      * The ETag value.
 95:      * 
 96:      * @var string 
 97:      */
 98:     private $etag;
 99:     
100:     /**
101:      * The parent container of this object.
102:      * 
103:      * @var CDNContainer 
104:      */
105:     private $container;
106: 
107:     /**
108:      * Is this data object a pseudo directory?
109:      * 
110:      * @var bool 
111:      */
112:     private $directory = false;
113:     
114:     /**
115:      * Used to translate header values (returned by requests) into properties.
116:      * 
117:      * @var array 
118:      */
119:     private $headerTranslate = array(
120:         'Etag'           => 'hash',
121:         'ETag'           => 'hash',
122:         'Last-Modified'  => 'last_modified',
123:         'Content-Length' => array('bytes', 'content_length'),
124:     );
125:     
126:     /**
127:      * These properties can be freely set by the user for CRUD operations.
128:      * 
129:      * @var array 
130:      */
131:     private $allowedProperties = array(
132:         'name',
133:         'content_type',
134:         'extra_headers',
135:         'send_etag'
136:     );
137:     
138:     /**
139:      * Option for clearing the status cache when objects are uploaded to API.
140:      * By default, it is set to FALSE for performance; but if you have files
141:      * that are rapidly and very often updated, you might want to clear the status
142:      * cache so PHP reads the files directly, instead of relying on the cache.
143:      * 
144:      * @link http://php.net/manual/en/function.clearstatcache.php
145:      * @var  bool 
146:      */
147:     public $clearStatusCache = false;
148: 
149:     /**
150:      * A DataObject is related to a container and has a name
151:      *
152:      * If `$name` is specified, then it attempts to retrieve the object from the
153:      * object store.
154:      *
155:      * @param Container $container the container holding this object
156:      * @param mixed $cdata if an object or array, it is treated as values
157:      *      with which to populate the object. If it is a string, it is
158:      *      treated as a name and the object's info is retrieved from
159:      *      the service.
160:      * @return void
161:      */
162:     public function __construct($container, $cdata = null)
163:     {
164:         parent::__construct();
165: 
166:         $this->container = $container;
167:    
168:         // For pseudo-directories, we need to ensure the name is set
169:         if (!empty($cdata->subdir)) {
170:             $this->name = $cdata->subdir;
171:             $this->directory = true;
172:         } else {
173:             $this->populate($cdata);
174:         }
175:     }
176:     
177:     /**
178:      * Is this data object a pseudo-directory?
179:      * 
180:      * @return bool
181:      */
182:     public function isDirectory()
183:     {
184:         return $this->directory;
185:     }
186:     
187:     /**
188:      * Allow other objects to know what the primary key is.
189:      * 
190:      * @return string
191:      */
192:     public function primaryKeyField()
193:     {
194:         return 'name';
195:     }
196:     
197:     /**
198:      * Is this a real file?
199:      * 
200:      * @param  string $filename
201:      * @return bool
202:      */
203:     private function isRealFile($filename)
204:     {
205:         return $filename != '/dev/null' && $filename != 'NUL';
206:     }
207:     
208:     /**
209:      * Set this file's content type.
210:      * 
211:      * @param string $contentType
212:      */
213:     public function setContentType($contentType)
214:     {
215:         $this->content_type = $contentType;
216:     }
217:     
218:     /**
219:      * Return the content type.
220:      * 
221:      * @return string
222:      */
223:     public function getContentType()
224:     {
225:         return $this->content_type;
226:     }
227: 
228:     /**
229:      * Returns the URL of the data object
230:      *
231:      * If the object is new and doesn't have a name, then an exception is
232:      * thrown.
233:      *
234:      * @param string $subresource Not used
235:      * @return string
236:      * @throws NoNameError
237:      */
238:     public function url($subresource = '')
239:     {
240:         if (!$this->name) {
241:             throw new Exceptions\NoNameError(Lang::translate('Object has no name'));
242:         }
243: 
244:         return Lang::noslash(
245:             $this->container->url()) . '/' . str_replace('%2F', '/', rawurlencode($this->name)
246:         );
247:     }
248: 
249:     /**
250:      * Creates (or updates; both the same) an instance of the object
251:      *
252:      * @api
253:      * @param array $params an optional associative array that can contain the
254:      *      'name' and 'content_type' of the object
255:      * @param string $filename if provided, then the object is loaded from the
256:      *      specified file
257:      * @return boolean
258:      * @throws CreateUpdateError
259:      */
260:     public function create($params = array(), $filename = null, $extractArchive = null)
261:     {
262:         // Set and validate params
263:         $this->setParams($params);
264: 
265:         // assume no file upload
266:         $fp = false;
267: 
268:         // if the filename is provided, process it
269:         if ($filename) {
270: 
271:             if (!$fp = @fopen($filename, 'r')) {
272:                 throw new Exceptions\IOError(sprintf(
273:                     Lang::translate('Could not open file [%s] for reading'),
274:                     $filename
275:                 ));
276:             }
277: 
278:             // @todo Maybe, for performance, we could set the "clear status cache"
279:             // feature to false by default - but allow users to set to true if required
280:             clearstatcache($this->clearStatusCache === true, $filename);
281: 
282:             // Cast filesize as a floating point
283:             $filesize = (float) filesize($filename);
284:             
285:             // Check it's below a reasonable size, and set
286:             // @codeCoverageIgnoreStart
287:             if ($filesize > AbstractService::MAX_OBJECT_SIZE) {
288:                 throw new Exceptions\ObjectError("File size exceeds maximum object size.");
289:             }
290:             // @codeCoverageIgnoreEnd
291:             $this->content_length = $filesize;
292:             
293:             // Guess the content type if necessary
294:             if (!$this->getContentType() && $this->isRealFile($filename)) {
295:                 $this->setContentType($this->inferContentType($filename));
296:             }
297:             
298:             // Send ETag checksum if necessary
299:             if ($this->send_etag) {
300:                 $this->etag = md5_file($filename);
301:             }
302: 
303:             // Announce to the world
304:             $this->getLogger()->info('Uploading {size} bytes from {name}', array(
305:                 'size' => $filesize, 
306:                 'name' => $filename
307:             ));
308:             
309:         } else {
310:             // compute the length
311:             $this->content_length = strlen($this->data);
312: 
313:             if ($this->send_etag) {
314:                 $this->etag = md5($this->data);
315:             }
316:         }
317: 
318:         // Only allow supported archive types
319:         // http://docs.rackspace.com/files/api/v1/cf-devguide/content/Extract_Archive-d1e2338.html
320:         $extractArchiveUrlArg = '';
321:         
322:         if ($extractArchive) {
323:             if ($extractArchive !== "tar.gz" && $extractArchive !== "tar.bz2") {
324:                 throw new Exceptions\ObjectError(
325:                     "Extract Archive only supports tar.gz and tar.bz2"
326:                 );
327:             } else {
328:                 $extractArchiveUrlArg = "?extract-archive=" . $extractArchive;
329:                 $this->etag = null;
330:                 $this->setContentType('');
331:             }
332:         }
333: 
334:         // Set headers
335:         $headers = $this->metadataHeaders();
336:         
337:         if (!empty($this->etag)) {
338:             $headers['ETag'] = $this->etag;
339:         }
340: 
341:         // Content-Type is no longer required; if not specified, it will
342:         // attempt to guess based on the file extension.
343:         if (!$this->getContentType()) {
344:             $headers['Content-Type'] = $this->getContentType();
345:         }
346:         
347:         $headers['Content-Length'] = $this->content_length;
348: 
349:         // Merge in extra headers
350:         if (!empty($this->extra_headers)) {
351:             $headers = $this->extra_headers + $headers;
352:         }
353: 
354:         // perform the request
355:         $response = $this->getService()->request(
356:             $this->url() . $extractArchiveUrlArg,
357:             'PUT',
358:             $headers,
359:             $fp ? $fp : $this->data
360:         );
361: 
362:         // check the status
363:         // @codeCoverageIgnoreStart
364:         if (($status = $response->httpStatus()) >= 300) {
365:             throw new Exceptions\CreateUpdateError(sprintf(
366:                 Lang::translate('Problem saving/updating object [%s] HTTP status [%s] response [%s]'),
367:                 $this->url() . $extractArchiveUrlArg,
368:                 $status,
369:                 $response->httpBody()
370:             ));
371:         }
372:         // @codeCoverageIgnoreEnd
373: 
374:         // set values from response
375:         $this->saveResponseHeaders($response);
376: 
377:         // close the file handle
378:         if ($fp) {
379:             fclose($fp);
380:         }
381: 
382:         return $response;
383:     }
384: 
385:     /**
386:      * Update() is provided as an alias for the Create() method
387:      *
388:      * Since update and create both use a PUT request, the different functions
389:      * may allow the developer to distinguish between the semantics in his or
390:      * her application.
391:      *
392:      * @api
393:      * @param array $params an optional associative array that can contain the
394:      *      'name' and 'type' of the object
395:      * @param string $filename if provided, the object is loaded from the file
396:      * @return boolean
397:      */
398:     public function update($params = array(), $filename = '')
399:     {
400:         return $this->create($params, $filename);
401:     }
402: 
403:     /**
404:      * UpdateMetadata() - updates headers
405:      *
406:      * Updates metadata headers
407:      *
408:      * @api
409:      * @param array $params an optional associative array that can contain the
410:      *      'name' and 'type' of the object
411:      * @return boolean
412:      */
413:     public function updateMetadata($params = array())
414:     {
415:         $this->setParams($params);
416: 
417:         // set the headers
418:         $headers = $this->metadataHeaders();
419:         $headers['Content-Type'] = $this->getContentType();
420: 
421:         $response = $this->getService()->request(
422:             $this->url(),
423:             'POST',
424:             $headers
425:         );
426: 
427:         // check the status
428:         // @codeCoverageIgnoreStart
429:         if (($stat = $response->httpStatus()) >= 204) {
430:             throw new Exceptions\UpdateError(sprintf(
431:                 Lang::translate('Problem updating object [%s] HTTP status [%s] response [%s]'),
432:                 $this->url(),
433:                 $stat,
434:                 $response->httpBody()
435:             ));
436:         }
437:         // @codeCoverageIgnoreEnd
438:         
439:         return $response;
440:     }
441: 
442:     /**
443:      * Deletes an object from the Object Store
444:      *
445:      * Note that we can delete without retrieving by specifying the name in the
446:      * parameter array.
447:      *
448:      * @api
449:      * @param array $params an array of parameters
450:      * @return HttpResponse if successful; FALSE if not
451:      * @throws DeleteError
452:      */
453:     public function delete($params = array())
454:     {
455:         $this->setParams($params);
456: 
457:         $response = $this->getService()->request($this->url(), 'DELETE');
458: 
459:         // check the status
460:         // @codeCoverageIgnoreStart
461:         if (($stat = $response->httpStatus()) >= 300) {
462:             throw new Exceptions\DeleteError(sprintf(
463:                 Lang::translate('Problem deleting object [%s] HTTP status [%s] response [%s]'),
464:                 $this->url(),
465:                 $stat,
466:                 $response->httpBody()
467:             ));
468:         }
469:         // @codeCoverageIgnoreEnd
470:         
471:         return $response;
472:     }
473: 
474:     /**
475:      * Copies the object to another container/object
476:      *
477:      * Note that this function, because it operates within the Object Store
478:      * itself, is much faster than downloading the object and re-uploading it
479:      * to a new object.
480:      *
481:      * @param DataObject $target the target of the COPY command
482:      */
483:     public function copy(DataObject $target)
484:     {
485:         $uri = sprintf('/%s/%s', $target->container()->name(), $target->name());
486: 
487:         $this->getLogger()->info('Copying object to [{uri}]', array('uri' => $uri));
488: 
489:         $response = $this->getService()->request(
490:             $this->url(),
491:             'COPY',
492:             array('Destination' => $uri)
493:         );
494: 
495:         // check response code
496:         // @codeCoverageIgnoreStart
497:         if ($response->httpStatus() > 202) {
498:             throw new Exceptions\ObjectCopyError(sprintf(
499:                 Lang::translate('Error copying object [%s], status [%d] response [%s]'),
500:                 $this->url(),
501:                 $response->httpStatus(),
502:                 $response->httpBody()
503:             ));
504:         }
505:         // @codeCoverageIgnoreEnd
506: 
507:         return $response;
508:     }
509: 
510:     /**
511:      * Returns the container of the object
512:      *
513:      * @return Container
514:      */
515:     public function container()
516:     {
517:         return $this->container;
518:     }
519: 
520:     /**
521:      * returns the TEMP_URL for the object
522:      *
523:      * Some notes:
524:      * * The `$secret` value is arbitrary; it must match the value set for
525:      *   the `X-Account-Meta-Temp-URL-Key` on the account level. This can be
526:      *   set by calling `$service->SetTempUrlSecret($secret)`.
527:      * * The `$expires` value is the number of seconds you want the temporary
528:      *   URL to be valid for. For example, use `60` to make it valid for a
529:      *   minute
530:      * * The `$method` must be either GET or PUT. No other methods are
531:      *   supported.
532:      *
533:      * @param string $secret the shared secret
534:      * @param integer $expires the expiration time (in seconds)
535:      * @param string $method either GET or PUT
536:      * @return string the temporary URL
537:      */
538:     public function tempUrl($secret, $expires, $method)
539:     {
540:         $method = strtoupper($method);
541:         $expiry_time = time() + $expires;
542: 
543:         // check for proper method
544:         if ($method != 'GET' && $method != 'PUT') {
545:             throw new Exceptions\TempUrlMethodError(sprintf(
546:                 Lang::translate(
547:                 'Bad method [%s] for TempUrl; only GET or PUT supported'),
548:                 $method
549:             ));
550:         }
551: 
552:         // construct the URL
553:         $url  = $this->url();
554:         $path = urldecode(parse_url($url, PHP_URL_PATH));
555: 
556:         $hmac_body = "$method\n$expiry_time\n$path";
557:         $hash = hash_hmac('sha1', $hmac_body, $secret);
558: 
559:         $this->getLogger()->info('URL [{url}]; SIG [{sig}]; HASH [{hash}]', array(
560:             'url'  => $url, 
561:             'sig'  => $hmac_body, 
562:             'hash' => $hash
563:         ));
564: 
565:         $temp_url = sprintf('%s?temp_url_sig=%s&temp_url_expires=%d', $url, $hash, $expiry_time);
566: 
567:         // debug that stuff
568:         $this->getLogger()->info('TempUrl generated [{url}]', array(
569:             'url' => $temp_url
570:         ));
571: 
572:         return $temp_url;
573:     }
574: 
575:     /**
576:      * Sets object data from string
577:      *
578:      * This is a convenience function to permit the use of other technologies
579:      * for setting an object's content.
580:      *
581:      * @param string $data
582:      * @return void
583:      */
584:     public function setData($data)
585:     {
586:         $this->data = (string) $data;
587:     }
588: 
589:     /**
590:      * Return object's data as a string
591:      *
592:      * @return string the entire object
593:      */
594:     public function saveToString()
595:     {
596:         return $this->getService()->request($this->url())->httpBody();
597:     }
598: 
599:     /**
600:      * Saves the object's data to local filename
601:      *
602:      * Given a local filename, the Object's data will be written to the newly
603:      * created file.
604:      *
605:      * Example:
606:      * <code>
607:      * # ... authentication/connection/container code excluded
608:      * # ... see previous examples
609:      *
610:      * # Whoops!  I deleted my local README, let me download/save it
611:      * #
612:      * $my_docs = $conn->get_container("documents");
613:      * $doc = $my_docs->get_object("README");
614:      *
615:      * $doc->SaveToFilename("/home/ej/cloudfiles/readme.restored");
616:      * </code>
617:      *
618:      * @param string $filename name of local file to write data to
619:      * @return boolean <kbd>TRUE</kbd> if successful
620:      * @throws IOException error opening file
621:      * @throws InvalidResponseException unexpected response
622:      */
623:     public function saveToFilename($filename)
624:     {
625:         if (!$fp = @fopen($filename, "wb")) {
626:             throw new Exceptions\IOError(sprintf(
627:                 Lang::translate('Could not open file [%s] for writing'),
628:                 $filename
629:             ));
630:         }
631:         
632:         $result = $this->getService()->request($this->url(), 'GET', array(), $fp);
633:         
634:         fclose($fp);
635:         
636:         return $result;
637:     }
638: 
639:     /**
640:      * Saves the object's to a stream filename
641:      *
642:      * Given a local filename, the Object's data will be written to the stream
643:      *
644:      * Example:
645:      * <code>
646:      * # ... authentication/connection/container code excluded
647:      * # ... see previous examples
648:      *
649:      * # If I want to write the README to a temporary memory string I
650:      * # do :
651:      * #
652:      * $my_docs = $conn->get_container("documents");
653:      * $doc = $my_docs->DataObject(array("name"=>"README"));
654:      *
655:      * $fp = fopen('php://temp', 'r+');
656:      * $doc->SaveToStream($fp);
657:      * fclose($fp);
658:      * </code>
659:      *
660:      * @param string $filename name of local file to write data to
661:      * @return boolean <kbd>TRUE</kbd> if successful
662:      * @throws IOException error opening file
663:      * @throws InvalidResponseException unexpected response
664:      */
665:     public function saveToStream($resource)
666:     {
667:         if (!is_resource($resource)) {
668:             throw new Exceptions\ObjectError(
669:                 Lang::translate("Resource argument not a valid PHP resource."
670:             ));
671:         }
672: 
673:         return $this->getService()->request($this->url(), 'GET', array(), $resource);
674:     }
675: 
676: 
677:     /**
678:      * Returns the object's MD5 checksum
679:      *
680:      * Accessor method for reading Object's private ETag attribute.
681:      *
682:      * @api
683:      * @return string MD5 checksum hexidecimal string
684:      */
685:     public function getETag()
686:     {
687:         return $this->etag;
688:     }
689: 
690:     /**
691:      * Purges the object from the CDN
692:      *
693:      * Note that the object will still be served up to the time of its
694:      * TTL value.
695:      *
696:      * @api
697:      * @param string $email An email address that will be notified when
698:      *      the object is purged.
699:      * @return void
700:      * @throws CdnError if the container is not CDN-enabled
701:      * @throws CdnHttpError if there is an HTTP error in the transaction
702:      */
703:     public function purgeCDN($email)
704:     {
705:         // @codeCoverageIgnoreStart
706:         if (!$cdn = $this->Container()->CDNURL()) {
707:             throw new Exceptions\CdnError(Lang::translate('Container is not CDN-enabled'));
708:         }
709:         // @codeCoverageIgnoreEnd
710: 
711:         $url = $cdn . '/' . $this->name;
712:         $headers['X-Purge-Email'] = $email;
713:         $response = $this->getService()->request($url, 'DELETE', $headers);
714: 
715:         // check the status
716:         // @codeCoverageIgnoreStart
717:         if ($response->httpStatus() > 204) {
718:             throw new Exceptions\CdnHttpError(sprintf(
719:                 Lang::translate('Error purging object, status [%d] response [%s]'),
720:                 $response->httpStatus(),
721:                 $response->httpBody()
722:             ));
723:         }
724:         // @codeCoverageIgnoreEnd
725:         
726:         return true;
727:     }
728: 
729:     /**
730:      * Returns the CDN URL (for managing the object)
731:      *
732:      * Note that the DataObject::PublicURL() method is used to return the
733:      * publicly-available URL of the object, while the CDNURL() is used
734:      * to manage the object.
735:      *
736:      * @return string
737:      */
738:     public function CDNURL()
739:     {
740:         return $this->container()->CDNURL() . '/' . $this->name;
741:     }
742: 
743:     /**
744:      * Returns the object's Public CDN URL, if available
745:      *
746:      * @api
747:      * @param string $type can be 'streaming', 'ssl', 'ios-streaming', 
748:      *      or anything else for the
749:      *      default URL. For example, `$object->PublicURL('ios-streaming')`
750:      * @return string
751:      */
752:     public function publicURL($type = null)
753:     {
754:         if (!$prefix = $this->container()->CDNURI()) {
755:             return null;
756:         }
757: 
758:         switch(strtoupper($type)) {
759:             case 'SSL':
760:                 $url = $this->container()->SSLURI().'/'.$this->name;
761:                 break;
762:             case 'STREAMING':
763:                 $url = $this->container()->streamingURI().'/'.$this->name;
764:                 break;
765:             case 'IOS':
766:             case 'IOS-STREAMING':
767:                 $url = $this->container()->iosStreamingURI().'/'.$this->name;
768:                 break;
769:             default:
770:                 $url = $prefix.'/'.$this->name;
771:                 break;
772:         }
773:         
774:         return $url;
775:     }
776: 
777:     /**
778:      * Sets parameters from an array and validates them.
779:      *
780:      * @param  array $params  Associative array of parameters
781:      * @return void
782:      */
783:     private function setParams(array $params = array())
784:     {
785:         // Inspect the user's array for any unapproved keys, and unset if necessary
786:         foreach (array_diff(array_keys($params), $this->allowedProperties) as $key) {
787:             $this->getLogger()->warning('You cannot use the {keyName} key when creating an object', array(
788:                 'keyName' => $key
789:             ));
790:             unset($params[$key]);
791:         }
792:         
793:         $this->populate($params);
794:     }
795: 
796:     /**
797:      * Retrieves a single object, parses headers
798:      *
799:      * @return void
800:      * @throws NoNameError, ObjFetchError
801:      */
802:     private function fetch()
803:     {
804:         if (!$this->name) {
805:             throw new Exceptions\NoNameError(Lang::translate('Cannot retrieve an unnamed object'));
806:         }
807: 
808:         $response = $this->getService()->request($this->url(), 'HEAD', array('Accept' => '*/*'));
809: 
810:         // check for errors
811:         // @codeCoverageIgnoreStart
812:         if ($response->httpStatus() >= 300) {
813:             throw new Exceptions\ObjFetchError(sprintf(
814:                 Lang::translate('Problem retrieving object [%s]'),
815:                 $this->url()
816:             ));
817:         }
818:         // @codeCoverageIgnoreEnd
819: 
820:         // set headers as metadata?
821:         $this->saveResponseHeaders($response);
822: 
823:         // parse the metadata
824:         $this->getMetadata($response);
825:     }
826:     
827:     /**
828:      * Extracts the headers from the response, and saves them as object 
829:      * attributes. Additional name conversions are done where necessary.
830:      * 
831:      * @param Http $response
832:      */
833:     private function saveResponseHeaders(Http $response, $fillExtraIfNotFound = true)
834:     {
835:         foreach ($response->headers() as $header => $value) {
836:             if (isset($this->headerTranslate[$header])) {
837:                 // This header needs to be translated
838:                 $property = $this->headerTranslate[$header];
839:                 // Are there multiple properties that need to be set?
840:                 if (is_array($property)) {
841:                     foreach ($property as $subProperty) {
842:                         $this->$subProperty = $value;
843:                     }
844:                 } else {
845:                     $this->$property = $value;
846:                 }
847:             } elseif ($fillExtraIfNotFound === true) {
848:                 // Otherwise, stock extra headers 
849:                 $this->extra_headers[$header] = $value;
850:             }
851:         }
852:     }
853: 
854:     /**
855:      * Compatability.
856:      */
857:     public function refresh()
858:     {
859:         return $this->fetch();
860:     }
861:     
862:     /**
863:      * Returns the service associated with this object
864:      *
865:      * It's actually the object's container's service, so this method will
866:      * simplify things a bit.
867:      */
868:     private function getService()
869:     {
870:         return $this->container->getService();
871:     }
872: 
873:     /**
874:      * Performs an internal check to get the proper MIME type for an object
875:      *
876:      * This function would go over the available PHP methods to get
877:      * the MIME type.
878:      *
879:      * By default it will try to use the PHP fileinfo library which is
880:      * available from PHP 5.3 or as an PECL extension
881:      * (http://pecl.php.net/package/Fileinfo).
882:      *
883:      * It will get the magic file by default from the system wide file
884:      * which is usually available in /usr/share/magic on Unix or try
885:      * to use the file specified in the source directory of the API
886:      * (share directory).
887:      *
888:      * if fileinfo is not available it will try to use the internal
889:      * mime_content_type function.
890:      *
891:      * @param string $handle name of file or buffer to guess the type from
892:      * @return boolean <kbd>TRUE</kbd> if successful
893:      * @throws BadContentTypeException
894:      * @codeCoverageIgnore
895:      */
896:     private function inferContentType($handle)
897:     {
898:         if ($contentType = $this->getContentType()) {
899:             return $contentType;
900:         }
901:         
902:         $contentType = false;
903:         
904:         $filePath = (is_string($handle)) ? $handle : (string) $handle;
905:         
906:         if (function_exists("finfo_open")) {
907:             
908:             $magicPath = dirname(__FILE__) . "/share/magic"; 
909:             $finfo = new FileInfo(FILEINFO_MIME, file_exists($magicPath) ? $magicPath : null);
910:             
911:             if ($finfo) {
912:                 
913:                 $contentType = is_file($filePath) 
914:                     ? $finfo->file($handle) 
915:                     : $finfo->buffer($handle);
916: 
917:                 /**
918:                  * PHP 5.3 fileinfo display extra information like charset so we 
919:                  * remove everything after the ; since we are not into that stuff
920:                  */  
921:                 if (null !== ($extraInfo = strpos($contentType, "; "))) {
922:                     $contentType = substr($contentType, 0, $extraInfo);
923:                 }
924:             }
925:             
926:             //unset($finfo);
927:         }
928: 
929:         if (!$contentType) {
930:             // Try different native function instead
931:             if (is_file((string) $handle) && function_exists("mime_content_type")) {
932:                 $contentType = mime_content_type($handle);
933:             } else {
934:                 $this->getLogger()->error('Content-Type cannot be found');
935:             }
936:         }
937: 
938:         return $contentType;
939:     }
940: 
941: }
942: 
PHP OpenCloud API API documentation generated by ApiGen 2.8.0