ElggFile.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. <?php
  2. /**
  3. * This class represents a physical file.
  4. *
  5. * Create a new \ElggFile object and specify a filename, and optionally a
  6. * FileStore (if one isn't specified then the default is assumed.)
  7. *
  8. * Open the file using the appropriate mode, and you will be able to
  9. * read and write to the file.
  10. *
  11. * Optionally, you can also call the file's save() method, this will
  12. * turn the file into an entity in the system and permit you to do
  13. * things like attach tags to the file. If you do not save the file, no
  14. * entity is created in the database. This is because there are occasions
  15. * when you may want access to file data on datastores using the \ElggFile
  16. * interface without a need to persist information such as temporary files.
  17. *
  18. * @package Elgg.Core
  19. * @subpackage DataModel.File
  20. */
  21. class ElggFile extends \ElggObject {
  22. /** Filestore */
  23. private $filestore;
  24. /** File handle used to identify this file in a filestore. Created by open. */
  25. private $handle;
  26. /**
  27. * Set subtype to 'file'.
  28. *
  29. * @return void
  30. */
  31. protected function initializeAttributes() {
  32. parent::initializeAttributes();
  33. $this->attributes['subtype'] = "file";
  34. }
  35. /**
  36. * Loads an \ElggFile entity.
  37. *
  38. * @param \stdClass $row Database result or null for new \ElggFile
  39. */
  40. public function __construct($row = null) {
  41. parent::__construct($row);
  42. // Set default filestore
  43. $this->filestore = $this->getFilestore();
  44. }
  45. /**
  46. * Set the filename of this file.
  47. *
  48. * @param string $name The filename.
  49. *
  50. * @return void
  51. */
  52. public function setFilename($name) {
  53. $this->filename = $name;
  54. }
  55. /**
  56. * Return the filename.
  57. *
  58. * @return string
  59. */
  60. public function getFilename() {
  61. return $this->filename;
  62. }
  63. /**
  64. * Return the filename of this file as it is/will be stored on the
  65. * filestore, which may be different to the filename.
  66. *
  67. * @return string
  68. */
  69. public function getFilenameOnFilestore() {
  70. return $this->filestore->getFilenameOnFilestore($this);
  71. }
  72. /**
  73. * Return the size of the filestore associated with this file
  74. *
  75. * @param string $prefix Storage prefix
  76. * @param int $container_guid The container GUID of the checked filestore
  77. *
  78. * @return int
  79. */
  80. public function getFilestoreSize($prefix = '', $container_guid = 0) {
  81. if (!$container_guid) {
  82. $container_guid = $this->container_guid;
  83. }
  84. $fs = $this->getFilestore();
  85. // @todo add getSize() to \ElggFilestore
  86. return $fs->getSize($prefix, $container_guid);
  87. }
  88. /**
  89. * Get the mime type of the file.
  90. *
  91. * @return string
  92. */
  93. public function getMimeType() {
  94. if ($this->mimetype) {
  95. return $this->mimetype;
  96. }
  97. // @todo Guess mimetype if not here
  98. }
  99. /**
  100. * Set the mime type of the file.
  101. *
  102. * @param string $mimetype The mimetype
  103. *
  104. * @return bool
  105. */
  106. public function setMimeType($mimetype) {
  107. return $this->mimetype = $mimetype;
  108. }
  109. /**
  110. * Detects mime types based on filename or actual file.
  111. *
  112. * @note This method can be called both dynamically and statically
  113. *
  114. * @param mixed $file The full path of the file to check. For uploaded files, use tmp_name.
  115. * @param mixed $default A default. Useful to pass what the browser thinks it is.
  116. * @since 1.7.12
  117. *
  118. * @return mixed Detected type on success, false on failure.
  119. * @todo Move this out into a utility class
  120. */
  121. public function detectMimeType($file = null, $default = null) {
  122. $class = __CLASS__;
  123. if (!$file && isset($this) && $this instanceof $class) {
  124. $file = $this->getFilenameOnFilestore();
  125. }
  126. if (!is_readable($file)) {
  127. return false;
  128. }
  129. $mime = $default;
  130. // for PHP5 folks.
  131. if (function_exists('finfo_file') && defined('FILEINFO_MIME_TYPE')) {
  132. $resource = finfo_open(FILEINFO_MIME_TYPE);
  133. if ($resource) {
  134. $mime = finfo_file($resource, $file);
  135. }
  136. }
  137. // for everyone else.
  138. if (!$mime && function_exists('mime_content_type')) {
  139. $mime = mime_content_type($file);
  140. }
  141. $original_filename = isset($this) && $this instanceof $class ? $this->originalfilename : basename($file);
  142. $params = array(
  143. 'filename' => $file,
  144. 'original_filename' => $original_filename, // @see file upload action
  145. 'default' => $default,
  146. );
  147. return _elgg_services()->hooks->trigger('mime_type', 'file', $params, $mime);
  148. }
  149. /**
  150. * Set the optional file description.
  151. *
  152. * @param string $description The description.
  153. *
  154. * @return bool
  155. */
  156. public function setDescription($description) {
  157. $this->description = $description;
  158. }
  159. /**
  160. * Open the file with the given mode
  161. *
  162. * @param string $mode Either read/write/append
  163. *
  164. * @return resource File handler
  165. *
  166. * @throws IOException|InvalidParameterException
  167. */
  168. public function open($mode) {
  169. if (!$this->getFilename()) {
  170. throw new \IOException("You must specify a name before opening a file.");
  171. }
  172. // See if file has already been saved
  173. // seek on datastore, parameters and name?
  174. // Sanity check
  175. if (
  176. ($mode != "read") &&
  177. ($mode != "write") &&
  178. ($mode != "append")
  179. ) {
  180. $msg = "Unrecognized file mode '" . $mode . "'";
  181. throw new \InvalidParameterException($msg);
  182. }
  183. // Get the filestore
  184. $fs = $this->getFilestore();
  185. // Ensure that we save the file details to object store
  186. //$this->save();
  187. // Open the file handle
  188. $this->handle = $fs->open($this, $mode);
  189. return $this->handle;
  190. }
  191. /**
  192. * Write data.
  193. *
  194. * @param string $data The data
  195. *
  196. * @return bool
  197. */
  198. public function write($data) {
  199. $fs = $this->getFilestore();
  200. return $fs->write($this->handle, $data);
  201. }
  202. /**
  203. * Read data.
  204. *
  205. * @param int $length Amount to read.
  206. * @param int $offset The offset to start from.
  207. *
  208. * @return mixed Data or false
  209. */
  210. public function read($length, $offset = 0) {
  211. $fs = $this->getFilestore();
  212. return $fs->read($this->handle, $length, $offset);
  213. }
  214. /**
  215. * Gets the full contents of this file.
  216. *
  217. * @return mixed The file contents.
  218. */
  219. public function grabFile() {
  220. $fs = $this->getFilestore();
  221. return $fs->grabFile($this);
  222. }
  223. /**
  224. * Close the file and commit changes
  225. *
  226. * @return bool
  227. */
  228. public function close() {
  229. $fs = $this->getFilestore();
  230. if ($fs->close($this->handle)) {
  231. $this->handle = null;
  232. return true;
  233. }
  234. return false;
  235. }
  236. /**
  237. * Delete this file.
  238. *
  239. * @return bool
  240. */
  241. public function delete() {
  242. $fs = $this->getFilestore();
  243. $result = $fs->delete($this);
  244. if ($this->getGUID() && $result) {
  245. $result = parent::delete();
  246. }
  247. return $result;
  248. }
  249. /**
  250. * Seek a position in the file.
  251. *
  252. * @param int $position Position in bytes
  253. *
  254. * @return bool
  255. */
  256. public function seek($position) {
  257. $fs = $this->getFilestore();
  258. // @todo add seek() to \ElggFilestore
  259. return $fs->seek($this->handle, $position);
  260. }
  261. /**
  262. * Return the current position of the file.
  263. *
  264. * @return int The file position
  265. */
  266. public function tell() {
  267. $fs = $this->getFilestore();
  268. return $fs->tell($this->handle);
  269. }
  270. /**
  271. * Return the size of the file in bytes.
  272. *
  273. * @return int
  274. * @since 1.9
  275. */
  276. public function getSize() {
  277. return $this->filestore->getFileSize($this);
  278. }
  279. /**
  280. * Return the size of the file in bytes.
  281. *
  282. * @return int
  283. * @deprecated 1.8 Use getSize()
  284. */
  285. public function size() {
  286. elgg_deprecated_notice("Use \ElggFile::getSize() instead of \ElggFile::size()", 1.9);
  287. return $this->getSize();
  288. }
  289. /**
  290. * Return a boolean value whether the file handle is at the end of the file
  291. *
  292. * @return bool
  293. */
  294. public function eof() {
  295. $fs = $this->getFilestore();
  296. return $fs->eof($this->handle);
  297. }
  298. /**
  299. * Returns if the file exists
  300. *
  301. * @return bool
  302. */
  303. public function exists() {
  304. $fs = $this->getFilestore();
  305. return $fs->exists($this);
  306. }
  307. /**
  308. * Set a filestore.
  309. *
  310. * @param \ElggFilestore $filestore The file store.
  311. *
  312. * @return void
  313. */
  314. public function setFilestore(\ElggFilestore $filestore) {
  315. $this->filestore = $filestore;
  316. }
  317. /**
  318. * Return a filestore suitable for saving this file.
  319. * This filestore is either a pre-registered filestore,
  320. * a filestore as recorded in metadata or the system default.
  321. *
  322. * @return \ElggFilestore
  323. *
  324. * @throws ClassNotFoundException
  325. */
  326. protected function getFilestore() {
  327. // Short circuit if already set.
  328. if ($this->filestore) {
  329. return $this->filestore;
  330. }
  331. // ask for entity specific filestore
  332. // saved as filestore::className in metadata.
  333. // need to get all filestore::* metadata because the rest are "parameters" that
  334. // get passed to filestore::setParameters()
  335. if ($this->guid) {
  336. $options = array(
  337. 'guid' => $this->guid,
  338. 'where' => array("n.string LIKE 'filestore::%'"),
  339. );
  340. $mds = elgg_get_metadata($options);
  341. $parameters = array();
  342. foreach ($mds as $md) {
  343. list( , $name) = explode("::", $md->name);
  344. if ($name == 'filestore') {
  345. $filestore = $md->value;
  346. }
  347. $parameters[$name] = $md->value;
  348. }
  349. }
  350. // need to check if filestore is set because this entity is loaded in save()
  351. // before the filestore metadata is saved.
  352. if (isset($filestore)) {
  353. if (!class_exists($filestore)) {
  354. $msg = "Unable to load filestore class " . $filestore . " for file " . $this->guid;
  355. throw new \ClassNotFoundException($msg);
  356. }
  357. $this->filestore = new $filestore();
  358. $this->filestore->setParameters($parameters);
  359. // @todo explain why $parameters will always be set here (PhpStorm complains)
  360. }
  361. // this means the entity hasn't been saved so fallback to default
  362. if (!$this->filestore) {
  363. $this->filestore = get_default_filestore();
  364. }
  365. return $this->filestore;
  366. }
  367. /**
  368. * Save the file
  369. *
  370. * Write the file's data to the filestore and save
  371. * the corresponding entity.
  372. *
  373. * @see \ElggObject::save()
  374. *
  375. * @return bool
  376. */
  377. public function save() {
  378. if (!parent::save()) {
  379. return false;
  380. }
  381. // Save datastore metadata
  382. $params = $this->filestore->getParameters();
  383. foreach ($params as $k => $v) {
  384. $this->setMetadata("filestore::$k", $v);
  385. }
  386. // Now make a note of the filestore class
  387. $this->setMetadata("filestore::filestore", get_class($this->filestore));
  388. return true;
  389. }
  390. /**
  391. * Get property names to serialize.
  392. *
  393. * @return string[]
  394. */
  395. public function __sleep() {
  396. return array_diff(array_keys(get_object_vars($this)), array(
  397. // Don't persist filestore, which contains CONFIG
  398. // https://github.com/Elgg/Elgg/issues/9081#issuecomment-152859856
  399. 'filestore',
  400. // a resource
  401. 'handle',
  402. ));
  403. }
  404. /**
  405. * Reestablish filestore property
  406. *
  407. * @return void
  408. * @throws ClassNotFoundException
  409. */
  410. public function __wakeup() {
  411. $this->getFilestore();
  412. }
  413. }