ElggFile::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 2
rs 10
c 0
b 0
f 0
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. If you do not save the file, no
15
 * entity is created in the database. This is because there are occasions
16
 * when you may want access to file data on datastores using the \ElggFile
17
 * interface without a need to persist information such as temporary files.
18
 *
19
 * @package    Elgg.Core
20
 * @subpackage DataModel.File
21
 */
22
class ElggFile extends \ElggObject {
23
	/** Filestore */
24
	private $filestore;
25
26
	/** File handle used to identify this file in a filestore. Created by open. */
27
	private $handle;
28
29
	/**
30
	 * Set subtype to 'file'.
31
	 *
32
	 * @return void
33
	 */
34
	protected function initializeAttributes() {
35
		parent::initializeAttributes();
36
37
		$this->attributes['subtype'] = "file";
38
	}
39
40
	/**
41
	 * Loads an \ElggFile entity.
42
	 *
43
	 * @param \stdClass $row Database result or null for new \ElggFile
44
	 */
45
	public function __construct($row = null) {
46
		parent::__construct($row);
47
48
		// Set default filestore
49
		$this->filestore = $this->getFilestore();
50
	}
51
52
	/**
53
	 * Set the filename of this file.
54
	 *
55
	 * @param string $name The filename.
56
	 *
57
	 * @return void
58
	 */
59
	public function setFilename($name) {
60
		$this->filename = $name;
61
	}
62
63
	/**
64
	 * Return the filename.
65
	 *
66
	 * @return string
67
	 */
68
	public function getFilename() {
69
		return $this->filename;
70
	}
71
72
	/**
73
	 * Return the filename of this file as it is/will be stored on the
74
	 * filestore, which may be different to the filename.
75
	 *
76
	 * @return string
77
	 */
78
	public function getFilenameOnFilestore() {
79
		return $this->filestore->getFilenameOnFilestore($this);
80
	}
81
82
	/**
83
	 * Return the size of the filestore associated with this file
84
	 *
85
	 * @param string $prefix         Storage prefix
86
	 * @param int    $container_guid The container GUID of the checked filestore
87
	 *
88
	 * @return int
89
	 */
90
	public function getFilestoreSize($prefix = '', $container_guid = 0) {
91
		if (!$container_guid) {
92
			$container_guid = $this->container_guid;
93
		}
94
		$fs = $this->getFilestore();
95
		// @todo add getSize() to \ElggFilestore
96
		return $fs->getSize($prefix, $container_guid);
97
	}
98
99
	/**
100
	 * Get the mime type of the file.
101
	 *
102
	 * @return string
103
	 */
104
	public function getMimeType() {
105
		if ($this->mimetype) {
106
			return $this->mimetype;
107
		}
108
109
		// @todo Guess mimetype if not here
110
	}
111
112
	/**
113
	 * Set the mime type of the file.
114
	 *
115
	 * @param string $mimetype The mimetype
116
	 *
117
	 * @return bool
118
	 */
119
	public function setMimeType($mimetype) {
120
		return $this->mimetype = $mimetype;
121
	}
122
123
	/**
124
	 * Detects mime types based on filename or actual file.
125
	 *
126
	 * @note This method can be called both dynamically and statically
127
	 *
128
	 * @param mixed $file    The full path of the file to check. For uploaded files, use tmp_name.
129
	 * @param mixed $default A default. Useful to pass what the browser thinks it is.
130
	 * @since 1.7.12
131
	 *
132
	 * @return mixed Detected type on success, false on failure.
133
	 * @todo Move this out into a utility class
134
	 */
135
	public function detectMimeType($file = null, $default = null) {
136
		$class = __CLASS__;
137
		if (!$file && isset($this) && $this instanceof $class) {
138
			$file = $this->getFilenameOnFilestore();
139
		}
140
141
		if (!is_readable($file)) {
142
			return false;
143
		}
144
145
		$mime = $default;
146
147
		// for PHP5 folks.
148
		if (function_exists('finfo_file') && defined('FILEINFO_MIME_TYPE')) {
149
			$resource = finfo_open(FILEINFO_MIME_TYPE);
150
			if ($resource) {
151
				$mime = finfo_file($resource, $file);
152
			}
153
		}
154
155
		// for everyone else.
156
		if (!$mime && function_exists('mime_content_type')) {
157
			$mime = mime_content_type($file);
158
		}
159
160
		$original_filename = isset($this) && $this instanceof $class ? $this->originalfilename : basename($file);
161
		$params = array(
162
			'filename' => $file,
163
			'original_filename' => $original_filename, // @see file upload action
164
			'default' => $default,
165
		);
166
		return _elgg_services()->hooks->trigger('mime_type', 'file', $params, $mime);
167
	}
168
169
	/**
170
	 * Set the optional file description.
171
	 *
172
	 * @param string $description The description.
173
	 *
174
	 * @return bool
175
	 */
176
	public function setDescription($description) {
177
		$this->description = $description;
178
	}
179
180
	/**
181
	 * Open the file with the given mode
182
	 *
183
	 * @param string $mode Either read/write/append
184
	 *
185
	 * @return resource File handler
186
	 *
187
	 * @throws IOException|InvalidParameterException
188
	 */
189
	public function open($mode) {
190
		if (!$this->getFilename()) {
191
			throw new \IOException("You must specify a name before opening a file.");
192
		}
193
194
		// See if file has already been saved
195
		// seek on datastore, parameters and name?
196
197
		// Sanity check
198
		if (
199
			($mode != "read") &&
200
			($mode != "write") &&
201
			($mode != "append")
202
		) {
203
			$msg = "Unrecognized file mode '" . $mode . "'";
204
			throw new \InvalidParameterException($msg);
205
		}
206
207
		// Get the filestore
208
		$fs = $this->getFilestore();
209
210
		// Ensure that we save the file details to object store
211
		//$this->save();
212
213
		// Open the file handle
214
		$this->handle = $fs->open($this, $mode);
215
216
		return $this->handle;
217
	}
218
219
	/**
220
	 * Write data.
221
	 *
222
	 * @param string $data The data
223
	 *
224
	 * @return bool
225
	 */
226
	public function write($data) {
227
		$fs = $this->getFilestore();
228
229
		return $fs->write($this->handle, $data);
230
	}
231
232
	/**
233
	 * Read data.
234
	 *
235
	 * @param int $length Amount to read.
236
	 * @param int $offset The offset to start from.
237
	 *
238
	 * @return mixed Data or false
239
	 */
240
	public function read($length, $offset = 0) {
241
		$fs = $this->getFilestore();
242
243
		return $fs->read($this->handle, $length, $offset);
244
	}
245
246
	/**
247
	 * Gets the full contents of this file.
248
	 *
249
	 * @return mixed The file contents.
250
	 */
251
	public function grabFile() {
252
		$fs = $this->getFilestore();
253
		return $fs->grabFile($this);
254
	}
255
256
	/**
257
	 * Close the file and commit changes
258
	 *
259
	 * @return bool
260
	 */
261
	public function close() {
262
		$fs = $this->getFilestore();
263
264
		if ($fs->close($this->handle)) {
265
			$this->handle = null;
266
267
			return true;
268
		}
269
270
		return false;
271
	}
272
273
	/**
274
	 * Delete this file.
275
	 *
276
	 * @return bool
277
	 */
278
	public function delete() {
279
		$fs = $this->getFilestore();
280
		
281
		$result = $fs->delete($this);
282
		
283
		if ($this->getGUID() && $result) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getGUID() of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
284
			$result = parent::delete();
285
		}
286
		
287
		return $result;
288
	}
289
290
	/**
291
	 * Seek a position in the file.
292
	 *
293
	 * @param int $position Position in bytes
294
	 *
295
	 * @return bool
296
	 */
297
	public function seek($position) {
298
		$fs = $this->getFilestore();
299
300
		// @todo add seek() to \ElggFilestore
301
		return $fs->seek($this->handle, $position);
302
	}
303
304
	/**
305
	 * Return the current position of the file.
306
	 *
307
	 * @return int The file position
308
	 */
309
	public function tell() {
310
		$fs = $this->getFilestore();
311
312
		return $fs->tell($this->handle);
313
	}
314
315
	/**
316
	 * Return the size of the file in bytes.
317
	 *
318
	 * @return int
319
	 * @since 1.9
320
	 */
321
	public function getSize() {
322
		return $this->filestore->getFileSize($this);
323
	}
324
325
	/**
326
	 * Return the size of the file in bytes.
327
	 *
328
	 * @return int
329
	 * @deprecated 1.8 Use getSize()
330
	 */
331
	public function size() {
332
		elgg_deprecated_notice("Use \ElggFile::getSize() instead of \ElggFile::size()", 1.9);
333
		return $this->getSize();
334
	}
335
336
	/**
337
	 * Return a boolean value whether the file handle is at the end of the file
338
	 *
339
	 * @return bool
340
	 */
