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