<?php

/**
 * Работа с директориями загруз-центра
 */
class files {

    protected $_config_file_name = '.!config.dir.ini'; // название конфиг-файла
    protected $_data = array(); // параметры директории
    protected $_need_save = false; // необходимость сохранения параметров директории
    protected $_screens = array(); // список скриншотов
    protected $_keys = array(); // ключи, доступные для сортировки
    public $user_sort = 'position';
    public $error; // последняя ошибка
    public $name;

    /**
     * Работа с директорией загруц-центра
     * @param string $path_abs Абсолютный путь к папке загруз-центра
     */
    function __construct($path_abs) {
        $path_abs = realpath($path_abs);
        $this->type = 'folder'; // тип содержимого для иконки (по-умолчанию: папка)
        $this->runame = convert::to_utf8(basename($path_abs)); // отображаемое название папки
        $this->group_show = 2; // группа пользователей, которой разрешен просмотр папки
        $this->group_write = 3; // группа пользователей, которой разрешено создание вложенных файлов и папок
        $this->group_edit = groups::max(); // группа пользователей, которой разрешено изменение параметров данной папки
        $this->position = 1; // позиция. Учитывается при сортировке
        $this->id_user = 0; // создатель папки
        $this->description = ''; // описание папки
        $this->time_last = 0; // время последних действий
        $this->sort_default = 'runame::asc'; // сортировка по-умолчанию

        if ($cfg_ini = ini::read($path_abs . '/' . $this->_config_file_name, true)) {
            // загружаем конфиг
            $this->_data = array_merge($this->_data, (array) @$cfg_ini ['CONFIG']);
            $this->_screens = array_merge($this->_screens, (array) @$cfg_ini ['SCREENS']);
            $this->_keys = array_merge($this->_keys, (array) @$cfg_ini ['ADDKEYS']);
        } else {
            $this->time_create = TIME; // время создания
        }
        // настоящее имя папки
        $this->name = basename($path_abs);
        // получение путей на основе абсолютного пути
        $this->_setPathes($path_abs);
    }

    /**
     * Импорт файла
     * @param string $url
     * @return boolean|\files_file
     */
    public function fileImport($url) {
        if (function_exists('set_time_limit')) {
            set_time_limit(30);
        }
        // Импортировать файл
        $this->error = false;

        misc::log($url, 'loads.import');

        $purl = parse_url($url);

        if (empty($purl ['path'])) {
            misc::log('Путь: ERR', 'loads.import');
            $this->error = __('Путь к файлу не распознан');
            return false;
        } else {
            misc::log('Путь: OK', 'loads.import');
        }


        if (!$fname = basename($purl ['path'])) {
            misc::log('Имя файла: ERR', 'loads.import');
            $this->error = __('Не удалось получить имя файла из пути');
            return false;
        } else {
            misc::log('Имя файла: OK', 'loads.import');
        }

        $curl = new http_client($url);
        $name = text::for_filename($fname);

        if (!$headers = $curl->get_headers()) {
            misc::log('Headers: ERR', 'loads.import');
            $this->error = __('Не удалось получить заголовок');
            return false;
        } else {
            misc::log('Headers: OK', 'loads.import');
        }

        if (!preg_match('#^Content-Length: ([0-9]+)#uim', $headers)) {
            misc::log('Размер файла: ERR', 'loads.import');
            $this->error = __('В заголовке не указан размер');
            return false;
        } else {
            misc::log('Размер файла: OK', 'loads.import');
        }

        if (file_exists($this->path_abs . '/' . $name)) {
            misc::log('Проверка на существование файла: ERR (файл с таким названием уже существует)', 'loads.import');
            $this->error = __('В данной папке уже имеется файл с именем %s', $name);
            return false;
        } else {
            misc::log('Проверка на существование файла: OK', 'loads.import');
        }

        if (!$curl->save_content($this->path_abs . '/' . $name)) {
            misc::log('Сохранение файла: ERR', 'loads.import');
            $this->error = __('Не удалось сохранить файл %s', $name);
            return false;
        } else {
            misc::log('Сохранение файла: OK', 'loads.import');
        }

        @chmod($this->path_abs . '/' . $name, filesystem::getChmodToWrite());
        $f_obj = new files_file($this->path_abs, $name);
        $f_obj->time_add = TIME;

        if (!$f_obj->size) {
            $f_obj->delete();
            misc::log('Размер файла = 0: ERR (файл будет удален)', 'loads.import');
            $this->error = __('Скачан файл с нулевым размером');
            return false;
        } else {
            misc::log('Проверка файла: OK', 'loads.import');
        }

        // очистка кэша
        $this->cacheClear();
        misc::log('Очистка кэша: OK', 'loads.import');
        misc::log('Импорт успешно завершен', 'loads.import');
        return $f_obj;
    }

