* @license PHP License * @package WB * @subpackage vfs */ WBClass::load('WBLog' , 'WBClock' , 'WBDatasource' , 'WBDatasource_Callback' , 'WBVFS' , 'WBUser' ); /** * Virtual File System: File * * Represent a single "file", whatever it is * * @version 0.11.1 * @package WB * @subpackage vfs */ class WBVFS_File extends WBVFS implements WBDatasource_Callback { /** * Default quota for each user in bytes * @var int */ protected $quotaSize = 104857600; /** * Default quota for each user * @var int */ protected $quotaFiles = 1000; /** * Quota is OK */ const QUOTA_OK = 0; /** * Quota exceeded: to many bytes */ const QUOTA_EXCEEDED_SIZE= 1; /** * Quota exceeded: too many files */ const QUOTA_EXCEEDED_FILES = 2; /** * minor mime type */ const MIME_MAJOR = 1; /** * minor mime type */ const MIME_MINOR = 2; /** * additional clause * @var array */ protected $clause = array(); /** * default values * @var array */ protected $skel = array(); /** * acces to database * @var WBDatasource_Table */ protected $table; /** * prefix */ const DIR_PREFIX = 'vfs'; /** * name of file table * @var string */ protected $fileTable; /** * name of directory table * @var string */ protected $dirTable; /** * id of current folder * @var string */ protected $dirCurrent = 0; /** * storage folder name * @var string */ protected $storage; /** * id of current file, if any * @var string */ protected $id = null; /** * file information as stored in DB * @var array */ protected $data = array(); /** * logger * @var WBLog */ protected $log; /** * cache quota values per user * @var array */ protected static $quotaCache = array(); /** * Server pool to deliver files * @var array */ private $serverpool = array(); /** * constructor * * Configure file utiltiy object * * @param array $parameter */ public function __construct($parameter = array()) { parent::__construct(); $this->log = WBLog::start(__CLASS__); $config = WBClass::create('WBConfig'); $config->load('vfs'); $this->storage = $config->get('file/storage', 'file01'); $this->fileTable = $config->get('file/filetable', 'vfsfile'); $this->dirTable = $config->get('dir/treetable', 'vfsdir'); $this->serverpool = $config->get('server', array()); if (empty($this->serverpool) || !is_array($this->serverpool)) { $this->serverpool = array(); } $this->table = WBClass::create('WBDatasource_Table', $this->getTableParameter($parameter)); $uPrimary = $this->table->getIdentifier(WBDatasource::TABLE_USER, true); if (isset($parameter['user'])) { $this->clause[] = array( 'field' => $uPrimary, 'value' => $parameter['user'] ); $this->skel = array( $uPrimary => $parameter['user'] ); // fetch per user quota if (isset(self::$quotaCache[$parameter['user']])) { $this->quotaSize = self::$quotaCache[$parameter['user']][0]; $this->quotaFiles = self::$quotaCache[$parameter['user']][1]; return; } else { $quotaTable = $config->get('quota/table', 'vfsfilequota'); $list = $this->table->get($quotaTable, null, $parameter['user']); if (count($list)) { $this->quotaSize = $list[0]['sizemax']; $this->quotaFiles = $list[0]['filemax']; self::$quotaCache[$parameter['user']] = array($this->quotaSize, $this->quotaFiles); return; } } } // get quota from configuration files $this->quotaSize = $config->get('quota/size', 100) * 1048576; $this->quotaFiles = $config->get('quota/files', 1000); if (isset($parameter['user'])) { self::$quotaCache[$parameter['user']] = array($this->quotaSize, $this->quotaFiles); } } /** * Receive Name of Primary Key * * @return string column name of primary key */ public function getIdentifier() { return $this->table->getIdentifier(WBVFS::TABLE_FILE, true); } /** * Actually switch translation * * @param bool $switch */ protected function doSwitchTranslation($switch) { $this->table->switchTranslation($switch); } /** * get quote information for current user * * Fetch array that tells how many bites/file are occupied * and free * * @param int $demand * @return array */ public function getQuota($demand = 0) { $opt = array( 'column' => array('size') ); $list = $this->table->get($this->fileTable, null, null, $this->clause, $opt); $size = $demand; $count = 0; foreach ($list as $l) { $size += $l['size']; ++$count; } $freeSize = $this->quotaSize - $size; if (0 == $this->quotaSize) { $freeSize = 0; } $freeFiles = $this->quotaFiles - $count; if (0 == $this->quotaFiles) { $freeFiles = 0; } return array( 'used' => array( 'size' => $size, 'files' => $count ), 'free' => array( 'size' => $freeSize, 'files' => $freeFiles ), 'quota' => array( 'size' => $this->quotaSize, 'files' => $this->quotaFiles ) ); } /** * check quota * * @uses getQuota() * @param int $demand * @return int */ public function checkQuota($demand = 0) { $q = $this->getQuota($demand); $ret = self::QUOTA_OK; if ($q['free']['size'] < 0) { $ret |= self::QUOTA_EXCEEDED_SIZE; } if ($q['free']['files'] < 0) { $ret |= self::QUOTA_EXCEEDED_FILES; } return $ret; } /** * get id of current file * * @return string */ public function getId() { return $this->id; } /** * receive folder id * * @return string */ public function getDirId() { if (!$this->id) { return null; } $dPrimary = $this->table->getIdentifier($this->dirTable, true); $dir = $this->data[$dPrimary]; if ('0' === $dir || empty($dir)) { // convert string to null for root folder return null; } return $dir; } /** * get obscure id * * @return string */ public function getObscureId() { if (!$this->id) { return null; } return $this->data['obscure'] . $this->id; } /** * get file's URI * * @param string major mimetype or major/minor (like image/jpeg) * @return string */ public function getUri($major = null) { if (!$this->id) { return null; } if (empty($major)) { $major = $this->getMime(self::MIME_MAJOR); } $minor = ''; if (strstr($major, '/')) { list($major, $minor) = explode('/', $major, 2); } /** @var WBVFS_Mime */ $mimeHdl = WBClass::create('WBVFS_Mime_' . ucfirst($major)); if (!$mimeHdl->setVirtualFile($this)) { // not convertable return null; } if (empty($minor)) { $minor = $mimeHdl->getMime(self::MIME_MINOR); } $ext = $mimeHdl->getExtension($minor); $uri = array(); $uri[] = $this->data['obscure'] . $this->id; $uri[] = $major; $uri[] = $minor; $uri[] = urlencode(str_replace('#', '', $this->data['name'])); return implode('/', $uri) . '.' . $ext; } /** * get file's URL * * * @see getUri() * @param string $major major mimetype * @param bool $full add http:/[[SERVER]] * @return string */ public function getUrl($major = null, $full = true) { $uri = $this->getUri($major); if (!$uri) { return null; } $url = '[[SERVICE_FILE]]' . $uri; if (!$full) { return $url; } return $this->getServer() . $url; } /** * Get server for this file * * Either use server from server pool or just global server * * @return string */ public function getServer() { $server = '[[PROTOCOL]]://[[SERVER]]'; if (empty($this->serverpool)) { return $server; } $i = intval(substr($this->getId(), -3)) % count($this->serverpool); return '[[PROTOCOL]]://' . $this->serverpool[$i]; } /** * get file name * * @return string */ public function getName() { if (!$this->id) { return null; } return $this->data['name']; } /** * Get Description * * @return string */ public function getDescription() { if (!$this->id) { return null; } return $this->data['description']; } /** * Get User Id of File Owner * * @return string */ public function getUserId() { if (!$this->id) { return null; } $uPrimary = $this->table->getIdentifier(WBDatasource::TABLE_USER, true); return $this->data[$uPrimary]; } /** * Get Folder Id * * @return string */ public function getDir() { if (!$this->id) { return null; } $dPrimary = $this->table->getIdentifier($this->dirTable, true); return $this->data[$dPrimary]; } /** * get location in real file system * * @return string */ public function getPath() { if (!$this->id) { return null; } return WBParam::get('wb/dir/base') . '/' . $this->id2path(); } /** * tell whether current file is usable * * @return bool */ public function isOK() { if ($this->id) { return true; } return false; } /** * get creation timestamp * * @return string */ public function getCreated() { if (!$this->id) { return null; } return $this->data['created']; } /** * get timestamp of last change * * @return string */ public function getChanged() { if (!$this->id) { return null; } return $this->data['changed']; } /** * receive extended information * * @param string $key * @return mixed */ public function getInfo($key = null) { if (!$this->id) { return null; } if (!$key) { return $this->data['info']; } if (isset($this->data['info'][$key])) { return $this->data['info'][$key]; } return null; } /** * get mime type string * * @param int $type * @return string */ public function getMime($type = self::MIME_MAJOR) { if (!$this->id) { return null; } if ($type == self::MIME_MAJOR) { return $this->data['mimemajor']; } if ($type == self::MIME_MINOR) { return $this->data['mimeminor']; } if ($type == (self::MIME_MAJOR | self::MIME_MINOR) ) { return $this->data['mimemajor'] . '/' . $this->data['mimeminor']; } return null; } /** * get file size * * @return int */ public function getSize() { if (!$this->id) { return null; } return $this->data['size']; } /** * get md5 sum of file * * @see md5_file() * @return string */ public function getMd5() { if (!$this->id) { return null; } return $this->data['md5']; } /** * get file views * * @return int */ public function getViews() { if (!$this->id) { return null; } $stat = WBClass::create('WBStatistic_VFSFile'); return $stat->count( $this->id ); } /** * get all * * @return array */ public function get() { if (!$this->id) { return null; } $columns = array( 'name', 'description', 'created', 'changed', 'size', 'mimemajor', 'mimeminor', 'md5', $this->table->getIdentifier(WBDatasource::TABLE_USER, true), $this->table->getIdentifier($this->dirTable, true) ); $data = array(); $data['id'] = $this->getId(); $data['obscureid'] = $this->getObscureId(); foreach ($columns as $c) { $data[$c] = $this->data[$c]; } return $data; } /** * load file by obscure id * * As "normal" file ids are easy to guess, you can use a random * 2-character attachement. Use those obscure ids in URLs, to * protect from scripted download of all published files * * @param string $obscure * @param bool $includeDeleted * @return WBVFS_File */ public function loadByObscureId($obscure, $includeDeleted = false) { // extract id and try to load file $id = substr($obscure, 2); $obj = $this->loadById($id, $includeDeleted); if (!$obj->isOK()) { return $obj; } // verify obscure id $ob = substr($obscure, 0, 2); if ($obj->data['obscure'] == $ob) { return $obj; } // unload $obj->data = array(); $obj->id = null; return $obj; } /** * load file from id * * Very simple interface to create file object from id * * @see loadByObscureId() * @param string $id * @param bool $includeDeleted * @return WBVFS_File */ public function loadById($id, $includeDeleted = false) { if ($includeDeleted) { $info = $this->table->getTableInfo($this->fileTable); $mend = array( $this->fileTable => array( 'delete' => '' ) ); $this->table->setTables($mend); } $data = $this->table->get($this->fileTable, $id, null, $this->clause); if (count($data) != 1) { $data[0] = array(); } if ($includeDeleted) { $mend = array( $this->fileTable => $info ); $this->table->setTables($mend); } return $this->loadByData($data[0]); } /** * load file by name * * Find name and try to load. Look only for first match of name, * all others will be ignored * * @param string $name Pattern to look up in database * @param string $uid user id to limit search for * @param string $relation somthing as like or * @return WBVFS_File loaded file */ public function loadByName($name, $uid = null, $relation = 'like') { $opt = array( 'limit' => 1 ); $clause = array(); $clause[] = array( 'field' => 'name', 'relation' => $relation, 'value' => $name ); // limit to user if ($uid) { $clause[] = array( 'field' => $this->table->getIdentifier(WBDatasource::TABLE_USER, true), 'value' => $uid ); } $data = $this->table->get('vfsfile', null, null, $clause, $opt); if (count($data) != 1) { $data[0] = array(); } return $this->loadByData($data[0]); } /** * load file using data * * helper function to load file object accoring data from database * * @param array $data * @return WBVFS_File */ public function loadByData($data) { $primary = $this->table->getIdentifier($this->fileTable, true); $obj = clone $this; if (!isset($data[$primary]) || empty($data[$primary])) { $obj->id = null; $obj->data = array(); return $obj; } $id = $data[$primary]; $data['obscureid'] = $data['obscure'] . $id; $data['mime'] = $data['mimemajor'] . '/' . $data['mimeminor']; $data['name_uri'] = urlencode($data['name']); $data['info'] = unserialize($data['info']); $obj->id = $id; $obj->data = $data; $obj->storage = $data['storage']; $obj->dirCurrent = $data[$this->table->getIdentifier($this->dirTable, true)]; return $obj; } /** * increment view counter * * View may only increased for other users than the file's owner * * @param WBRequest */ public function incrementView() { if (!$this->id) { return null; } $stat = WBClass::create('WBStatistic_VFSFile'); $stat->add($this->id); } /** * set id of current folder * * @param string $id * @param bool $userFromDir Select user id according directory */ public function setCurrentDir($id, $userFromDir = false) { $this->dirCurrent = $id; if (!$userFromDir) { return; } $dir = $this->table->get($this->dirTable, $id); $dir = $dir[0]; $uPrimary = $this->table->getIdentifier(WBDatasource::TABLE_USER, true); $this->skel[$uPrimary] = $dir[$uPrimary]; $found = false; foreach ($this->clause as $i => $c) { if ($uPrimary == $c['field']) { $this->clause[$i]['value'] = $dir[$uPrimary]; $found = true; break; } } if ($found) { return; } $this->clause[] = array( 'field' => $uPrimary, 'value' => $dir[$uPrimary] ); } /** * list files by obscure ids * * Load list of files and check obscure code * * @todo implement verfification of obscure code * @see listId() * @param array|string $obscure * @param array $columns * @return array */ public function listObscureId($obscure, $columns = null) { if (!is_array($obscure)) { $obscure = array($obscure); } $ids = array(); foreach ($obscure as $o) { $id = substr($o, 2); $ob = substr($o, 0, 2); $ids[$id] = $ob; } $files = $this->listId(array_keys($ids), $columns); // verify obscure id return $files; } /** * list files by id * * Load list of file using list of ids * * @param array|string $id * @param array $columns * @param array $order * @return array */ public function listId($id, $columns = null, $order = null) { if (!is_array($id)) { $id = array($id); } $clause = $this->clause; $clause[] = array( 'field' => $this->table->getIdentifier($this->fileTable, true), 'relation' => 'in', 'value' => $id ); return $this->fetchList($clause, $columns); } /** * Search Files by Name and Description * * @param string query * @param string $idOnly whether to select just file ids instead of whole records * @param array $order * @return array */ public function find($query, $idOnly = false, $order = null) { $clause = $this->clause; $query = trim($query); $query = array_map('trim', explode(' ', $query)); for ($i = 0; $i < count($query); ++$i) { // every search word must have at least three chars if (3 > strlen($query[$i])) { unset($query[$i]); continue; } } $query = array_values($query); $c = array( 'type' => 'complex', 'bond' => 'OR', 'clause' => array( array( 'field' => 'name', 'value' => '', 'relation' => 'like' ), array( 'field' => 'description', 'value' => '', 'relation' => 'like' ) ) ); foreach ($query as $q) { $c['clause'][0]['value'] = $q; $c['clause'][1]['value'] = $q; $clause[] = $c; } $columns = null; if ($idOnly) { $primary = $this->table->getIdentifier($this->fileTable, true); $columns = array($primary); } $list = $this->fetchList($clause, $columns, $order); if (!$idOnly) { return $list; } $ids = array(); foreach ($list as $l) { $ids[] = $l[$primary]; } return $ids; } /** * list files in folder * * @param string $dir * @param string $idOnly whether to select just file ids instead of whole records * @param array $order * @return array */ public function listDir($dir = null, $idOnly = false, $order = null) { $columns = null; if ($idOnly) { $primary = $this->table->getIdentifier($this->fileTable, true); $columns = array($primary); } $list = $this->ls($dir, $columns, $order); if (!$idOnly) { return $list; } $ids = array(); foreach ($list as $l) { $ids[] = $l[$primary]; } return $ids; } /** * list files in folder, but for internal use * * @see listDir() * @param string $dir * @param array $columns * @param array $order * @return array */ protected function ls($dir, $columns = null, $order = null) { if (empty($dir)) { $dir = 0; } $clause = $this->clause; $clause[] = array( 'field' => $this->table->getIdentifier($this->dirTable, true), 'value' => $dir ); return $this->fetchList($clause, $columns, $order); } /** * Fetch List From File Table * * Interface to WBDatasource_Table also add callback * * @param array $clause * @param array $columns * @param array $order * @return array */ protected function fetchList($clause, $columns = null, $order = null) { $options = array(); if (!is_array($columns)) { $options['callback'] = $this; } else { $options['column'] = $columns; } // add order option if (!empty($order)) { $options['order'] = $order; } return $this->table->get($this->fileTable, null, null, $clause, $options); } /** * save file data * * Allow to update file data. Of course you can only update * fields stored in database. One can update anything besides: * - file's id * - obscure * - changed * - created * - mimemajor * - mimeminor * - anything that is in skeleton * * Also consider, that the attribute "name" may change during save to make * sure the name is unique in this folder * * @param array $data * @param string $id * @return string */ public function save($data, $id = null) { if ($id === null) { $id = $this->id; } if (!$id) { WBClass::load('WBException_Argument'); throw new WBException_Argument('Cannot save file info, id required!', 2, __CLASS__); } $dPrimary = $this->table->getIdentifier($this->dirTable, true); $desDir = $this->data[$dPrimary]; // map "dir" to folder primary key if (isset($data['dir'])) { $desDir = $data['dir']; $data[$dPrimary] = $data['dir']; unset($data['dir']); } $this->log->err($data); // verify fields to change $prohibit = array( $this->table->getIdentifier($this->fileTable, true), 'obscure', 'changed', 'created', 'mimemajor', 'mimeminor' ); $prohibit = array_merge(array_keys($this->skel), $prohibit); foreach (array_keys($data) as $k) { if (in_array($k, $prohibit)) { WBClass::load('WBException_Argument'); throw new WBException_Argument('It is prohibited to save any of these columns: ' . implode(', ', $prohibit), 3, __CLASS__); } } // verify folder's id if (isset($data[$dPrimary]) && $data[$dPrimary]) { $dir = $this->table->get($this->dirTable, $data[$dPrimary], null, $this->clause); if (count($dir) != 1) { WBClass::load('WBException_Argument'); throw new WBException_Argument('Destination folder: ' . $data[$dPrimary] . ' does not exist.' , 4, __CLASS__); } } // serialize info if (isset($data['info'])) { $data['info'] = serialize($data['info']); } // make a unique name if (isset($data['name'])) { $sibs = $this->ls($desDir, array('name')); $data['name'] = $this->makeUniqueName($data['name'], $sibs); } // update local data if ($this->id == $id) { foreach ($data as $k => $v) { $this->data[$k] = $v; } $this->data['changed'] = gmdate('Y-m-d H:i:s'); } $clause = $this->clause; return $this->table->save($this->fileTable, $id, $data, $clause); } /** * import a real file to virtual file system * * Check import file with mime-type importer and save file information in * database * * @param string $file file name * @param string $name optional file name * @param bool $copy whether to copy file or move it * @return WBVFS_File */ public function import($file, $name = null, $copy = false) { $now = WBClock::now(); if (!is_file($file) || !is_readable($file)) { WBClass::load('WBException_Argument'); throw new WBException_Argument('File does not exist or is not readable.', 1, __CLASS__); } /** @var WBFile */ $xFile = WBClass::create('WBFile'); // temporary file $xFile->tempnam(self::DIR_PREFIX); if ($copy) { $xFile->copy($file); } else { $xFile->rename($file); } // extract real file name and create backup $tmp = $xFile->realpath(); copy($tmp, $tmp . '.org'); chmod($tmp, 0666); chmod($tmp . '.org', 0666); $mime = $xFile->getMimeType(); // start mime handler $mimeHdl = WBClass::create('WBVFS_Mime_' . ucfirst($mime[0])); /** @var $mimeHdl WBVFS_Mime */ $mimeHdl->setFile($tmp, $mime[1], $name); $info = $mimeHdl->getInfo(); $mimeHdl->import(); // initial name if (!$name) { $name = basename($file); } // remove well known file extension from name $ext = strtolower($mimeHdl->getExtension()); $name = explode('.', $name); /* if (strtolower(end($name)) == $ext) { array_pop($name); } */ $name = implode('.', $name); // make name unique $dPrimary = $this->table->getIdentifier($this->dirTable, true); $sibs = $this->ls($this->dirCurrent, array('name')); $name = $this->makeUniqueName($name, $sibs); // use a new file object $obj = clone $this; clearstatcache(); // save file data $save = $this->skel; $save[$dPrimary] = $this->dirCurrent; $save['storage'] = $this->storage; $save['md5'] = md5_file($tmp . '.org'); $save['size'] = filesize($tmp . '.org'); $save['name'] = $name; $save['obscure'] = $this->mkRandObscure(); $save['info'] = serialize($info); // again get mime type, use mime handler this time $save['mimemajor'] = $mimeHdl->getMime(WBVFS_File::MIME_MAJOR); $save['mimeminor'] = $mimeHdl->getMime(WBVFS_File::MIME_MINOR); $obj->id = $obj->table->save($obj->fileTable, '__new', $save); $path = $obj->id2path(); $xFile->touch($path); $xFile->rename($tmp); // backup original file rename($tmp .'.org', $xFile->realpath() . '.org'); $obj->data = $save; $obj->data['description'] = ''; $obj->data['created'] = gmdate('Y-m-d H:i:s'); $obj->data['changed'] = $obj->data['created']; $log = array( 'action' => 'import', 'id' => $obj->id, 'storage' => $save['storage'], 'mimemajor' => $save['mimemajor'], 'mimeminor' => $save['mimeminor'], 'size' => $save['size'], 'elapsed' => WBClock::stop($now, 1000, 1) . 'ms', ); $this->log->notice($log); WBEvent::trigger('vfs:file:import:', $save['mimemajor'], 'Imported new file {ID}', $log); return $obj; } /** * import any URL * * Download URL to temporary file and import it * * @see import() * @param string $url * @return WBVFS_File */ public function importUrl($url) { WBClass::load('WBStream'); $stream = WBStream::open($url, 'r'); $varDir = WBParam::get('wb/dir/base') . '/var'; $tmp = tempnam($varDir . '/tmp', self::DIR_PREFIX); $tmpFh = fopen($tmp, 'w'); while (!feof($stream)) { fwrite($tmpFh, fread($stream, 8192)); } fclose($stream); fclose($tmpFh); $info = parse_url($url); $name = $info['host']; if (isset($info['path'])) { $name .= $info['path']; } return $this->import($tmp, $name); } /** * Re run importer * * Run mime handler on current file and re import original file * * @param array $mime alternative mime type. * @return WBVFS_File */ public function reImport($mime = array()) { $now = WBClock::now(); if (empty($mime)) { $mime = array( $this->getMime(self::MIME_MAJOR), $this->getMime(self::MIME_MINOR) ); } $file = $this->id2path(); $xFile = WBClass::create('WBFile'); $xFile->exists($file . '.org'); $org = $xFile->realpath(); $xFile->exists($file); $xFile->copy($org); // start mime handler $mimeHdl = WBClass::create('WBVFS_Mime_' . ucfirst($mime[0])); /** @var $mimeHdl WBVFS_Mime */ $mimeHdl->setFile($xFile->realpath(), $mime[1]); $info = $mimeHdl->getInfo(); $mimeHdl->import(); $mime = $xFile->getMimeType(); $save = array(); $save['md5'] = md5_file($org); $save['size'] = filesize($org); $save['info'] = serialize($info); $save['mimemajor'] = $mime[0]; $save['mimeminor'] = $mime[1]; $this->table->save($this->fileTable, $this->getId(), $save); $this->flushCache(); $log = array( 'action' => 'reimport', 'id' => $this->getId(), 'storage' => $this->data['storage'], 'mimemajor' => $save['mimemajor'], 'mimeminor' => $save['mimeminor'], 'size' => $save['size'], 'elapsed' => WBClock::stop($now, 1000, 1) . 'ms' ); $this->log->notice($log); return $this; } /** * Remove file * * @return string $id of deleted file */ public function delete() { if (!$this->id) { return null; } $id = $this->id; $this->table->delete($this->fileTable, $id, null, $this->clause); $this->id = null; $this->data = array(); return $id; } /** * Undelete file * * Please notice, that deleted file must reloaded first with parameter "includeDeleted" * * @see loadByObscureId * @see loadById * @return string $id of undeleted file */ public function undelete() { if (!$this->id) { return null; } $this->table->undelete($this->fileTable, $this->id); return $this->id; } /** * Actually Remove Files * * @param string date * @return int number of deleted files */ public function flushDeleted($until = '-1 year') { $option = array( 'deleted' => 1, 'limit' => 100, 'offset' => 0 ); $clause = $this->clause; $clause[] = array( 'field' => 'deleted', 'value' => 1 ); $clause[] = array( 'field' => 'changed', 'value' => date('Y-m-d 00:00:00', strtotime($until)), 'relation' => 'lt' ); $list = $this->table->get($this->fileTable, null, null, $clause, $option); if (empty($list)) { return 0; } $table = clone $this->table; $tInfo = $this->table->getTableInfo($this->fileTable); $tInfo['foreign'] = null; $tInfo['delete'] = null; $table->setTables(array($this->fileTable => $tInfo)); $count = 0; while (!empty($list)) { foreach ($list as $l) { if (!$this->flushDeletedFile($l)) { ++$option['offset']; } else { $table->delete($this->fileTable, $l['id']); } ++$count; } // get more files $list = $this->table->get($this->fileTable, null, null, $clause, $option); } return $count; } /** * Wipe Out File * * @param array file data * @return bool true if files where deleted */ private function flushDeletedFile($data) { $file = $this->loadByData($data); $this->debugPrint(sprintf("action: flush, id: %s, size: %d", $file->getId(), $file->getSize()), 1); if (5 > WBParam::get('wb/debug', 0)) { // remove cache $file->flushCache(); // remove original files $path = $file->getPath(); if (file_exists($path . '.org')) { unlink($path . '.org'); } if (file_exists($path)) { unlink($path); } return true; } return false; } /** * Remove file's cached data * * @param string $sub cache sub folder e.g. image */ public function flushCache($sub = null) { $dir = $this->getCacheDir(); if (!empty($sub)) { $dir .= '/' . $sub; } $file = WBClass::create('WBFile'); $file->removeDir($dir); $file->mkdir($dir); } /** * Get cache folder for vfsfiles * * @return string */ public function getCacheDir($withBasdir = false) { $dir = 'var/cache/' . self::DIR_PREFIX . '/' . $this->id2path(false); if (!$withBasdir) { return $dir; } $dir = WBParam::get('wb/dir/base') . '/' . $dir; return $dir; } /** * calculate file name from id * * As there are * * @param string $id * @return string */ protected function id2path($withPrefix = true) { $idEx = sprintf('%024s', $this->id); $idEx = str_split($idEx, 4); array_pop($idEx); $dir = $this->storage . '/' . implode('/', $idEx) . '/' . $this->id; if ($withPrefix) { $dir = 'var/' . self::DIR_PREFIX . '/' . $dir; } return $dir; } /** * create a random obscure code * * Make an alphanumeric random string to be used to obscure ids * * @param int $length * @return string */ protected function mkRandObscure($length = 2) { WBClass::load('WBDatasource_ObscureCode'); return WBDatasource_ObscureCode::mkRandObscure($length); } /** * callback function to list files * * This function should not be called for incomplete columns * * @param string $table * @param string $k * @param array $v */ public function onDatasourceGet($table, $k, &$v) { if ($table != $this->fileTable) { return $v; } $file = $this->loadByData($v); $v['obscureid'] = $file->getObscureId(); $v['uri'] = $file->getUri(); $v['views'] = $file->getViews(); } }