forked from Public/pics
		
	
		
			
				
	
	
		
			772 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			772 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/*****************************************************************************
 | 
						|
 * Asset.php
 | 
						|
 * Contains key class Asset.
 | 
						|
 *
 | 
						|
 * Kabuki CMS (C) 2013-2015, Aaron van Geffen
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
class Asset
 | 
						|
{
 | 
						|
	public $id_asset;
 | 
						|
	public $id_user_uploaded;
 | 
						|
	public $subdir;
 | 
						|
	public $filename;
 | 
						|
	public $title;
 | 
						|
	public $slug;
 | 
						|
	public $mimetype;
 | 
						|
	public $image_width;
 | 
						|
	public $image_height;
 | 
						|
	public $date_captured;
 | 
						|
	public $priority;
 | 
						|
 | 
						|
	protected $meta;
 | 
						|
	protected $tags;
 | 
						|
	protected $thumbnails;
 | 
						|
 | 
						|
	public function __construct(array $data)
 | 
						|
	{
 | 
						|
		foreach ($data as $attribute => $value)
 | 
						|
		{
 | 
						|
			if (property_exists($this, $attribute))
 | 
						|
				$this->$attribute = $value;
 | 
						|
		}
 | 
						|
 | 
						|
		if (isset($data['date_captured']) && $data['date_captured'] !== null && !is_object($data['date_captured']))
 | 
						|
			$this->date_captured = new DateTime($data['date_captured']);
 | 
						|
	}
 | 
						|
 | 
						|
	public function canBeEditedBy(User $user)
 | 
						|
	{
 | 
						|
		return $this->isOwnedBy($user) || $user->isAdmin();
 | 
						|
	}
 | 
						|
 | 
						|
	public static function cleanSlug($slug)
 | 
						|
	{
 | 
						|
		// Only alphanumerical chars, underscores and forward slashes are allowed
 | 
						|
		if (!preg_match_all('~([A-z0-9\/_]+)~', $slug, $allowedTokens, PREG_PATTERN_ORDER))
 | 
						|
			throw new UnexpectedValueException('Slug does not make sense.');
 | 
						|
 | 
						|
		// Join valid substrings together with hyphens
 | 
						|
		return implode('-', $allowedTokens[1]);
 | 
						|
	}
 | 
						|
 | 
						|
	public static function fromId($id_asset, $return_format = 'object')
 | 
						|
	{
 | 
						|
		$row = Registry::get('db')->queryAssoc('
 | 
						|
			SELECT *
 | 
						|
			FROM assets
 | 
						|
			WHERE id_asset = :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 = :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 = :id_asset',
 | 
						|
			[
 | 
						|
				'id_asset' => $row['id_asset'],
 | 
						|
			]);
 | 
						|
 | 
						|
		// And thumbnails.
 | 
						|
		$row['thumbnails'] = $db->queryPair('
 | 
						|
			SELECT
 | 
						|
				CONCAT(
 | 
						|
					width, :x, height,
 | 
						|
					IF(mode != :empty1, CONCAT(:_, mode), :empty2)
 | 
						|
				) AS selector, filename
 | 
						|
			FROM assets_thumbs
 | 
						|
			WHERE id_asset = :id_asset',
 | 
						|
			[
 | 
						|
				'id_asset' => $row['id_asset'],
 | 
						|
				'empty1' => '',
 | 
						|
				'empty2' => '',
 | 
						|
				'x' => 'x',
 | 
						|
				'_' => '_',
 | 
						|
			]);
 | 
						|
 | 
						|
		return $return_format === 'object' ? new static($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 (@id_assets)
 | 
						|
			ORDER BY id_asset',
 | 
						|
			[
 | 
						|
				'id_assets' => $id_assets,
 | 
						|
			]);
 | 
						|
 | 
						|
		$assets = [];
 | 
						|
		while ($asset = $db->fetchAssoc($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 (@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, :x, height,
 | 
						|
					IF(mode != :empty1, CONCAT(:_, mode), :empty2)
 | 
						|
				) AS selector, filename
 | 
						|
			FROM assets_thumbs
 | 
						|
			WHERE id_asset IN (@id_assets)
 | 
						|
			ORDER BY id_asset',
 | 
						|
			[
 | 
						|
				'id_assets' => $id_assets,
 | 
						|
				'empty1' => '',
 | 
						|
				'empty2' => '',
 | 
						|
				'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'] ?? self::cleanSlug(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
 | 
						|
			(:id_user_uploaded, :subdir, :filename, :title, :slug, :mimetype,
 | 
						|
			 :image_width, :image_height,
 | 
						|
			 ' . (!empty($date_captured) ? 'FROM_UNIXTIME(:date_captured)' : 'NULL') . ',
 | 
						|
			 :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->insertId();
 | 
						|
		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 = :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 moveToSubDir($destSubDir)
 | 
						|
	{
 | 
						|
		// Verify the original exists
 | 
						|
		$source = ASSETSDIR . '/' . $this->subdir . '/' . $this->filename;
 | 
						|
		if (!file_exists($source))
 | 
						|
			return -1;
 | 
						|
 | 
						|
		// Ensure the intended target file doesn't exist yet
 | 
						|
		$destDir = ASSETSDIR . '/' . $destSubDir;
 | 
						|
		$destFile = $destDir . '/' . $this->filename;
 | 
						|
 | 
						|
		if (file_exists($destFile))
 | 
						|
			return -2;
 | 
						|
 | 
						|
		// Can we write to the target directory?
 | 
						|
		if (!is_writable($destDir))
 | 
						|
			return -3;
 | 
						|
 | 
						|
		// Perform move
 | 
						|
		if (rename($source, $destFile))
 | 
						|
		{
 | 
						|
			$this->subdir = $destSubDir;
 | 
						|
			$this->slug = $this->subdir . '/' . $this->title;
 | 
						|
			Registry::get('db')->query('
 | 
						|
				UPDATE assets
 | 
						|
				SET subdir = {string:subdir},
 | 
						|
					slug = {string:slug}
 | 
						|
				WHERE id_asset = {int:id_asset}',
 | 
						|
				[
 | 
						|
					'id_asset' => $this->id_asset,
 | 
						|
					'subdir' => $this->subdir,
 | 
						|
					'slug' => $this->slug,
 | 
						|
				]);
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
 | 
						|
		return -4;
 | 
						|
	}
 | 
						|
 | 
						|
	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 = :mimetype,
 | 
						|
				image_width = :image_width,
 | 
						|
				image_height = :image_height,
 | 
						|
				date_captured = :date_captured,
 | 
						|
				priority = :priority
 | 
						|
			WHERE id_asset = :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 = :id_asset AND
 | 
						|
						variable IN(@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');
 | 
						|
 | 
						|
		// Delete any and all thumbnails, if this is an image.
 | 
						|
		if ($this->isImage())
 | 
						|
		{
 | 
						|
			$image = $this->getImage();
 | 
						|
			$image->removeAllThumbnails();
 | 
						|
		}
 | 
						|
 | 
						|
		// Delete all meta info for this asset.
 | 
						|
		$db->query('
 | 
						|
			DELETE FROM assets_meta
 | 
						|
			WHERE id_asset = :id_asset',
 | 
						|
			['id_asset' => $this->id_asset]);
 | 
						|
 | 
						|
		// Figure out what tags to recount cardinality for
 | 
						|
		$recount_tags = $db->queryValues('
 | 
						|
			SELECT id_tag
 | 
						|
			FROM assets_tags
 | 
						|
			WHERE id_asset = :id_asset',
 | 
						|
			['id_asset' => $this->id_asset]);
 | 
						|
 | 
						|
		// Delete asset association for these tags
 | 
						|
		$db->query('
 | 
						|
			DELETE FROM assets_tags
 | 
						|
			WHERE id_asset = :id_asset',
 | 
						|
			['id_asset' => $this->id_asset]);
 | 
						|
 | 
						|
		Tag::recount($recount_tags);
 | 
						|
 | 
						|
		// Reset asset ID for tags that use this asset for their thumbnail
 | 
						|
		$rows = $db->queryValues('
 | 
						|
			SELECT id_tag
 | 
						|
			FROM tags
 | 
						|
			WHERE id_asset_thumb = :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 = :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 = :id_asset AND id_tag IN (@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 static function getOffset($offset, $limit, $order, $direction)
 | 
						|
	{
 | 
						|
		$order = $order . ($direction == 'up' ? ' ASC' : ' DESC');
 | 
						|
 | 
						|
		return Registry::get('db')->queryAssocs('
 | 
						|
			SELECT a.id_asset, a.subdir, a.filename,
 | 
						|
				a.image_width, a.image_height, a.mimetype,
 | 
						|
				u.id_user, u.first_name, u.surname
 | 
						|
			FROM assets AS a
 | 
						|
			LEFT JOIN users AS u ON a.id_user_uploaded = u.id_user
 | 
						|
			ORDER BY ' . $order . '
 | 
						|
			LIMIT :offset, :limit',
 | 
						|
			[
 | 
						|
				'offset' => $offset,
 | 
						|
				'limit' => $limit,
 | 
						|
			]);
 | 
						|
	}
 | 
						|
 | 
						|
	public function save()
 | 
						|
	{
 | 
						|
		if (empty($this->id_asset))
 | 
						|
			throw new UnexpectedValueException();
 | 
						|
 | 
						|
		return Registry::get('db')->query('
 | 
						|
			UPDATE assets
 | 
						|
			SET subdir = :subdir,
 | 
						|
				filename = :filename,
 | 
						|
				title = :title,
 | 
						|
				slug = :slug,
 | 
						|
				mimetype = :mimetype,
 | 
						|
				image_width = :image_width,
 | 
						|
				image_height = :image_height,
 | 
						|
				date_captured = :date_captured,
 | 
						|
				priority = :priority
 | 
						|
			WHERE id_asset = :id_asset',
 | 
						|
			get_object_vars($this));
 | 
						|
	}
 | 
						|
 | 
						|
	protected function getUrlForAdjacentInSet($prevNext, ?Tag $tag, $activeFilter)
 | 
						|
	{
 | 
						|
		$next = $prevNext === 'next';
 | 
						|
		$previous = !$next;
 | 
						|
 | 
						|
		$where = [];
 | 
						|
		$params = [
 | 
						|
			'id_asset' => $this->id_asset,
 | 
						|
			'date_captured' => $this->date_captured,
 | 
						|
		];
 | 
						|
 | 
						|
		// Direction depends on whether we're browsing a tag or timeline
 | 
						|
		if (isset($tag))
 | 
						|
		{
 | 
						|
			$where[] = 't.id_tag = :id_tag';
 | 
						|
			$params['id_tag'] = $tag->id_tag;
 | 
						|
			$where_op = $previous ? '<' : '>';
 | 
						|
			$order_dir = $previous ? 'DESC' : 'ASC';
 | 
						|
		}
 | 
						|
		else
 | 
						|
		{
 | 
						|
			$where_op = $previous ? '>' : '<';
 | 
						|
			$order_dir = $previous ? 'ASC' : 'DESC';
 | 
						|
		}
 | 
						|
 | 
						|
		// Take active filter into account as well
 | 
						|
		if (!empty($activeFilter) && ($user = Member::fromSlug($activeFilter)) !== false)
 | 
						|
		{
 | 
						|
			$where[] = 'id_user_uploaded = :id_user_uploaded';
 | 
						|
			$params['id_user_uploaded'] = $user->getUserId();
 | 
						|
		}
 | 
						|
 | 
						|
		// Use complete ordering when sorting the set
 | 
						|
		$where[] = '(a.date_captured, a.id_asset) ' . $where_op .
 | 
						|
			' (:date_captured, :id_asset)';
 | 
						|
 | 
						|
		// Stringify conditions together
 | 
						|
		$where = '(' . implode(') AND (', $where) . ')';
 | 
						|
 | 
						|
		// Run query, leaving out tags table if not required
 | 
						|
		$row = Registry::get('db')->queryAssoc('
 | 
						|
			SELECT a.*
 | 
						|
			FROM assets AS a
 | 
						|
			' . (isset($tag) ? '
 | 
						|
			INNER JOIN assets_tags AS t ON a.id_asset = t.id_asset' : '') . '
 | 
						|
			WHERE ' . $where . '
 | 
						|
			ORDER BY a.date_captured ' . $order_dir . ', a.id_asset ' . $order_dir . '
 | 
						|
			LIMIT 1',
 | 
						|
			$params);
 | 
						|
 | 
						|
		if (!$row)
 | 
						|
			return false;
 | 
						|
 | 
						|
		$obj = self::byRow($row, 'object');
 | 
						|
 | 
						|
		$urlParams = [];
 | 
						|
		if (isset($tag))
 | 
						|
			$urlParams['in'] = $tag->id_tag;
 | 
						|
		if (!empty($activeFilter))
 | 
						|
			$urlParams['by'] = $activeFilter;
 | 
						|
 | 
						|
		$queryString = !empty($urlParams) ? '?' . http_build_query($urlParams) : '';
 | 
						|
 | 
						|
		return $obj->getPageUrl() . $queryString;
 | 
						|
	}
 | 
						|
 | 
						|
	public function getUrlForPreviousInSet(?Tag $tag, $activeFilter)
 | 
						|
	{
 | 
						|
		return $this->getUrlForAdjacentInSet('previous', $tag, $activeFilter);
 | 
						|
	}
 | 
						|
 | 
						|
	public function getUrlForNextInSet(?Tag $tag, $activeFilter)
 | 
						|
	{
 | 
						|
		return $this->getUrlForAdjacentInSet('next', $tag, $activeFilter);
 | 
						|
	}
 | 
						|
}
 |