    /**
     * Добавление локальных(или выгруженных) файлов в папку
     * @param type $files
     * @return \files_file
     */
    public function filesAdd($files) {
        // добавление файлов в папку
        $ok = array();
        $files = (array) $files;
        foreach ($files as $path => $runame) {
            $name = text::for_filename($runame);

            if (@move_uploaded_file($path, $this->path_abs . '/' . $name)) {
                @chmod($this->path_abs . '/' . $name, filesystem::getChmodToWrite());

                $ok [$path] = $f_obj = new files_file($this->path_abs, $name);
                $f_obj->runame = $runame;
                $f_obj->time_add = TIME;
            } elseif (@copy($path, $this->path_abs . '/' . $name)) {
                @chmod($this->path_abs . '/' . $name, filesystem::getChmodToWrite());

                $ok [$path] = $f_obj = new files_file($this->path_abs, $name);
                $f_obj->runame = $runame;
                $f_obj->time_add = TIME;
            }
        }
        // очистка кэша


        $this->cacheClear();
        return $ok;
    }

    /**
     * Очистка кэша листинга директории
     */
    public function cacheClear() {
        // очистка кэша директории (а также проверка соответствия записей в базе реальным файлам)
        $path_rel_ru = convert::to_utf8($this->path_rel);
        $q = mysql_query("SELECT * FROM `files_cache` WHERE `path_file_rel` LIKE '" . my_esc($path_rel_ru) . "/%'");
        while ($files = @mysql_fetch_assoc($q)) {
            $abs_path = FILES . convert::of_utf8($files ['path_file_rel']);
            if (is_file($abs_path)) {
                continue;
                // если файл существует, то все ОК, пропускаем
            }
            // НО!!! Если файла нет, то это лишняя запись в базе, которую необходимо "похерить"
            // удаление файла из кэша базы
            mysql_query("DELETE FROM `files_cache` WHERE `id` = '" . intval($files ['id']) . "' LIMIT 1");
            // удаление комментов к файлу
            mysql_query("DELETE FROM `files_comments` WHERE `id_file` = '" . intval($files ['id']) . "'");
            // удаление рейтингов файла
            mysql_query("DELETE FROM `files_rating` WHERE `id_file` = '" . intval($files ['id']) . "'");
        }
        // а так же очистка кэша содержимого папки
        cache::set('files.' . $this->path_rel, false, 1);
    }

    /**
     * Создает папку в данной папке
     * @param type $runame
     * @param type $name
     * @return boolean|\files
     */
    public function mkdir($runame, $name = false) {
        // создание папки
        if ($name)
            $name = text::for_filename($name);
        else
            $name = text::for_filename($runame);

        if (!filesystem::mkdir($this->path_abs . '/' . $name)) {
            return false;
        }
        $new_dir = new files($this->path_abs . '/' . $name);
        $new_dir->runame = $runame;
        $new_dir->group_show = $this->group_show;
        $new_dir->group_write = $this->group_write;
        $new_dir->group_edit = $this->group_edit;
        $new_dir->time_create = TIME;

        $this->cacheClear();
        return $new_dir;
    }

