Aaron van Geffen
e28fcd8b03
Removes the intermediate confirmation page, instead using JavaScript for confirmation. Fixes an XSS issue, in that the previous method was not passing or checking the session (!)
713 lines
17 KiB
PHP
713 lines
17 KiB
PHP
<?php
|
|
/*****************************************************************************
|
|
* Asset.php
|
|
* Contains key class Asset.
|
|
*
|
|
* Kabuki CMS (C) 2013-2015, Aaron van Geffen
|
|
*****************************************************************************/
|
|
|
|
class Asset
|
|
{
|
|
protected $id_asset;
|
|
protected $id_user_uploaded;
|
|
protected $subdir;
|
|
protected $filename;
|
|
protected $title;
|
|
protected $slug;
|
|
protected $mimetype;
|
|
protected $image_width;
|
|
protected $image_height;
|
|
protected $date_captured;
|
|
protected $priority;
|
|
|
|
protected $meta;
|
|
protected $tags;
|
|
protected $thumbnails;
|
|
|
|
protected function __construct(array $data)
|
|
{
|
|
foreach ($data as $attribute => $value)
|
|
{
|
|
if (property_exists($this, $attribute))
|
|
$this->$attribute = $value;
|
|
}
|
|
|
|
if (!empty($data['date_captured']) && $data['date_captured'] !== 'NULL')
|
|
$this->date_captured = new DateTime($data['date_captured']);
|
|
}
|
|
|
|
public static function fromId($id_asset, $return_format = 'object')
|
|
{
|
|
$row = Registry::get('db')->queryAssoc('
|
|
SELECT *
|
|
FROM assets
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $id_asset,
|
|
]);
|
|
|
|
return empty($row) ? false : self::byRow($row, $return_format);
|
|
}
|
|
|
|
public static function fromSlug($slug, $return_format = 'object')
|
|
{
|
|
$row = Registry::get('db')->queryAssoc('
|
|
SELECT *
|
|
FROM assets
|
|
WHERE slug = {string:slug}',
|
|
[
|
|
'slug' => $slug,
|
|
]);
|
|
|
|
return empty($row) ? false : self::byRow($row, $return_format);
|
|
}
|
|
|
|
public static function byRow(array $row, $return_format = 'object')
|
|
{
|
|
$db = Registry::get('db');
|
|
|
|
// Supplement with metadata.
|
|
$row['meta'] = $db->queryPair('
|
|
SELECT variable, value
|
|
FROM assets_meta
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $row['id_asset'],
|
|
]);
|
|
|
|
// And thumbnails.
|
|
$row['thumbnails'] = $db->queryPair('
|
|
SELECT
|
|
CONCAT(
|
|
width,
|
|
{string:x},
|
|
height,
|
|
IF(mode != {string:empty}, CONCAT({string:_}, mode), {string:empty})
|
|
) AS selector, filename
|
|
FROM assets_thumbs
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $row['id_asset'],
|
|
'empty' => '',
|
|
'x' => 'x',
|
|
'_' => '_',
|
|
]);
|
|
|
|
return $return_format == 'object' ? new Asset($row) : $row;
|
|
}
|
|
|
|
public static function fromIds(array $id_assets, $return_format = 'array')
|
|
{
|
|
if (empty($id_assets))
|
|
return [];
|
|
|
|
$db = Registry::get('db');
|
|
|
|
$res = $db->query('
|
|
SELECT *
|
|
FROM assets
|
|
WHERE id_asset IN ({array_int:id_assets})
|
|
ORDER BY id_asset',
|
|
[
|
|
'id_assets' => $id_assets,
|
|
]);
|
|
|
|
$assets = [];
|
|
while ($asset = $db->fetch_assoc($res))
|
|
{
|
|
$assets[$asset['id_asset']] = $asset;
|
|
$assets[$asset['id_asset']]['meta'] = [];
|
|
$assets[$asset['id_asset']]['thumbnails'] = [];
|
|
}
|
|
|
|
$metas = $db->queryRows('
|
|
SELECT id_asset, variable, value
|
|
FROM assets_meta
|
|
WHERE id_asset IN ({array_int:id_assets})
|
|
ORDER BY id_asset',
|
|
[
|
|
'id_assets' => $id_assets,
|
|
]);
|
|
|
|
foreach ($metas as $meta)
|
|
$assets[$meta[0]]['meta'][$meta[1]] = $meta[2];
|
|
|
|
$thumbnails = $db->queryRows('
|
|
SELECT id_asset,
|
|
CONCAT(
|
|
width,
|
|
{string:x},
|
|
height,
|
|
IF(mode != {string:empty}, CONCAT({string:_}, mode), {string:empty})
|
|
) AS selector, filename
|
|
FROM assets_thumbs
|
|
WHERE id_asset IN ({array_int:id_assets})
|
|
ORDER BY id_asset',
|
|
[
|
|
'id_assets' => $id_assets,
|
|
'empty' => '',
|
|
'x' => 'x',
|
|
'_' => '_',
|
|
]);
|
|
|
|
foreach ($thumbnails as $thumb)
|
|
$assets[$thumb[0]]['thumbnails'][$thumb[1]] = $thumb[2];
|
|
|
|
if ($return_format == 'array')
|
|
return $assets;
|
|
else
|
|
{
|
|
$objects = [];
|
|
foreach ($assets as $id => $asset)
|
|
$objects[$id] = new Asset($asset);
|
|
return $objects;
|
|
}
|
|
}
|
|
|
|
public static function createNew(array $data, $return_format = 'object')
|
|
{
|
|
// Extract the data array.
|
|
extract($data);
|
|
|
|
// No filename? Abort!
|
|
if (!isset($filename_to_copy) || !is_file($filename_to_copy))
|
|
return false;
|
|
|
|
// No subdir? Use YYYY/MM
|
|
if (!isset($preferred_subdir))
|
|
$preferred_subdir = date('Y') . '/' . date('m');
|
|
|
|
// Does this dir exist yet? If not, create it.
|
|
if (!is_dir(ASSETSDIR . '/' . $preferred_subdir))
|
|
mkdir(ASSETSDIR . '/' . $preferred_subdir, 0755, true);
|
|
|
|
// Construct the destination filename. Make sure we don't accidentally overwrite anything.
|
|
if (!isset($preferred_filename))
|
|
$preferred_filename = basename($filename_to_copy);
|
|
|
|
$new_filename = $preferred_filename;
|
|
$destination = ASSETSDIR . '/' . $preferred_subdir . '/' . $preferred_filename;
|
|
for ($i = 1; file_exists($destination); $i++)
|
|
{
|
|
$suffix = $i;
|
|
$filename = pathinfo($preferred_filename, PATHINFO_FILENAME) . ' (' . $suffix . ')';
|
|
$extension = pathinfo($preferred_filename, PATHINFO_EXTENSION);
|
|
$new_filename = $filename . '.' . $extension;
|
|
$destination = dirname($destination) . '/' . $new_filename;
|
|
}
|
|
|
|
// Can we write to the target directory? Then copy the file.
|
|
if (is_writable(ASSETSDIR . '/' . $preferred_subdir))
|
|
copy($filename_to_copy, $destination);
|
|
else
|
|
return false;
|
|
|
|
// Figure out the mime type for the file.
|
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
|
$mimetype = finfo_file($finfo, $destination);
|
|
finfo_close($finfo);
|
|
|
|
// We're going to need the base name a few times...
|
|
$basename = pathinfo($new_filename, PATHINFO_FILENAME);
|
|
|
|
// Do we have a title yet? Otherwise, use the filename.
|
|
$title = $data['title'] ?? $basename;
|
|
|
|
// Same with the slug.
|
|
$slug = $data['slug'] ?? sprintf('%s/%s', $preferred_subdir, $basename);
|
|
|
|
// Detected an image?
|
|
if (substr($mimetype, 0, 5) == 'image')
|
|
{
|
|
$image = new Imagick($destination);
|
|
$d = $image->getImageGeometry();
|
|
|
|
// Get image dimensions, bearing orientation in mind.
|
|
switch ($image->getImageOrientation())
|
|
{
|
|
case Imagick::ORIENTATION_LEFTBOTTOM:
|
|
case Imagick::ORIENTATION_RIGHTTOP:
|
|
$image_width = $d['height'];
|
|
$image_height = $d['width'];
|
|
break;
|
|
|
|
default:
|
|
$image_width = $d['width'];
|
|
$image_height = $d['height'];
|
|
}
|
|
|
|
unset($image);
|
|
|
|
$exif = EXIF::fromFile($destination);
|
|
$date_captured = $exif->created_timestamp > 0 ? $exif->created_timestamp : time();
|
|
}
|
|
|
|
$db = Registry::get('db');
|
|
$res = $db->query('
|
|
INSERT INTO assets
|
|
(id_user_uploaded, subdir, filename, title, slug, mimetype, image_width, image_height, date_captured, priority)
|
|
VALUES
|
|
({int:id_user_uploaded}, {string:subdir}, {string:filename}, {string:title}, {string:slug}, {string:mimetype},
|
|
{int:image_width}, {int:image_height},
|
|
IF({int:date_captured} > 0, FROM_UNIXTIME({int:date_captured}), NULL),
|
|
{int:priority})',
|
|
[
|
|
'id_user_uploaded' => isset($id_user) ? $id_user : Registry::get('user')->getUserId(),
|
|
'subdir' => $preferred_subdir,
|
|
'filename' => $new_filename,
|
|
'title' => $title,
|
|
'slug' => $slug,
|
|
'mimetype' => $mimetype,
|
|
'image_width' => isset($image_width) ? $image_width : 'NULL',
|
|
'image_height' => isset($image_height) ? $image_height : 'NULL',
|
|
'date_captured' => isset($date_captured) ? $date_captured : 'NULL',
|
|
'priority' => isset($priority) ? (int) $priority : 0,
|
|
]);
|
|
|
|
if (!$res)
|
|
{
|
|
unlink($destination);
|
|
return false;
|
|
}
|
|
|
|
$data['id_asset'] = $db->insert_id();
|
|
return $return_format === 'object' ? new self($data) : $data;
|
|
}
|
|
|
|
public function getId()
|
|
{
|
|
return $this->id_asset;
|
|
}
|
|
|
|
public function getAuthor()
|
|
{
|
|
return Member::fromId($this->id_user_uploaded);
|
|
}
|
|
|
|
public function getDateCaptured()
|
|
{
|
|
return $this->date_captured;
|
|
}
|
|
|
|
public function getDeleteUrl()
|
|
{
|
|
return BASEURL . '/editasset/?id=' . $this->id_asset . '&delete';
|
|
}
|
|
|
|
public function getEditUrl()
|
|
{
|
|
return BASEURL . '/editasset/?id=' . $this->id_asset;
|
|
}
|
|
|
|
public function getFilename()
|
|
{
|
|
return $this->filename;
|
|
}
|
|
|
|
public function getLinkedPosts()
|
|
{
|
|
$posts = Registry::get('db')->queryValues('
|
|
SELECT id_post
|
|
FROM posts_assets
|
|
WHERE id_asset = {int:id_asset}',
|
|
['id_asset' => $this->id_asset]);
|
|
|
|
// TODO: fix empty post iterator.
|
|
if (empty($posts))
|
|
return [];
|
|
|
|
return PostIterator::getByOptions([
|
|
'ids' => $posts,
|
|
'type' => '',
|
|
]);
|
|
}
|
|
|
|
public function getMeta()
|
|
{
|
|
return $this->meta;
|
|
}
|
|
|
|
public function getFullPath()
|
|
{
|
|
return ASSETSDIR . '/' . $this->subdir . '/' . $this->filename;
|
|
}
|
|
|
|
public function getSlug()
|
|
{
|
|
return $this->slug;
|
|
}
|
|
|
|
public function getSubdir()
|
|
{
|
|
return $this->subdir;
|
|
}
|
|
|
|
public function getPriority()
|
|
{
|
|
return $this->priority;
|
|
}
|
|
|
|
public function getTags()
|
|
{
|
|
if (!isset($this->tags))
|
|
$this->tags = Tag::byAssetId($this->id_asset);
|
|
|
|
return $this->tags;
|
|
}
|
|
|
|
public function getTitle()
|
|
{
|
|
return $this->title;
|
|
}
|
|
|
|
public function getUrl()
|
|
{
|
|
return BASEURL . '/assets/' . $this->subdir . '/' . $this->filename;
|
|
}
|
|
|
|
public function getPageUrl()
|
|
{
|
|
return BASEURL . '/' . $this->slug . '/';
|
|
}
|
|
|
|
public function getType()
|
|
{
|
|
return substr($this->mimetype, 0, strpos($this->mimetype, '/'));
|
|
}
|
|
|
|
public function getDimensions($as_type = 'string')
|
|
{
|
|
return $as_type === 'string' ? $this->image_width . 'x' . $this->image_height : [$this->image_width, $this->image_height];
|
|
}
|
|
|
|
public function isImage()
|
|
{
|
|
return isset($this->mimetype) && substr($this->mimetype, 0, 5) === 'image';
|
|
}
|
|
|
|
public function getImage()
|
|
{
|
|
if (!$this->isImage())
|
|
throw new Exception('Trying to upgrade an Asset to an Image while the Asset is not an image!');
|
|
|
|
return new Image(get_object_vars($this));
|
|
}
|
|
|
|
public function isOwnedBy(User $user)
|
|
{
|
|
return $this->id_user_uploaded == $user->getUserId();
|
|
}
|
|
|
|
public function replaceFile($filename)
|
|
{
|
|
// No filename? Abort!
|
|
if (!isset($filename) || !is_readable($filename))
|
|
return false;
|
|
|
|
// Can we write to the target file?
|
|
$destination = ASSETSDIR . '/' . $this->subdir . '/' . $this->filename;
|
|
if (!is_writable($destination))
|
|
return false;
|
|
|
|
copy($filename, $destination);
|
|
|
|
// Figure out the mime type for the file.
|
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
|
$this->mimetype = finfo_file($finfo, $destination);
|
|
finfo_close($finfo);
|
|
|
|
// Detected an image?
|
|
if (substr($this->mimetype, 0, 5) === 'image')
|
|
{
|
|
$image = new Imagick($destination);
|
|
$d = $image->getImageGeometry();
|
|
$this->image_width = $d['width'];
|
|
$this->image_height = $d['height'];
|
|
unset($image);
|
|
|
|
$exif = EXIF::fromFile($destination);
|
|
if (!empty($exif->created_timestamp))
|
|
$this->date_captured = new DateTime(date('r', $exif->created_timestamp));
|
|
else
|
|
$this->date_captured = null;
|
|
}
|
|
else
|
|
{
|
|
$this->image_width = null;
|
|
$this->image_height = null;
|
|
$this->date_captured = null;
|
|
}
|
|
|
|
return Registry::get('db')->query('
|
|
UPDATE assets
|
|
SET
|
|
mimetype = {string:mimetype},
|
|
image_width = {int:image_width},
|
|
image_height = {int:image_height},
|
|
date_captured = {datetime:date_captured},
|
|
priority = {int:priority}
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
'mimetype' => $this->mimetype,
|
|
'image_width' => isset($this->image_width) ? $this->image_width : 'NULL',
|
|
'image_height' => isset($this->image_height) ? $this->image_height : 'NULL',
|
|
'date_captured' => isset($this->date_captured) ? $this->date_captured : 'NULL',
|
|
'priority' => $this->priority,
|
|
]);
|
|
}
|
|
|
|
protected function saveMetaData()
|
|
{
|
|
$this->setMetaData($this->meta);
|
|
}
|
|
|
|
public function setMetaData(array $new_meta, $mode = 'replace')
|
|
{
|
|
$db = Registry::get('db');
|
|
|
|
// If we're replacing, delete current data first.
|
|
if ($mode === 'replace')
|
|
{
|
|
$to_remove = array_diff_key($this->meta, $new_meta);
|
|
if (!empty($to_remove))
|
|
$db->query('
|
|
DELETE FROM assets_meta
|
|
WHERE id_asset = {int:id_asset} AND
|
|
variable IN({array_string:variables})',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
'variables' => array_keys($to_remove),
|
|
]);
|
|
}
|
|
|
|
// Build rows
|
|
$to_insert = [];
|
|
foreach ($new_meta as $key => $value)
|
|
$to_insert[] = [
|
|
'id_asset' => $this->id_asset,
|
|
'variable' => $key,
|
|
'value' => $value,
|
|
];
|
|
|
|
// Do the insertion
|
|
$res = Registry::get('db')->insert('replace', 'assets_meta', [
|
|
'id_asset' => 'int',
|
|
'variable' => 'string',
|
|
'value' => 'string',
|
|
], $to_insert, ['id_asset', 'variable']);
|
|
|
|
if ($res)
|
|
$this->meta = $new_meta;
|
|
}
|
|
|
|
public function delete()
|
|
{
|
|
$db = Registry::get('db');
|
|
|
|
// First: delete associated metadata
|
|
$db->query('
|
|
DELETE FROM assets_meta
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
]);
|
|
|
|
// Second: figure out what tags to recount cardinality for
|
|
$recount_tags = $db->queryValues('
|
|
SELECT id_tag
|
|
FROM assets_tags
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
]);
|
|
|
|
$db->query('
|
|
DELETE FROM assets_tags
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
]);
|
|
|
|
Tag::recount($recount_tags);
|
|
|
|
// Third: figure out what associated thumbs to delete
|
|
$thumbs_to_delete = $db->queryValues('
|
|
SELECT filename
|
|
FROM assets_thumbs
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
]);
|
|
|
|
foreach ($thumbs_to_delete as $filename)
|
|
{
|
|
$thumb_path = THUMBSDIR . '/' . $this->subdir . '/' . $filename;
|
|
if (is_file($thumb_path))
|
|
unlink($thumb_path);
|
|
}
|
|
|
|
$db->query('
|
|
DELETE FROM assets_thumbs
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
]);
|
|
|
|
// Reset asset ID for tags that use this asset for their thumbnail
|
|
$rows = $db->query('
|
|
SELECT id_tag
|
|
FROM tags
|
|
WHERE id_asset_thumb = {int:id_asset}',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
]);
|
|
|
|
if (!empty($rows))
|
|
{
|
|
foreach ($rows as $row)
|
|
{
|
|
$tag = Tag::fromId($row['id_tag']);
|
|
$tag->resetIdAsset();
|
|
}
|
|
}
|
|
|
|
// Finally, delete the actual asset
|
|
if (!unlink(ASSETSDIR . '/' . $this->subdir . '/' . $this->filename))
|
|
return false;
|
|
|
|
$return = $db->query('
|
|
DELETE FROM assets
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
]);
|
|
|
|
return $return;
|
|
}
|
|
|
|
public function linkTags(array $id_tags)
|
|
{
|
|
if (empty($id_tags))
|
|
return true;
|
|
|
|
$pairs = [];
|
|
foreach ($id_tags as $id_tag)
|
|
$pairs[] = ['id_asset' => $this->id_asset, 'id_tag' => $id_tag];
|
|
|
|
Registry::get('db')->insert('ignore', 'assets_tags', [
|
|
'id_asset' => 'int',
|
|
'id_tag' => 'int',
|
|
], $pairs, ['id_asset', 'id_tag']);
|
|
|
|
Tag::recount($id_tags);
|
|
}
|
|
|
|
public function unlinkTags(array $id_tags)
|
|
{
|
|
if (empty($id_tags))
|
|
return true;
|
|
|
|
Registry::get('db')->query('
|
|
DELETE FROM assets_tags
|
|
WHERE id_asset = {int:id_asset} AND id_tag IN ({array_int:id_tags})',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
'id_tags' => $id_tags,
|
|
]);
|
|
|
|
Tag::recount($id_tags);
|
|
}
|
|
|
|
public static function getCount()
|
|
{
|
|
return Registry::get('db')->queryValue('
|
|
SELECT COUNT(*)
|
|
FROM assets');
|
|
}
|
|
|
|
public function setKeyData($title, $slug, DateTime $date_captured = null, $priority)
|
|
{
|
|
$params = [
|
|
'id_asset' => $this->id_asset,
|
|
'title' => $title,
|
|
'slug' => $slug,
|
|
'priority' => $priority,
|
|
];
|
|
|
|
if (isset($date_captured))
|
|
$params['date_captured'] = $date_captured->format('Y-m-d H:i:s');
|
|
|
|
return Registry::get('db')->query('
|
|
UPDATE assets
|
|
SET title = {string:title},
|
|
slug = {string:slug},' . (isset($date_captured) ? '
|
|
date_captured = {datetime:date_captured},' : '') . '
|
|
priority = {int:priority}
|
|
WHERE id_asset = {int:id_asset}',
|
|
$params);
|
|
}
|
|
|
|
public function getUrlForPreviousInSet($id_tag = null)
|
|
{
|
|
$row = Registry::get('db')->queryAssoc('
|
|
SELECT a.*
|
|
' . (isset($id_tag) ? '
|
|
FROM assets_tags AS t
|
|
INNER JOIN assets AS a ON a.id_asset = t.id_asset
|
|
WHERE t.id_tag = {int:id_tag} AND
|
|
(a.date_captured, a.id_asset) < ({datetime:date_captured}, {int:id_asset})
|
|
ORDER BY a.date_captured DESC, a.id_asset DESC'
|
|
: '
|
|
FROM assets AS a
|
|
WHERE (a.date_captured, a.id_asset) > ({datetime:date_captured}, {int:id_asset})
|
|
ORDER BY date_captured ASC, a.id_asset ASC')
|
|
. '
|
|
LIMIT 1',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
'id_tag' => $id_tag,
|
|
'date_captured' => $this->date_captured,
|
|
]);
|
|
|
|
if ($row)
|
|
{
|
|
$obj = self::byRow($row, 'object');
|
|
return $obj->getPageUrl() . ($id_tag ? '?in=' . $id_tag : '');
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
public function getUrlForNextInSet($id_tag = null)
|
|
{
|
|
$row = Registry::get('db')->queryAssoc('
|
|
SELECT a.*
|
|
' . (isset($id_tag) ? '
|
|
FROM assets_tags AS t
|
|
INNER JOIN assets AS a ON a.id_asset = t.id_asset
|
|
WHERE t.id_tag = {int:id_tag} AND
|
|
(a.date_captured, a.id_asset) > ({datetime:date_captured}, {int:id_asset})
|
|
ORDER BY a.date_captured ASC, a.id_asset ASC'
|
|
: '
|
|
FROM assets AS a
|
|
WHERE (a.date_captured, a.id_asset) < ({datetime:date_captured}, {int:id_asset})
|
|
ORDER BY date_captured DESC, a.id_asset DESC')
|
|
. '
|
|
LIMIT 1',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
'id_tag' => $id_tag,
|
|
'date_captured' => $this->date_captured,
|
|
]);
|
|
|
|
if ($row)
|
|
{
|
|
$obj = self::byRow($row, 'object');
|
|
return $obj->getPageUrl() . ($id_tag ? '?in=' . $id_tag : '');
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
}
|