<?php
/**
* @brief Dotclear media manage
*
* This class handles Dotclear media items.
*
* @package Dotclear
* @subpackage Core
*
* @copyright Olivier Meunier & Association Dotclear
* @copyright GPL-2.0-only
*/
if (!defined('DC_RC_PATH')) {
return;
}
class dcMedia extends filemanager
{
protected $core; ///< <b>dcCore</b> dcCore instance
protected $con; ///< <b>connection</b> Database connection
protected $table; ///< <b>string</b> Media table name
protected $type; ///< <b>string</b> Media type filter
protected $file_sort = 'name-asc';
protected $path;
protected $relpwd;
protected $file_handler = []; ///< <b>array</b> Array of callbacks
protected $postmedia;
public $thumb_tp = '%s/.%s_%s.jpg'; ///< <b>string</b> Thumbnail file pattern
public $thumb_tp_alpha = '%s/.%s_%s.png'; ///< <b>string</b> Thumbnail file pattern (with alpha layer)
public $thumb_tp_webp = '%s/.%s_%s.webp'; ///< <b>string</b> Thumbnail file pattern (webp)
/**
* Tubmnail sizes:
* - m: medium image
* - s: small image
* - t: thumbnail image
* - sq: square image
*
* @var array
*/
public $thumb_sizes = [
'm' => [448, 'ratio', 'medium'],
's' => [240, 'ratio', 'small'],
't' => [100, 'ratio', 'thumbnail'],
'sq' => [48, 'crop', 'square'],
];
public $icon_img = 'images/media/%s.png'; ///< <b>string</b> Icon file pattern
/**
* Constructs a new instance.
*
* @param dcCore $core The core
* @param string $type The media type filter
*
* @throws Exception (description)
*/
public function __construct(dcCore $core, $type = '')
{
$this->core = &$core;
$this->con = &$core->con;
$this->postmedia = new dcPostMedia($core);
if ($this->core->blog == null) {
throw new Exception(__('No blog defined.'));
}
$this->table = $this->core->prefix . 'media';
$root = $this->core->blog->public_path;
if (preg_match('#^http(s)?://#', $this->core->blog->settings->system->public_url)) {
$root_url = rawurldecode($this->core->blog->settings->system->public_url);
} else {
$root_url = rawurldecode($this->core->blog->host . path::clean($this->core->blog->settings->system->public_url));
}
if (!is_dir($root)) {
# Check public directory
if ($core->auth->isSuperAdmin()) {
throw new Exception(__('There is no writable directory /public/ at the location set in about:config "public_path". You must create this directory with sufficient rights (or change this setting).'));
}
throw new Exception(__('There is no writable root directory for the media manager. You should contact your administrator.'));
}
$this->type = $type;
parent::__construct($root, $root_url);
$this->chdir('');
$this->path = $this->core->blog->settings->system->public_path;
$this->addExclusion(DC_RC_PATH);
$this->addExclusion(__DIR__ . '/../');
$this->exclude_pattern = $core->blog->settings->system->media_exclusion;
# Event handlers
$this->addFileHandler('image/jpeg', 'create', [$this, 'imageThumbCreate']);
$this->addFileHandler('image/png', 'create', [$this, 'imageThumbCreate']);
$this->addFileHandler('image/gif', 'create', [$this, 'imageThumbCreate']);
$this->addFileHandler('image/webp', 'create', [$this, 'imageThumbCreate']);
$this->addFileHandler('image/png', 'update', [$this, 'imageThumbUpdate']);
$this->addFileHandler('image/jpeg', 'update', [$this, 'imageThumbUpdate']);
$this->addFileHandler('image/gif', 'update', [$this, 'imageThumbUpdate']);
$this->addFileHandler('image/webp', 'update', [$this, 'imageThumbUpdate']);
$this->addFileHandler('image/png', 'remove', [$this, 'imageThumbRemove']);
$this->addFileHandler('image/jpeg', 'remove', [$this, 'imageThumbRemove']);
$this->addFileHandler('image/gif', 'remove', [$this, 'imageThumbRemove']);
$this->addFileHandler('image/webp', 'remove', [$this, 'imageThumbRemove']);
$this->addFileHandler('image/jpeg', 'create', [$this, 'imageMetaCreate']);
$this->addFileHandler('image/webp', 'create', [$this, 'imageMetaCreate']);
$this->addFileHandler('image/jpeg', 'recreate', [$this, 'imageThumbCreate']);
$this->addFileHandler('image/png', 'recreate', [$this, 'imageThumbCreate']);
$this->addFileHandler('image/gif', 'recreate', [$this, 'imageThumbCreate']);
$this->addFileHandler('image/webp', 'recreate', [$this, 'imageThumbCreate']);
# Thumbnails sizes
$this->thumb_sizes['m'][0] = abs($core->blog->settings->system->media_img_m_size);
$this->thumb_sizes['s'][0] = abs($core->blog->settings->system->media_img_s_size);
$this->thumb_sizes['t'][0] = abs($core->blog->settings->system->media_img_t_size);
# Thumbnails sizes names
$this->thumb_sizes['m'][2] = __($this->thumb_sizes['m'][2]);
$this->thumb_sizes['s'][2] = __($this->thumb_sizes['s'][2]);
$this->thumb_sizes['t'][2] = __($this->thumb_sizes['t'][2]);
$this->thumb_sizes['sq'][2] = __($this->thumb_sizes['sq'][2]);
# --BEHAVIOR-- coreMediaConstruct
$this->core->callBehavior('coreMediaConstruct', $this);
}
/**
* Changes working directory.
*
* @param string $dir The directory name
*/
public function chdir($dir)
{
parent::chdir($dir);
$this->relpwd = preg_replace('/^' . preg_quote($this->root, '/') . '\/?/', '', $this->pwd);
}
/**
* Adds a new file handler for a given media type and event.
*
* Available events are:
* - create: file creation
* - update: file update
* - remove: file deletion
*
* @param string $type The media type
* @param string $event The event
* @param callable $function The callback
*/
public function addFileHandler($type, $event, $function)
{
if (is_callable($function)) {
$this->file_handler[$type][$event][] = $function;
}
}
protected function callFileHandler($type, $event, ...$args)
{
if (!empty($this->file_handler[$type][$event])) {
foreach ($this->file_handler[$type][$event] as $f) {
call_user_func_array($f, $args);
}
}
}
/**
* Returns HTML breadCrumb for media manager navigation.
*
* @param string $href The URL pattern
* @param string $last The last item pattern
*
* @return string HTML code
*/
public function breadCrumb($href, $last = '')
{
$res = '';
if ($this->relpwd && $this->relpwd != '.') {
$pwd = '';
$arr = explode('/', $this->relpwd);
$count = count($arr);
foreach ($arr as $v) {
if (($last != '') && (0 === --$count)) {
$res .= sprintf($last, $v);
} else {
$pwd .= rawurlencode($v) . '/';
$res .= '<a href="' . sprintf($href, $pwd) . '">' . $v . '</a> / ';
}
}
}
return $res;
}
protected function fileRecord($rs)
{
if ($rs->isEmpty()) {
return;
}
if (!$this->isFileExclude($this->root . '/' . $rs->media_file) && is_file($this->root . '/' . $rs->media_file)) {
$f = new fileItem($this->root . '/' . $rs->media_file, $this->root, $this->root_url);
if ($this->type && $f->type_prefix != $this->type) {
return;
}
$meta = @simplexml_load_string((string) $rs->media_meta);
$f->editable = true;
$f->media_id = $rs->media_id;
$f->media_title = $rs->media_title;
$f->media_meta = $meta instanceof SimpleXMLElement ? $meta : simplexml_load_string('<meta></meta>');
$f->media_user = $rs->user_id;
$f->media_priv = (bool) $rs->media_private;
$f->media_dt = strtotime($rs->media_dt);
$f->media_dtstr = dt::str('%Y-%m-%d %H:%M', $f->media_dt);
$f->media_image = false;
if (!$this->core->auth->check('media_admin', $this->core->blog->id)
&& $this->core->auth->userID() != $f->media_user) {
$f->del = false;
$f->editable = false;
}
$type_prefix = explode('/', $f->type);
$type_prefix = $type_prefix[0];
switch ($type_prefix) {
case 'image':
$f->media_image = true;
$f->media_icon = 'image';
break;
case 'audio':
$f->media_icon = 'audio';
break;
case 'text':
$f->media_icon = 'text';
break;
case 'video':
$f->media_icon = 'video';
break;
default:
$f->media_icon = 'blank';
}
switch ($f->type) {
case 'application/msword':
case 'application/vnd.oasis.opendocument.text':
case 'application/vnd.sun.xml.writer':
case 'application/pdf':
case 'application/postscript':
$f->media_icon = 'document';
break;
case 'application/msexcel':
case 'application/vnd.oasis.opendocument.spreadsheet':
case 'application/vnd.sun.xml.calc':
$f->media_icon = 'spreadsheet';
break;
case 'application/mspowerpoint':
case 'application/vnd.oasis.opendocument.presentation':
case 'application/vnd.sun.xml.impress':
$f->media_icon = 'presentation';
break;
case 'application/x-debian-package':
case 'application/x-bzip':
case 'application/x-gzip':
case 'application/x-java-archive':
case 'application/rar':
case 'application/x-redhat-package-manager':
case 'application/x-tar':
case 'application/x-gtar':
case 'application/zip':
$f->media_icon = 'package';
break;
case 'application/octet-stream':
$f->media_icon = 'executable';
break;
case 'application/x-shockwave-flash':
$f->media_icon = 'video';
break;
case 'application/ogg':
$f->media_icon = 'audio';
break;
case 'text/html':
$f->media_icon = 'html';
break;
}
$f->media_type = $f->media_icon;
$f->media_icon = sprintf($this->icon_img, $f->media_icon);
# Thumbnails
$f->media_thumb = [];
$p = path::info($f->relname);
$alpha = strtolower($p['extension']) === 'png';
$webp = strtolower($p['extension']) === 'webp';
$thumb = sprintf(
($alpha ? $this->thumb_tp_alpha :
($webp ? $this->thumb_tp_webp : $this->thumb_tp)),
$this->root . '/' . $p['dirname'],
$p['base'],
'%s'
);
$thumb_url = sprintf(
($alpha ? $this->thumb_tp_alpha :
($webp ? $this->thumb_tp_webp : $this->thumb_tp)),
$this->root_url . $p['dirname'],
$p['base'],
'%s'
);
# Cleaner URLs
$thumb_url = preg_replace('#\./#', '/', $thumb_url);
$thumb_url = preg_replace('#(?<!:)/+#', '/', $thumb_url);
$thumb_alt = '';
$thumb_url_alt = '';
if ($alpha || $webp) {
$thumb_alt = sprintf($this->thumb_tp, $this->root . '/' . $p['dirname'], $p['base'], '%s');
$thumb_url_alt = sprintf($this->thumb_tp, $this->root_url . $p['dirname'], $p['base'], '%s');
# Cleaner URLs
$thumb_url_alt = preg_replace('#\./#', '/', $thumb_url_alt);
$thumb_url_alt = preg_replace('#(?<!:)/+#', '/', $thumb_url_alt);
}
foreach ($this->thumb_sizes as $suffix => $s) {
if (file_exists(sprintf($thumb, $suffix))) {
$f->media_thumb[$suffix] = sprintf($thumb_url, $suffix);
} elseif (($alpha || $webp) && file_exists(sprintf($thumb_alt, $suffix))) {
$f->media_thumb[$suffix] = sprintf($thumb_url_alt, $suffix);
}
}
if ($f->media_type === 'image') {
if (isset($f->media_thumb['sq'])) {
$f->media_icon = $f->media_thumb['sq'];
} elseif (strtolower($p['extension']) === 'svg') {
$f->media_icon = $this->root_url . $p['dirname'] . '/' . $p['base'] . '.' . $p['extension'];
}
}
return $f;
}
}
/**
* Sets the file sort.
*
* @param string $type The type
*/
public function setFileSort($type = 'name')
{
if (in_array($type, ['size-asc', 'size-desc', 'name-asc', 'name-desc', 'date-asc', 'date-desc'])) {
$this->file_sort = $type;
}
}
protected function sortFileHandler($a, $b)
{
if (is_null($a) || is_null($b)) {
return (is_null($a) ? 1 : -1);
}
switch ($this->file_sort) {
case 'size-asc':
if ($a->size == $b->size) {
return 0;
}
return ($a->size < $b->size) ? -1 : 1;
case 'size-desc':
if ($a->size == $b->size) {
return 0;
}
return ($a->size > $b->size) ? -1 : 1;
case 'date-asc':
if ($a->media_dt == $b->media_dt) {
return 0;
}
return ($a->media_dt < $b->media_dt) ? -1 : 1;
case 'date-desc':
if ($a->media_dt == $b->media_dt) {
return 0;
}
return ($a->media_dt > $b->media_dt) ? -1 : 1;
case 'name-desc':
return strcasecmp($b->basename, $a->basename);
case 'name-asc':
default:
return strcasecmp($a->basename, $b->basename);
}
}
/**
* Gets current working directory content (using filesystem).
*/
public function getFSDir()
{
parent::getDir();
}
/**
* Gets current working directory content.
*
* @param mixed $type The media type filter
*
* @throws Exception
*/
public function getDir($type = null)
{
if ($type) {
$this->type = $type;
}
$media_dir = $this->relpwd ?: '.';
$sql = new dcSelectStatement($this->core, 'dcMediaGetDir');
$sql
->columns([
'media_file',
'media_id',
'media_path',
'media_title',
'media_meta',
'media_dt',
'media_creadt',
'media_upddt',
'media_private',
'user_id',
])
->from($this->table)
->where('media_path = ' . $sql->quote($this->path))
->and('media_dir = ' . $sql->quote($media_dir, true));
if (!$this->core->auth->check('media_admin', $this->core->blog->id)) {
$list = ['media_private <> 1'];
if ($user_id = $this->core->auth->userID()) {
$list[] = 'user_id = ' . $sql->quote($user_id, true);
}
$sql->and($sql->orGroup($list));
}
$rs = $sql->select();
// Get list of private files in dir
$sql = new dcSelectStatement($this->core, 'dcMediaGetDir');
$sql
->columns([
'media_file',
'media_id',
'media_path',
'media_title',
'media_meta',
'media_dt',
'media_creadt',
'media_upddt',
'media_private',
'user_id',
])
->from($this->table)
->where('media_path = ' . $sql->quote($this->path))
->and('media_dir = ' . $sql->quote($media_dir, true))
->and('media_private = 1');
$rsp = $sql->select();
$privates = [];
while ($rsp->fetch()) {
# File in subdirectory, forget about it!
if (dirname($rsp->media_file) != '.' && dirname($rsp->media_file) != $this->relpwd) {
continue;
}
if ($f = $this->fileRecord($rsp)) {
$privates[] = $f->relname;
}
}
parent::getDir();
$f_res = [];
$p_dir = $this->dir;
# If type is set, remove items from p_dir
if ($this->type) {
foreach ($p_dir['files'] as $k => $f) {
if ($f->type_prefix != $this->type) {
unset($p_dir['files'][$k]);
}
}
}
$f_reg = [];
while ($rs->fetch()) {
# File in subdirectory, forget about it!
if (dirname($rs->media_file) != '.' && dirname($rs->media_file) != $this->relpwd) {
continue;
}
if ($this->inFiles($rs->media_file)) {
$f = $this->fileRecord($rs);
if ($f !== null) {
if (isset($f_reg[$rs->media_file])) {
# That media is duplicated in the database,
# time to do a bit of house cleaning.
$sql = new dcDeleteStatement($this->core, 'dcMediaGetDir');
$sql
->from($this->table)
->where('media_id = ' . $this->fileRecord($rs)->media_id);
$sql->delete();
} else {
$f_res[] = $this->fileRecord($rs);
$f_reg[$rs->media_file] = 1;
}
}
} elseif (!empty($p_dir['files']) && $this->relpwd == '') {
# Physical file does not exist remove it from DB
# Because we don't want to erase everything on
# dotclear upgrade, do it only if there are files
# in directory and directory is root
$sql = new dcDeleteStatement($this->core, 'dcMediaGetDir');
$sql
->from($this->table)
->where('media_path = ' . $sql->quote($this->path, true))
->and('media_file = ' . $sql->quote($rs->media_file, true));
$sql->delete();
$this->callFileHandler(files::getMimeType($rs->media_file), 'remove', $this->pwd . '/' . $rs->media_file);
}
}
$this->dir['files'] = $f_res;
foreach ($this->dir['dirs'] as $k => $v) {
$v->media_icon = sprintf($this->icon_img, ($v->parent ? 'folder-up' : 'folder'));
}
# Check files that don't exist in database and create them
if ($this->core->auth->check('media,media_admin', $this->core->blog->id)) {
foreach ($p_dir['files'] as $f) {
// Warning a file may exist in DB but in private mode for the user, so we don't have to recreate it
if (!isset($f_reg[$f->relname]) && !in_array($f->relname, $privates)) {
if (($id = $this->createFile($f->basename, null, false, null, false)) !== false) {
$this->dir['files'][] = $this->getFile($id);
}
}
}
}
try {
usort($this->dir['files'], [$this, 'sortFileHandler']);
} catch (Exception $e) {
}
}
/**
* Gets file by its id. Returns a filteItem object.
*
* @param mixed $id The file identifier
*
* @return fileItem The file.
*/
public function getFile($id)
{
$sql = new dcSelectStatement($this->core, 'dcMediaGetFile');
$sql
->from($this->table)
->columns([
'media_id',
'media_path',
'media_title',
'media_file',
'media_meta',
'media_dt',
'media_creadt',
'media_upddt',
'media_private',
'user_id',
])
->where('media_path = ' . $sql->quote($this->path))
->and('media_id = ' . (int) $id);
if (!$this->core->auth->check('media_admin', $this->core->blog->id)) {
$list = ['media_private <> 1'];
if ($user_id = $this->core->auth->userID()) {
$list[] = 'user_id = ' . $sql->quote($user_id, true);
}
$sql->and($sql->orGroup($list));
}
$rs = $sql->select();
return $this->fileRecord($rs);
}
/**
* Search into media db (only).
*
* @param string $query The search query
*
* @return bool true or false if nothing found
*/
public function searchMedia($query)
{
if ($query == '') {
return false;
}
$sql = new dcSelectStatement($this->core, 'dcMediaGetFile');
$sql
->from($this->table)
->columns([
'media_file',
'media_id',
'media_path',
'media_title',
'media_meta',
'media_dt',
'media_creadt',
'media_upddt',
'media_private',
'user_id',
])
->where('media_path = ' . $sql->quote($this->path))
->and($sql->orGroup([
$sql->like('media_title', '%' . $sql->escape($query) . '%'),
$sql->like('media_file', '%' . $sql->escape($query) . '%'),
$sql->like('media_meta', '%<Description>%' . $sql->escape($query) . '%</Description>%'),
]));
if (!$this->core->auth->check('media_admin', $this->core->blog->id)) {
$list = ['media_private <> 1'];
if ($user_id = $this->core->auth->userID()) {
$list[] = 'user_id = ' . $sql->quote($user_id, true);
}
$sql->and($sql->orGroup($list));
}
$rs = $sql->select();
$this->dir = ['dirs' => [], 'files' => []];
$f_res = [];
while ($rs->fetch()) {
$fr = $this->fileRecord($rs);
if ($fr) {
$f_res[] = $fr;
}
}
$this->dir['files'] = $f_res;
try {
usort($this->dir['files'], [$this, 'sortFileHandler']);
} catch (Exception $e) {
}
return (count($f_res) > 0 ? true : false);
}
/**
* Returns media items attached to a blog post. Result is an array containing
* fileItems objects.
*
* @param integer $post_id The post identifier
* @param mixed $media_id The media identifier
* @param mixed $link_type The link type
*
* @return array Array of fileItems.
*/
public function getPostMedia($post_id, $media_id = null, $link_type = null)
{
$params = [
'post_id' => $post_id,
'media_path' => $this->path,
];
if ($media_id) {
$params['media_id'] = (int) $media_id;
}
if ($link_type) {
$params['link_type'] = $link_type;
}
$rs = $this->postmedia->getPostMedia($params);
$res = [];
while ($rs->fetch()) {
$f = $this->fileRecord($rs);
if ($f !== null) {
$res[] = $f;
}
}
return $res;
}
/**
* Rebuilds database items collection. Optional <var>$pwd</var> parameter is
* the path where to start rebuild.
*
* @param string $pwd The directory to rebuild
*
* @throws Exception
*/
public function rebuild($pwd = '')
{
if (!$this->core->auth->isSuperAdmin()) {
throw new Exception(__('You are not a super administrator.'));
}
$this->chdir($pwd);
parent::getDir();
$dir = $this->dir;
foreach ($dir['dirs'] as $d) {
if (!$d->parent) {
$this->rebuild($d->relname);
}
}
foreach ($dir['files'] as $f) {
$this->chdir(dirname($f->relname));
$this->createFile($f->basename);
}
$this->rebuildDB($pwd);
}
protected function rebuildDB($pwd)
{
$media_dir = $pwd ?: '.';
$sql = new dcSelectStatement($this->core, 'dcMediaRebuildDB');
$sql
->from($this->table)
->columns([
'media_file',
'media_id',
])
->where('media_path = ' . $sql->quote($this->path))
->and('media_dir = ' . $sql->quote($media_dir, true));
$rs = $sql->select();
$del_ids = [];
while ($rs->fetch()) {
if (!is_file($this->root . '/' . $rs->media_file)) {
$del_ids[] = (int) $rs->media_id;
}
}
if (!empty($del_ids)) {
$sql = new dcDeleteStatement($this->core, 'dcMediaRebuildDB');
$sql
->from($this->core)
->where('media_id' . $sql->in($del_ids));
$sql->delete();
}
}
/**
* Makes a dir.
*
* @param string $d the directory to create
*/
public function makeDir($d)
{
$d = files::tidyFileName($d);
parent::makeDir($d);
}
/**
* Creates or updates a file in database. Returns new media ID or false if
* file does not exist.
*
* @param string $name The file name (relative to working directory)
* @param mixed $title The file title
* @param bool $private File is private
* @param mixed $dt File date
* @param bool $force The force flag
*
* @throws Exception
*
* @return integer|bool New media ID or false
*/
public function createFile($name, $title = null, $private = false, $dt = null, $force = true)
{
if (!$this->core->auth->check('media,media_admin', $this->core->blog->id)) {
throw new Exception(__('Permission denied.'));
}
$file = $this->pwd . '/' . $name;
if (!file_exists($file)) {
return false;
}
$media_file = $this->relpwd ? path::clean($this->relpwd . '/' . $name) : path::clean($name);
$media_type = files::getMimeType($name);
$cur = $this->con->openCursor($this->table);
$sql = new dcSelectStatement($this->core, 'dcMediaCreateFile');
$sql
->from($this->table)
->column('media_id')
->where('media_path = ' . $sql->quote($this->path, true))
->and('media_file = ' . $sql->quote($media_file, true));
$rs = $sql->select();
if ($rs->isEmpty()) {
$this->con->writeLock($this->table);
try {
$sql = new dcSelectStatement($this->core, 'dcMediaCreateFile');
$sql
->from($this->table)
->column($sql->max('media_id'));
$rs = $sql->select();
$media_id = (int) $rs->f(0) + 1;
$cur->media_id = $media_id;
$cur->user_id = (string) $this->core->auth->userID();
$cur->media_path = (string) $this->path;
$cur->media_file = (string) $media_file;
$cur->media_dir = (string) dirname($media_file);
$cur->media_creadt = date('Y-m-d H:i:s');
$cur->media_upddt = date('Y-m-d H:i:s');
$cur->media_title = !$title ? (string) $name : (string) $title;
$cur->media_private = (int) (bool) $private;
if ($dt) {
$cur->media_dt = (string) $dt;
} else {
$cur->media_dt = @strftime('%Y-%m-%d %H:%M:%S', filemtime($file));
}
try {
$cur->insert();
} catch (Exception $e) {
@unlink($name);
throw $e;
}
$this->con->unlock();
} catch (Exception $e) {
$this->con->unlock();
throw $e;
}
} else {
$media_id = (int) $rs->media_id;
$cur->media_upddt = date('Y-m-d H:i:s');
$sql = new dcUpdateStatement($this->core, 'dcMediaCreateFile');
$sql->where('media_id = ' . $media_id);
$sql->update($cur);
}
$this->callFileHandler($media_type, 'create', $cur, $name, $media_id, $force);
return $media_id;
}
/**
* Updates a file in database.
*
* @param fileItem $file The file
* @param fileItem $newFile The new file
*
* @throws Exception
*/
public function updateFile($file, $newFile)
{
if (!$this->core->auth->check('media,media_admin', $this->core->blog->id)) {
throw new Exception(__('Permission denied.'));
}
$id = (int) $file->media_id;
if (!$id) {
throw new Exception('No file ID');
}
if (!$this->core->auth->check('media_admin', $this->core->blog->id)
&& $this->core->auth->userID() != $file->media_user) {
throw new Exception(__('You are not the file owner.'));
}
$cur = $this->con->openCursor($this->table);
# We need to tidy newFile basename. If dir isn't empty, concat to basename
$newFile->relname = files::tidyFileName($newFile->basename);
if ($newFile->dir) {
$newFile->relname = $newFile->dir . '/' . $newFile->relname;
}
if ($file->relname != $newFile->relname) {
$newFile->file = $this->root . '/' . $newFile->relname;
if ($this->isFileExclude($newFile->relname)) {
throw new Exception(__('This file is not allowed.'));
}
if (file_exists($newFile->file)) {
throw new Exception(__('New file already exists.'));
}
$this->moveFile($file->relname, $newFile->relname);
$cur->media_file = (string) $newFile->relname;
$cur->media_dir = (string) dirname($newFile->relname);
}
$cur->media_title = (string) $newFile->media_title;
$cur->media_dt = (string) $newFile->media_dtstr;
$cur->media_upddt = date('Y-m-d H:i:s');
$cur->media_private = (int) $newFile->media_priv;
if ($newFile->media_meta instanceof SimpleXMLElement) {
$cur->media_meta = $newFile->media_meta->asXML();
}
$sql = new dcUpdateStatement($this->core, 'dcMediaCreateFile');
$sql->where('media_id = ' . $id);
$sql->update($cur);
$this->callFileHandler($file->type, 'update', $file, $newFile);
}
/**
* Uploads a file.
*
* @param string $tmp The full path of temporary uploaded file
* @param string $name The file name (relative to working directory)me
* @param mixed $title The file title
* @param bool $private File is private
* @param bool $overwrite File should be overwrite
*
* @throws Exception
*
* @return mixed New media ID or false
*/
public function uploadFile($tmp, $name, $title = null, $private = false, $overwrite = false)
{
if (!$this->core->auth->check('media,media_admin', $this->core->blog->id)) {
throw new Exception(__('Permission denied.'));
}
$name = files::tidyFileName($name);
parent::uploadFile($tmp, $name, $overwrite);
return $this->createFile($name, $title, $private);
}
/**
* Creates a file from binary content.
*
* @param string $name The file name (relative to working directory)
* @param mixed $bits The binary file contentits
*
* @throws Exception
*
* @return mixed New media ID or false
*/
public function uploadBits($name, $bits)
{
if (!$this->core->auth->check('media,media_admin', $this->core->blog->id)) {
throw new Exception(__('Permission denied.'));
}
$name = files::tidyFileName($name);
parent::uploadBits($name, $bits);
return $this->createFile($name, null, false);
}
/**
* Removes a file.
*
* @param string $f filename
*
* @throws Exception
*/
public function removeFile($f)
{
if (!$this->core->auth->check('media,media_admin', $this->core->blog->id)) {
throw new Exception(__('Permission denied.'));
}
$media_file = $this->relpwd ? path::clean($this->relpwd . '/' . $f) : path::clean($f);
$sql = new dcDeleteStatement($this->core, 'dcMediaRemoveFile');
$sql
->from($this->table)
->where('media_path = ' . $sql->quote($this->path, true))
->and('media_file = ' . $sql->quote($media_file));
if (!$this->core->auth->check('media_admin', $this->core->blog->id)) {
$sql->and('user_id = ' . $sql->quote($this->core->auth->userID(), true));
}
$sql->delete();
if ($this->con->changes() == 0) {
throw new Exception(__('File does not exist in the database.'));
}
parent::removeFile($f);
$this->callFileHandler(files::getMimeType($media_file), 'remove', $f);
}
/**
* Root directories
*
* Returns an array of directory under {@link $root} directory.
*
* @uses fileItem
*
* @return array
*/
public function getDBDirs()
{
$dir = [];
$media_dir = $this->relpwd ?: '.';
$sql = new dcSelectStatement($this->core, 'dcMediaGetDBDirs');
$sql
->from($this->table)
->column('distinct media_dir')
->where('media_path = ' . $sql->quote($this->path));
$rs = $sql->select();
while ($rs->fetch()) {
if (is_dir($this->root . '/' . $rs->media_dir)) {
$dir[] = ($rs->media_dir == '.' ? '' : $rs->media_dir);
}
}
return $dir;
}
/**
* Extract zip file in current location.
*
* @param fileItem $f fileItem object
* @param bool $create_dir Create dir
*
* @throws Exception
*
* @return string destination
*/
public function inflateZipFile($f, $create_dir = true)
{
$zip = new fileUnzip($f->file);
$zip->setExcludePattern($this->exclude_pattern);
$list = $zip->getList(false, '#(^|/)(__MACOSX|\.svn|\.hg.*|\.git.*|\.DS_Store|\.directory|Thumbs\.db)(/|$)#');
if ($create_dir) {
$zip_root_dir = $zip->getRootDir();
if ($zip_root_dir != false) {
$destination = $zip_root_dir;
$target = $f->dir;
} else {
$destination = preg_replace('/\.([^.]+)$/', '', $f->basename);
$target = $f->dir . '/' . $destination;
}
if (is_dir($f->dir . '/' . $destination)) {
throw new Exception(sprintf(__('Extract destination directory %s already exists.'), dirname($f->relname) . '/' . $destination));
}
} else {
$target = $f->dir;
$destination = '';
}
$zip->unzipAll($target);
$zip->close();
// Clean-up all extracted filenames
$clean = function ($name) {
$n = text::deaccent($name);
$n = preg_replace('/^[.]/u', '', $n);
return preg_replace('/[^A-Za-z0-9._\-\/]/u', '_', $n);
};
foreach ($list as $zk => $zv) {
// Check if extracted file exists
$zf = $target . '/' . $zk;
if (!$zv['is_dir'] && file_exists($zf)) {
$zt = $clean($zf);
if ($zt != $zf) {
rename($zf, $zt);
}
}
}
return dirname($f->relname) . '/' . $destination;
}
/**
* Gets the zip content.
*
* @param fileItem $f fileItem object
*
* @return array The zip content.
*/
public function getZipContent($f)
{
$zip = new fileUnzip($f->file);
$list = $zip->getList(false, '#(^|/)(__MACOSX|\.svn|\.hg.*|\.git.*|\.DS_Store|\.directory|Thumbs\.db)(/|$)#');
$zip->close();
return $list;
}
/**
* Calls file handlers registered for recreate event.
*
* @param fileItem $f fileItem object
*/
public function mediaFireRecreateEvent($f)
{
$media_type = files::getMimeType($f->basename);
$this->callFileHandler($media_type, 'recreate', null, $f->basename); // Args list to be completed as necessary (Franck)
}
/* Image handlers
------------------------------------------------------- */
/**
* Create image thumbnails
*
* @param mixed $cur The cursor
* @param string $f Image filename
* @param bool $force Force creation
*
* @return mixed
*/
public function imageThumbCreate($cur, $f, $force = true)
{
$file = $this->pwd . '/' . $f;
if (!file_exists($file)) {
return false;
}
$p = path::info($file);
$alpha = strtolower($p['extension']) === 'png';
$webp = strtolower($p['extension']) === 'webp';
$thumb = sprintf(
($alpha ? $this->thumb_tp_alpha :
($webp ? $this->thumb_tp_webp :
$this->thumb_tp)),
$p['dirname'],
$p['base'],
'%s'
);
try {
$img = new imageTools();
$img->loadImage($file);
$w = $img->getW();
$h = $img->getH();
if ($force) {
$this->imageThumbRemove($f);
}
foreach ($this->thumb_sizes as $suffix => $s) {
$thumb_file = sprintf($thumb, $suffix);
if (!file_exists($thumb_file) && $s[0] > 0 && ($suffix == 'sq' || $w > $s[0] || $h > $s[0])) {
$rate = ($s[0] < 100 ? 95 : ($s[0] < 600 ? 90 : 85));
$img->resize($s[0], $s[0], $s[1]);
$img->output(($alpha || $webp ? strtolower($p['extension']) : 'jpeg'), $thumb_file, $rate);
$img->loadImage($file);
}
}
$img->close();
} catch (Exception $e) {
if ($cur === null) {
# Called only if cursor is null (public call)
throw $e;
}
}
}
/**
* Update image thumbnails
*
* @param fileItem $file The file
* @param fileItem $newFile The new file
*/
protected function imageThumbUpdate($file, $newFile)
{
if ($file->relname != $newFile->relname) {
$p = path::info($file->relname);
$alpha = strtolower($p['extension']) === 'png';
$webp = strtolower($p['extension']) === 'webp';
$thumb_old = sprintf(
($alpha ? $this->thumb_tp_alpha :
($webp ? $this->thumb_tp_webp :
$this->thumb_tp)),
$p['dirname'],
$p['base'],
'%s'
);
$p = path::info($newFile->relname);
$alpha = strtolower($p['extension']) === 'png';
$webp = strtolower($p['extension']) === 'webp';
$thumb_new = sprintf(
($alpha ? $this->thumb_tp_alpha :
($webp ? $this->thumb_tp_webp :
$this->thumb_tp)),
$p['dirname'],
$p['base'],
'%s'
);
foreach ($this->thumb_sizes as $suffix => $s) {
try {
parent::moveFile(sprintf($thumb_old, $suffix), sprintf($thumb_new, $suffix));
} catch (Exception $e) {
}
}
}
}
/**
* Remove image thumbnails
*
* @param string $f Image filename
*/
public function imageThumbRemove($f)
{
$p = path::info($f);
$alpha = strtolower($p['extension']) === 'png';
$webp = strtolower($p['extension']) === 'webp';
$thumb = sprintf(
($alpha ? $this->thumb_tp_alpha :
($webp ? $this->thumb_tp_webp :
$this->thumb_tp)),
'',
$p['base'],
'%s'
);
foreach ($this->thumb_sizes as $suffix => $s) {
try {
parent::removeFile(sprintf($thumb, $suffix));
} catch (Exception $e) {
}
}
}
/**
* Create image meta
*
* @param cursor $cur The cursor
* @param string $f Image filename
* @param mixed $id The media identifier
*
* @return mixed
*/
protected function imageMetaCreate($cur, $f, $id)
{
$file = $this->pwd . '/' . $f;
if (!file_exists($file)) {
return false;
}
$xml = new xmlTag('meta');
$meta = imageMeta::readMeta($file);
$xml->insertNode($meta);
$c = $this->core->con->openCursor($this->table);
$c->media_meta = $xml->toXML();
if ($cur->media_title !== null && $cur->media_title == basename($cur->media_file)) {
if ($meta['Title']) {
$c->media_title = $meta['Title'];
}
}
if ($meta['DateTimeOriginal'] && $cur->media_dt === '') {
# We set picture time to user timezone
$media_ts = strtotime($meta['DateTimeOriginal']);
if ($media_ts !== false) {
$o = dt::getTimeOffset($this->core->auth->getInfo('user_tz'), $media_ts);
$c->media_dt = dt::str('%Y-%m-%d %H:%M:%S', $media_ts + $o);
}
}
# --BEHAVIOR-- coreBeforeImageMetaCreate
$this->core->callBehavior('coreBeforeImageMetaCreate', $c);
$sql = new dcUpdateStatement($this->core, 'dcMediaImageMetaCreate');
$sql->where('media_id = ' . $id);
$sql->update($c);
}
/**
* Returns HTML code for audio player (HTML5)
*
* @param string $type The audio mime type (not used)
* @param string $url The audio URL to play
* @param mixed $player The player URL (not used)
* @param mixed $args The player arguments (not used)
* @param bool $fallback The fallback (not more used)
* @param bool $preload Add preload="auto" attribute if true, else preload="none"
*
* @return string
*/
public static function audioPlayer($type, $url, $player = null, $args = null, $fallback = false, $preload = true)
{
return
'<audio controls preload="' . ($preload ? 'auto' : 'none') . '">' .
'<source src="' . $url . '">' .
'</audio>';
}
/**
* Returns HTML code for video player (HTML5)
*
* @param string $type The video mime type
* @param string $url The video URL to play
* @param mixed $player The player URL
* @param mixed $args The player arguments
* @param bool $fallback The fallback (not more used)
* @param bool $preload Add preload="auto" attribute if true, else preload="none"
*
* @return string
*/
public static function videoPlayer($type, $url, $player = null, $args = null, $fallback = false, $preload = true)
{
$video = '';
if ($type != 'video/x-flv') {
// Cope with width and height, if given
$width = 400;
$height = 300;
if (is_array($args)) {
if (!empty($args['width'])) {
$width = (int) $args['width'];
}
if (!empty($args['height'])) {
$height = (int) $args['height'];
}
}
$video = '<video controls preload="' . ($preload ? 'auto' : 'none') . '"' .
($width ? ' width="' . $width . '"' : '') .
($height ? ' height="' . $height . '"' : '') . '>' .
'<source src="' . $url . '">' .
'</video>';
}
return $video;
}
/**
* Returns HTML code for MP3 player (HTML5)
*
* @param string $url The audio URL to play
* @param mixed $player The player URL
* @param mixed $args The player arguments
* @param bool $fallback The fallback (not more used)
* @param bool $preload Add preload="auto" attribute if true, else preload="none"
*
* @return string
*/
public static function mp3player($url, $player = null, $args = null, $fallback = false, $preload = true)
{
return self::audioPlayer('audio/mp3', $url, $player, $args, false, $preload);
}
/**
* Returns HTML code for FLV player
*
* @obsolete since 2.15
*
* @param string $url The url
* @param mixed $player The player
* @param mixed $args The arguments
*
* @return string
*/
public static function flvplayer($url, $player = null, $args = null)
{
return '';
}
}