    /**
     * Удаление текущей папки со всем содержимым
     * @return boolean
     */
    public function delete() {
        // удаление папки со всем содержимым
        // если папки не существует, то и удалять ее не можем.
        if (!is_dir($this->path_abs))
            return false;
        // папки и файлы с точкой являются системными и их случайное удаление крайне нежелательно
        if ($this->name {0} === '.')
            return false;
        $od = opendir($this->path_abs);
        while ($rd = readdir($od)) {
            if ($rd {0} == '.')
                continue;

            if (is_dir($this->path_abs . '/' . $rd)) {
                if (function_exists('set_time_limit')) {
                    set_time_limit(30);
                }

                $dir = new files($this->path_abs . '/' . $rd);
                $dir->delete(); // "Правильное" рекурсивное удаление папки
            } elseif (is_file($this->path_abs . '/' . $rd)) {
                $file = new files_file($this->path_abs, $rd);
                $file->delete(); // "Правильное" удаление файла
            }
        }

        closedir($od);

        if (function_exists('set_time_limit'))
            set_time_limit(30);
        if (filesystem::rmdir($this->path_abs)) {
            $ld = pathinfo($this->path_abs);
            $last_dir = new files($ld ['dirname']);
            $last_dir->cacheClear();

            $this->__destruct();
            return true;
        }
    }

    /**
     * Возвращает массив новых файлов
     * @global \user $user
     * @return \files_file
     */
    public function getNewFiles() {
        $time = NEW_TIME;
        global $user;
        $content = array('dirs' => array(), 'files' => array());
        $path_rel_ru = convert::to_utf8($this->path_rel);
        $q = mysql_query("SELECT * FROM `files_cache` WHERE `group_show` <= '" . intval($user->group) . "' AND `path_file_rel` LIKE '" . my_esc($path_rel_ru) . "/%' AND `path_file_rel` NOT LIKE '" . my_esc($path_rel_ru) . "/.%' AND `time_add` > '$time' ORDER BY `time_add` DESC");
        while ($files = mysql_fetch_assoc($q)) {
            $abs_path = FILES . convert::of_utf8($files['path_file_rel']);
            $pathinfo = pathinfo($abs_path);
            $file = new files_file($pathinfo ['dirname'], $pathinfo ['basename']);

            if (!is_file($abs_path)) {
                $file->delete(); // если файл не существует, то удаляем всю информацию о нем
                continue;
            }
            $content ['files'] [] = $file;
        }

        return $content;
    }

    /**
     * Поиск файлов в данной папке и во всех вложенных папках
     * @global \user $user
     * @param string $search часть имени файла
     * @return \files_file
     */
    protected function _search($search) {
        global $user;
        $content = array('dirs' => array(), 'files' => array());
        $path_rel_ru = convert::to_utf8($this->path_rel);
        $q = mysql_query("SELECT * FROM `files_cache` WHERE `group_show` <= '" . intval($user->group) . "' AND `path_file_rel` LIKE '" . my_esc($path_rel_ru) . "/%' AND `path_file_rel` NOT LIKE '" . my_esc($path_rel_ru) . "/.%' AND `runame` LIKE '%" . my_esc($search) . "%'");
        while ($files = mysql_fetch_assoc($q)) {
            $abs_path = FILES . convert::of_utf8($files ['path_file_rel']);
            $pathinfo = pathinfo($abs_path);
            $file = new files_file($pathinfo ['dirname'], $pathinfo ['basename']);

            if (!is_file($abs_path)) {
                $file->delete(); // если файл не существует, то удаляем всю информацию о нем
                continue;
            }
            $content ['files'] [] = $file;
        }

        return $content;
    }

    /**
     * Кол-во файлов в папке
     * @global \user $user
     * @param boolean $is_new считать только новые файлы
     * @return int
     */
    public function count($is_new = false) {
        if ($is_new) {
            $time = NEW_TIME;
        } else {
            $time = 0;
        }

        global $user;
        $group = (int) $user->group;
        if ($count = cache_counters::get('files.' . $this->path_rel . '.' . (int) $is_new . '.' . $group)) {
            return $count;
        }

        $path_rel_ru = convert::to_utf8($this->path_rel);
        $count = mysql_result(mysql_query("SELECT COUNT(*) FROM `files_cache` WHERE `group_show` <= '$group' AND `time_add` > '$time' AND `path_file_rel` LIKE '" . my_esc($path_rel_ru) . "/%' AND `path_file_rel` NOT LIKE '" . my_esc($path_rel_ru) . "/.%'"), 0);

        cache_counters::set('files.' . $this->path_rel . '.' . (int) $is_new . '.' . $group, $count, 600);
        return $count;
    }

