583 lines
18 KiB
PHP
583 lines
18 KiB
PHP
<?php
|
||
|
||
require_once 'core/path.php';
|
||
require_once 'core/file.php';
|
||
|
||
interface IFileSystem
|
||
{
|
||
// Операции над файлами
|
||
public function makeDirectory($name);
|
||
public function deleteDirectory($name);
|
||
|
||
public function deleteFile($name);
|
||
public function renameFile($source, $destination);
|
||
public function copyFile($source, $destination);
|
||
public function moveUploadedFile($source, $destination);
|
||
// deleteDirectoryRecursive
|
||
public function isDir($name);
|
||
|
||
public function readFile($source);
|
||
public function writeFile($source, $content);
|
||
// Содержание директории
|
||
public function directoryFiles($name);
|
||
public function directoryFilesRecursive($name);
|
||
}
|
||
|
||
interface IFileControl
|
||
{
|
||
public function commitFile($name, $who, $message);
|
||
public function readFileVersion($name, $version = false);
|
||
// Информация о файле
|
||
public function getFileLog($name);
|
||
public function getFileInfo($name);
|
||
}
|
||
|
||
// Реальная файловая система
|
||
class FileSystem implements IFileSystem
|
||
{
|
||
protected $hidden = array('.', '..');
|
||
protected $visible = null;
|
||
|
||
public function __construct()
|
||
{
|
||
}
|
||
|
||
public function setVisibleFiles(array $visible)
|
||
{
|
||
$this->visible = $visible;
|
||
}
|
||
|
||
public function setHiddenFiles(array $hidden)
|
||
{
|
||
$this->hidden = array_merge($this->hidden, $hidden);
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function makeDirectory($name)
|
||
{
|
||
if (file_exists($name) === false) {
|
||
mkdir($name);
|
||
}
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function makeFile($name)
|
||
{
|
||
if (file_exists($name) === false) {
|
||
file_put_contents($name, '');
|
||
}
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function deleteDirectory($name)
|
||
{
|
||
rmdir($name);
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function deleteDirectoryRecursive($name)
|
||
{
|
||
if ($handle = opendir($name)) {
|
||
while (false !== ($file = readdir($handle))) {
|
||
if ($file != "." && $file != "..") {
|
||
$sf = $name . DIRECTORY_SEPARATOR . $file;
|
||
if (is_dir($sf) && !is_link($sf)) {
|
||
self::deleteDirectoryRecursive($sf);
|
||
} else {
|
||
unlink($sf);
|
||
}
|
||
}
|
||
}
|
||
closedir($handle);
|
||
@rmdir($name);
|
||
}
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function deleteFile($name)
|
||
{
|
||
if (file_exists($name)) {
|
||
unlink($name);
|
||
}
|
||
}
|
||
|
||
// При перемещении или все файлы если есть совпадения переписываются
|
||
/**
|
||
*
|
||
*/
|
||
public function renameFile($source, $destination)
|
||
{
|
||
rename($source, $destination);
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function copyFile($source, $destination)
|
||
{
|
||
copy($source, $destination);
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function copyDirectory($source, $destination)
|
||
{
|
||
if (is_dir($source)) {
|
||
if (! file_exists($destination)) mkdir($destination);
|
||
$handle = opendir($source);
|
||
while (false !== ($file = readdir($handle))) {
|
||
$entry = $source . DIRECTORY_SEPARATOR . $file;
|
||
if (is_dir($entry)) {
|
||
self::copyDirectory($entry, $destination . DIRECTORY_SEPARATOR . $file);
|
||
} else {
|
||
copy($entry, $destination . DIRECTORY_SEPARATOR . $file);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function moveUploadedFile($source, $destination)
|
||
{
|
||
move_uploaded_file($source, $destination);
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function isVisible($file)
|
||
{
|
||
if (in_array(basename($file), $this->hidden) === true) {
|
||
return false;
|
||
}
|
||
return ($this->isDir($file) || $this->visible == null) || in_array(pathinfo($file, PATHINFO_EXTENSION), $this->visible);
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function directoryFiles($name)
|
||
{
|
||
$result = array();
|
||
$files = scandir($name);
|
||
foreach ($files as $file) {
|
||
$fullname = $name . DIRECTORY_SEPARATOR . $file;
|
||
if ($this->isVisible($fullname)) {
|
||
$result [$file] = new FileRecord(array(), $fullname);
|
||
}
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function readFile($name)
|
||
{
|
||
return file_get_contents($name);
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function writeFile($name, $content)
|
||
{
|
||
file_put_contents($name, $content);
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function directoryFilesRecursive($name)
|
||
{
|
||
}
|
||
|
||
function isDir($name)
|
||
{
|
||
return is_dir($name);
|
||
}
|
||
}
|
||
|
||
// То что хранится в базе данных
|
||
class EFileSystem implements IFileSystem, IFileControl
|
||
{
|
||
|
||
protected $basepath;
|
||
protected $db;
|
||
|
||
public function __construct($basepath, $db, $fs)
|
||
{
|
||
$this->basepath = $basepath;
|
||
$this->db = $db;
|
||
$this->fs = $fs;
|
||
}
|
||
|
||
/*function createExtendRecord($index)
|
||
{
|
||
static $fileSQL = "INSERT INTO file (id_record) VALUES (?)";
|
||
$query = $this->db->prepareStatement($fileSQL);
|
||
$query->setString(1, $index);
|
||
$query->executeQuery();
|
||
}*/
|
||
|
||
private function createRecord($name, $type, $path)
|
||
{
|
||
static $recordSQL = "INSERT INTO files (filename, idfile, lastrevdate, filepath, filetype) VALUES (?, ?, ?, ?, ?)";
|
||
$last = $this->db->getIdGenerator();
|
||
$index = $last->getId('files_idfile_seq');
|
||
|
||
$query = $this->db->prepareStatement($recordSQL);
|
||
$query->setString(1, $name);
|
||
$query->setInt(2, $index);
|
||
$query->setInt(3, 0);
|
||
$query->setString(4, $path);
|
||
$query->setString(5, $type);
|
||
$query->executeQuery();
|
||
/*if ($type == 0) {
|
||
$this->createExtendRecord($index);
|
||
}*/
|
||
|
||
return $index;
|
||
}
|
||
|
||
function setVisibleFiles(array $visible)
|
||
{
|
||
$this->fs->setVisibleFiles($visible);
|
||
}
|
||
|
||
function setHiddenFiles(array $hidden)
|
||
{
|
||
$this->fs->setHiddenFiles($hidden);
|
||
}
|
||
|
||
public function getFullPath($name)
|
||
{
|
||
return Path::join($this->basepath, $name);
|
||
}
|
||
|
||
private function getRecordId($name, $path)
|
||
{
|
||
static $recordSQL = "SELECT idfile FROM files WHERE filename = ? AND filepath = ?";
|
||
|
||
$query = $this->db->prepareStatement($recordSQL);
|
||
$query->setString(1, $name);
|
||
$query->setString(2, $path);
|
||
$result = $query->executeQuery();
|
||
if ($result->next()) {
|
||
$index = $result->getInt('idfile');
|
||
return $index;
|
||
}
|
||
return false; // Может лучше кидать исключение ??
|
||
}
|
||
|
||
function getIdFromPath($name)
|
||
{
|
||
return $this->getRecordId(basename($name), self::getPathName($name));
|
||
}
|
||
|
||
// Создание новой директории
|
||
public function makeDirectory($name)
|
||
{
|
||
$path = new Path($name);
|
||
$fullpath = $this->basepath;
|
||
$temp_path = '';
|
||
foreach ($path->getParts() as $subpath)
|
||
{
|
||
$index = $this->getRecordId($subpath, $temp_path);
|
||
if ($index === false) {
|
||
$index = $this->createRecord($subpath, 1, $temp_path);
|
||
}
|
||
$temp_path = Path::join($temp_path, $subpath);
|
||
}
|
||
|
||
$this->fs->makeDirectory($this->getFullPath($name));
|
||
}
|
||
|
||
public function isDir($name)
|
||
{
|
||
return $this->fs->isDir($this->getFullPath($name));
|
||
}
|
||
|
||
// Переименование файла или директории все изменения должны записываться в базу чтобы можно было сделать отмену !!!
|
||
public function renameFile($source, $destination)
|
||
{
|
||
// При перемещении файлы могут совпадать
|
||
$stmt = $this->db->prepareStatement('UPDATE files SET filepath = ?, filename = ? WHERE filepath = ? AND filename = ?');
|
||
$stmt->setString(1, self::getPathName($destination));
|
||
$stmt->setString(2, basename($destination));
|
||
$stmt->setString(3, self::getPathName($source));
|
||
$stmt->setString(4, basename($source));
|
||
$stmt->executeQuery();
|
||
|
||
if ($this->isDir($source)) {
|
||
$length = strlen($from) + 1;
|
||
$stmt = $this->db->prepareStatement("UPDATE file
|
||
SET filepath = '?' || substr(filepath, ?) WHERE filepath LIKE (?) OR filepath LIKE (? || '/%')");
|
||
$stmt->setString(1, $destination);
|
||
$stmt->setInt(2, $length);
|
||
$stmt->setString(3, $source);
|
||
$stmt->setString(4, $source);
|
||
}
|
||
|
||
$this->fs->renameFile($this->getFullPath($source), $this->getFullPath($destination));
|
||
}
|
||
|
||
// Копирование файла или директории
|
||
public function copyFile($source, $destination)
|
||
{
|
||
// При копировании файлы могут совпадать
|
||
$stmt = $this->db->prepareStatement('INSERT INTO files (filepath, filename, lastrevdate) VALUES (?, ?, ?)');
|
||
$stmt->setString(1, self::getPathName($destination));
|
||
$stmt->setString(2, basename($destination));
|
||
$stmt->setString(3, time());
|
||
$stmt->executeQuery();
|
||
|
||
if ($this->isDir($source)) {
|
||
$stmt = $this->db->prepareStatement("INSERT INTO files (filepath, filename, lastrevdate)
|
||
SELECT '?' || substr(filepath, ?) AS filepath, filename, lastrevdate WHERE WHERE filepath LIKE (?) OR filepath LIKE (? || '/%')");
|
||
|
||
$stmt->setString(1, $destination);
|
||
$stmt->setInt(2, $length);
|
||
$stmt->setString(3, $source);
|
||
$stmt->setString(4, $source);
|
||
}
|
||
|
||
$this->fs->copyFile($this->getFullPath($source), $this->getFullPath($destination));
|
||
}
|
||
|
||
private function getPathName($name)
|
||
{
|
||
$path = dirname($name);
|
||
return ($path == '.') ? '' : $path;
|
||
}
|
||
|
||
public function makeFile($name)
|
||
{
|
||
$base = self::getPathName($name);
|
||
$this->makeDirectory($base);
|
||
|
||
$filename = basename($name);
|
||
$index = $this->getRecordId($filename, $base);
|
||
|
||
if ($index === false) {
|
||
$index = $this->createRecord($filename, 0, $base);
|
||
}
|
||
$this->fs->makeFile($this->getFullPath($name));
|
||
}
|
||
|
||
public function readFile($name)
|
||
{
|
||
return $this->fs->readFile($this->getFullPath($name));
|
||
}
|
||
|
||
public function readFileVersion($name, $revision = false)
|
||
{
|
||
if ($revision === false) {
|
||
return $this->readFile($name);
|
||
} else {
|
||
$id_file = $this->getIdFromPath($name);
|
||
$query = $this->db->prepareStatement("SELECT * FROM history WHERE revision = ? AND idfile = ?");
|
||
$query->setInt(1, $revision);
|
||
$query->setInt(2, $id_file);
|
||
$file = $query->executeQuery();
|
||
if ($file->next()) {
|
||
return gzuncompress($file->getBlob('content'));
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
public function writeFile($name, $content)
|
||
{
|
||
$this->makeFile($name);
|
||
$this->fs->writeFile($this->getFullPath($name), $content);
|
||
}
|
||
|
||
public function getLastRevision($name)
|
||
{
|
||
$id_file = $this->getIdFromPath($name);
|
||
$stmt = $this->db->prepareStatement("SELECT * FROM history WHERE revision IN (SELECT MAX(revision) AS lastrev FROM history WHERE idfile = ?)");
|
||
$stmt->setInt(1, $id_file);
|
||
$rev = $stmt->executeQuery();
|
||
if ($rev->next()) {
|
||
return $rev;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function commitFile($name, $owner, $message)
|
||
{
|
||
$id_file = $this->getIdFromPath($name);
|
||
$content = $this->readFile($name);
|
||
|
||
$stmt = $this->db->prepareStatement("SELECT MAX(revision) AS lastrev FROM history WHERE idfile = ?");
|
||
$stmt->setInt(1, $id_file);
|
||
$rev = $stmt->executeQuery();
|
||
$revision = ($rev->next()) ? $rev->getInt('lastrev') + 1 : 1;
|
||
|
||
$query = $this->db->prepareStatement("INSERT INTO history (content, owner, revsummary, revdate, revision, idfile) VALUES (?, ?, ?, ?, ?, ?)");
|
||
$query->setBlob(1, gzcompress($content));
|
||
$query->setString(2, $owner);
|
||
$query->setString(3, $message);
|
||
$query->setInt(4, time());
|
||
$query->setInt(5, $revision);
|
||
$query->setInt(6, $id_file);
|
||
$query->executeQuery();
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function getFileDifference($name, $revision1, $revision2 = false)
|
||
{
|
||
$first = $this->readFileVersion($name, $revision1);
|
||
$second = $this->readFileVersion($name, $revision2);
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
public function getFileLog($name)
|
||
{
|
||
$id_file = $this->getIdFromPath($name);
|
||
$query = $this->db->prepareStatement("SELECT revision,revsummary,owner,revdate FROM history WHERE idfile = ? ORDER BY revision");
|
||
$query->setInt(1, $id_file);
|
||
$list = $query->executeQuery();
|
||
return iterator_to_array($list->getIterator());
|
||
}
|
||
|
||
public function directoryFiles($name)
|
||
{
|
||
$result = $this->fs->directoryFiles($this->getFullPath($name));
|
||
|
||
/* Список файлов из базы данных */
|
||
$query = $this->db->prepareStatement("SELECT * FROM files WHERE filepath = ?");
|
||
$query->setString(1, $name);
|
||
$list = $query->executeQuery();
|
||
|
||
foreach ($list as $file) {
|
||
$fullpath = $this->getFullPath($name . DIRECTORY_SEPARATOR . $file['filename']);
|
||
if ($this->fs->isVisible($fullpath)) {
|
||
$file['state'] =
|
||
((isset($result[$file['filename']])) ?
|
||
(($file['lastrevdate'] > $file['change']) ? 'exclamation' : 'unchanged')
|
||
: 'expected');
|
||
$record = new FileRecord($file, $fullpath);
|
||
$result [$file['filename']] = $record;
|
||
}
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
public function getFileInfo($name)
|
||
{
|
||
$index = $this->getIdFromPath($name);
|
||
$fullpath = $this->basepath . DIRECTORY_SEPARATOR . $name;
|
||
if ($index !== false) {
|
||
$query = $this->db->prepareStatement("SELECT * FROM files AS r LEFT JOIN filemeta AS f ON r.idfile = f.id_record WHERE r.idfile = ?");
|
||
$query->setInt(1, $index);
|
||
$list = $query->executeQuery();
|
||
$list->next();
|
||
$file = $list->getRow();
|
||
$file['state'] = (file_exists($fullpath) ? 'unchanged' : 'expected');
|
||
$result = new FileRecord($file, $fullpath);
|
||
} else {
|
||
$result = new FileRecord(array(), $fullpath);
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
public function setFileInfo($name, Collection $list)
|
||
{
|
||
$index = $this->getIdFromPath($name);
|
||
if ($index !== false) {
|
||
$stmt = $this->db->prepareStatement("UPDATE files SET title = ? WHERE idfile = ?");
|
||
$stmt->setString(1, $list->get('title'));
|
||
$stmt->setInt(2, $index);
|
||
$stmt->executeQuery();
|
||
/*if (some($list, array('keywords', 'author', 'description'))) {
|
||
$hasfile = $this->db->executeQuery("SELECT * FROM file WHERE id_record = $index");
|
||
if(!$hasfile->next()) {
|
||
static $fileSQL = "INSERT INTO file (id_record) VALUES (?)";
|
||
$query = $this->db->prepareStatement($fileSQL);
|
||
$query->setString(1, $index);
|
||
$query->executeQuery();
|
||
}
|
||
$query = $this->db->prepareStatement("UPDATE file SET keywords = ?, author = ?, description = ? WHERE id_record = ?");
|
||
$query->setString(1, $list->get('keywords'));
|
||
$query->setString(2, $list->get('author'));
|
||
$query->setString(3, $list->get('description'));
|
||
$query->setInt(4, $index);
|
||
$query->executeQuery();
|
||
}*/
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Удаляем директорию если она не пустая
|
||
*/
|
||
function deleteDirectory($name)
|
||
{
|
||
$index = $this->getIdFromPath($name);
|
||
|
||
$query = $this->db->prepareStatement("SELECT COUNT(*) AS col FROM files WHERE filepath = (?) OR filepath LIKE(? || '/%')");
|
||
$query->setString(1, $name);
|
||
$query->setString(2, $name);
|
||
$result = $query->executeQuery();
|
||
$result->next();
|
||
|
||
if ($index && $result->getInt('col') == 0) {
|
||
$query = $this->db->prepareStatement("DELETE FROM files WHERE idfile = ?");
|
||
$query->setInt(1, $index);
|
||
$query->executeQuery();
|
||
}
|
||
$this->fs->deleteDirectory($this->getFullPath($name));
|
||
}
|
||
|
||
function deleteFile($name)
|
||
{
|
||
$index = $this->getIdFromPath($name);
|
||
if ($index) {
|
||
|
||
$query = $this->db->prepareStatement("DELETE FROM history WHERE idfile = ?;
|
||
DELETE FROM filemeta WHERE id_record = ?;DELETE FROM files WHERE idfile = ?");
|
||
$query->setInt(1, $index);
|
||
$query->setInt(2, $index);
|
||
$query->setInt(3, $index);
|
||
$query->executeQuery();
|
||
}
|
||
$this->fs->deleteFile($this->getFullPath($name));
|
||
}
|
||
|
||
function moveUploadedFile($source, $destination)
|
||
{
|
||
$this->fs->moveUploadedFile($source, $this->getFullPath($destination));
|
||
$this->makeFile($destination);
|
||
}
|
||
|
||
function directoryFilesRecursive($name)
|
||
{
|
||
$files = $this->fs->directoryFilesRecursive($this->getFullPath($name));
|
||
|
||
$query = $this->db->prepareStatement("DELETE FROM files WHERE filepath = (?) OR filepath LIKE (? || '/%')");
|
||
$query->setString(1, $name);
|
||
$query->setString(2, $name);
|
||
$query->executeQuery();
|
||
}
|
||
}
|