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