    /**
     * Возможные ключи для сортировки файлов в данной папке
     * @return array
     */
    public function getKeys() {
        // получение возможных ключей для сортировки папки
        $keys = array();
        $keys ['runame:asc'] = __('Название');
        $keys ['size:desc'] = __('Размер');
        $keys ['time_add:desc'] = __('Время добавления');

        return array_merge($keys, $this->_keys);
    }

    /**
     * Содержимое данной директории
     * @return \files_file
     */
    protected function _getListFull() {

        if ($content = cache::get('files.' . $this->path_rel)) {
            return $content;
        }
        $this->_keys = array();
        $content = array('dirs' => array(), 'files' => array());
        $od = opendir($this->path_abs);
        while ($rd = readdir($od)) {
            if ($rd {0} == '.')
                continue; // все файлы и папки начинающиеся с точки пропускаем
            if (is_dir($this->path_abs . '/' . $rd)) {
                $content ['dirs'] [] = new files($this->path_abs . '/' . $rd);
            } elseif (is_file($this->path_abs . '/' . $rd)) {
                $content ['files'] [] = $file = new files_file($this->path_abs, $rd);
                $this->_keys = array_merge($this->_keys, $file->getKeys());
            }
        }
        // если файлов и папок мало, то нет необходимости в испозовании кэша
        if (count($content ['dirs']) + count($content ['files']) > 30) {
            cache::set('files.' . $this->path_rel, $content, 60);
        }
        closedir($od);
        return $content;
    }

    /**
     * Callback для сортировки директорий
     * @param type $f1
     * @param type $f2
     * @return type
     */
    function _sort_cmp_dir($f1, $f2) {
        if ($f1->position == $f2->position) {
            return strcmp($f1->runame, $f2->runame);
        }
        return ($f1->position < $f2->position) ? -1 : 1;
    }

    /**
     * callback для сортировки файлов
     * @param type $f1
     * @param type $f2
     * @return int
     */
    protected function _sort_cmp_files($f1, $f2) {
        $sn = $this->user_sort;
        if ($f1->$sn == $f2->$sn) {
            return 0;
        }
        return ($f1->$sn < $f2->$sn) ? - 1 : 1;
    }

    /**
     * Получение списка папок и файлов с применением сортировки
     * @param type $list
     * @param type $sort
     * @return type
     */
    protected function _listSort($list, $sort) {
        usort($list ['dirs'], array($this, '_sort_cmp_dir'));

        if (!$sort) {
            $sort = $this->sort_default;
        }
        @list ($this->user_sort, $order ) = @explode(':', $sort);
        usort($list ['files'], array($this, '_sort_cmp_files'));
        if ($order == 'desc') {
            $list ['files'] = array_reverse($list ['files']);
        }

        return $list;
    }

    /**
     * Фильтрация недоступных пользователю папок и файлов
     * @global \user $user
     * @param type $list
     * @return type
     */
    protected function _listFilter($list) {
        global $user;
        $list2 = array();
        $c = count($list);
        for ($i = 0; $i < $c; $i++) {
            if ($list [$i]->group_show <= $user->group)
                $list2 [] = $list [$i];
        }
        return $list2;
    }

    /**
     * Проверка на существование подпапки с указанным именем
     * @param string $name Имя подпапки
     * @return boolean
     */
    public function is_dir($name) {
        $list = $this->getList();

        foreach ($list ['dirs'] as $dir) {
            if ($dir->name === $name)
                return true;
        }

        return false;
    }

    /**
     * Проверка на существование файла с указанным именем
     * @param string $name Имя файла
     * @return boolean
     */
    public function is_file($name) {
        $list = $this->getList();

        foreach ($list ['files'] as $file) {
            if ($file->name === $name)
                return true;
        }

        return false;
    }

    /**
     * Возвращает содержимое директории
     * @param string $sort Ключ сортировки (список ключей можно получить методом getKeys)
     * @param string $search Фильтр по имени файла
     * @return array
     */
    public function getList($sort = false, $search = false) {
        if ($search) {
            $list = $this->_search($search); // получение списка файлов по запросу
        } else {
            $list = $this->_getListFull(); // получение списка содержимого
        }

        $list ['dirs'] = $this->_listFilter($list ['dirs']); // отсеиваем недоступные папки
        $list ['files'] = $this->_listFilter($list ['files']); // отсеиваем недоступные файлы
        $list = $this->_listSort($list, $sort); // сортируем
        return $list;
    }

