pics/models/Asset.php

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