ElggDiskFilestore.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. <?php
  2. /**
  3. * A filestore that uses disk as storage.
  4. *
  5. * @warning This should be used by a wrapper class
  6. * like {@link \ElggFile}.
  7. *
  8. * @package Elgg.Core
  9. * @subpackage FileStore.Disk
  10. */
  11. class ElggDiskFilestore extends \ElggFilestore {
  12. /**
  13. * Directory root.
  14. */
  15. private $dir_root;
  16. /**
  17. * Number of entries per matrix dir.
  18. * You almost certainly don't want to change this.
  19. */
  20. const BUCKET_SIZE = 5000;
  21. /**
  22. * Global Elgg configuration
  23. *
  24. * @var \stdClass
  25. */
  26. private $CONFIG;
  27. /**
  28. * Construct a disk filestore using the given directory root.
  29. *
  30. * @param string $directory_root Root directory, must end in "/"
  31. */
  32. public function __construct($directory_root = "") {
  33. global $CONFIG;
  34. $this->CONFIG = $CONFIG;
  35. if ($directory_root) {
  36. $this->dir_root = $directory_root;
  37. } else {
  38. $this->dir_root = $this->CONFIG->dataroot;
  39. }
  40. }
  41. /**
  42. * Open a file for reading, writing, or both.
  43. *
  44. * @note All files are opened binary safe.
  45. * @note This will try to create the a directory if it doesn't exist and is opened
  46. * in write or append mode.
  47. *
  48. * @param \ElggFile $file The file to open
  49. * @param string $mode read, write, or append.
  50. *
  51. * @throws InvalidParameterException
  52. * @return resource File pointer resource
  53. * @todo This really shouldn't try to create directories if not writing.
  54. */
  55. public function open(\ElggFile $file, $mode) {
  56. $fullname = $this->getFilenameOnFilestore($file);
  57. // Split into path and name
  58. $ls = strrpos($fullname, "/");
  59. if ($ls === false) {
  60. $ls = 0;
  61. }
  62. $path = substr($fullname, 0, $ls);
  63. if (($mode != 'write') && (!file_exists($fullname))) {
  64. return false;
  65. }
  66. // Try to create the dir for valid write modes
  67. if ($mode == 'write' || $mode == 'append') {
  68. try {
  69. $this->makeDirectoryRoot($path);
  70. } catch (Exception $e) {
  71. _elgg_services()->logger->warn("Couldn't create directory: $path");
  72. return false;
  73. }
  74. }
  75. switch ($mode) {
  76. case "read" :
  77. $mode = "rb";
  78. break;
  79. case "write" :
  80. $mode = "w+b";
  81. break;
  82. case "append" :
  83. $mode = "a+b";
  84. break;
  85. default:
  86. $msg = "Unrecognized file mode '" . $mode . "'";
  87. throw new \InvalidParameterException($msg);
  88. }
  89. return fopen($fullname, $mode);
  90. }
  91. /**
  92. * Write data to a file.
  93. *
  94. * @param resource $f File pointer resource
  95. * @param mixed $data The data to write.
  96. *
  97. * @return bool
  98. */
  99. public function write($f, $data) {
  100. return fwrite($f, $data);
  101. }
  102. /**
  103. * Read data from a file.
  104. *
  105. * @param resource $f File pointer resource
  106. * @param int $length The number of bytes to read
  107. * @param int $offset The number of bytes to start after
  108. *
  109. * @return mixed Contents of file or false on fail.
  110. */
  111. public function read($f, $length, $offset = 0) {
  112. if ($offset) {
  113. $this->seek($f, $offset);
  114. }
  115. return fread($f, $length);
  116. }
  117. /**
  118. * Close a file pointer
  119. *
  120. * @param resource $f A file pointer resource
  121. *
  122. * @return bool
  123. */
  124. public function close($f) {
  125. return fclose($f);
  126. }
  127. /**
  128. * Delete an \ElggFile file.
  129. *
  130. * @param \ElggFile $file File to delete
  131. *
  132. * @return bool
  133. */
  134. public function delete(\ElggFile $file) {
  135. $filename = $this->getFilenameOnFilestore($file);
  136. if (file_exists($filename)) {
  137. return unlink($filename);
  138. } else {
  139. return true;
  140. }
  141. }
  142. /**
  143. * Seek to the specified position.
  144. *
  145. * @param resource $f File resource
  146. * @param int $position Position in bytes
  147. *
  148. * @return bool
  149. */
  150. public function seek($f, $position) {
  151. return fseek($f, $position);
  152. }
  153. /**
  154. * Return the current location of the internal pointer
  155. *
  156. * @param resource $f File pointer resource
  157. *
  158. * @return int|false
  159. */
  160. public function tell($f) {
  161. return ftell($f);
  162. }
  163. /**
  164. * Tests for end of file on a file pointer
  165. *
  166. * @param resource $f File pointer resource
  167. *
  168. * @return bool
  169. */
  170. public function eof($f) {
  171. return feof($f);
  172. }
  173. /**
  174. * Returns the file size of an \ElggFile file.
  175. *
  176. * @param \ElggFile $file File object
  177. *
  178. * @return int The file size
  179. */
  180. public function getFileSize(\ElggFile $file) {
  181. return filesize($this->getFilenameOnFilestore($file));
  182. }
  183. /**
  184. * Get the filename as saved on disk for an \ElggFile object
  185. *
  186. * Returns an empty string if no filename set
  187. *
  188. * @param \ElggFile $file File object
  189. *
  190. * @return string The full path of where the file is stored
  191. * @throws InvalidParameterException
  192. */
  193. public function getFilenameOnFilestore(\ElggFile $file) {
  194. $owner_guid = $file->getOwnerGuid();
  195. if (!$owner_guid) {
  196. $owner_guid = _elgg_services()->session->getLoggedInUserGuid();
  197. }
  198. if (!$owner_guid) {
  199. $msg = "File " . $file->getFilename() . " (file guid:" . $file->guid . ") is missing an owner!";
  200. throw new \InvalidParameterException($msg);
  201. }
  202. $filename = $file->getFilename();
  203. if (!$filename) {
  204. return '';
  205. }
  206. $dir = new \Elgg\EntityDirLocator($owner_guid);
  207. return $this->dir_root . $dir . $file->getFilename();
  208. }
  209. /**
  210. * Returns the contents of the \ElggFile file.
  211. *
  212. * @param \ElggFile $file File object
  213. *
  214. * @return string
  215. */
  216. public function grabFile(\ElggFile $file) {
  217. return file_get_contents($file->getFilenameOnFilestore());
  218. }
  219. /**
  220. * Tests if an \ElggFile file exists.
  221. *
  222. * @param \ElggFile $file File object
  223. *
  224. * @return bool
  225. */
  226. public function exists(\ElggFile $file) {
  227. if (!$file->getFilename()) {
  228. return false;
  229. }
  230. return file_exists($this->getFilenameOnFilestore($file));
  231. }
  232. /**
  233. * Returns the size of all data stored under a directory in the disk store.
  234. *
  235. * @param string $prefix The prefix to check under.
  236. * @param string $container_guid The guid of the entity whose data you want to check.
  237. *
  238. * @return int|false
  239. */
  240. public function getSize($prefix, $container_guid) {
  241. if ($container_guid) {
  242. $dir = new \Elgg\EntityDirLocator($container_guid);
  243. return get_dir_size($this->dir_root . $dir . $prefix);
  244. } else {
  245. return false;
  246. }
  247. }
  248. // @codingStandardsIgnoreStart
  249. /**
  250. * Create a directory $dirroot
  251. *
  252. * @param string $dirroot The full path of the directory to create
  253. *
  254. * @throws IOException
  255. * @return true
  256. * @deprecated 1.8 Use \ElggDiskFilestore::makeDirectoryRoot()
  257. */
  258. protected function make_directory_root($dirroot) {
  259. elgg_deprecated_notice('\ElggDiskFilestore::make_directory_root() is deprecated by ::makeDirectoryRoot()', 1.8);
  260. return $this->makeDirectoryRoot($dirroot);
  261. }
  262. // @codingStandardsIgnoreEnd
  263. /**
  264. * Create a directory $dirroot
  265. *
  266. * @param string $dirroot The full path of the directory to create
  267. *
  268. * @throws IOException
  269. * @return true
  270. */
  271. protected function makeDirectoryRoot($dirroot) {
  272. if (!file_exists($dirroot)) {
  273. if (!@mkdir($dirroot, 0700, true)) {
  274. throw new \IOException("Could not make " . $dirroot);
  275. }
  276. }
  277. return true;
  278. }
  279. /**
  280. * Returns a list of attributes to save to the database when saving
  281. * the \ElggFile object using this file store.
  282. *
  283. * @return array
  284. */
  285. public function getParameters() {
  286. return array("dir_root" => $this->dir_root);
  287. }
  288. /**
  289. * Sets parameters that should be saved to database.
  290. *
  291. * @param array $parameters Set parameters to save to DB for this filestore.
  292. *
  293. * @return bool
  294. */
  295. public function setParameters(array $parameters) {
  296. if (isset($parameters['dir_root'])) {
  297. $this->dir_root = $parameters['dir_root'];
  298. return true;
  299. }
  300. return false;
  301. }
  302. /**
  303. * Deprecated methods
  304. */
  305. // @codingStandardsIgnoreStart
  306. /**
  307. * Construct a file path matrix for an entity.
  308. *
  309. * @param int $identifier The guid of the entity to store the data under.
  310. *
  311. * @return string The path where the entity's data will be stored.
  312. * @deprecated 1.8 Use \Elgg\EntityDirLocator
  313. */
  314. protected function make_file_matrix($identifier) {
  315. elgg_deprecated_notice('\ElggDiskFilestore::make_file_matrix() is deprecated by \Elgg\EntityDirLocator', 1.8);
  316. return $this->makeFileMatrix($identifier);
  317. }
  318. // @codingStandardsIgnoreEnd
  319. // @codingStandardsIgnoreStart
  320. /**
  321. * Construct a filename matrix.
  322. *
  323. * Generates a matrix using the entity's creation time and
  324. * unique guid.
  325. *
  326. * @param int $guid The entity to contrust a matrix for
  327. *
  328. * @deprecated 1.8 Use \ElggDiskFileStore::makeFileMatrix()
  329. * @return string The
  330. */
  331. protected function user_file_matrix($guid) {
  332. elgg_deprecated_notice('\ElggDiskFilestore::user_file_matrix() is deprecated by \Elgg\EntityDirLocator', 1.8);
  333. return $this->makeFileMatrix($guid);
  334. }
  335. // @codingStandardsIgnoreEnd
  336. // @codingStandardsIgnoreStart
  337. /**
  338. * Multibyte string tokeniser.
  339. *
  340. * Splits a string into an array. Will fail safely if mbstring is
  341. * not installed.
  342. *
  343. * @param string $string String
  344. * @param string $charset The charset, defaults to UTF8
  345. *
  346. * @return array
  347. * @deprecated 1.8 Files are stored by date and guid; no need for this.
  348. */
  349. private function mb_str_split($string, $charset = 'UTF8') {
  350. elgg_deprecated_notice('\ElggDiskFilestore::mb_str_split() is deprecated.', 1.8);
  351. if (is_callable('mb_substr')) {
  352. $length = mb_strlen($string);
  353. $array = array();
  354. while ($length) {
  355. $array[] = mb_substr($string, 0, 1, $charset);
  356. $string = mb_substr($string, 1, $length, $charset);
  357. $length = mb_strlen($string);
  358. }
  359. return $array;
  360. } else {
  361. return str_split($string);
  362. }
  363. }
  364. // @codingStandardsIgnoreEnd
  365. /**
  366. * Construct a file path matrix for an entity.
  367. *
  368. * @param int $guid The guid of the entity to store the data under.
  369. *
  370. * @return string The path where the entity's data will be stored relative to the data dir.
  371. * @deprecated 1.9 Use \Elgg\EntityDirLocator()
  372. */
  373. protected function makeFileMatrix($guid) {
  374. elgg_deprecated_notice('\ElggDiskFilestore::makeFileMatrix() is deprecated by \Elgg\EntityDirLocator', 1.9);
  375. $entity = get_entity($guid);
  376. if (!$entity instanceof \ElggEntity) {
  377. return false;
  378. }
  379. $dir = new \Elgg\EntityDirLocator($guid);
  380. return $dir->getPath();
  381. }
  382. }