PageRenderTime 54ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/application/libraries/cloudfiles/CF_Object.php

http://github.com/ushahidi/Ushahidi_Web
PHP | 711 lines | 277 code | 34 blank | 400 comment | 76 complexity | 0d1221dc846aefcee1f44b1398dfbc1c MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * Object operations
  4. *
  5. * An Object is analogous to a file on a conventional filesystem. You can
  6. * read data from, or write data to your Objects. You can also associate
  7. * arbitrary metadata with them.
  8. *
  9. * @package php-cloudfiles
  10. */
  11. class CF_Object
  12. {
  13. public $container;
  14. public $name;
  15. public $last_modified;
  16. public $content_type;
  17. public $content_length;
  18. public $metadata;
  19. public $headers;
  20. public $manifest;
  21. private $etag;
  22. /**
  23. * Class constructor
  24. *
  25. * @param obj $container CF_Container instance
  26. * @param string $name name of Object
  27. * @param boolean $force_exists if set, throw an error if Object doesn't exist
  28. */
  29. function __construct(&$container, $name, $force_exists=False, $dohead=True)
  30. {
  31. if (isset($name[0]) && $name[0] == "/") {
  32. $r = "Object name '".$name;
  33. $r .= "' cannot contain begin with a '/' character.";
  34. throw new Kohana_Exception($r);
  35. }
  36. if (strlen($name) > MAX_OBJECT_NAME_LEN) {
  37. throw new Kohana_Exception("Object name exceeds "
  38. . "maximum allowed length.");
  39. }
  40. $this->container = $container;
  41. $this->name = $name;
  42. $this->etag = NULL;
  43. $this->_etag_override = False;
  44. $this->last_modified = NULL;
  45. $this->content_type = NULL;
  46. $this->content_length = 0;
  47. $this->metadata = array();
  48. $this->headers = array();
  49. $this->manifest = NULL;
  50. if ($dohead) {
  51. if (!$this->_initialize() && $force_exists) {
  52. throw new Kohana_Exception("No such object '".$name."'");
  53. }
  54. }
  55. }
  56. /**
  57. * String representation of Object
  58. *
  59. * Pretty print the Object's location and name
  60. *
  61. * @return string Object information
  62. */
  63. function __toString()
  64. {
  65. return $this->container->name . "/" . $this->name;
  66. }
  67. /**
  68. * Internal check to get the proper mimetype.
  69. *
  70. * This function would go over the available PHP methods to get
  71. * the MIME type.
  72. *
  73. * By default it will try to use the PHP fileinfo library which is
  74. * available from PHP 5.3 or as an PECL extension
  75. * (http://pecl.php.net/package/Fileinfo).
  76. *
  77. * It will get the magic file by default from the system wide file
  78. * which is usually available in /usr/share/magic on Unix or try
  79. * to use the file specified in the source directory of the API
  80. * (share directory).
  81. *
  82. * if fileinfo is not available it will try to use the internal
  83. * mime_content_type function.
  84. *
  85. * @param string $handle name of file or buffer to guess the type from
  86. * @return boolean <kbd>True</kbd> if successful
  87. * @throws BadContentTypeException
  88. */
  89. function _guess_content_type($handle) {
  90. if ($this->content_type)
  91. return;
  92. if (function_exists("finfo_open")) {
  93. $local_magic = dirname(__FILE__) . "/share/magic";
  94. $finfo = @finfo_open(FILEINFO_MIME, $local_magic);
  95. if (!$finfo)
  96. $finfo = @finfo_open(FILEINFO_MIME);
  97. if ($finfo) {
  98. if (is_file((string)$handle))
  99. $ct = @finfo_file($finfo, $handle);
  100. else
  101. $ct = @finfo_buffer($finfo, $handle);
  102. /* PHP 5.3 fileinfo display extra information like
  103. charset so we remove everything after the ; since
  104. we are not into that stuff */
  105. if ($ct) {
  106. $extra_content_type_info = strpos($ct, "; ");
  107. if ($extra_content_type_info)
  108. $ct = substr($ct, 0, $extra_content_type_info);
  109. }
  110. if ($ct && $ct != 'application/octet-stream')
  111. $this->content_type = $ct;
  112. @finfo_close($finfo);
  113. }
  114. }
  115. if (!$this->content_type && (string)is_file($handle) && function_exists("mime_content_type")) {
  116. $this->content_type = @mime_content_type($handle);
  117. }
  118. if (!$this->content_type) {
  119. throw new Kohana_Exception("Required Content-Type not set");
  120. }
  121. return True;
  122. }
  123. /**
  124. * String representation of the Object's public URI
  125. *
  126. * A string representing the Object's public URI assuming that it's
  127. * parent Container is CDN-enabled.
  128. *
  129. * Example:
  130. * <code>
  131. * # ... authentication/connection/container code excluded
  132. * # ... see previous examples
  133. *
  134. * # Print out the Object's CDN URI (if it has one) in an HTML img-tag
  135. * #
  136. * print "<img src='$pic->public_uri()' />\n";
  137. * </code>
  138. *
  139. * @return string Object's public URI or NULL
  140. */
  141. function public_uri()
  142. {
  143. if ($this->container->cdn_enabled) {
  144. return $this->container->cdn_uri . "/" . $this->name;
  145. }
  146. return NULL;
  147. }
  148. /**
  149. * String representation of the Object's public SSL URI
  150. *
  151. * A string representing the Object's public SSL URI assuming that it's
  152. * parent Container is CDN-enabled.
  153. *
  154. * Example:
  155. * <code>
  156. * # ... authentication/connection/container code excluded
  157. * # ... see previous examples
  158. *
  159. * # Print out the Object's CDN SSL URI (if it has one) in an HTML img-tag
  160. * #
  161. * print "<img src='$pic->public_ssl_uri()' />\n";
  162. * </code>
  163. *
  164. * @return string Object's public SSL URI or NULL
  165. */
  166. function public_ssl_uri()
  167. {
  168. if ($this->container->cdn_enabled) {
  169. return $this->container->cdn_ssl_uri . "/" . $this->name;
  170. }
  171. return NULL;
  172. }
  173. /**
  174. * String representation of the Object's public Streaming URI
  175. *
  176. * A string representing the Object's public Streaming URI assuming that it's
  177. * parent Container is CDN-enabled.
  178. *
  179. * Example:
  180. * <code>
  181. * # ... authentication/connection/container code excluded
  182. * # ... see previous examples
  183. *
  184. * # Print out the Object's CDN Streaming URI (if it has one) in an HTML img-tag
  185. * #
  186. * print "<img src='$pic->public_streaming_uri()' />\n";
  187. * </code>
  188. *
  189. * @return string Object's public Streaming URI or NULL
  190. */
  191. function public_streaming_uri()
  192. {
  193. if ($this->container->cdn_enabled) {
  194. return $this->container->cdn_streaming_uri . "/" . $this->name;
  195. }
  196. return NULL;
  197. }
  198. /**
  199. * Read the remote Object's data
  200. *
  201. * Returns the Object's data. This is useful for smaller Objects such
  202. * as images or office documents. Object's with larger content should use
  203. * the stream() method below.
  204. *
  205. * Pass in $hdrs array to set specific custom HTTP headers such as
  206. * If-Match, If-None-Match, If-Modified-Since, Range, etc.
  207. *
  208. * Example:
  209. * <code>
  210. * # ... authentication/connection/container code excluded
  211. * # ... see previous examples
  212. *
  213. * $my_docs = $conn->get_container("documents");
  214. * $doc = $my_docs->get_object("README");
  215. * $data = $doc->read(); # read image content into a string variable
  216. * print $data;
  217. *
  218. * # Or see stream() below for a different example.
  219. * #
  220. * </code>
  221. *
  222. * @param array $hdrs user-defined headers (Range, If-Match, etc.)
  223. * @return string Object's data
  224. * @throws InvalidResponseException unexpected response
  225. */
  226. function read($hdrs=array())
  227. {
  228. list($status, $reason, $data) =
  229. $this->container->cfs_http->get_object_to_string($this, $hdrs);
  230. #if ($status == 401 && $this->_re_auth()) {
  231. # return $this->read($hdrs);
  232. #}
  233. if (($status < 200) || ($status > 299
  234. && $status != 412 && $status != 304)) {
  235. throw new Kohana_Exception("Invalid response (".$status."): "
  236. . $this->container->cfs_http->get_error());
  237. }
  238. return $data;
  239. }
  240. /**
  241. * Streaming read of Object's data
  242. *
  243. * Given an open PHP resource (see PHP's fopen() method), fetch the Object's
  244. * data and write it to the open resource handle. This is useful for
  245. * streaming an Object's content to the browser (videos, images) or for
  246. * fetching content to a local file.
  247. *
  248. * Pass in $hdrs array to set specific custom HTTP headers such as
  249. * If-Match, If-None-Match, If-Modified-Since, Range, etc.
  250. *
  251. * Example:
  252. * <code>
  253. * # ... authentication/connection/container code excluded
  254. * # ... see previous examples
  255. *
  256. * # Assuming this is a web script to display the README to the
  257. * # user's browser:
  258. * #
  259. * <?php
  260. * // grab README from storage system
  261. * //
  262. * $my_docs = $conn->get_container("documents");
  263. * $doc = $my_docs->get_object("README");
  264. *
  265. * // Hand it back to user's browser with appropriate content-type
  266. * //
  267. * header("Content-Type: " . $doc->content_type);
  268. * $output = fopen("php://output", "w");
  269. * $doc->stream($output); # stream object content to PHP's output buffer
  270. * fclose($output);
  271. * ?>
  272. *
  273. * # See read() above for a more simple example.
  274. * #
  275. * </code>
  276. *
  277. * @param resource $fp open resource for writing data to
  278. * @param array $hdrs user-defined headers (Range, If-Match, etc.)
  279. * @return string Object's data
  280. * @throws InvalidResponseException unexpected response
  281. */
  282. function stream(&$fp, $hdrs=array())
  283. {
  284. list($status, $reason) =
  285. $this->container->cfs_http->get_object_to_stream($this,$fp,$hdrs);
  286. #if ($status == 401 && $this->_re_auth()) {
  287. # return $this->stream($fp, $hdrs);
  288. #}
  289. if (($status < 200) || ($status > 299
  290. && $status != 412 && $status != 304)) {
  291. throw new Kohana_Exception("Invalid response (".$status."): "
  292. .$reason);
  293. }
  294. return True;
  295. }
  296. /**
  297. * Store new Object metadata
  298. *
  299. * Write's an Object's metadata to the remote Object. This will overwrite
  300. * an prior Object metadata.
  301. *
  302. * Example:
  303. * <code>
  304. * # ... authentication/connection/container code excluded
  305. * # ... see previous examples
  306. *
  307. * $my_docs = $conn->get_container("documents");
  308. * $doc = $my_docs->get_object("README");
  309. *
  310. * # Define new metadata for the object
  311. * #
  312. * $doc->metadata = array(
  313. * "Author" => "EJ",
  314. * "Subject" => "How to use the PHP tests",
  315. * "Version" => "1.2.2"
  316. * );
  317. *
  318. * # Define additional headers for the object
  319. * #
  320. * $doc->headers = array(
  321. * "Content-Disposition" => "attachment",
  322. * );
  323. *
  324. * # Push the new metadata up to the storage system
  325. * #
  326. * $doc->sync_metadata();
  327. * </code>
  328. *
  329. * @return boolean <kbd>True</kbd> if successful, <kbd>False</kbd> otherwise
  330. * @throws InvalidResponseException unexpected response
  331. */
  332. function sync_metadata()
  333. {
  334. if (!empty($this->metadata) || !empty($this->headers) || $this->manifest) {
  335. $status = $this->container->cfs_http->update_object($this);
  336. #if ($status == 401 && $this->_re_auth()) {
  337. # return $this->sync_metadata();
  338. #}
  339. if ($status != 202) {
  340. throw new Kohana_Exception("Invalid response ("
  341. .$status."): ".$this->container->cfs_http->get_error());
  342. }
  343. return True;
  344. }
  345. return False;
  346. }
  347. /**
  348. * Store new Object manifest
  349. *
  350. * Write's an Object's manifest to the remote Object. This will overwrite
  351. * an prior Object manifest.
  352. *
  353. * Example:
  354. * <code>
  355. * # ... authentication/connection/container code excluded
  356. * # ... see previous examples
  357. *
  358. * $my_docs = $conn->get_container("documents");
  359. * $doc = $my_docs->get_object("README");
  360. *
  361. * # Define new manifest for the object
  362. * #
  363. * $doc->manifest = "container/prefix";
  364. *
  365. * # Push the new manifest up to the storage system
  366. * #
  367. * $doc->sync_manifest();
  368. * </code>
  369. *
  370. * @return boolean <kbd>True</kbd> if successful, <kbd>False</kbd> otherwise
  371. * @throws InvalidResponseException unexpected response
  372. */
  373. function sync_manifest()
  374. {
  375. return $this->sync_metadata();
  376. }
  377. /**
  378. * Upload Object's data to Cloud Files
  379. *
  380. * Write data to the remote Object. The $data argument can either be a
  381. * PHP resource open for reading (see PHP's fopen() method) or an in-memory
  382. * variable. If passing in a PHP resource, you must also include the $bytes
  383. * parameter.
  384. *
  385. * Example:
  386. * <code>
  387. * # ... authentication/connection/container code excluded
  388. * # ... see previous examples
  389. *
  390. * $my_docs = $conn->get_container("documents");
  391. * $doc = $my_docs->get_object("README");
  392. *
  393. * # Upload placeholder text in my README
  394. * #
  395. * $doc->write("This is just placeholder text for now...");
  396. * </code>
  397. *
  398. * @param string|resource $data string or open resource
  399. * @param float $bytes amount of data to upload (required for resources)
  400. * @param boolean $verify generate, send, and compare MD5 checksums
  401. * @return boolean <kbd>True</kbd> when data uploaded successfully
  402. * @throws SyntaxException missing required parameters
  403. * @throws BadContentTypeException if no Content-Type was/could be set
  404. * @throws MisMatchedChecksumException $verify is set and checksums unequal
  405. * @throws InvalidResponseException unexpected response
  406. */
  407. function write($data=NULL, $bytes=0, $verify=True)
  408. {
  409. if (!$data && !is_string($data)) {
  410. throw new Kohana_Exception("Missing data source.");
  411. }
  412. if ($bytes > MAX_OBJECT_SIZE) {
  413. throw new Kohana_Exception("Bytes exceeds maximum object size.");
  414. }
  415. if ($verify) {
  416. if (!$this->_etag_override) {
  417. $this->etag = $this->compute_md5sum($data);
  418. }
  419. } else {
  420. $this->etag = NULL;
  421. }
  422. $close_fh = False;
  423. if (!is_resource($data)) {
  424. # A hack to treat string data as a file handle. php://memory feels
  425. # like a better option, but it seems to break on Windows so use
  426. # a temporary file instead.
  427. #
  428. $fp = fopen("php://temp", "wb+");
  429. #$fp = fopen("php://memory", "wb+");
  430. fwrite($fp, $data, strlen($data));
  431. rewind($fp);
  432. $close_fh = True;
  433. $this->content_length = (float) strlen($data);
  434. if ($this->content_length > MAX_OBJECT_SIZE) {
  435. throw new Kohana_Exception("Data exceeds maximum object size");
  436. }
  437. $ct_data = substr($data, 0, 64);
  438. } else {
  439. $this->content_length = $bytes;
  440. $fp = $data;
  441. $ct_data = fread($data, 64);
  442. rewind($data);
  443. }
  444. $this->_guess_content_type($ct_data);
  445. list($status, $reason, $etag) =
  446. $this->container->cfs_http->put_object($this, $fp);
  447. #if ($status == 401 && $this->_re_auth()) {
  448. # return $this->write($data, $bytes, $verify);
  449. #}
  450. if ($status == 412) {
  451. if ($close_fh) { fclose($fp); }
  452. throw new Kohana_Exception("Missing Content-Type header");
  453. }
  454. if ($status == 422) {
  455. if ($close_fh) { fclose($fp); }
  456. throw new Kohana_Exception(
  457. "Supplied and computed checksums do not match.");
  458. }
  459. if ($status != 201) {
  460. if ($close_fh) { fclose($fp); }
  461. throw new Kohana_Exception("Invalid response (".$status."): "
  462. . $this->container->cfs_http->get_error());
  463. }
  464. if (!$verify) {
  465. $this->etag = $etag;
  466. }
  467. if ($close_fh) { fclose($fp); }
  468. return True;
  469. }
  470. /**
  471. * Upload Object data from local filename
  472. *
  473. * This is a convenience function to upload the data from a local file. A
  474. * True value for $verify will cause the method to compute the Object's MD5
  475. * checksum prior to uploading.
  476. *
  477. * Example:
  478. * <code>
  479. * # ... authentication/connection/container code excluded
  480. * # ... see previous examples
  481. *
  482. * $my_docs = $conn->get_container("documents");
  483. * $doc = $my_docs->get_object("README");
  484. *
  485. * # Upload my local README's content
  486. * #
  487. * $doc->load_from_filename("/home/ej/cloudfiles/readme");
  488. * </code>
  489. *
  490. * @param string $filename full path to local file
  491. * @param boolean $verify enable local/remote MD5 checksum validation
  492. * @return boolean <kbd>True</kbd> if data uploaded successfully
  493. * @throws SyntaxException missing required parameters
  494. * @throws BadContentTypeException if no Content-Type was/could be set
  495. * @throws MisMatchedChecksumException $verify is set and checksums unequal
  496. * @throws InvalidResponseException unexpected response
  497. * @throws IOException error opening file
  498. */
  499. function load_from_filename($filename, $verify=True)
  500. {
  501. $fp = @fopen($filename, "r");
  502. if (!$fp) {
  503. throw new Kohana_Exception("Could not open file for reading: ".$filename);
  504. }
  505. clearstatcache();
  506. $size = (float) sprintf("%u", filesize($filename));
  507. if ($size > MAX_OBJECT_SIZE) {
  508. throw new Kohana_Exception("File size exceeds maximum object size.");
  509. }
  510. $this->_guess_content_type($filename);
  511. $this->write($fp, $size, $verify);
  512. fclose($fp);
  513. return True;
  514. }
  515. /**
  516. * Save Object's data to local filename
  517. *
  518. * Given a local filename, the Object's data will be written to the newly
  519. * created file.
  520. *
  521. * Example:
  522. * <code>
  523. * # ... authentication/connection/container code excluded
  524. * # ... see previous examples
  525. *
  526. * # Whoops! I deleted my local README, let me download/save it
  527. * #
  528. * $my_docs = $conn->get_container("documents");
  529. * $doc = $my_docs->get_object("README");
  530. *
  531. * $doc->save_to_filename("/home/ej/cloudfiles/readme.restored");
  532. * </code>
  533. *
  534. * @param string $filename name of local file to write data to
  535. * @return boolean <kbd>True</kbd> if successful
  536. * @throws IOException error opening file
  537. * @throws InvalidResponseException unexpected response
  538. */
  539. function save_to_filename($filename)
  540. {
  541. $fp = @fopen($filename, "wb");
  542. if (!$fp) {
  543. throw new Kohana_Exception("Could not open file for writing: ".$filename);
  544. }
  545. $result = $this->stream($fp);
  546. fclose($fp);
  547. return $result;
  548. }
  549. /**
  550. * Purge this Object from CDN Cache.
  551. * Example:
  552. * <code>
  553. * # ... authentication code excluded (see previous examples) ...
  554. * #
  555. * $conn = new CF_Authentication($auth);
  556. * $container = $conn->get_container("cdn_enabled");
  557. * $obj = $container->get_object("object");
  558. * $obj->purge_from_cdn("user@domain.com");
  559. * # or
  560. * $obj->purge_from_cdn();
  561. * # or
  562. * $obj->purge_from_cdn("user1@domain.com,user2@domain.com");
  563. * @returns boolean True if successful
  564. * @throws CDNNotEnabledException if CDN Is not enabled on this connection
  565. * @throws InvalidResponseException if the response expected is not returned
  566. */
  567. function purge_from_cdn($email=null)
  568. {
  569. if (!$this->container->cfs_http->getCDNMUrl())
  570. {
  571. throw new Kohana_Exception(
  572. "Authentication response did not indicate CDN availability");
  573. }
  574. $status = $this->container->cfs_http->purge_from_cdn($this->container->name . "/" . $this->name, $email);
  575. if ($status < 199 or $status > 299) {
  576. throw new Kohana_Exception(
  577. "Invalid response (".$status."): ".$this->container->cfs_http->get_error());
  578. }
  579. return True;
  580. }
  581. /**
  582. * Set Object's MD5 checksum
  583. *
  584. * Manually set the Object's ETag. Including the ETag is mandatory for
  585. * Cloud Files to perform end-to-end verification. Omitting the ETag forces
  586. * the user to handle any data integrity checks.
  587. *
  588. * @param string $etag MD5 checksum hexidecimal string
  589. */
  590. function set_etag($etag)
  591. {
  592. $this->etag = $etag;
  593. $this->_etag_override = True;
  594. }
  595. /**
  596. * Object's MD5 checksum
  597. *
  598. * Accessor method for reading Object's private ETag attribute.
  599. *
  600. * @return string MD5 checksum hexidecimal string
  601. */
  602. function getETag()
  603. {
  604. return $this->etag;
  605. }
  606. /**
  607. * Compute the MD5 checksum
  608. *
  609. * Calculate the MD5 checksum on either a PHP resource or data. The argument
  610. * may either be a local filename, open resource for reading, or a string.
  611. *
  612. * <b>WARNING:</b> if you are uploading a big file over a stream
  613. * it could get very slow to compute the md5 you probably want to
  614. * set the $verify parameter to False in the write() method and
  615. * compute yourself the md5 before if you have it.
  616. *
  617. * @param filename|obj|string $data filename, open resource, or string
  618. * @return string MD5 checksum hexidecimal string
  619. */
  620. function compute_md5sum(&$data)
  621. {
  622. if (function_exists("hash_init") && is_resource($data)) {
  623. $ctx = hash_init('md5');
  624. while (!feof($data)) {
  625. $buffer = fgets($data, 65536);
  626. hash_update($ctx, $buffer);
  627. }
  628. $md5 = hash_final($ctx, false);
  629. rewind($data);
  630. } elseif ((string)is_file($data)) {
  631. $md5 = md5_file($data);
  632. } else {
  633. $md5 = md5($data);
  634. }
  635. return $md5;
  636. }
  637. /**
  638. * PRIVATE: fetch information about the remote Object if it exists
  639. */
  640. private function _initialize()
  641. {
  642. list($status, $reason, $etag, $last_modified, $content_type,
  643. $content_length, $metadata, $manifest, $headers) =
  644. $this->container->cfs_http->head_object($this);
  645. #if ($status == 401 && $this->_re_auth()) {
  646. # return $this->_initialize();
  647. #}
  648. if ($status == 404) {
  649. return False;
  650. }
  651. if ($status < 200 || $status > 299) {
  652. throw new Kohana_Exception("Invalid response (".$status."): "
  653. . $this->container->cfs_http->get_error());
  654. }
  655. $this->etag = $etag;
  656. $this->last_modified = $last_modified;
  657. $this->content_type = $content_type;
  658. $this->content_length = $content_length;
  659. $this->metadata = $metadata;
  660. $this->headers = $headers;
  661. $this->manifest = $manifest;
  662. return True;
  663. }
  664. #private function _re_auth()
  665. #{
  666. # $new_auth = new CF_Authentication(
  667. # $this->cfs_auth->username,
  668. # $this->cfs_auth->api_key,
  669. # $this->cfs_auth->auth_host,
  670. # $this->cfs_auth->account);
  671. # $new_auth->authenticate();
  672. # $this->container->cfs_auth = $new_auth;
  673. # $this->container->cfs_http->setCFAuth($this->cfs_auth);
  674. # return True;
  675. #}
  676. }
  677. ?>