    /**
     * Путь директории на русском языке
     * @return string
     */
    public function getPathRu() {
        $return = array();
        $all_path = array();
        if ($this->path_rel) {
            $path_rel = preg_split('#/+#', $this->path_rel);
            if ($path_rel) {
                // $all_path[]='/';
                foreach ($path_rel as $key => $value) {
                    $path = '';
                    for ($i = 0; $i < $key; $i++) {
                        $path .= $path_rel [$i] . '/';
                    }
                    if ($path)
                        $all_path [] = $path;
                }
            }

            if ($all_path) {
                for ($i = 0; $i < count($all_path); $i++) {
                    $cnf = new files(FILES . '/' . $all_path [$i]);
                    $return [] = $cnf->runame;
                }
            }
        }

        return ($return ? implode('/', $return) . '/' : '') . $this->runame;
    }

    /**
     * Получение пути к папке для ссылки
     * @return type
     */
    public function getPath() {
        $path_rel = preg_split('#/+#', $this->path_rel);
        foreach ($path_rel as $key => $value) {
            $path_rel [$key] = urlencode($value);
        }
        return implode('/', $path_rel) . '/';
    }

    /**
     * Список скриншотов (относительные пути)
     * @return array
     */
    public function getScreens() {
        // получение скриншотов папки
        $screens = array();
        foreach ($this->_screens as $key => $value) {
            if (is_file($this->path_abs . '/' . $value))
                $screens [] = $this->path_rel . '/' . $value;
        }
        return $screens;
    }

    /**
     * Массив путей для возврата к родительским директориям
     * @global \user $user
     * @param int $k максимальное кол-во путей
     * @return array [path, runame]
     */
    public function ret($k = 5) {
        // вывод массива путей для возврата
        global $user;
        $return = array();
        $all_path = array();
        if ($this->path_rel) {
            $path_rel = preg_split('#/+#', $this->path_rel);
            if ($path_rel) {
                // $all_path[]='/';
                foreach ($path_rel as $key => $value) {
                    $path = '';
                    for ($i = 0; $i < $key; $i++) {
                        $path .= $path_rel [$i] . '/';
                    }
                    if ($path)
                        $all_path [] = $path;
                }
            }

            if ($all_path) {
                $all_path = array_reverse($all_path);
                for ($i = 0; $i < $k && $i < count($all_path); $i++) {
                    $cnf = new files(FILES . '/' . $all_path [$i]);

                    if ($cnf->group_show > $user->group) {
                        // если пользователь не сможет зайти в папку, то и ссылку показывать не будем
                        $k++;
                        continue;
                    }
                    $return [] = array('path' => $cnf->getPath(), 'runame' => $cnf->runame);
                }
            }
        }
        return $return;
    }

    /**
     * Иконка папки
     * @return string
     */
    public function icon() {
        return $this->type;
    }

    /**
     * Перемещение папки
     * @param string $new_path_abs абсолютный путь к папке в загруз-центре
     * @return boolean
     */
    public function move($new_path_abs) {
        // перемещение папки
        $new_path_abs = filesystem::unixpath($new_path_abs);
        // если новое расположение выходит за рамки Папки загрузок
        if (strpos($new_path_abs, filesystem::unixpath(FILES)) !== 0) {
            return false;
        }
        // новое расположение не может находиться внутри текущего
        if (strpos($new_path_abs, $this->path_abs) === 0) {
            return false;
        }
        // если нихрена не прокатило
        if (!rename($this->path_abs, $new_path_abs)) {
            return false;
        }
        $path_rel_ru_old = convert::to_utf8($this->path_rel);
        $this->_setPathes($new_path_abs);
        $path_rel_ru_new = convert::to_utf8($this->path_rel);
        // не забываем и в базе изменить путь вложенных файлов
        mysql_query("UPDATE `files_cache` SET `path_file_rel` = REPLACE(`path_file_rel`, '" . my_esc($path_rel_ru_old) . "', '" . my_esc($path_rel_ru_new) . "') WHERE `path_file_rel` LIKE '" . my_esc($path_rel_ru_old) . "/%'");
        $np = pathinfo($new_path_abs);
        $to_dir = new files($np ['dirname']);
        $to_dir->cacheClear();
        return true;
    }

    protected function _setPathes($path_dir_abs) {
        // установка путей
        // полный путь к папке
        $this->path_abs = filesystem::unixpath($path_dir_abs);
        // относительный путь к папке
        $this->path_rel = str_replace(filesystem::unixpath(FILES), '', $this->path_abs);
    }

    /**
     * Переименование папки
     * @param string $runame Отображаемое имя папки
     * @param string $realname Реально имя папки на сервере
     * @return boolean
     */
    public function rename($runame, $realname) {
        // переименование папки
        if ($this->path_rel && $this->name {0} !== '.') {
            $path_new = preg_replace('#[^\/\\\]+$#u', $realname, $this->path_rel);

            if (!@rename($this->path_abs, FILES . $path_new))
                return false;
            else {
                $this->name = basename($path_new);
            }
            $path_rel_ru_old = convert::to_utf8($this->path_rel);
            $this->_setPathes(FILES . $path_new);
            $path_rel_ru_new = convert::to_utf8($this->path_rel);
            // не забываем и в базе изменить путь вложенных файлов
            mysql_query("UPDATE `files_cache` SET `path_file_rel` = REPLACE(`path_file_rel`, '" . my_esc($path_rel_ru_old) . "', '" . my_esc($path_rel_ru_new) . "') WHERE `path_file_rel` LIKE '" . my_esc($path_rel_ru_old) . "/%'");
        }
        $np = pathinfo($this->path_abs);
        $to_dir = new files($np ['dirname']);
        $to_dir->cacheClear();
        $this->runame = $runame;
        return true;
    }

    /**
     * Список всех вложенных папок (рекурсивно)
     * @param string $exclude Абсолютный путь, который будет исключен из перебора
     * @return array \files
     */
    public function getPathesRecurse($exclude = false) {
        // получение всех объектов папок (рекурсивно)
        $dirs = array();
        $od = opendir($this->path_abs);
        while ($rd = readdir($od)) {
            if ($rd == '.' || $rd == '..')
                continue;
            if (is_dir($this->path_abs . '/' . $rd)) {
                if (function_exists('set_time_limit')) {
                    set_time_limit(30);
                }
                $dir = new files($this->path_abs . '/' . $rd);

                $dirs [] = $dir;
                // обработка исключений
                if ($exclude && strpos($dir->path_abs, $exclude->path_abs) === 0) {
                    continue;
                }
                $dirs = array_merge($dirs, $dir->getPathesRecurse($exclude));
            }
        }

        closedir($od);
        return $dirs;
    }

    /**
     * установка группы пользователей, которым разрешено просматривать директорию
     * данный параметр будет рекурсивно применен ко всем вложенным объектам
     * @param int $group_show
     */
    public function setGroupShowRecurse($group_show) {
        $od = @opendir($this->path_abs);
        while ($rd = @readdir($od)) {
            if ($rd {0} == '.')
                continue;
            if (is_dir($this->path_abs . '/' . $rd)) {
                if (function_exists('set_time_limit'))
                    set_time_limit(30);
                $dir = new files($this->path_abs . '/' . $rd);
                $dir->setGroupShowRecurse($group_show);
            } elseif (is_file($this->path_abs . '/' . $rd)) {
                $file = new files_file($this->path_abs, $rd);
                $file->group_show = $group_show;
            }
        }
        @closedir($od);
        $this->cacheClear();
        $this->group_show = $group_show;
    }

    function __get($n) {
        if (array_key_exists($n, $this->_data))
            return $this->_data [$n];
        else
            return false;
    }

    function __set($n, $v) {
        $this->_data [$n] = $v;
        $this->_need_save = true;
    }

    function __destruct() {
        if ($this->_need_save) {
            $this->time_last = TIME; // время последних действий
            ini::save($this->path_abs . '/' . $this->_config_file_name, array('CONFIG' => $this->_data, 'SCREENS' => $this->_screens, 'ADDKEYS' => $this->_keys), true);
        }
    }

}

?>
