pics/models/Asset.php
Dennis Brentjes 16ec547064 Changes the ConfirmDelete page and updates database code.
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.
2018-07-13 23:00:36 +02:00

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;
}
}