Dennis Brentjes
16ec547064
The ConfirmDelete page now uses parts of the photopage. The Confirmation dialog is its own template which is based on Alert. The database now updates the album thumb to the most recent addition to the album, when the album thumb asset is being deleted. When there are no pictures left in the album 0 will be set.
672 lines
16 KiB
PHP
672 lines
16 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 $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)
|
|
$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;
|
|
while (file_exists($destination))
|
|
{
|
|
$filename = pathinfo($preferred_filename, PATHINFO_FILENAME) . '_' . mt_rand(10, 99);
|
|
$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);
|
|
|
|
// Do we have a title yet? Otherwise, use the filename.
|
|
$title = isset($data['title']) ? $data['title'] : pathinfo($preferred_filename, PATHINFO_FILENAME);
|
|
|
|
// Same with the slug.
|
|
$slug = isset($data['slug']) ? $data['slug'] : $preferred_subdir . '/' . pathinfo($preferred_filename, PATHINFO_FILENAME);
|
|
|
|
// 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 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 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 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');
|
|
|
|
if (!unlink(ASSETSDIR . '/' . $this->subdir . '/' . $this->filename))
|
|
return false;
|
|
|
|
$db->query('
|
|
DELETE FROM assets_meta
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
]);
|
|
|
|
$rows = $db->query('
|
|
SELECT id_tag
|
|
FROM assets_tags
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
]);
|
|
|
|
$recount_tags = [];
|
|
if(!empty($rows))
|
|
foreach($rows as $row)
|
|
$recount_tags[] = $row['id_tag'];
|
|
|
|
$db->query('
|
|
DELETE FROM assets_tags
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
]);
|
|
|
|
Tag::recount($recount_tags);
|
|
|
|
$return = $db->query('
|
|
DELETE FROM assets
|
|
WHERE id_asset = {int:id_asset}',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
]);
|
|
|
|
$rows = $db->query('
|
|
SELECT id_tag
|
|
FROM tags
|
|
WHERE id_asset_thumb = {int:id_asset} AND kind = "Album"',
|
|
[
|
|
'id_asset' => $this->id_asset,
|
|
]);
|
|
|
|
if (!empty($rows))
|
|
{
|
|
foreach ($rows as $row)
|
|
{
|
|
$tag = Tag::fromId($row['id_tag']);
|
|
$tag->resetIdAsset();
|
|
}
|
|
}
|
|
|
|
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 <= {datetime:date_captured} AND
|
|
a.id_asset != {int:id_asset}
|
|
ORDER BY a.date_captured DESC'
|
|
: '
|
|
FROM assets AS a
|
|
WHERE date_captured >= {datetime:date_captured} AND
|
|
a.id_asset != {int:id_asset}
|
|
ORDER BY date_captured 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 >= {datetime:date_captured} AND
|
|
a.id_asset != {int:id_asset}
|
|
ORDER BY a.date_captured ASC'
|
|
: '
|
|
FROM assets AS a
|
|
WHERE date_captured <= {datetime:date_captured} AND
|
|
a.id_asset != {int:id_asset}
|
|
ORDER BY date_captured 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;
|
|
}
|
|
}
|