1<?php
2
3/**
4 * Simple elFinder driver for MySQL.
5 *
6 * @author Dmitry (dio) Levashov
7 **/
8class elFinderVolumeMySQL extends elFinderVolumeDriver
9{
10
11    /**
12     * Driver id
13     * Must be started from letter and contains [a-z0-9]
14     * Used as part of volume id
15     *
16     * @var string
17     **/
18    protected $driverId = 'm';
19
20    /**
21     * Database object
22     *
23     * @var mysqli
24     **/
25    protected $db = null;
26
27    /**
28     * Tables to store files
29     *
30     * @var string
31     **/
32    protected $tbf = '';
33
34    /**
35     * Directory for tmp files
36     * If not set driver will try to use tmbDir as tmpDir
37     *
38     * @var string
39     **/
40    protected $tmpPath = '';
41
42    /**
43     * Numbers of sql requests (for debug)
44     *
45     * @var int
46     **/
47    protected $sqlCnt = 0;
48
49    /**
50     * Last db error message
51     *
52     * @var string
53     **/
54    protected $dbError = '';
55
56    /**
57     * This root has parent id
58     *
59     * @var        boolean
60     */
61    protected $rootHasParent = false;
62
63    /**
64     * Constructor
65     * Extend options with required fields
66     *
67     * @author Dmitry (dio) Levashov
68     */
69    public function __construct()
70    {
71        $opts = array(
72            'host' => 'localhost',
73            'user' => '',
74            'pass' => '',
75            'db' => '',
76            'port' => null,
77            'socket' => null,
78            'files_table' => 'elfinder_file',
79            'tmbPath' => '',
80            'tmpPath' => '',
81            'rootCssClass' => 'elfinder-navbar-root-sql',
82            'noSessionCache' => array('hasdirs'),
83            'isLocalhost' => false
84        );
85        $this->options = array_merge($this->options, $opts);
86        $this->options['mimeDetect'] = 'internal';
87    }
88
89    /*********************************************************************/
90    /*                        INIT AND CONFIGURE                         */
91    /*********************************************************************/
92
93    /**
94     * Prepare driver before mount volume.
95     * Connect to db, check required tables and fetch root path
96     *
97     * @return bool
98     * @author Dmitry (dio) Levashov
99     **/
100    protected function init()
101    {
102
103        if (!($this->options['host'] || $this->options['socket'])
104            || !$this->options['user']
105            || !$this->options['pass']
106            || !$this->options['db']
107            || !$this->options['path']
108            || !$this->options['files_table']) {
109            return $this->setError('Required options "host", "socket", "user", "pass", "db", "path" or "files_table" are undefined.');
110        }
111
112        $err = null;
113        if ($this->db = @new mysqli($this->options['host'], $this->options['user'], $this->options['pass'], $this->options['db'], $this->options['port'], $this->options['socket'])) {
114            if ($this->db && $this->db->connect_error) {
115                $err = $this->db->connect_error;
116            }
117        } else {
118            $err = mysqli_connect_error();
119        }
120        if ($err) {
121            return $this->setError(array('Unable to connect to MySQL server.', $err));
122        }
123
124        if (!$this->needOnline && empty($this->ARGS['init'])) {
125            $this->db->close();
126            $this->db = null;
127            return true;
128        }
129
130        $this->db->set_charset('utf8');
131
132        if ($res = $this->db->query('SHOW TABLES')) {
133            while ($row = $res->fetch_array()) {
134                if ($row[0] == $this->options['files_table']) {
135                    $this->tbf = $this->options['files_table'];
136                    break;
137                }
138            }
139        }
140
141        if (!$this->tbf) {
142            return $this->setError('The specified database table cannot be found.');
143        }
144
145        $this->updateCache($this->options['path'], $this->_stat($this->options['path']));
146
147        // enable command archive
148        $this->options['useRemoteArchive'] = true;
149
150        // check isLocalhost
151        $this->isLocalhost = $this->options['isLocalhost'] || $this->options['host'] === 'localhost' || $this->options['host'] === '127.0.0.1' || $this->options['host'] === '::1';
152
153        return true;
154    }
155
156
157    /**
158     * Set tmp path
159     *
160     * @return void
161     * @throws elFinderAbortException
162     * @author Dmitry (dio) Levashov
163     */
164    protected function configure()
165    {
166        parent::configure();
167
168        if (($tmp = $this->options['tmpPath'])) {
169            if (!file_exists($tmp)) {
170                if (mkdir($tmp)) {
171                    chmod($tmp, $this->options['tmbPathMode']);
172                }
173            }
174
175            $this->tmpPath = is_dir($tmp) && is_writable($tmp) ? $tmp : false;
176        }
177        if (!$this->tmpPath && ($tmp = elFinder::getStaticVar('commonTempPath'))) {
178            $this->tmpPath = $tmp;
179        }
180
181        // fallback of $this->tmp
182        if (!$this->tmpPath && $this->tmbPathWritable) {
183            $this->tmpPath = $this->tmbPath;
184        }
185
186        $this->mimeDetect = 'internal';
187    }
188
189    /**
190     * Close connection
191     *
192     * @return void
193     * @author Dmitry (dio) Levashov
194     **/
195    public function umount()
196    {
197        $this->db && $this->db->close();
198    }
199
200    /**
201     * Return debug info for client
202     *
203     * @return array
204     * @author Dmitry (dio) Levashov
205     **/
206    public function debug()
207    {
208        $debug = parent::debug();
209        $debug['sqlCount'] = $this->sqlCnt;
210        if ($this->dbError) {
211            $debug['dbError'] = $this->dbError;
212        }
213        return $debug;
214    }
215
216    /**
217     * Perform sql query and return result.
218     * Increase sqlCnt and save error if occured
219     *
220     * @param  string $sql query
221     *
222     * @return bool|mysqli_result
223     * @author Dmitry (dio) Levashov
224     */
225    protected function query($sql)
226    {
227        $this->sqlCnt++;
228        $res = $this->db->query($sql);
229        if (!$res) {
230            $this->dbError = $this->db->error;
231        }
232        return $res;
233    }
234
235    /**
236     * Create empty object with required mimetype
237     *
238     * @param  string $path parent dir path
239     * @param  string $name object name
240     * @param  string $mime mime type
241     *
242     * @return bool
243     * @author Dmitry (dio) Levashov
244     **/
245    protected function make($path, $name, $mime)
246    {
247        $sql = 'INSERT INTO %s (`parent_id`, `name`, `size`, `mtime`, `mime`, `content`, `read`, `write`, `locked`, `hidden`, `width`, `height`) VALUES (\'%s\', \'%s\', 0, %d, \'%s\', \'\', \'%d\', \'%d\', \'%d\', \'%d\', 0, 0)';
248        $sql = sprintf($sql, $this->tbf, $path, $this->db->real_escape_string($name), time(), $mime, $this->defaults['read'], $this->defaults['write'], $this->defaults['locked'], $this->defaults['hidden']);
249        // echo $sql;
250        return $this->query($sql) && $this->db->affected_rows > 0;
251    }
252
253    /*********************************************************************/
254    /*                               FS API                              */
255    /*********************************************************************/
256
257    /**
258     * Cache dir contents
259     *
260     * @param  string $path dir path
261     *
262     * @return string
263     * @author Dmitry Levashov
264     **/
265    protected function cacheDir($path)
266    {
267        $this->dirsCache[$path] = array();
268
269        $sql = 'SELECT f.id, f.parent_id, f.name, f.size, f.mtime AS ts, f.mime, f.read, f.write, f.locked, f.hidden, f.width, f.height, IF(ch.id, 1, 0) AS dirs
270                FROM ' . $this->tbf . ' AS f
271                LEFT JOIN ' . $this->tbf . ' AS ch ON ch.parent_id=f.id AND ch.mime=\'directory\'
272                WHERE f.parent_id=\'' . $path . '\'
273                GROUP BY f.id, ch.id';
274
275        $res = $this->query($sql);
276        if ($res) {
277            while ($row = $res->fetch_assoc()) {
278                $id = $row['id'];
279                if ($row['parent_id'] && $id != $this->root) {
280                    $row['phash'] = $this->encode($row['parent_id']);
281                }
282
283                if ($row['mime'] == 'directory') {
284                    unset($row['width']);
285                    unset($row['height']);
286                    $row['size'] = 0;
287                } else {
288                    unset($row['dirs']);
289                }
290
291                unset($row['id']);
292                unset($row['parent_id']);
293
294
295                if (($stat = $this->updateCache($id, $row)) && empty($stat['hidden'])) {
296                    $this->dirsCache[$path][] = $id;
297                }
298            }
299        }
300
301        return $this->dirsCache[$path];
302    }
303
304    /**
305     * Return array of parents paths (ids)
306     *
307     * @param  int $path file path (id)
308     *
309     * @return array
310     * @author Dmitry (dio) Levashov
311     **/
312    protected function getParents($path)
313    {
314        $parents = array();
315
316        while ($path) {
317            if ($file = $this->stat($path)) {
318                array_unshift($parents, $path);
319                $path = isset($file['phash']) ? $this->decode($file['phash']) : false;
320            }
321        }
322
323        if (count($parents)) {
324            array_pop($parents);
325        }
326        return $parents;
327    }
328
329    /**
330     * Return correct file path for LOAD_FILE method
331     *
332     * @param  string $path file path (id)
333     *
334     * @return string
335     * @author Troex Nevelin
336     **/
337    protected function loadFilePath($path)
338    {
339        $realPath = realpath($path);
340        if (DIRECTORY_SEPARATOR == '\\') { // windows
341            $realPath = str_replace('\\', '\\\\', $realPath);
342        }
343        return $this->db->real_escape_string($realPath);
344    }
345
346    /**
347     * Recursive files search
348     *
349     * @param  string $path dir path
350     * @param  string $q    search string
351     * @param  array  $mimes
352     *
353     * @return array
354     * @throws elFinderAbortException
355     * @author Dmitry (dio) Levashov
356     */
357    protected function doSearch($path, $q, $mimes)
358    {
359        if (!empty($this->doSearchCurrentQuery['matchMethod'])) {
360            // has custom match method use elFinderVolumeDriver::doSearch()
361            return parent::doSearch($path, $q, $mimes);
362        }
363
364        $dirs = array();
365        $timeout = $this->options['searchTimeout'] ? $this->searchStart + $this->options['searchTimeout'] : 0;
366
367        if ($path != $this->root || $this->rootHasParent) {
368            $dirs = $inpath = array(intval($path));
369            while ($inpath) {
370                $in = '(' . join(',', $inpath) . ')';
371                $inpath = array();
372                $sql = 'SELECT f.id FROM %s AS f WHERE f.parent_id IN ' . $in . ' AND `mime` = \'directory\'';
373                $sql = sprintf($sql, $this->tbf);
374                if ($res = $this->query($sql)) {
375                    $_dir = array();
376                    while ($dat = $res->fetch_assoc()) {
377                        $inpath[] = $dat['id'];
378                    }
379                    $dirs = array_merge($dirs, $inpath);
380                }
381            }
382        }
383
384        $result = array();
385
386        if ($mimes) {
387            $whrs = array();
388            foreach ($mimes as $mime) {
389                if (strpos($mime, '/') === false) {
390                    $whrs[] = sprintf('f.mime LIKE \'%s/%%\'', $this->db->real_escape_string($mime));
391                } else {
392                    $whrs[] = sprintf('f.mime = \'%s\'', $this->db->real_escape_string($mime));
393                }
394            }
395            $whr = join(' OR ', $whrs);
396        } else {
397            $whr = sprintf('f.name LIKE \'%%%s%%\'', $this->db->real_escape_string($q));
398        }
399        if ($dirs) {
400            $whr = '(' . $whr . ') AND (`parent_id` IN (' . join(',', $dirs) . '))';
401        }
402
403        $sql = 'SELECT f.id, f.parent_id, f.name, f.size, f.mtime AS ts, f.mime, f.read, f.write, f.locked, f.hidden, f.width, f.height, 0 AS dirs
404                FROM %s AS f
405                WHERE %s';
406
407        $sql = sprintf($sql, $this->tbf, $whr);
408
409        if (($res = $this->query($sql))) {
410            while ($row = $res->fetch_assoc()) {
411                if ($timeout && $timeout < time()) {
412                    $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->path($this->encode($path)));
413                    break;
414                }
415
416                if (!$this->mimeAccepted($row['mime'], $mimes)) {
417                    continue;
418                }
419                $id = $row['id'];
420                if ($id == $this->root) {
421                    continue;
422                }
423                if ($row['parent_id'] && $id != $this->root) {
424                    $row['phash'] = $this->encode($row['parent_id']);
425                }
426                $row['path'] = $this->_path($id);
427
428                if ($row['mime'] == 'directory') {
429                    unset($row['width']);
430                    unset($row['height']);
431                } else {
432                    unset($row['dirs']);
433                }
434
435                unset($row['id']);
436                unset($row['parent_id']);
437
438                if (($stat = $this->updateCache($id, $row)) && empty($stat['hidden'])) {
439                    $result[] = $stat;
440                }
441            }
442        }
443        return $result;
444    }
445
446
447    /*********************** paths/urls *************************/
448
449    /**
450     * Return parent directory path
451     *
452     * @param  string $path file path
453     *
454     * @return string
455     * @author Dmitry (dio) Levashov
456     **/
457    protected function _dirname($path)
458    {
459        return ($stat = $this->stat($path)) ? (!empty($stat['phash']) ? $this->decode($stat['phash']) : $this->root) : false;
460    }
461
462    /**
463     * Return file name
464     *
465     * @param  string $path file path
466     *
467     * @return string
468     * @author Dmitry (dio) Levashov
469     **/
470    protected function _basename($path)
471    {
472        return (($stat = $this->stat($path)) && isset($stat['name'])) ? $stat['name'] : false;
473    }
474
475    /**
476     * Join dir name and file name and return full path
477     *
478     * @param  string $dir
479     * @param  string $name
480     *
481     * @return string
482     * @author Dmitry (dio) Levashov
483     **/
484    protected function _joinPath($dir, $name)
485    {
486        $sql = 'SELECT id FROM ' . $this->tbf . ' WHERE parent_id=\'' . $dir . '\' AND name=\'' . $this->db->real_escape_string($name) . '\'';
487
488        if (($res = $this->query($sql)) && ($r = $res->fetch_assoc())) {
489            $this->updateCache($r['id'], $this->_stat($r['id']));
490            return $r['id'];
491        }
492        return -1;
493    }
494
495    /**
496     * Return normalized path, this works the same as os.path.normpath() in Python
497     *
498     * @param  string $path path
499     *
500     * @return string
501     * @author Troex Nevelin
502     **/
503    protected function _normpath($path)
504    {
505        return $path;
506    }
507
508    /**
509     * Return file path related to root dir
510     *
511     * @param  string $path file path
512     *
513     * @return string
514     * @author Dmitry (dio) Levashov
515     **/
516    protected function _relpath($path)
517    {
518        return $path;
519    }
520
521    /**
522     * Convert path related to root dir into real path
523     *
524     * @param  string $path file path
525     *
526     * @return string
527     * @author Dmitry (dio) Levashov
528     **/
529    protected function _abspath($path)
530    {
531        return $path;
532    }
533
534    /**
535     * Return fake path started from root dir
536     *
537     * @param  string $path file path
538     *
539     * @return string
540     * @author Dmitry (dio) Levashov
541     **/
542    protected function _path($path)
543    {
544        if (($file = $this->stat($path)) == false) {
545            return '';
546        }
547
548        $parentsIds = $this->getParents($path);
549        $path = '';
550        foreach ($parentsIds as $id) {
551            $dir = $this->stat($id);
552            $path .= $dir['name'] . $this->separator;
553        }
554        return $path . $file['name'];
555    }
556
557    /**
558     * Return true if $path is children of $parent
559     *
560     * @param  string $path   path to check
561     * @param  string $parent parent path
562     *
563     * @return bool
564     * @author Dmitry (dio) Levashov
565     **/
566    protected function _inpath($path, $parent)
567    {
568        return $path == $parent
569            ? true
570            : in_array($parent, $this->getParents($path));
571    }
572
573    /***************** file stat ********************/
574    /**
575     * Return stat for given path.
576     * Stat contains following fields:
577     * - (int)    size    file size in b. required
578     * - (int)    ts      file modification time in unix time. required
579     * - (string) mime    mimetype. required for folders, others - optionally
580     * - (bool)   read    read permissions. required
581     * - (bool)   write   write permissions. required
582     * - (bool)   locked  is object locked. optionally
583     * - (bool)   hidden  is object hidden. optionally
584     * - (string) alias   for symlinks - link target path relative to root path. optionally
585     * - (string) target  for symlinks - link target path. optionally
586     * If file does not exists - returns empty array or false.
587     *
588     * @param  string $path file path
589     *
590     * @return array|false
591     * @author Dmitry (dio) Levashov
592     **/
593    protected function _stat($path)
594    {
595        $sql = 'SELECT f.id, f.parent_id, f.name, f.size, f.mtime AS ts, f.mime, f.read, f.write, f.locked, f.hidden, f.width, f.height, IF(ch.id, 1, 0) AS dirs
596                FROM ' . $this->tbf . ' AS f
597                LEFT JOIN ' . $this->tbf . ' AS ch ON ch.parent_id=f.id AND ch.mime=\'directory\'
598                WHERE f.id=\'' . $path . '\'
599                GROUP BY f.id, ch.id';
600
601        $res = $this->query($sql);
602
603        if ($res) {
604            $stat = $res->fetch_assoc();
605            if ($stat['id'] == $this->root) {
606                $this->rootHasParent = true;
607                $stat['parent_id'] = '';
608            }
609            if ($stat['parent_id']) {
610                $stat['phash'] = $this->encode($stat['parent_id']);
611            }
612            if ($stat['mime'] == 'directory') {
613                unset($stat['width']);
614                unset($stat['height']);
615                $stat['size'] = 0;
616            } else {
617                if (!$stat['mime']) {
618                    unset($stat['mime']);
619                }
620                unset($stat['dirs']);
621            }
622            unset($stat['id']);
623            unset($stat['parent_id']);
624            return $stat;
625
626        }
627        return array();
628    }
629
630    /**
631     * Return true if path is dir and has at least one childs directory
632     *
633     * @param  string $path dir path
634     *
635     * @return bool
636     * @author Dmitry (dio) Levashov
637     **/
638    protected function _subdirs($path)
639    {
640        return ($stat = $this->stat($path)) && isset($stat['dirs']) ? $stat['dirs'] : false;
641    }
642
643    /**
644     * Return object width and height
645     * Usualy used for images, but can be realize for video etc...
646     *
647     * @param  string $path file path
648     * @param  string $mime file mime type
649     *
650     * @return string
651     * @author Dmitry (dio) Levashov
652     **/
653    protected function _dimensions($path, $mime)
654    {
655        return ($stat = $this->stat($path)) && isset($stat['width']) && isset($stat['height']) ? $stat['width'] . 'x' . $stat['height'] : '';
656    }
657
658    /******************** file/dir content *********************/
659
660    /**
661     * Return files list in directory.
662     *
663     * @param  string $path dir path
664     *
665     * @return array
666     * @author Dmitry (dio) Levashov
667     **/
668    protected function _scandir($path)
669    {
670        return isset($this->dirsCache[$path])
671            ? $this->dirsCache[$path]
672            : $this->cacheDir($path);
673    }
674
675    /**
676     * Open file and return file pointer
677     *
678     * @param  string $path file path
679     * @param  string $mode open file mode (ignored in this driver)
680     *
681     * @return resource|false
682     * @author Dmitry (dio) Levashov
683     **/
684    protected function _fopen($path, $mode = 'rb')
685    {
686        $fp = $this->tmpPath
687            ? fopen($this->getTempFile($path), 'w+')
688            : $this->tmpfile();
689
690
691        if ($fp) {
692            if (($res = $this->query('SELECT content FROM ' . $this->tbf . ' WHERE id=\'' . $path . '\''))
693                && ($r = $res->fetch_assoc())) {
694                fwrite($fp, $r['content']);
695                rewind($fp);
696                return $fp;
697            } else {
698                $this->_fclose($fp, $path);
699            }
700        }
701
702        return false;
703    }
704
705    /**
706     * Close opened file
707     *
708     * @param  resource $fp file pointer
709     * @param string    $path
710     *
711     * @return void
712     * @author Dmitry (dio) Levashov
713     */
714    protected function _fclose($fp, $path = '')
715    {
716        is_resource($fp) && fclose($fp);
717        if ($path) {
718            $file = $this->getTempFile($path);
719            is_file($file) && unlink($file);
720        }
721    }
722
723    /********************  file/dir manipulations *************************/
724
725    /**
726     * Create dir and return created dir path or false on failed
727     *
728     * @param  string $path parent dir path
729     * @param string  $name new directory name
730     *
731     * @return string|bool
732     * @author Dmitry (dio) Levashov
733     **/
734    protected function _mkdir($path, $name)
735    {
736        return $this->make($path, $name, 'directory') ? $this->_joinPath($path, $name) : false;
737    }
738
739    /**
740     * Create file and return it's path or false on failed
741     *
742     * @param  string $path parent dir path
743     * @param string  $name new file name
744     *
745     * @return string|bool
746     * @author Dmitry (dio) Levashov
747     **/
748    protected function _mkfile($path, $name)
749    {
750        return $this->make($path, $name, '') ? $this->_joinPath($path, $name) : false;
751    }
752
753    /**
754     * Create symlink. FTP driver does not support symlinks.
755     *
756     * @param  string $target link target
757     * @param  string $path   symlink path
758     * @param string  $name
759     *
760     * @return bool
761     * @author Dmitry (dio) Levashov
762     */
763    protected function _symlink($target, $path, $name)
764    {
765        return false;
766    }
767
768    /**
769     * Copy file into another file
770     *
771     * @param  string $source    source file path
772     * @param  string $targetDir target directory path
773     * @param  string $name      new file name
774     *
775     * @return bool
776     * @author Dmitry (dio) Levashov
777     **/
778    protected function _copy($source, $targetDir, $name)
779    {
780        $this->clearcache();
781        $id = $this->_joinPath($targetDir, $name);
782
783        $sql = $id > 0
784            ? sprintf('REPLACE INTO %s (id, parent_id, name, content, size, mtime, mime, width, height, `read`, `write`, `locked`, `hidden`) (SELECT %d, %d, name, content, size, mtime, mime, width, height, `read`, `write`, `locked`, `hidden` FROM %s WHERE id=%d)', $this->tbf, $id, $this->_dirname($id), $this->tbf, $source)
785            : sprintf('INSERT INTO %s (parent_id, name, content, size, mtime, mime, width, height, `read`, `write`, `locked`, `hidden`) SELECT %d, \'%s\', content, size, %d, mime, width, height, `read`, `write`, `locked`, `hidden` FROM %s WHERE id=%d', $this->tbf, $targetDir, $this->db->real_escape_string($name), time(), $this->tbf, $source);
786
787        return $this->query($sql);
788    }
789
790    /**
791     * Move file into another parent dir.
792     * Return new file path or false.
793     *
794     * @param  string $source source file path
795     * @param         $targetDir
796     * @param  string $name   file name
797     *
798     * @return bool|string
799     * @internal param string $target target dir path
800     * @author   Dmitry (dio) Levashov
801     */
802    protected function _move($source, $targetDir, $name)
803    {
804        $sql = 'UPDATE %s SET parent_id=%d, name=\'%s\' WHERE id=%d LIMIT 1';
805        $sql = sprintf($sql, $this->tbf, $targetDir, $this->db->real_escape_string($name), $source);
806        return $this->query($sql) && $this->db->affected_rows > 0 ? $source : false;
807    }
808
809    /**
810     * Remove file
811     *
812     * @param  string $path file path
813     *
814     * @return bool
815     * @author Dmitry (dio) Levashov
816     **/
817    protected function _unlink($path)
818    {
819        return $this->query(sprintf('DELETE FROM %s WHERE id=%d AND mime!=\'directory\' LIMIT 1', $this->tbf, $path)) && $this->db->affected_rows;
820    }
821
822    /**
823     * Remove dir
824     *
825     * @param  string $path dir path
826     *
827     * @return bool
828     * @author Dmitry (dio) Levashov
829     **/
830    protected function _rmdir($path)
831    {
832        return $this->query(sprintf('DELETE FROM %s WHERE id=%d AND mime=\'directory\' LIMIT 1', $this->tbf, $path)) && $this->db->affected_rows;
833    }
834
835    /**
836     * undocumented function
837     *
838     * @param $path
839     * @param $fp
840     *
841     * @author Dmitry Levashov
842     */
843    protected function _setContent($path, $fp)
844    {
845        elFinder::rewind($fp);
846        $fstat = fstat($fp);
847        $size = $fstat['size'];
848
849
850    }
851
852    /**
853     * Create new file and write into it from file pointer.
854     * Return new file path or false on error.
855     *
856     * @param  resource $fp   file pointer
857     * @param  string   $dir  target dir path
858     * @param  string   $name file name
859     * @param  array    $stat file stat (required by some virtual fs)
860     *
861     * @return bool|string
862     * @author Dmitry (dio) Levashov
863     **/
864    protected function _save($fp, $dir, $name, $stat)
865    {
866        $this->clearcache();
867
868        $mime = !empty($stat['mime']) ? $stat['mime'] : $this->mimetype($name, true);
869        $w = !empty($stat['width']) ? $stat['width'] : 0;
870        $h = !empty($stat['height']) ? $stat['height'] : 0;
871        $ts = !empty($stat['ts']) ? $stat['ts'] : time();
872
873        $id = $this->_joinPath($dir, $name);
874        if (!isset($stat['size'])) {
875            $stat = fstat($fp);
876            $size = $stat['size'];
877        } else {
878            $size = $stat['size'];
879        }
880
881        if ($this->isLocalhost && ($tmpfile = tempnam($this->tmpPath, $this->id))) {
882            if (($trgfp = fopen($tmpfile, 'wb')) == false) {
883                unlink($tmpfile);
884            } else {
885                elFinder::rewind($fp);
886                stream_copy_to_stream($fp, $trgfp);
887                fclose($trgfp);
888                chmod($tmpfile, 0644);
889
890                $sql = $id > 0
891                    ? 'REPLACE INTO %s (id, parent_id, name, content, size, mtime, mime, width, height) VALUES (' . $id . ', %d, \'%s\', LOAD_FILE(\'%s\'), %d, %d, \'%s\', %d, %d)'
892                    : 'INSERT INTO %s (parent_id, name, content, size, mtime, mime, width, height) VALUES (%d, \'%s\', LOAD_FILE(\'%s\'), %d, %d, \'%s\', %d, %d)';
893                $sql = sprintf($sql, $this->tbf, $dir, $this->db->real_escape_string($name), $this->loadFilePath($tmpfile), $size, $ts, $mime, $w, $h);
894
895                $res = $this->query($sql);
896                unlink($tmpfile);
897
898                if ($res) {
899                    return $id > 0 ? $id : $this->db->insert_id;
900                }
901            }
902        }
903
904
905        $content = '';
906        elFinder::rewind($fp);
907        while (!feof($fp)) {
908            $content .= fread($fp, 8192);
909        }
910
911        $sql = $id > 0
912            ? 'REPLACE INTO %s (id, parent_id, name, content, size, mtime, mime, width, height) VALUES (' . $id . ', %d, \'%s\', \'%s\', %d, %d, \'%s\', %d, %d)'
913            : 'INSERT INTO %s (parent_id, name, content, size, mtime, mime, width, height) VALUES (%d, \'%s\', \'%s\', %d, %d, \'%s\', %d, %d)';
914        $sql = sprintf($sql, $this->tbf, $dir, $this->db->real_escape_string($name), $this->db->real_escape_string($content), $size, $ts, $mime, $w, $h);
915
916        unset($content);
917
918        if ($this->query($sql)) {
919            return $id > 0 ? $id : $this->db->insert_id;
920        }
921
922        return false;
923    }
924
925    /**
926     * Get file contents
927     *
928     * @param  string $path file path
929     *
930     * @return string|false
931     * @author Dmitry (dio) Levashov
932     **/
933    protected function _getContents($path)
934    {
935        return ($res = $this->query(sprintf('SELECT content FROM %s WHERE id=%d', $this->tbf, $path))) && ($r = $res->fetch_assoc()) ? $r['content'] : false;
936    }
937
938    /**
939     * Write a string to a file
940     *
941     * @param  string $path    file path
942     * @param  string $content new file content
943     *
944     * @return bool
945     * @author Dmitry (dio) Levashov
946     **/
947    protected function _filePutContents($path, $content)
948    {
949        return $this->query(sprintf('UPDATE %s SET content=\'%s\', size=%d, mtime=%d WHERE id=%d LIMIT 1', $this->tbf, $this->db->real_escape_string($content), strlen($content), time(), $path));
950    }
951
952    /**
953     * Detect available archivers
954     *
955     * @return void
956     **/
957    protected function _checkArchivers()
958    {
959        return;
960    }
961
962    /**
963     * chmod implementation
964     *
965     * @param string $path
966     * @param string $mode
967     *
968     * @return bool
969     */
970    protected function _chmod($path, $mode)
971    {
972        return false;
973    }
974
975    /**
976     * Unpack archive
977     *
978     * @param  string $path archive path
979     * @param  array  $arc  archiver command and arguments (same as in $this->archivers)
980     *
981     * @return void
982     * @author Dmitry (dio) Levashov
983     * @author Alexey Sukhotin
984     **/
985    protected function _unpack($path, $arc)
986    {
987        return;
988    }
989
990    /**
991     * Extract files from archive
992     *
993     * @param  string $path archive path
994     * @param  array  $arc  archiver command and arguments (same as in $this->archivers)
995     *
996     * @return true
997     * @author Dmitry (dio) Levashov,
998     * @author Alexey Sukhotin
999     **/
1000    protected function _extract($path, $arc)
1001    {
1002        return false;
1003    }
1004
1005    /**
1006     * Create archive and return its path
1007     *
1008     * @param  string $dir   target dir
1009     * @param  array  $files files names list
1010     * @param  string $name  archive name
1011     * @param  array  $arc   archiver options
1012     *
1013     * @return string|bool
1014     * @author Dmitry (dio) Levashov,
1015     * @author Alexey Sukhotin
1016     **/
1017    protected function _archive($dir, $files, $name, $arc)
1018    {
1019        return false;
1020    }
1021
1022} // END class
1023