1: <?php
2: /**
3: * File engine For XOOPS
4: *
5: * You may not change or alter any portion of this comment or credits
6: * of supporting developers from this source code or any supporting source code
7: * which is considered copyrighted (c) material of the original comment or credit authors.
8: * This program is distributed in the hope that it will be useful,
9: * but WITHOUT ANY WARRANTY; without even the implied warranty of
10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11: *
12: * @copyright (c) 2005-2016 XOOPS Project (www.xoops.org)
13: * @license GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14: * @package class
15: * @subpackage file
16: * @since 2.3.0
17: * @author Taiwen Jiang <phppp@users.sourceforge.net>
18: */
19: defined('XOOPS_ROOT_PATH') || exit('Restricted access');
20:
21: /**
22: * Convenience class for reading, writing and appending to files.
23: *
24: * PHP versions 4 and 5
25: *
26: * CakePHP(tm) : Rapid Development Framework <https://www.cakephp.org/>
27: * Copyright 2005-2008, Cake Software Foundation, Inc.
28: * 1785 E. Sahara Avenue, Suite 490-204
29: * Las Vegas, Nevada 89104
30: *
31: * Licensed under The MIT License
32: * Redistributions of files must retain the above copyright notice.
33: *
34: * @filesource
35: * @copyright Copyright 2005-2008, Cake Software Foundation, Inc.
36: * @link https://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
37: * @package cake
38: * @subpackage cake.cake.libs
39: * @since CakePHP(tm) v 0.2.9
40: * @modifiedby $LastChangedBy: beckmi $
41: * @lastmodified $Date: 2015-06-06 17:59:41 -0400 (Sat, 06 Jun 2015) $
42: * @license https://www.opensource.org/licenses/mit-license.php The MIT License
43: */
44:
45: /**
46: * Convenience class for reading, writing and appending to files.
47: *
48: * @package cake
49: * @subpackage cake.cake.libs
50: */
51: class XoopsFileHandler
52: {
53: /**
54: * folder object of the File
55: *
56: * @var object
57: * @access public
58: */
59: public $folder;
60:
61: /**
62: * Filename
63: *
64: * @var string
65: * @access public
66: */
67: public $name;
68:
69: /**
70: * file info
71: *
72: * @var string
73: * @access public
74: */
75: public $info = array();
76:
77: /**
78: * Holds the file handler resource if the file is opened
79: *
80: * @var resource
81: * @access public
82: */
83: public $handle;
84:
85: /**
86: * enable locking for file reading and writing
87: *
88: * @var boolean
89: * @access public
90: */
91: public $lock;
92:
93: /**
94: * Constructor
95: *
96: * @param string $path Path to file
97: * @param boolean $create Create file if it does not exist (if true)
98: * @param integer $mode Mode to apply to the folder holding the file
99: * @access private
100: */
101: public function __construct($path, $create = false, $mode = 0755)
102: {
103: XoopsLoad::load('XoopsFile');
104: $this->folder = XoopsFile::getHandler('folder', dirname($path), $create, $mode);
105: if (!is_dir($path)) {
106: $this->name = basename($path);
107: }
108: if (!$this->exists()) {
109: if ($create === true) {
110: if ($this->safe($path) && $this->create() === false) {
111: return false;
112: }
113: } else {
114: return false;
115: }
116: }
117: return null;
118: }
119:
120: /**
121: * Closes the current file if it is opened
122: *
123: * @access private
124: */
125: public function __destruct()
126: {
127: $this->close();
128: }
129:
130: /**
131: * Creates the File.
132: *
133: * @return boolean Success
134: * @access public
135: */
136: public function create()
137: {
138: $dir = $this->folder->pwd();
139: if (is_dir($dir) && is_writable($dir) && !$this->exists()) {
140: if (touch($this->pwd())) {
141: return true;
142: }
143: }
144:
145: return false;
146: }
147:
148: /**
149: * Opens the current file with a given $mode
150: *
151: * @param string $mode A valid 'fopen' mode string (r|w|a ...)
152: * @param boolean $force If true then the file will be re-opened even if its already opened, otherwise it won't
153: * @return boolean True on success, false on failure
154: * @access public
155: */
156: public function open($mode = 'r', $force = false)
157: {
158: if (!$force && is_resource($this->handle)) {
159: return true;
160: }
161: if ($this->exists() === false) {
162: if ($this->create() === false) {
163: return false;
164: }
165: }
166: $this->handle = fopen($this->pwd(), $mode);
167: if (is_resource($this->handle)) {
168: return true;
169: }
170:
171: return false;
172: }
173:
174: /**
175: * Return the contents of this File as a string.
176: *
177: * @param bool|string|int $bytes where to start
178: * @param string $mode
179: * @param boolean $force If true then the file will be re-opened even if its already opened, otherwise it won't
180: *
181: * @return mixed string on success, false on failure
182: * @access public
183: */
184: public function read($bytes = false, $mode = 'rb', $force = false)
185: {
186: $success = false;
187: if ($this->lock !== null) {
188: if (flock($this->handle, LOCK_SH) === false) {
189: return false;
190: }
191: }
192: if ($bytes === false) {
193: $success = file_get_contents($this->pwd());
194: } elseif ($this->open($mode, $force) === true) {
195: if (is_int($bytes)) {
196: $success = fread($this->handle, $bytes);
197: } else {
198: $data = '';
199: while (!feof($this->handle)) {
200: $data .= fgets($this->handle, 4096);
201: }
202: $success = trim($data);
203: }
204: }
205: if ($this->lock !== null) {
206: flock($this->handle, LOCK_UN);
207: }
208:
209: return $success;
210: }
211:
212: /**
213: * Sets or gets the offset for the currently opened file.
214: *
215: * @param mixed $offset The $offset in bytes to seek. If set to false then the current offset is returned.
216: * @param integer $seek PHP Constant SEEK_SET | SEEK_CUR | SEEK_END determining what the $offset is relative to
217: * @return mixed True on success, false on failure (set mode), false on failure or integer offset on success (get mode)
218: * @access public
219: */
220: public function offset($offset = false, $seek = SEEK_SET)
221: {
222: if ($offset === false) {
223: if (is_resource($this->handle)) {
224: return ftell($this->handle);
225: }
226: } elseif ($this->open() === true) {
227: return fseek($this->handle, $offset, $seek) === 0;
228: }
229:
230: return false;
231: }
232:
233: /**
234: * Prepares a ascii string for writing
235: * fixes line endings
236: *
237: * @param string $data Data to prepare for writing.
238: * @return string
239: * @access public
240: */
241: public function prepare($data)
242: {
243: $lineBreak = "\n";
244: if (substr(PHP_OS, 0, 3) === 'WIN') {
245: $lineBreak = "\r\n";
246: }
247:
248: return strtr($data, array(
249: "\r\n" => $lineBreak,
250: "\n" => $lineBreak,
251: "\r" => $lineBreak));
252: }
253:
254: /**
255: * Write given data to this File.
256: *
257: * @param string $data Data to write to this File.
258: * @param string $mode Mode of writing. {@link https://php.net/fwrite See fwrite()}.
259: * @param bool|string $force force the file to open
260: * @return boolean Success
261: * @access public
262: */
263: public function write($data, $mode = 'w', $force = false)
264: {
265: $success = false;
266: if ($this->open($mode, $force) === true) {
267: if ($this->lock !== null) {
268: if (flock($this->handle, LOCK_EX) === false) {
269: return false;
270: }
271: }
272: if (fwrite($this->handle, $data) !== false) {
273: $success = true;
274: }
275: if ($this->lock !== null) {
276: flock($this->handle, LOCK_UN);
277: }
278: }
279:
280: return $success;
281: }
282:
283: /**
284: * Append given data string to this File.
285: *
286: * @param string $data Data to write
287: * @param bool|string $force force the file to open
288: * @return boolean Success
289: * @access public
290: */
291: public function append($data, $force = false)
292: {
293: return $this->write($data, 'a', $force);
294: }
295:
296: /**
297: * Closes the current file if it is opened.
298: *
299: * @return boolean True if closing was successful or file was already closed, otherwise false
300: * @access public
301: */
302: public function close()
303: {
304: if (!is_resource($this->handle)) {
305: return true;
306: }
307:
308: return fclose($this->handle);
309: }
310:
311: /**
312: * Deletes the File.
313: *
314: * @return boolean Success
315: * @access public
316: */
317: public function delete()
318: {
319: if ($this->exists()) {
320: return unlink($this->pwd());
321: }
322:
323: return false;
324: }
325:
326: /**
327: * Returns the File extension.
328: *
329: * @return string The File extension
330: * @access public
331: */
332: public function info()
333: {
334: if ($this->info == null) {
335: $this->info = pathinfo($this->pwd());
336: }
337: if (!isset($this->info['filename'])) {
338: $this->info['filename'] = $this->name();
339: }
340:
341: return $this->info;
342: }
343:
344: /**
345: * Returns the File extension.
346: *
347: * @return string|false The File extension
348: * @access public
349: */
350: public function ext()
351: {
352: if ($this->info == null) {
353: $this->info();
354: }
355: if (isset($this->info['extension'])) {
356: return $this->info['extension'];
357: }
358:
359: return false;
360: }
361:
362: /**
363: * Returns the File name without extension.
364: *
365: * @return string|false The File name without extension.
366: * @access public
367: */
368: public function name()
369: {
370: if ($this->info == null) {
371: $this->info();
372: }
373: if (isset($this->info['extension'])) {
374: return basename($this->name, '.' . $this->info['extension']);
375: } elseif ($this->name) {
376: return $this->name;
377: }
378:
379: return false;
380: }
381:
382: /**
383: * makes filename safe for saving
384: *
385: * @param string $name the name of the file to make safe if different from $this->name
386: * @param null|string $ext
387: * @return string $ext the extension of the file
388: * @access public
389: */
390: public function safe($name = null, $ext = null)
391: {
392: if (!$name) {
393: $name = $this->name;
394: }
395: if (!$ext) {
396: $ext = $this->ext();
397: }
398:
399: return preg_replace('/[^\w\.-]+/', '_', basename($name, $ext));
400: }
401:
402: /**
403: * Get md5 Checksum of file with previous check of Filesize
404: *
405: * @param mixed $maxsize in MB or true to force
406: * @return string|false md5 Checksum {@link https://php.net/md5_file See md5_file()}
407: * @access public
408: */
409: public function md5($maxsize = 5)
410: {
411: if ($maxsize === true) {
412: return md5_file($this->pwd());
413: } else {
414: $size = $this->size();
415: if ($size && $size < ($maxsize * 1024) * 1024) {
416: return md5_file($this->pwd());
417: }
418: }
419:
420: return false;
421: }
422:
423: /**
424: * Returns the full path of the File.
425: *
426: * @return string Full path to file
427: * @access public
428: */
429: public function pwd()
430: {
431: return $this->folder->slashTerm($this->folder->pwd()) . $this->name;
432: }
433:
434: /**
435: * Returns true if the File exists.
436: *
437: * @return boolean true if it exists, false otherwise
438: * @access public
439: */
440: public function exists()
441: {
442: $exists = (file_exists($this->pwd()) && is_file($this->pwd()));
443:
444: return $exists;
445: }
446:
447: /**
448: * Returns the "chmod" (permissions) of the File.
449: *
450: * @return string|false Permissions for the file
451: * @access public
452: */
453: public function perms()
454: {
455: if ($this->exists()) {
456: return substr(sprintf('%o', fileperms($this->pwd())), -4);
457: }
458:
459: return false;
460: }
461:
462: /**
463: * Returns the Filesize, either in bytes or in human-readable format.
464: *
465: * @return string|false |int filesize as int or as a human-readable string
466: * @access public
467: */
468: public function size()
469: {
470: if ($this->exists()) {
471: return filesize($this->pwd());
472: }
473:
474: return false;
475: }
476:
477: /**
478: * Returns true if the File is writable.
479: *
480: * @return boolean true if its writable, false otherwise
481: * @access public
482: */
483: public function writable()
484: {
485: return is_writable($this->pwd());
486: }
487:
488: /**
489: * Returns true if the File is executable.
490: *
491: * @return boolean true if its executable, false otherwise
492: * @access public
493: */
494: public function executable()
495: {
496: return is_executable($this->pwd());
497: }
498:
499: /**
500: * Returns true if the File is readable.
501: *
502: * @return boolean true if file is readable, false otherwise
503: * @access public
504: */
505: public function readable()
506: {
507: return is_readable($this->pwd());
508: }
509:
510: /**
511: * Returns the File's owner.
512: *
513: * @return integer|false the Fileowner
514: */
515: public function owner()
516: {
517: if ($this->exists()) {
518: return fileowner($this->pwd());
519: }
520:
521: return false;
522: }
523:
524: /**
525: * Returns the File group.
526: *
527: * @return integer|false the Filegroup
528: * @access public
529: */
530: public function group()
531: {
532: if ($this->exists()) {
533: return filegroup($this->pwd());
534: }
535:
536: return false;
537: }
538:
539: /**
540: * Returns last access time.
541: *
542: * @return integer|false timestamp Timestamp of last access time
543: * @access public
544: */
545: public function lastAccess()
546: {
547: if ($this->exists()) {
548: return fileatime($this->pwd());
549: }
550:
551: return false;
552: }
553:
554: /**
555: * Returns last modified time.
556: *
557: * @return integer|false timestamp Timestamp of last modification
558: * @access public
559: */
560: public function lastChange()
561: {
562: if ($this->exists()) {
563: return filemtime($this->pwd());
564: }
565:
566: return false;
567: }
568:
569: /**
570: * Returns the current folder.
571: *
572: * @return Folder Current folder
573: * @access public
574: */
575: public function &folder()
576: {
577: return $this->folder;
578: }
579: }
580: