plugins/sys/admin_modules/yf_file_manager.class.php

Summary

Maintainability
F
6 days
Test Coverage
<?php

/**
 * File manager module.
 *
 * @author        YFix Team <yfix.dev@gmail.com>
 * @version        1.0
 */
class yf_file_manager
{
    /** @var string Color of the subdirs */
    public $color_dir = '#0066cc';
    /** @var string Color of the files */
    public $color_file = '#00aa66';
    /** @var string Color of the ZIP archives */
    public $color_zip = 'navy';
    /** @var int */
    public $_copy_dir_deepness = 2;
    /** @var string */
    public $_default_email = '';
    /** @var string @conf_skip Include files pattern */
    public $_include_pattern = '';
    /** @var string @conf_skip Exclude files pattern */
    public $_exclude_pattern = '#\.(svn|git)#';
    /** @var int @conf_skip */
    // TODO: need to connect them
    public $_default_chmod = 0777;


    public function show()
    {
        // TODO: show preview if file is image
        // TODO: doubleclick should edit file
        // TODO: buttons near eacj table row (edit/show revisions)
        // TODO: show revisions
        // TODO: make table condensed
        // TODO: completely avoid tpl(): speedup, code in one place
        // TODO: overall cleanup and improve UI from bootstrap
        // TODO: left area show directory tree with ajax
        // TODO: ajax file/image upload
        // TODO: wanr if we cannot edit this file _before_ edit (inside table listing)
        // TODO: rewrite JS into angularjs for UI interanction
        // TODO: copy_item rewrite to use infinite recursion
        // TODO: add merge dirs with dialog for new name
        // TODO: test on large number of files inside dir
        // TODO: add ability to allow some paths for specific admiin groups (fine grained internal permissions or ACL)
        // TODO: add ajax calculate dirs size (one-by-one to not kill server performance)
        // TODO: add filesystem stats (disks usage, mounted filesystems, iostat if possible, etc)
        $path_info = pathinfo($_SERVER['SCRIPT_FILENAME']);
        $_old_dir_name = str_replace('\\', '/', getcwd());
        $_dir_name = $_GET['dir_name'] ?: $_GET['id'];
        if ($_dir_name) {
            $dir_name = urldecode($_dir_name);
            chdir($dir_name);
        } else {
            $dir_name = $path_info['dirname'];
        }
        $path_info_up = pathinfo($dir_name);
        $cur_dir_name = str_replace('\\', '/', getcwd());
        $encoded_cur_dir = urlencode($cur_dir_name);
        $dir_contents = $this->_get_dir_contents($cur_dir_name);
        chdir($_old_dir_name);

        if ( ! empty($dir_contents['dirs']) || ! empty($dir_contents['files'])) {
            foreach ((array) $dir_contents['dirs'] as $k => $v) {
                $replace2 = [
                    'encoded_name' => urlencode($v),
                    'name' => $v,
                    'go_into_link' => './?object=' . $_GET['object'] . '&dir_name=' . urlencode($dir_name . '/' . $v) . _add_get(['dir_name']),
                    'color' => $this->color_dir,
                    'm_date' => date('Y-m-d H:i:s', filemtime($cur_dir_name . '/' . $v)),
                    'perms' => $this->_get_perms($cur_dir_name . '/' . $v),
                    'size' => 0,
                    'is_dir' => 1,
                    'is_file' => 0,
                ];
                $items .= tpl()->parse($_GET['object'] . '/item', $replace2);
            }
            foreach ((array) $dir_contents['files'] as $k => $v) {
                if (substr($v, -3) == 'zip') {
                    $color = $this->color_zip;
                } else {
                    $color = $this->color_file;
                }
                $file_size = filesize($cur_dir_name . '/' . $v);
                $total_files_size += $file_size;
                $replace2 = [
                    'encoded_name' => urlencode($v),
                    'name' => $v,
                    'go_into_link' => '',
                    'color' => $color,
                    'm_date' => date('Y-m-d H:i:s', filemtime($cur_dir_name . '/' . $v)),
                    'perms' => $this->_get_perms($cur_dir_name . '/' . $v),
                    'size' => $file_size,
                    'is_dir' => 0,
                    'is_file' => 1,
                ];
                $items .= tpl()->parse($_GET['object'] . '/item', $replace2);
            }
        }
        clearstatcache();

        $_tmp_path = '';
        $_tmp_array = [];
        foreach ((array) explode('/', $cur_dir_name) as $_folder) {
            $_tmp_path .= $_folder . '/';
            $_tmp_array[] = '<a href="./?object=' . $_GET['object'] . '&dir_name=' . urlencode($_tmp_path) . _add_get(['dir_name']) . '" class="btn btn-mini btn-xs">' . _prepare_html($_folder) . '</a>';
        }
        if ($_tmp_array) {
            $cur_dir_name = implode('/', $_tmp_array);
        }
        $replace = [
            'form_action' => './?object=' . $_GET['object'] . _add_get(['dir_name']) . '&action=',
            'upload_form_action' => './?object=' . $_GET['object'] . '&action=upload_file&dir_name=' . $encoded_cur_dir . _add_get(['dir_name']),
            'mkdir_form_action' => './?object=' . $_GET['object'] . '&action=make_dir&dir_name=' . $encoded_cur_dir . _add_get(['dir_name']),
            'cur_dir_name' => $cur_dir_name,
            'encoded_dir_name' => $encoded_cur_dir,
            'go_up_level_link' => './?object=' . $_GET['object'] . '&dir_name=' . urlencode($path_info_up['dirname']) . _add_get(['dir_name']),
            'go_home_link' => './?object=' . $_GET['object'] . _add_get(['dir_name']),
            'total_files_size' => (int) $total_files_size,
            'total_files' => (int) (count((array) $dir_contents['files'])),
            'total_dirs' => (int) (count((array) $dir_contents['dirs'])),
            'default_zip_name' => 'tmp12345',
            'default_email' => conf('webmaster_mail'),
            'default_chmod' => 755,
            'items' => $items,
        ];
        return tpl()->parse($_GET['object'] . '/main', $replace);
    }

    /**
     * Return sorted directory contents.
     * @param mixed $abs_dir_name
     */
    public function _get_dir_contents($abs_dir_name)
    {
        $contents = [
            'dirs' => [],
            'files' => [],
        ];
        $handle = opendir($abs_dir_name);
        while (false !== ($tmp_file = readdir($handle))) {
            if ($tmp_file == '.' || $tmp_file == '..') {
                continue;
            }
            if ( ! empty($this->_include_pattern)) {
                if ( ! preg_match($this->_include_pattern . 'ims', $tmp_file)) {
                    continue;
                }
            }
            if ( ! empty($this->_exclude_pattern)) {
                if (preg_match($this->_exclude_pattern . 'ims', $tmp_file)) {
                    continue;
                }
            }
            if (is_dir($tmp_file)) {
                $contents['dirs'][] = $tmp_file;
            } elseif (is_file($tmp_file)) {
                $contents['files'][] = $tmp_file;
            }
        }
        natsort($contents['dirs']);
        natsort($contents['files']);
        return $contents;
    }


    public function view()
    {
        return $this->view_item();
    }


    public function view_item()
    {
        if ( ! empty($_GET['id'])) {
            $file_name = urldecode($_GET['id']);
            $file_path = $file_name;
            $dir_name = dirname($file_path);
        } else {
            foreach ((array) $_REQUEST as $k => $v) {
                $tmp = substr($k, 0, 2);
                if ($tmp == 'd_' || $tmp == 'f_') {
                    $name = $v;
                    break;
                }
            }
            $dir_name = urldecode($_REQUEST['dir_name']);
            $file_name = str_replace('\\', '/', $dir_name . '/' . $name);
            $file_path = $file_name;
        }
        $_tmp_array = [];
        $tmp_path = '/';
        foreach ((array) explode('/', dirname($file_name)) as $_folder) {
            if ($_folder) {
                $tmp_path .= $_folder . '/';
                $_tmp_array[] = a('/@object/show/' . urlencode($tmp_path), $_folder);
            }
        }
        if ($_tmp_array) {
            $file_name = '/' . implode('/', $_tmp_array) . '/' . _prepare_html(basename($file_name), 0);
        }
        $file_text = _prepare_html(file_get_contents($file_path), 0);
        $replace = [
            'back_link' => url('/@object/show/' . urlencode($_REQUEST['dir_name'])),
        ];
        $div_id = 'editor_html';
        $hidden_id = 'file_text_hidden';
        return '<h4>View: ' . $file_name . '</h4>' .
            form($replace)
            ->container('<div id="' . $div_id . '" style="width: 90%; height: 500px;">' . $file_text . '</div>', '', [
                'id' => $div_id,
                'wide' => 1,
                'ace_editor' => ['mode' => common()->get_file_ext($file_path)],
            ])
            ->hidden($hidden_id);
    }