341
	public function eof() {
342
		$fs = $this->getFilestore();
343
344
		return $fs->eof($this->handle);
345
	}
346
347
	/**
348
	 * Returns if the file exists
349
	 *
350
	 * @return bool
351
	 */
352
	public function exists() {
353
		$fs = $this->getFilestore();
354
355
		return $fs->exists($this);
356
	}
357
358
	/**
359
	 * Set a filestore.
360
	 *
361
	 * @param \ElggFilestore $filestore The file store.
362
	 *
363
	 * @return void
364
	 */
365
	public function setFilestore(\ElggFilestore $filestore) {
366
		$this->filestore = $filestore;
367
	}
368
369
	/**
370
	 * Return a filestore suitable for saving this file.
371
	 * This filestore is either a pre-registered filestore,
372
	 * a filestore as recorded in metadata or the system default.
373
	 *
374
	 * @return \ElggFilestore
375
	 *
376
	 * @throws ClassNotFoundException
377
	 */
378
	protected function getFilestore() {
379
		// Short circuit if already set.
380
		if ($this->filestore) {
381
			return $this->filestore;
382
		}
383
384
		// ask for entity specific filestore
385
		// saved as filestore::className in metadata.
386
		// need to get all filestore::* metadata because the rest are "parameters" that
387
		// get passed to filestore::setParameters()
388
		if ($this->guid) {
389
			$options = array(
390
				'guid' => $this->guid,
391
				'where' => array("n.string LIKE 'filestore::%'"),
392
			);
393
394
			$mds = elgg_get_metadata($options);
395
396
			$parameters = array();
397
			foreach ($mds as $md) {
398
				list( , $name) = explode("::", $md->name);
399
				if ($name == 'filestore') {
400
					$filestore = $md->value;
401
				}
402
				$parameters[$name] = $md->value;
403
			}
404
		}
405
406
		// need to check if filestore is set because this entity is loaded in save()
407
		// before the filestore metadata is saved.
408
		if (isset($filestore)) {
409
			if (!class_exists($filestore)) {
410
				$msg = "Unable to load filestore class " . $filestore . " for file " . $this->guid;
411
				throw new \ClassNotFoundException($msg);
412
			}
413
414
			$this->filestore = new $filestore();
415
			$this->filestore->setParameters($parameters);
0 ignored issues
show
Bug introduced by
The variable $parameters does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
416
			// @todo explain why $parameters will always be set here (PhpStorm complains)
417
		}
418
419
		// this means the entity hasn't been saved so fallback to default
420
		if (!$this->filestore) {
421
			$this->filestore = get_default_filestore();
422
		}
423
424
		return $this->filestore;
425
	}
426
427
	/**
428
	 * Save the file
429
	 *
430
	 * Write the file's data to the filestore and save
431
	 * the corresponding entity.
432
	 *
433
	 * @see \ElggObject::save()
434
	 *
435
	 * @return bool
436
	 */
437
	public function save() {
438
		if (!parent::save()) {
439
			return false;
440
		}
441
442
		// Save datastore metadata
443
		$params = $this->filestore->getParameters();
444
		foreach ($params as $k => $v) {
445
			$this->setMetadata("filestore::$k", $v);
446
		}
447
448
		// Now make a note of the filestore class
449
		$this->setMetadata("filestore::filestore", get_class($this->filestore));
450
451
		return true;
452
	}
453
454
	/**
455
	 * Get property names to serialize.
456
	 *
457
	 * @return string[]
458
	 */
459
	public function __sleep() {
460
		return array_diff(array_keys(get_object_vars($this)), array(
461
			// Don't persist filestore, which contains CONFIG
462
			// https://github.com/Elgg/Elgg/issues/9081#issuecomment-152859856
463
			'filestore',
464
465
			// a resource
466
			'handle',
467
		));
468
	}
469
470
	/**
471
	 * Reestablish filestore property
472
	 *
473
	 * @return void
474
	 * @throws ClassNotFoundException
475
	 */
476
	public function __wakeup() {
477
		$this->getFilestore();
478
	}
479
}
480