    public function edit()
    {
        return $this->edit_item();
    }


    public function edit_item()
    {
        // TODO: save file revision to db on each save
        if ( ! empty($_GET['id'])) {
            $file_name = urldecode($_GET['id']);
            $file_path = $file_name;
            $dir_name = dirname($file_path);
        } else {
            foreach ((array) $_REQUEST as $k => $v) {
                $tmp = substr($k, 0, 2);
                if ($tmp == 'd_' || $tmp == 'f_') {
                    $name = $v;
                    break;
                }
            }
            $dir_name = urldecode($_REQUEST['dir_name']);
            $file_name = str_replace('\\', '/', $dir_name . '/' . $name);
            $file_path = $file_name;
        }
        if (main()->is_post()) {
            $file_name = urldecode($_GET['file_name']);
            file_put_contents($file_name, $_POST['file_text_hidden']);
            return js_redirect('/@object/show/' . urlencode($_GET['dir_name']));
        }
        $_tmp_array = [];
        $tmp_path = '/';
        foreach ((array) explode('/', dirname($file_name)) as $_folder) {
            if ($_folder) {
                $tmp_path .= $_folder . '/';
                $_tmp_array[] = a('/@object/show/' . urlencode($tmp_path), $_folder);
            }
        }
        if ($_tmp_array) {
            $file_name = '/' . implode('/', $_tmp_array) . '/' . _prepare_html(basename($file_name), 0);
        }
        $file_text = _prepare_html(file_get_contents($file_path), 0);
        $replace = [
            'form_action' => url('/@object/@action/' . urlencode($file_path)),
            'back_link' => url('/@object/show/' . urlencode($_REQUEST['dir_name'])),
        ];
        $div_id = 'editor_html';
        $hidden_id = 'file_text_hidden';
        $ace_mode = common()->get_file_ext($file_path);
        $ace_mode == 'stpl' && $ace_mode = 'html';

        jquery('
            var h = $(window).height() - $(".navbar").height() - $("h4").height() * 2 - $("[type=submit]").height() * 5
            $("#' . $div_id . '").height(h);
        ');
        return '<h4>Edit: ' . $file_name . '</h4>' .
            form($replace, [
                'data-onsubmit' => '$(this).find("#' . $hidden_id . '").val( $("#' . $div_id . '").data("ace_editor").session.getValue() );',
            ])
            ->container('<div id="' . $div_id . '" style="width: 100%; min-height: 500px;">' . $file_text . '</div>', '', [
                'id' => $div_id,
                'wide' => 1,
                'ace_editor' => ['mode' => $ace_mode, 'hScrollBarAlwaysVisible' => false, 'vScrollBarAlwaysVisible' => false],
            ])
            ->hidden($hidden_id)
            ->save_and_back();
    }

    /**
     * Show multi-level tree with folders inside.
     * @param mixed $dir_name
     * @param mixed $level
     */
    public function _get_all_dirs($dir_name = '', $level = 1)
    {
        if ( ! file_exists($dir_name)) {
            return;
        }
        $_old_dir_name = str_replace('\\', '/', getcwd());
        chdir($dir_name);
        $dir_contents = $this->_get_dir_contents($dir_name);
        chdir($_old_dir_name);

        foreach ((array) $dir_contents['dirs'] as $cur_name) {
            $dir_next = $dir_name . '/' . $cur_name;
            $replace = [
                'color' => $this->color_dir,
                'name' => $cur_name,
                'encoded_name' => urlencode($dir_next),
                'padding' => ($level - 1) * 30,
            ];
            $body .= tpl()->parse($_GET['object'] . '/copy_dir_item', $replace);
            if ($level < $this->_copy_dir_deepness) {
                $body .= $this->_get_all_dirs($dir_next, $level + 1);
            }
        }
        return $body;
    }


    public function copy_item()
    {
        $dir_name = urldecode($_POST['dir_name']);
        $items_to_copy = [];
        foreach ((array) $_POST as $k => $v) {
            $tmp = substr($k, 0, 2);
            if ($tmp != 'd_' && $tmp != 'f_') {
                continue;
            }
            if ($tmp == 'd_') {
                $color = $this->color_dir;
                $type = strtoupper(t('directory'));
            } elseif ($tmp == 'f_') {
                $color = $this->color_file;
                $type = strtoupper(t('file'));
            }
            $file_name = str_replace('\\', '/', $dir_name . '/' . $v);
            $items_to_copy[] = $tmp . $file_name;
            $replace2 = [
                'color' => $color,
                'file_name' => $file_name,
                'type' => $type,
            ];
            $items .= tpl()->parse($_GET['object'] . '/copy_item', $replace2);
        }
        $replace = [
            'form_action' => './?object=' . $_GET['object'] . '&action=copy_item2&dir_name=' . $_POST['dir_name'] . _add_get(['dir_name']),
            'items' => $items,
            'items_to_copy' => urlencode(serialize($items_to_copy)),
            'dest_dirs' => $this->_get_all_dirs(dirname($dir_name)),
            'back' => back('./?object=' . $_GET['object'] . '&dir_name=' . $_POST['dir_name'] . _add_get(['dir_name'])),
        ];
        return tpl()->parse($_GET['object'] . '/copy', $replace);
    }

    /**
     * Copy selected items into selected destination folders.
     */
    public function copy_item2()
    {
        if ($_POST['items_to_copy']) {
            $items = unserialize(urldecode($_POST['items_to_copy']));
            foreach ((array) $items as $k => $v) {
                $tmp = substr($v, 0, 2);
                if ($tmp == 'd_' || $tmp == 'f_') {
                    $items_to_copy[] = substr($v, 2);
                }
            }
            foreach ((array) $_POST as $k => $v) {
                $tmp = substr($k, 0, 2);
                if ($tmp == 'd_' || $tmp == 'f_') {
                    $dest_folders[] = urldecode(substr($k, 2));
                }
            }
            if (count((array) $items_to_copy)) {
                if (count((array) $dest_folders)) {
                    foreach ((array) $items_to_copy as $k => $v) {
                        if (is_dir($v)) {
                            foreach ((array) $dest_folders as $k1 => $v1) {
                                // Check if dirs are different
                                // !! Need to add checking of parentess of dirs (to avoid infinite copy recursion)
                                if ($v != $v1) {
                                    $new_name = $v1 . '/' . basename($v);
                                    mkdir($new_name, 0777);
                                    _class('dir')->copy_dir($v, $new_name);
                                }
                            }
                        } else {
                            foreach ((array) $dest_folders as $k1 => $v1) {
                                copy($v, $v1 . '/' . basename($v));
                            }
                        }
                    }
                    js_redirect('./?object=' . $_GET['object'] . '&dir_name=' . $_GET['dir_name'] . _add_get(['dir_name']));
                } else {
                    $body .= t('please_select_destinatin_folders');
                }
            } else {
                $body .= t('please_select_items_to_copy');
            }
        } else {
            $body .= t('please_select_items_to_copy');
        }

        $body .= back('./?object=' . $_GET['object'] . '&dir_name=' . $_GET['dir_name']);

        return $body;
    }


    public function delete_item()
    {
        $dir_name = urldecode($_POST['dir_name']);
        foreach ((array) $_POST as $k => $v) {
            $file_name = str_replace('\\', '/', $dir_name . '/' . $v);

            $tmp = substr($k, 0, 2);
            if ($tmp != 'd_' && $tmp != 'f_') {
                continue;
            }
            if ( ! file_exists($file_name)) {
                continue;
            }
            if (is_dir($file_name)) {
                _class('dir')->delete_dir($file_name);
                @closedir($file_name);
                @rmdir($file_name);
            } else {
                @unlink($file_name);
            }
        }
        return js_redirect('./?object=' . $_GET['object'] . '&dir_name=' . $_POST['dir_name'] . _add_get(['dir_name']));
    }


    public function chmod_item()
    {
        $dir_name = urldecode($_POST['dir_name']);
        if ( ! $_POST['new_chmod']) {
            $_POST['new_chmod'] = '755';
        }
        $_old_dir_name = str_replace('\\', '/', getcwd());
        chdir($dir_name);
        foreach ((array) $_POST as $k => $v) {
            $tmp = substr($k, 0, 2);
            if ($tmp != 'd_' && $tmp != 'f_') {
                continue;
            }
            $file_name = str_replace('\\', '/', $dir_name . '/' . $v);
            chmod($file_name, '0' . $_POST['new_chmod']);
        }
        chdir($_old_dir_name);
        return js_redirect('./?object=' . $_GET['object'] . '&dir_name=' . $_POST['dir_name'] . _add_get(['dir_name']));
    }


    public function make_zip_item()
    {
        $dir_name = urldecode($_POST['dir_name']);
        if ( ! $_POST['new_zip_name']) {
            $_POST['new_zip_name'] = 'tmp_1234';
        }
        $new_zip_name = $dir_name . '/' . $_POST['new_zip_name'] . '.zip';

        $_old_dir_name = str_replace('\\', '/', getcwd());
        chdir($dir_name);

        $item_list = [];
        foreach ((array) $_POST as $k => $v) {
            $tmp = substr($k, 0, 2);
            if ($tmp != 'd_' && $tmp != 'f_') {
                continue;
            }
            $file_name = str_replace('\\', '/', $dir_name . '/' . $v);
            if (file_exists($file_name) && is_readable($file_name)) {
                $item_list[] = $v;
            }
        }
        $_old_dir_name = str_replace('\\', '/', getcwd());
        chdir($_old_dir_name);
        // TODO: replace making ZIP with php built-in http://php.net/manual/en/zip.examples.php
        //        _class_safe('pclzip', 'classes/common/')->create($item_list);
        return js_redirect('./?object=' . $_GET['object'] . '&dir_name=' . $_POST['dir_name'] . _add_get(['dir_name']));
    }


    public function unzip_item()
    {
        $dir_name = urldecode($_POST['dir_name']);
        $_old_dir_name = str_replace('\\', '/', getcwd());
        foreach ((array) $_POST as $k => $name) {
            $tmp = substr($k, 0, 2);
            if ($tmp != 'd_' && $tmp != 'f_') {
                continue;
            }
            $ext = array_pop(explode('.', $name));
            if ($ext != 'zip') {
                continue;
            }
            $file_name = str_replace('\\', '/', $dir_name . '/' . $name);
            $extraction_dir = $dir_name . '/' . substr($name, 0, -4);
            if ( ! file_exists($extraction_dir)) {
                mkdir($extraction_dir, 0777);
            }
            chdir($extraction_dir);
            // TODO: check or replace pclzip
//            _class_safe('pclzip', 'classes/common/')->extract();
        }
        chdir($_old_dir_name);
        return js_redirect('./?object=' . $_GET['object'] . '&dir_name=' . $_POST['dir_name'] . _add_get(['dir_name']));
    }


    public function download_item()
    {
        $dir_name = urldecode($_POST['dir_name']);

        $_old_dir_name = str_replace('\\', '/', getcwd());
        chdir($dir_name);

        foreach ((array) $_POST as $k => $v) {
            $file_name = str_replace('\\', '/', $dir_name . '/' . $v);

            $tmp = substr($k, 0, 2);
            if ($tmp != 'd_' && $tmp != 'f_') {
                continue;
            }
            if (is_readable($file_name) && is_file($file_name)) {
                clearstatcache();
                header('Content-Type: application/force-download; name="' . $v . '"');
                header('Content-Type: text/plain');
                header('Content-Transfer-Encoding: binary');
                header('Content-Length: ' . filesize($file_name));
                header('Content-Disposition: attachment; filename="' . $v . '"');
                readfile($file_name);
                main()->NO_GRAPHICS = true;
                exit();
            }
            chdir($_old_dir_name);
            return js_redirect('./?object=' . $_GET['object'] . '&dir_name=' . $_POST['dir_name'] . _add_get(['dir_name']));
        }
    }


    public function email_item()
    {
        $dir_name = urldecode($_POST['dir_name']);

        $_old_dir_name = str_replace('\\', '/', getcwd());
        chdir($dir_name);

        $attach = [];
        foreach ((array) $_POST as $k => $v) {
            $file_name = str_replace('\\', '/', $dir_name . '/' . $v);

            $tmp = substr($k, 0, 2);
            if ($tmp != 'd_' && $tmp != 'f_') {
                continue;
            }
            if (file_exists($file_name) && is_readable($file_name)) {
                $attach[] = $v;
            }
        }
        if (count((array) $attach)) {
            $email_from = 'yfix.dev auto-sender';
            if ( ! $_POST['target_email']) {
                $email_to = 'admin@yfix.net';
            } else {
                $email_to = $_POST['target_email'];
            }
            $to_name = 'auto-sender destination';
            $subject = 'yfix.dev auto-sender generated email';
            $HTML = 'see attachment';
            $TEXT = 'see attachment';
            $result = common()->send_mail('PHP-Mailer', $email_from, $email_to, $to_name, $subject, $TEXT, $HTML, $attach);
        }
        chdir($_old_dir_name);
        return js_redirect('./?object=' . $_GET['object'] . '&dir_name=' . $_POST['dir_name'] . _add_get(['dir_name']));
    }


    public function upload_file()
    {
        if ($_POST['verify']) {
            if ($_POST['name']) {
                $Name = $_POST['name'];
            } else {
                $Name = $_FILES['file']['name'];
            }
            $fileName = urldecode($_POST['dir_name']) . '/' . $Name;
            if ( ! move_uploaded_file($_FILES['file']['tmp_name'], $fileName)) {
                $body .= error_back();
            } else {
                $body .= t('save_successful') . '<br />' . PHP_EOL;
            }
        }
        $body .= $this->show();
        return $body;
    }


    public function make_dir()
    {
        if ($_POST['verify'] && $_POST['name']) {
            $new_dir = urldecode($_POST['dir_name']) . '/' . $_POST['name'];
            if ( ! @file_exists($new_dir)) {
                mkdir($new_dir, 0777);
            }
        }
        $body .= $this->show();
        return $body;
    }

    /**
     * @param mixed $file_name
     */
    public function _get_perms($file_name)
    {
        return substr(sprintf('%o', fileperms($file_name)), -3);
    }

    /**
     * @param mixed $params
     */
    public function _hook_widget__file_manager($params = [])
    {
        // TODO
    }
}