Dirty hack for moregallery v1.5.6 adding [[+first.view_url]] and [[+last.view_url]] file_urls to simgleImage layouts
<?php
/**
* Class mgImage
*/
class mgImage extends xPDOSimpleObject
{
const MODE_UPLOAD = 'upload';
const MODE_IMPORT = 'import';
const MODE_VIDEO = 'video';
protected $checkedForThumb = false;
protected $iptcHeaderArray = array ( // thank you, stranger http://php.net/manual/en/function.iptcparse.php#113148
'2#005'=>'DocumentTitle',
'2#010'=>'Urgency',
'2#015'=>'Category',
'2#020'=>'Subcategories',
'2#025'=>'Keywords', //added
'2#040'=>'SpecialInstructions',
'2#055'=>'CreationDate',
'2#080'=>'AuthorByline',
'2#085'=>'AuthorTitle',
'2#090'=>'City',
'2#095'=>'State',
'2#101'=>'Country',
'2#103'=>'OTR',
'2#105'=>'Headline',
'2#110'=>'Source',
'2#115'=>'PhotoSource',
'2#116'=>'Copyright',
'2#120'=>'Caption',
'2#122'=>'CaptionWriter'
);
/**
* mgImage constructor.
* @param xPDO|modX $xpdo
*/
public function __construct(xPDO & $xpdo) {
parent::__construct($xpdo);
if (!isset($xpdo->moregallery)) {
$this->_loadMoreGalleryService();
}
}
public function get($k, $format = null, $formatTemplate= null)
{
$value = parent::get($k, $format, $formatTemplate);
switch ($k)
{
case 'width':
case 'height':
if ($value < 1) {
$resource = $this->getResource();
if ($resource && $resource->_getSource()) {
$relativeUrl = $resource->getSourceRelativeUrl();
$fileName = $this->get('file');
if (empty($fileName)) {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, '[moregallery] Image record ' . $this->get('id') . ' on resource ' . $this->get('resource') . ' does not have a file value. This may indicate a corrupt upload. Unable to calculate ' . $k . '.');
return 0;
}
$filePath = $resource->source->getBasePath().$relativeUrl. $fileName;
if (file_exists($filePath) && is_file($filePath)) {
/**
* Using the included Imagine lib we crop the image.
*/
try {
/** @var \Imagine\Image\ImagineInterface $imagine */
$imagine = $this->xpdo->moregallery->getImagine();
// Load the image with imagine and create a resized version
$img = $imagine->open($filePath);
$size = $img->getSize();
$width = $size->getWidth();
$height = $size->getHeight();
$this->set('width', $width);
$this->set('height', $height);
if (!$this->isNew()) {
$this->save();
}
$value = $$k;
} catch (Exception $e) {
$this->xpdo->log(modX::LOG_LEVEL_ERROR, '[moregallery] Exception ' . get_class($e) . ' fetching size for image record ' . $this->get('id') . ' from path ' . $filePath . ': ' . $e->getMessage());
}
}
}
}
break;
}
return $value;
}
public function getCropsAsArray()
{
$array = array();
$crops = $this->getCrops();
/** @var mgImageCrop $crop */
foreach ($crops as $key => $crop)
{
$array[$key] = $crop->toArray();
}
return $array;
}
/**
* Grabs (and creates) crops for an image.
*
* @return mgImageCrop[]
*/
public function getCrops()
{
/**
* Grab source and relative url info for creating thumbs if necessary
*/
$resource = $this->getResource();
$source = false;
$relativeUrl = '';
if ($resource && $source = $resource->_getSource()) {
$relativeUrl = $resource->getSourceRelativeUrl();
}
/**
* Grab the crops definitions so we can prepare a $crops array with the crop objects
*/
$cropDefinition = $this->xpdo->moregallery->getCrops($resource);
$crops = array();
foreach ($cropDefinition as $key => $options)
{
$crops[$key] = false;
}
/**
* Grab all crops for this image from the database, and loop over them to add them to $crops
* but also to get rid of any that isn't used (e.g. after changing the crops setting)
*
* @var array $existingCrops
*/
$existingCrops = $this->xpdo->getCollection('mgImageCrop', array('image' => $this->get('id')));
/** @var mgImageCrop $cropObject */
foreach ($existingCrops as $cropObject)
{
$cropKey = $cropObject->get('crop');
if (isset($crops[$cropKey]))
{
$url = $cropObject->getThumb($source, $relativeUrl);
$cropObject->set('thumbnail_url', $url);
$path = $cropObject->getThumb($source, $relativeUrl, true);
$cropObject->set('thumbnail_path', $path);
$crops[$cropKey] = $cropObject;
}
else
{
$cropObject->remove();
}
}
/**
* Loop over the collected crops to make sure all mgImageCrop objects have been loaded
* and create the ones that don't exist yet.
*/
foreach ($crops as $key => $crop)
{
if ($crop === false)
{
/** @var mgImageCrop $crop */
$crop = $this->xpdo->newObject('mgImageCrop');
$crop->fromArray(
array(
'image' => $this->get('id'),
'crop' => $key,
)
);
$url = $crop->getThumb($source, $relativeUrl);
$crop->set('thumbnail_url', $url);
$path = $crop->getThumb($source, $relativeUrl, true);
$crop->set('thumbnail_path', $path);
$crop->save();
$crops[$key] = $crop;
}
}
return $crops;
}
/**
* @param string $keyPrefix
* @param bool $rawValues
* @param bool $excludeLazy
* @param bool $includeRelated
*
* @return array
*/
public function toArray($keyPrefix= '', $rawValues= false, $excludeLazy= false, $includeRelated= false) {
$array = parent::toArray($keyPrefix, $rawValues, $excludeLazy, $includeRelated);
$resource = $this->getResource();
if (!$rawValues) {
if ($resource && $resource->_getSource()) {
// Check if we have a manager thumbnail, and regenerate it if necessary
if (!$this->checkedForThumb) {
$this->checkedForThumb = true;
$this->checkManagerThumb();
}
$thumb = $this->get('mgr_thumb');
$relativeUrl = $resource->getSourceRelativeUrl();
$thumbPath = $resource->source->getBasePath() . $relativeUrl . $thumb;
$array[$keyPrefix . 'mgr_thumb_path'] = $thumbPath;
$array[$keyPrefix . 'mgr_thumb'] = $resource->source->getObjectUrl($relativeUrl . $thumb);
$array[$keyPrefix . 'file_url'] = $resource->source->getObjectUrl($relativeUrl . $array[$keyPrefix . 'file']);
$array[$keyPrefix . 'file_path'] = $resource->source->getBasePath() . $relativeUrl . $array[$keyPrefix . 'file'];
$array[$keyPrefix . '_source_is_local'] = ($resource->source->get('class_key') === 'sources.modFileMediaSource');
$array[$keyPrefix . 'view_url'] = $this->xpdo->makeUrl($resource->get('id'), '', array(
$this->xpdo->moregallery->getOption('moregallery.single_image_url_param', null, 'iid') => $this->get('id'),
), $this->xpdo->moregallery->getOption('link_tag_scheme', null, 'full'));
}
// As of MoreGallery 1.5, all uploaded files already have their EXIF and IPTC data cleansed. However it's
// possible for older images to break the processor loading of images due to old data. The additional
// cleaning here ensures that everything works as expected, even if it contains invalid characters.
$cleanExif = $this->cleanInvalidData($array[$keyPrefix.'exif']);
$array[$keyPrefix . 'exif'] = $cleanExif;
$array[$keyPrefix . 'exif_dump'] = print_r($cleanExif, true);
$array[$keyPrefix . 'exif_json'] = $this->xpdo->toJSON($cleanExif);
$cleanIptc = $this->cleanInvalidData($array[$keyPrefix.'iptc']);
$array[$keyPrefix . 'iptc'] = $cleanIptc;
$array[$keyPrefix . 'iptc_dump'] = print_r($cleanIptc, true);
$array[$keyPrefix . 'iptc_json'] = $this->xpdo->toJSON($cleanIptc);
$array[$keyPrefix . 'full_view'] = $this->getManagerEmbed();
}
return $array;
}
/**
* @return mgResource|null
*/
public function getResource() {
$id = $this->get('resource');
return $this->xpdo->moregallery->getResource($id);
}
/**
* Removes files along with the image record.
*
* @param array $ancestors
*
* @return bool
*/
public function remove (array $ancestors = array ()) {
$resource = $this->getResource();
if ($resource && $resource->_getSource()) {
$relativeUrl = $resource->getSourceRelativeUrl();
$mgrThumb = $this->get('mgr_thumb');
if (!empty($mgrThumb)) {
$resource->source->removeObject($relativeUrl . $mgrThumb);
}
$file = $this->get('file');
if (!empty($file)) {
$resource->source->removeObject($relativeUrl . $file);
}
$crops = $this->getCrops();
/** @var mgImageCrop $crop */
foreach ($crops as $crop) {
$cropThumbnail = $crop->get('thumbnail');
if (!empty($cropThumbnail)) {
$resource->source->removeObject($relativeUrl . $cropThumbnail);
}
}
if ($resource->source->hasErrors()) {
$errors = $resource->source->getErrors();
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Error(s) while removing file(s) for mgImage ' . $this->toJSON() . ':' . implode("\n", $errors));
}
}
$this->clearCache();
return parent::remove($ancestors);
}
/**
* @param null $cacheFlag
*
* @return bool
*/
public function save($cacheFlag= null) {
if ($this->isNew()) {
$this->setIfEmpty('uploadedon', time());
$this->setIfEmpty('uploadedby', $this->xpdo->user ? $this->xpdo->user->get('id') : 0);
}
$saved = parent::save($cacheFlag);
$this->clearCache();
return $saved;
}
/**
* Used by {@see self::save()}, this function only calls $this->set if there is not yet a value for it.
*
* @param $key
* @param $value
*/
protected function setIfEmpty($key, $value) {
$current = $this->get($key);
if (empty($current)) {
$this->set($key, $value);
}
}
public function clearCache() {
$cacheOptions = array(xPDO::OPT_CACHE_KEY => 'moregallery');
$resource = $this->get('resource');
$this->xpdo->cacheManager->delete('single-image/' . $resource . '/', $cacheOptions);
$this->xpdo->cacheManager->delete('image-collection/' . $resource . '/', $cacheOptions);
$this->xpdo->cacheManager->delete('mgimage/'.$resource.'/', $cacheOptions);
$this->xpdo->cacheManager->delete('mgimages/'.$resource.'/', $cacheOptions);
}
/**
* ppb: Gets the first image in a set.
*
* @param string $sortBy
*
* @param bool $activeOnly
* @return null|mgImage
*/
public function getFirst($sortBy = 'sortorder', $activeOnly = true) {
$c = $this->xpdo->newQuery('mgImage');
$c->where(array(
'resource' => $this->get('resource')
//,'AND:'.$sortBy.':<' => $this->get($sortBy),
));
if ($activeOnly)
{
$c->where(array('active' => true));
}
$c->sortby($sortBy, 'ASC');
$c->limit(1);
return $this->xpdo->getObject('mgImage', $c);
}
/**
* Gets the image before this one.
*
* @param string $sortBy
*
* @param bool $activeOnly
* @return null|mgImage
*/
public function getPrevious($sortBy = 'sortorder', $activeOnly = true) {
$c = $this->xpdo->newQuery('mgImage');
$c->where(array(
'resource' => $this->get('resource')
,'AND:'.$sortBy.':<' => $this->get($sortBy),
));
if ($activeOnly)
{
$c->where(array('active' => true));
}
$c->sortby($sortBy, 'DESC');
$c->limit(1);
return $this->xpdo->getObject('mgImage', $c);
}
/**
* Gets the image after this one.
*
* @param string $sortBy
*
* @param bool $activeOnly
* @return null|mgImage
*/
public function getNext($sortBy = 'sortorder', $activeOnly = true) {
$c = $this->xpdo->newQuery('mgImage');
$c->where(array(
'resource' => $this->get('resource')
,'AND:'.$sortBy.':>' => $this->get($sortBy),
));
if ($activeOnly)
{
$c->where(array('active' => true));
}
$c->sortby($sortBy, 'ASC');
$c->limit(1);
return $this->xpdo->getObject('mgImage', $c);
}
/**
* ppb: Gets the last image in a set of images.
*
* @param string $sortBy
*
* @param bool $activeOnly
* @return null|mgImage
*/
public function getLast($sortBy = 'sortorder', $activeOnly = true) {
$c = $this->xpdo->newQuery('mgImage');
$c->where(array(
'resource' => $this->get('resource')
//,'AND:'.$sortBy.':>' => $this->get($sortBy),
));
if ($activeOnly)
{
$c->where(array('active' => true));
}
$c->sortby($sortBy, 'DESC');
$c->limit(1);
return $this->xpdo->getObject('mgImage', $c);
}
/**
* @return array|mixed
*/
public function getTags() {
$co = array(xPDO::OPT_CACHE_KEY => 'moregallery');
$tags = $this->xpdo->cacheManager->get('tags/image/'.$this->get('id'), $co);
if (is_array($tags)) return $tags;
$tags = array();
$c = $this->xpdo->newQuery('mgTag');
$c->innerJoin('mgImageTag', 'Images');
$c->where(array(
'Images.image' => $this->get('id'),
));
/** @var mgTag $tag */
foreach ($this->xpdo->getIterator('mgTag', $c) as $tag) {
$tags[] = $tag->toArray();
}
$this->xpdo->cacheManager->set('tags/image/' . $this->get('id'), $tags, 0, $co);
return $tags;
}
/**
* Resize the image to a smaller one for use as mgr_thumb
*
* @param $content
* @param int $width
* @param int $height
* @return bool|string
*/
public function createThumbnail($content, $extension = 'jpg', $width = 250, $height = 250) {
/** @var \Imagine\Image\ImagineInterface $imagine */
$imagine = $this->xpdo->moregallery->getImagine();
// If the image is a PDF file, it needs a bit more work to get it propery parsed.
if ($extension === 'pdf') {
$extension = 'png';
$content = $this->xpdo->moregallery->writePdfAsImageAndReturnContent($content);
}
elseif (strtolower($extension) === 'svg') {
$extension = 'png';
}
try {
$img = $imagine->load($content);
} catch (Exception $e) {
$this->xpdo->log(modX::LOG_LEVEL_ERROR, '[moreGallery] Unable to load image for record ' . $this->get('id') . 'to create thumbnail: ' . $e->getMessage());
return $e->getMessage();
}
// Get the size to calculate the way we need to crop this image
$size = $img->getSize();
$actualWidth = $size->getWidth();
$actualHeight = $size->getHeight();
// Figure out the right size to make sure wide or tall images don't get super blurry
// Basically this makes sure that the images are at least the defined size, rather than e.g. 250x50.
if ($actualWidth > $actualHeight) {
$width = ceil(($actualWidth / $actualHeight) * $width);
}
else {
$height = ceil(($actualHeight / $actualWidth) * $height);
}
try {
// Load the image with imagine and create a resized version
$thumb = $img->resize(new \Imagine\Image\Box($width, $height));
// Output the thumbnail as a string
$options = array(
'jpeg_quality' => (int)$this->xpdo->moregallery->getOption('moregallery.crop_jpeg_quality', null, '90'),
'png_compression_level' => (int)$this->xpdo->moregallery->getOption('moregallery.crop_png_compression', null, '9'),
);
$thumbContents = $thumb->get($extension, $options);
// Make the filename the ID, followed by a hash, and the extension (of course).
$hash = md5(implode('-', $this->get(array('id', 'filename', 'file'))));
$mgrThumb = $this->get('id') . '_' . $hash . '.' . $extension;
// Grab the path and create the thumbnail
$resource = $this->getResource();
$resource->_getSource();
$path = $resource->getSourceRelativeUrl();
$resource->source->createContainer($path . '_thumbs/' , '/');
$resource->source->errors = array();
$resource->source->createObject($path . '_thumbs/', $mgrThumb, $thumbContents);
$this->set('mgr_thumb', '_thumbs/' . $mgrThumb);
} catch (Exception $e) {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, '[moreGallery] Exception ' . get_class($e) . ' while creating thumbnail: ' . $e->getMessage());
return $e->getMessage();
}
return true;
}
public function loadExifData($file) {
$ext = pathinfo($file, PATHINFO_EXTENSION);
if (in_array(strtolower($ext), array('jpeg', 'jpg', 'tiff'), true) && function_exists('exif_read_data')) {
try {
// Fetch EXIF data if we have it.
$exif = exif_read_data($file, NULL, false, false);
if (is_array($exif)) {
foreach ($exif as $key => $value) {
$exif[$key] = $this->cleanInvalidData($value);
}
$this->set('exif', $exif);
}
} catch (Exception $e) {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, '[moreGallery] Exception while trying to read exif data: ' . $e->getMessage());
}
} else {
$this->xpdo->log(xPDO::LOG_LEVEL_WARN, '[moreGallery] This server does not have the exif_read_data function installed. MoreGallery cannot extract exif data now.');
}
}
/**
* Parses the IPTC data into something a bit more usable
*
* @param $data
* @return array
*/
public function loadIPTCData($data) {
$iptc = iptcparse($data);
$newIptc = array();
if (is_array($iptc)) {
foreach ($iptc as $key => $value) {
if (array_key_exists($key, $this->iptcHeaderArray)) {
$key = $this->iptcHeaderArray[$key];
}
foreach ($value as &$v) {
$v = $this->cleanInvalidData($v);
}
unset ($v);
if (count($value) === 1) {
$value = $value[0];
}
$newIptc[$key] = $value;
}
// Store the cleaned iptc data in the database
$this->set('iptc', $newIptc);
}
return $newIptc;
}
/**
* Prefills the name and tags from the provided IPTC data
*
* @param array $iptc
*/
public function prefillFromIPTC(array $iptc = array())
{
$name = '';
$iptcNameHeaders = array("Caption", "Headline", "DocumentTitle");
foreach ($iptcNameHeaders as $key) {
if (isset($iptc[$key]) && !empty($iptc[$key])) {
$name = $iptc[$key];
}
}
if (!empty($name)) {
$this->set('name', $name);
}
$tags = array();
$iptcTagHeaders = array('Category', 'Subcategories', 'Keywords');
foreach ($iptcTagHeaders as $key) {
if (isset($iptc[$key]) && !empty($iptc[$key])) {
if (is_array($iptc[$key])) {
$tags = array_merge($tags, array_values($iptc[$key]));
}
else {
$tags[] = $iptc[$key];
}
}
}
$tags = array_unique($tags);
if (!empty($tags)) {
foreach ($tags as $tag) {
/** @var mgTag $tagObj */
$tagObj = $this->xpdo->getObject('mgTag', array('display' => $tag));
if (!$tagObj) {
$tagObj = $this->xpdo->newObject('mgTag');
$tagObj->fromArray(array(
'display' => $tag,
));
$tagObj->save();
}
/** @var mgImageTag $link */
$link = $this->xpdo->newObject('mgImageTag');
$link->fromArray(array(
'resource' => $this->get('resource'),
'image' => $this->get('id'),
'tag' => $tagObj->get('id')
));
$link->save();
}
}
}
/**
* @param $content
* @param $orientation
* @return bool|string
*/
public function fixOrientation($content, $orientation, $format)
{
try {
/** @var \Imagine\Image\ImagineInterface $imagine */
$imagine = $this->xpdo->moregallery->getImagine();
// Load the image with imagine and create a resized version
$thumb = $imagine->load($content);
$degrees = 0;
switch ($orientation) {
case 3:
$degrees = 180;
break;
case 6:
$degrees = 90;
break;
case 8:
$degrees = 270;
break;
}
if ($degrees > 0) {
$thumb->rotate($degrees);
$options = array(
'jpeg_quality' => (int)$this->xpdo->moregallery->getOption('moregallery.crop_jpeg_quality', null, '90'),
'png_compression_level' => (int)$this->xpdo->moregallery->getOption('moregallery.crop_png_compression', null, '9'),
);
return $thumb->get($format, $options);
}
} catch (Exception $e) {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, '[moreGallery] Exception while creating mgr_thumb: ' . $e->getMessage());
}
return false;
}
public function copyTo(mgResource $resource, mgResource $oldResource)
{
// Get the raw contents of the current image
$oldImageData = $this->toArray('', true);
unset($oldImageData['id']);
/** @var mgImage $newImage */
// Create the new image record
$newImage = $this->xpdo->newObject('mgImage');
$newImage->fromArray($oldImageData, '', true);
$newImage->set('resource', $resource->get('id'));
$newImage->save();
// Copy across tags for this image
$tags = $this->xpdo->getIterator('mgImageTag', array('image' => $this->get('id')));
foreach ($tags as $tag) {
/** @var mgImageTag $newTag */
$newTag = $this->xpdo->newObject('mgImageTag');
$newTag->fromArray(array(
'resource' => $resource->get('id'),
'image' => $newImage->get('id'),
'tag' => $tag->get('tag'),
), '', true);
$newTag->save();
}
// Get the files that need to be copied
$files = array();
$files[] = $this->get('file');
$files[] = $this->get('mgr_thumb');
// Get the crops for this image and copy those, also add the cropped thumbnails to the $files array
$crops = $this->getCrops();
foreach ($crops as $crop) {
/** @var mgImageCrop $crop */
$newCrop = $this->xpdo->newObject('mgImageCrop');
$newCrop->fromArray($crop->toArray());
$newCrop->set('image', $newImage->get('id'));
$newCrop->save();
$files[] = $crop->get('thumbnail');
}
$this->xpdo->moregallery->setResource($oldResource);
$relativeUrl = $oldResource->getSourceRelativeUrl();
$oldBasePath = $oldResource->_getSource()->getBasePath() . $relativeUrl;
$this->xpdo->moregallery->setResource($resource);
$relativeUrl = $resource->getSourceRelativeUrl();
$newBasePath = $resource->_getSource()->getBasePath() . $relativeUrl;
if (!is_dir($newBasePath)) {
$resource->source->createContainer($relativeUrl, '');
$resource->source->createContainer($relativeUrl . '_thumbs/', '');
}
foreach ($files as $file) {
if (!file_exists($oldBasePath . $file)) {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, '[moreGallery] Error copying file ' . $file . ' from ' . $oldBasePath . ' to ' . $newBasePath . ' while trying to duplicate image ' . $this->get('id') . ' because the source image does not exist.');
}
elseif (!copy($oldBasePath . $file, $newBasePath . $file)) {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, '[moreGallery] Error copying file ' . $file . ' from ' . $oldBasePath . ' to ' . $newBasePath . ' while trying to duplicate image ' . $this->get('id'));
}
}
return $newImage;
}
private function _loadMoreGalleryService()
{
$corePath = $this->xpdo->getOption('moregallery.core_path', null, $this->xpdo->getOption('core_path') . 'components/moregallery/');
$moreGallery = $this->xpdo->getService('moregallery', 'moreGallery', $corePath . 'model/moregallery/');
if (!($moreGallery instanceof moreGallery)) {
$this->xpdo->log(modX::LOG_LEVEL_ERROR, 'Error loading moreGallery class from ' . $corePath, '', __METHOD__, __FILE__, __LINE__);
}
}
/**
* @param $value
* @return string
*/
public function cleanInvalidData($value)
{
if (is_array($value)) {
foreach ($value as $key => $subValue) {
$value[$key] = $this->cleanInvalidData($subValue);
}
return $value;
}
else {
return preg_replace('/[^\PC\s]/u', '', $value);
}
}
/**
* Gets an extended property on the object. Use this instead of interacting with the `properties` value directly.
*
* @param $key
* @param null $default
* @return null
*/
public function getProperty($key, $default = null)
{
$properties = $this->getProperties();
if (isset($properties[$key])) {
return $properties[$key];
}
return $default;
}
/**
* Returns all saved properties
*
* @return array
*/
public function getProperties()
{
$properties = $this->get('properties');
if (!is_array($properties)) {
$properties = array();
}
return $properties;
}
/**
* Sets an extended property on the object. Use this instead of interacting with the `properties` value directly.
*
* @param string $key
* @param null $value
*/
public function setProperty($key, $value = null)
{
$properties = $this->getProperties();
if (!is_array($properties)) {
$properties = array();
}
$properties[$key] = $value;
$this->setProperties($properties);
}
/**
* Sets an array of properties on the object. If $merge is true, it will do an array_merge with the current data first
* and otherwise it will overwrite it completely.
*
* @param $properties
* @param bool $merge
*/
public function setProperties($properties, $merge = true)
{
if ($merge) {
$properties = array_merge($this->getProperties(), $properties);
}
$this->set('properties', $properties);
}
/**
* Unsets the property specified by $key, and returns its value (if any).
*
* @param $key
* @return mixed
*/
public function unsetProperty($key)
{
$properties = $this->getProperties();
if (isset($properties[$key])) {
$value = $properties[$key];
unset($properties[$key]);
$this->setProperties($properties, false);
return $value;
}
return null;
}
/**
* Unsets all properties defined in $keys. Returns an array of $key => $oldValue values.
*
* @param array $keys
* @return array
*/
public function unsetProperties(array $keys = array())
{
$values = array();
foreach ($keys as $key) {
$values[$key] = $this->unsetProperty($key);
}
return $values;
}
/**
* Returns a HTML embed code for the video.
*
* @return string
*/
public function getManagerEmbed() {
$resource = $this->getResource();
if (!$resource || !$resource->_getSource()) {
return '';
}
$relativeUrl = $resource->getSourceRelativeUrl();
$file = $this->get('file');
$fileUrl = $resource->source->getBaseUrl() . $relativeUrl . $file;
$extension = pathinfo($file, PATHINFO_EXTENSION);
if (strtolower($extension) === 'pdf') {
return <<<HTML
<object width="100%" height="500" type="application/pdf" data="$fileUrl">
<p>Unable to preview PDF file.</p>
</object>
HTML;
}
return '<img src="' . $fileUrl . '">';
}
/**
* Checks if the manager thumb is available, and creates it if not.
*
* @return bool
*/
public function checkManagerThumb()
{
$resource = $this->getResource();
if (!$resource || !$resource->_getSource()) {
return false;
}
$relativeUrl = $resource->getSourceRelativeUrl();
$thumb = $this->get('mgr_thumb');
$thumbPath = $resource->source->getBasePath() . $relativeUrl . $thumb;
if (empty($thumb) || !file_exists($thumbPath)) {
$resource->source->errors = array();
$file = $this->get('file');
if (empty($file)) {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, '[moregallery] Image record ' . $this->get('id') . ' on resource ' . $this->get('resource') . ' does not have a file value. This may indicate a corrupt upload. Unable to create manager thumbnail.');
return false;
}
$content = $resource->source->getObjectContents($relativeUrl . $file);
if (!$resource->source->hasErrors()) {
$extension = pathinfo($this->get('file'), PATHINFO_EXTENSION);
if ($this->createThumbnail($content['content'], $extension)) {
return $this->save();
}
}
else {
$this->xpdo->log(xPDO::LOG_LEVEL_ERROR, 'Error(s) loading file ' . $relativeUrl . $this->get('file') . ' to regenerate thumbnail for image ' . $this->get('id') . ' in gallery ' . $this->get('resource') . ' from media source ' . $resource->source->get('id') . ' : ' . print_r($resource->source->getErrors(), true));
}
}
return false;
}
}
<div class="item">
<div class="row">
<div id="item_[[+resource.id]]">
<div class="col-lg-1 col-md-1"></div>
<div class="col-lg-4 col-md-5 left">
<a href="[[+next.view_url:default=`[[+first.view_url]]`]]" class="item_image small small_[[+custom.class]]">
[[pthumb?
&input=`[[+file_url]]`
&options=`w=[[+custom.class:is=`hoch`:then=`400`:else=`500`]]`
&toPlaceholder=`thumb`
]]
<img
src="[[+thumb]]"
width="[[+thumb.width]]"
height="[[+thumb.height]]"
alt="[[+name]]"
/>
</a>
</div>
<div class="col-lg-1 col-md-1"></div>
<div class="col-lg-6 col-ms-5 right">
<div class="item_caption" style="height: [[+thumb.height]]px">
<div class="item_details">
[[+idx:is=`0`:then=`
<p class="item_description">
[[+resource.longtitle]], [[#[[+resource.parent]].pagetitle]]<br/>
[[+resource.description:nl2br]]
</p>
<p class="image_description">
[[+description:notempty=`[[+total:gt=`1`:then=`[[+description:replace=`Ausstellungsansicht==Ausstellungsansichten`:nl2br]]`:else=`[[+description:nl2br]]`]]<br/>`]]
Foto[[+total:gt=`1`:then=`s`:else=``]]: [[+custom.fotograf]]
</p>
`:else=`
<p class="image_description">
[[+description:nl2br]]
</p>
`]]
<p>
<span>Abbildung [[+idx]] / [[+image_count]]</span>
<br/>
<a href="[[+prev.view_url:default=`[[+last.view_url]]`]]">vorherige Abbildung</a>
<br>
<a href="[[+next.view_url:default=`[[+first.view_url]]`]]">nächste Abbildung</a>
<br>
<a href="[[~[[+resource.id]]]]">weitere Abbildungen (Übersicht)</a>
[[+idx:is=`0`:then=`
<br/>
<a href="[[~[[+resource.parent]]]]?back=[[*id]]&details=0#item_[[+resource.id]]">
zurück zu Bildarchiv – [[#[[+resource.parent]].pagetitle]]
</a>
`:else=``]]
<br/>
<a href="[[+file_url]]">
Download ([[+width]] x [[+height]] px)
</a>
</p>
<p>
Test: [[+xyz]]
</p>
</div>
<!-- Backlings, etc -->
</div>
</div>
</div>
<!-- #item[[+resource.id]] -->
</div>
</div>
<?php
use modmore\Alpacka\Snippet;
use modmore\Alpacka\Properties\SimpleProperty;
use modmore\Alpacka\Properties\BooleanProperty;
use modmore\Alpacka\Properties\EnumProperty;
/**
* Class GetImagesSnippet
*
* Gets a collection of images and videos.
*
* @property moreGallery $service
*/
class GetImages extends Snippet {
protected $resourceFields = array(
'id', 'alias', 'uri', 'uri_override',
'pagetitle', 'longtitle', 'menutitle',
'description', 'introtext', 'link_attributes',
'parent', 'context_key', 'template', 'class_key', 'content_type',
'published', 'pub_date', 'unpub_date', 'publishedon', 'publishedby',
'isfolder', 'richtext', 'searchable', 'cacheable', 'deleted', 'hide_children_in_tree', 'show_in_tree',
'createdby', 'createdon', 'editedby', 'editedon', 'deletedby', 'deletedon',
);
protected $cacheOptions = array(
xPDO::OPT_CACHE_KEY => 'moregallery'
);
/** @var modResource[] */
protected $_resources = array();
/** @var array[] */
protected $_resourceData = array();
protected $chunkHash = '';
protected $_idx = 0;
protected $singleImageParam = '';
/**
* The available properties for this snippet.
*
* @return array
*/
public function getPropertiesDefinition()
{
return array(
'cache' => new BooleanProperty(true),
'resource' => new SimpleProperty(0),
'activeOnly' => new BooleanProperty(true),
'sortBy' => new SimpleProperty('sortorder'),
'sortDir' => new EnumProperty('ASC', array('ASC', 'DESC')),
'where' => new SimpleProperty(),
'tags' => new SimpleProperty(),
'tagsFromUrl' => new SimpleProperty(),
'tagSeparator' => new SimpleProperty("\n"),
'getTags' => new BooleanProperty(true),
'getResourceContent' => new BooleanProperty(false),
'getResourceProperties' => new BooleanProperty(false),
'getResourceFields' => new BooleanProperty(false),
'getResourceTVs' => new SimpleProperty(),
'tagTpl' => new SimpleProperty('mgtag'),
'imageTpl' => new SimpleProperty('mgimage'),
'youtubeTpl' => new SimpleProperty('mgYoutube'),
'vimeoTpl' => new SimpleProperty('mgVimeo'),
'imageSeparator' => new SimpleProperty("\n"),
'singleImageTpl' => new SimpleProperty('mgimagesingle'),
'singleYoutubeTpl' => new SimpleProperty('mgYoutubeSingle'),
'singleVimeoTpl' => new SimpleProperty('mgVimeoSingle'),
'singleImageEnabled' => new BooleanProperty(true),
'singleImageParam' => new SimpleProperty(),
'singleImageResource' => new SimpleProperty(0),
'wrapperTpl' => new SimpleProperty(),
'wrapperIfEmpty' => new BooleanProperty(true),
'toPlaceholder' => new SimpleProperty(),
'limit' => new SimpleProperty(0),
'offset' => new SimpleProperty(0),
'totalVar' => new SimpleProperty('total'),
'scheme' => new SimpleProperty($this->service->getOption('link_tag_scheme')),
'debug' => new BooleanProperty(false),
'timing' => new BooleanProperty(false),
);
}
public function initialize()
{
$resourceId = $this->getProperty('resource');
if ($resourceId < 1 && $this->modx->resource) {
$resourceId = $this->modx->resource->get('id');
$this->setProperty('resource', $resourceId);
}
// Get the singleImageParam property and default it to the setting if empty
$singleImageParam = $this->getProperty('singleImageParam');
if (empty($singleImageParam)) {
$singleImageParam = $this->service->getOption('moregallery.single_image_url_param');
}
$this->singleImageParam = $singleImageParam;
$context = 'web';
if ($resourceId > 0) {
if ($this->modx->resource && $this->modx->resource->get('id') == $resourceId) {
$context = $this->modx->resource->get('context_key');
}
else {
$resource = $this->modx->getObject('modResource', (int)$resourceId);
if ($resource instanceof modResource) {
$context = $resource->get('context_key');
}
}
}
$this->debug('Setting working context to ' . $context);
$this->service->setWorkingContext($context);
}
/**
* @return string
*/
public function process()
{
$this->initialize();
if (array_key_exists($this->singleImageParam, $_REQUEST)) {
$this->debug('URL parameter "' . $this->singleImageParam . '" exists on $_REQUEST, so showing single image.');
return $this->getSingleImage((int)$_REQUEST[$this->singleImageParam]);
}
$this->debug('No URL parameter ' . $this->singleImageParam . ' exists on $_REQUEST, so showing image collection.');
return $this->getImageCollection();
}
/**
* Returns image $imageId, from cache if possible.
*
* @param $imageId
* @return string
* @throws \modmore\Alpacka\Exceptions\InvalidPropertyException
*/
public function getSingleImage($imageId)
{
// Try to load from cache first
$cacheKey = 'single-image/' . $this->getProperty('resource') . '/' . $imageId . '_' . $this->getPropertyHash() . '_' . $this->getChunkHash();
if ($this->getProperty('cache')) {
$cached = $this->modx->cacheManager->get($cacheKey, $this->cacheOptions);
if (is_array($cached)) {
$this->debug('Loaded single image ' . $imageId . ' from cache using cacheKey ' . $cacheKey);
return $this->finish($cached['formatted']);
}
}
// No cache available, so fetch it from the database.
$filter = array(
'id' => $imageId,
'resource' => $this->getProperty('resource'),
);
if ($this->getProperty('activeOnly')) {
$filter['active'] = true;
}
/** @var mgImage $image */
$image = $this->modx->getObject('mgImage', $filter);
// If the image can't be found, we send the user to the error page.
// @todo Consider redirecting the user to the "parent" page (i.e. current page without &iid url param) instead?
if (!$image) {
$this->debug('Image not found with filter' . print_r($filter, true));
$this->modx->sendErrorPage();
}
$this->debug('Image loaded, now loading all placeholders.');
// Turn the image into placeholders, including previous and next images.
$phs = $this->getImagePlaceholders($image, true);
// Format it
$tpl = $this->determineSingleTpl($phs);
$this->debug('Formatting image with chunk ' . $tpl);
$formatted = $this->service->getChunk($tpl, $phs);
// If cache is enabled, write the data to the proper cache file.
if ($this->getProperty('cache')) {
$cached = array(
'placeholders' => $phs,
'formatted' => $formatted,
);
$this->debug('Caching image information and formatted output to cacheKey ' . $cacheKey);
$this->modx->cacheManager->set($cacheKey, $cached, 0, $this->cacheOptions);
}
return $this->finish($formatted);
}
/**
*
*
* @return string
* @throws \modmore\Alpacka\Exceptions\InvalidPropertyException
*/
public function getImageCollection()
{
// Set the tags propery to include tags from the URL, so that the cacheKey is updated when tags change
$this->setProperty('tags', $this->getTags());
$random = in_array(strtolower($this->getProperty('sortBy')), array('random', 'rand', 'rand()'), true);
$limit = $this->getProperty('limit');
// Try to load from cache
$cacheKey = 'image-collection/' . $this->getProperty('resource') . '/' . $this->getPropertyHash() . '_' . $this->getChunkHash();
if ($this->getProperty('cache')) {
$cached = $this->modx->cacheManager->get($cacheKey, $this->cacheOptions);
if (is_array($cached)) {
$this->debug('Loaded image collection from cache using cacheKey ' . $cacheKey);
$formatted = $cached['formatted'];
if ($random && array_key_exists('result_set', $cached)) {
$this->debug('Randomising and parsing result set from cache.');
shuffle($cached['result_set']);
if ($limit > 0) {
$this->debug('Limiting result set to ' . $limit);
$cached['result_set'] = array_slice($cached['result_set'], 0, $limit);
}
$formatted = $this->parseCollection($cached['result_set'], $cached['total']);
}
return $this->finish($formatted);
}
}
$this->debug('Preparing SQL query to retrieve images and related data.');
$c = $this->modx->newQuery('mgImage');
$c->distinct(true);
$resource = $this->getProperty('resource');
if (strpos($resource, ',') !== false) {
$c->where(array(
'resource:IN' => explode(',', $resource),
));
}
else {
$c->where(array(
'resource' => $resource,
));
}
if ($this->getProperty('activeOnly')) {
$c->where(array(
'active' => true,
));
}
$customCondition = $this->getProperty('where');
if (!empty($customCondition)) {
$customConditionArray = json_decode($customCondition, true);
if (is_array($customConditionArray)) {
$c->where($customConditionArray);
}
else {
$this->debug('WARNING: Custom condition ' . $customCondition . ' is not valid JSON.');
}
}
$this->addTagsCondition($c);
// Get the total and make it available for getPage support
$this->debug('Fetching total count for query');
$total = $this->modx->getCount('mgImage', $c);
$this->debug('Total results: ' . $total);
$this->modx->setPlaceholder($this->getProperty('totalVar'), $total);
// Apply the limit if we're not using a random sort
if (!$random && $limit > 0) {
$c->limit($limit, $this->getProperty('offset'));
}
// Apply sorting if this isn't a random sort
if (!$random) {
$c->sortby($this->getProperty('sortBy'), $this->getProperty('sortDir'));
}
else {
$c->sortby('RAND()');
}
if ($this->getProperty('debug')) {
$c->prepare();
$this->debug('Executing query: ' . $c->toSQL());
}
// Loop over the images and put them into $data
$data = array();
$i = 0;
$this->debug('Iterating over images');
/** @var mgImage[] $iterator */
$iterator = $this->modx->getIterator('mgImage', $c);
$lastIndex = count($iterator) -1;
foreach ($iterator as $image) {
$phs = $this->getImagePlaceholders($image);
$data[$i] = $phs;
// Add prev and next options
if (isset($data[$i - 1])) {
$data[$i]['prev'] = $data[$i - 1];
$data[$i - 1]['next'] = $phs;
// Prevent some sort of recursive array
unset($data[$i]['prev']['prev']);
}
$i++;
}
// If we're dealing with random images, the limit wasn't applied to the SQL query.
// So we splice it here.
$fullResultSet = $data;
if ($random && $limit > 0) {
$this->debug('Splicing result set for random sort');
$data = array_splice($data, 0, $limit);
}
$this->debug('Parsing image collection');
// Loop over the items and parse them through the imageTpl
$formatted = $this->parseCollection($data, $total);
// If cache is enabled, write the data to the proper cache file.
if ($this->getProperty('cache')) {
$this->debug('Caching formatted and raw results to ' . $cacheKey);
$cached = array(
'total' => $total,
'formatted' => $formatted,
'result_set' => $fullResultSet,
);
$this->modx->cacheManager->set($cacheKey, $cached, 0, $this->cacheOptions);
}
return $this->finish($formatted);
}
/**
* Turns an image object into placeholders, including loading related data (crops, tags, url, resource stuff).
*
* @param mgImage $image
* @return array
*/
public function getImagePlaceholders(mgImage $image, $previousAndNext = false)
{
// Start collecting all placeholders with just the standard image info.
$phs = $image->toArray();
$phs['idx'] = $this->_idx++;
// Get the crops for the image
$phs['crops'] = $image->getCropsAsArray();
// Process the url placeholder into a link tag
if (is_numeric($phs['url']) && $phs['url'] > 0) {
$phs['url'] = '[[~' . $phs['url'] . ']]';
}
// Generate a view_url placeholder for a detail page
$singleImageResource = $this->getProperty('singleImageResource');
if ($singleImageResource < 1) {
$singleImageResource = $phs['resource'];
}
$phs['view_url'] = $this->modx->makeUrl($singleImageResource, '', array(
$this->singleImageParam => $image->get('id'),
), $this->getProperty('scheme'));
// If requested, load all the resource fields
if ($this->getProperty('getResourceFields')) {
$phs = array_merge($phs, $this->getResourceFields($image->get('resource')));
}
if ($previousAndNext) {
$first = $image->getFirst($this->getProperty('sortBy'), $this->getProperty('activeOnly'));
if ($first instanceof mgImage) {
$phs['first'] = $first->toArray();
if (is_numeric($phs['first']['url']) && $phs['first']['url'] > 0) {
$phs['first']['url'] = '[[~' . $phs['first']['url'] . ']]';
}
$phs['first']['view_url'] = $this->modx->makeUrl($singleImageResource, '', array(
$this->singleImageParam => $first->get('id'),
), $this->getProperty('scheme'));
$phs['first']['crops'] = $first->getCropsAsArray();
}
$previous = $image->getPrevious($this->getProperty('sortBy'), $this->getProperty('activeOnly'));
if ($previous instanceof mgImage) {
$phs['prev'] = $previous->toArray();
if (is_numeric($phs['prev']['url']) && $phs['prev']['url'] > 0) {
$phs['prev']['url'] = '[[~' . $phs['prev']['url'] . ']]';
}
$phs['prev']['view_url'] = $this->modx->makeUrl($singleImageResource, '', array(
$this->singleImageParam => $previous->get('id'),
), $this->getProperty('scheme'));
$phs['prev']['crops'] = $previous->getCropsAsArray();
}
$next = $image->getNext($this->getProperty('sortBy'), $this->getProperty('activeOnly'));
if ($next instanceof mgImage) {
$phs['next'] = $next->toArray();
if (is_numeric($phs['next']['url']) && $phs['next']['url'] > 0) {
$phs['next']['url'] = '[[~' . $phs['next']['url'] . ']]';
}
$phs['next']['view_url'] = $this->modx->makeUrl($singleImageResource, '', array(
$this->singleImageParam => $next->get('id'),
), $this->getProperty('scheme'));
$phs['next']['crops'] = $next->getCropsAsArray();
}
$last = $image->getLast($this->getProperty('sortBy'), $this->getProperty('activeOnly'));
if ($last instanceof mgImage) {
$phs['last'] = $last->toArray();
if (is_numeric($phs['last']['url']) && $phs['last']['url'] > 0) {
$phs['last']['url'] = '[[~' . $phs['last']['url'] . ']]';
}
$phs['last']['view_url'] = $this->modx->makeUrl($singleImageResource, '', array(
$this->singleImageParam => $last->get('id'),
), $this->getProperty('scheme'));
$phs['last']['crops'] = $last->getCropsAsArray();
}
// If the sortorder is descending, we need to swap out prev and next placeholders to keep things in order
if ($this->getProperty('sortDir') === 'DESC') {
$prev = isset($phs['prev']) ? $phs['prev'] : null;
$phs['prev'] = isset($phs['next']) ? $phs['next'] : null;
$phs['next'] = $prev;
}
//ppb-todo do this for first an last
}
if ($this->getProperty('getTags')) {
$phs['tags_raw'] = $image->getTags();
$phs['tags'] = array();
foreach ($phs['tags_raw'] as $tag) {
$phs['tags'][] = $this->service->getChunk($this->getProperty('tagTpl'), $tag);
}
$phs['tags'] = implode($this->getProperty('tagSeparator'), $phs['tags']);
}
// Return it
return $phs;
}
/**
* Returns an array of resource fields (if the resource can be loaded, otherwise an empty array)
*
* @param $resourceId
* @return array
*/
public function getResourceFields($resourceId)
{
// See if we already have the resource data available as an array, if so return it.
if (array_key_exists($resourceId, $this->_resourceData)) {
return $this->_resourceData[$resourceId];
}
// See if we already have the resource, if not load it.
if (!array_key_exists($resourceId, $this->_resources)) {
$this->_resources[$resourceId] = $this->modx->getObject('modResource', (int)$resourceId);
}
// Local alias just to make it easier
$resource = $this->_resources[$resourceId];
// Make sure it's a modResource and then grab the data
if ($resource instanceof modResource) {
$data = $resource->get($this->resourceFields);
// Only get the content if specifically requested
if ($this->getProperty('getResourceContent')) {
$data['content'] = $resource->get('content');
}
// Only get the properties field if specifically requested
if ($this->getProperty('getResourceProperties')) {
$data['properties'] = $resource->get('properties');
}
// Load the TVs that the user requested
$tvs = $this->getProperty('getResourceTVs');
if (!empty($tvs)) {
$tvs = explode(',', $tvs);
foreach ($tvs as $tv) {
$data[$tv] = $resource->getTVValue($tv);
}
}
// Prefix 'resource.' to the values so we don't have to conflict with mgImage.resource
$data = $this->prefix($data, 'resource.');
// Store a local copy of it so we don't have to do this over and over
$this->_resourceData[$resourceId] = $data;
// Return it
return $data;
}
return array();
}
/**
* Prefixes the data with the specified prefix.
*
* @param $data
* @return array
*/
public function prefix($data, $prefix)
{
$prefixed = array();
foreach ($data as $key => $value) {
$prefixed[$prefix . $key] = $value;
}
return $prefixed;
}
/**
* Finishes the snippet by either returning the snippet output or by setting a requested placeholder.
*
* @param $formatted
* @return string
*/
public function finish($formatted)
{
$this->debug('Finished processing, outputting results.');
if ($this->getProperty('debug')) {
$formatted .= '<h3>Debug Information</h3><pre>' . print_r($this->_debug, true) . '</pre>';
}
if ($this->getProperty('timing')) {
$time = microtime(true);
$spent = ($time - $this->_startTime) * 1000;
$formatted .= '<p>Finished in ' . number_format($spent, 2) . 'ms</p>';
}
if ($placeholder = $this->getProperty('toPlaceholder')) {
$this->modx->setPlaceholder($placeholder, $formatted);
return '';
}
return $formatted;
}
/**
* Gets a comma separated list of tags. This is either the defined tags in the snippet call,
* or provided by a $_REQUEST parameter if &tagsFromUrl is specified and filled.
*
* @return string
*/
public function getTags()
{
$rawTags = $this->getProperty('tags');
$param = $this->getProperty('tagsFromUrl');
if (isset($_REQUEST[$param])) {
$rawTags = $_REQUEST[$param];
}
$rawTags = explode(',', $rawTags);
// Sanitise tags
$tags = array();
foreach ($rawTags as $tag) {
$tags[] = $this->modx->sanitizeString($tag);
}
return implode(',', $tags);
}
/**
* This method adds the conditionals for tags to the collection query.
*
* @param xPDOQuery $c
*/
public function addTagsCondition(xPDOQuery $c)
{
$allTagIds = $this->service->getTagIds();
$tagIds = array();
$excludedTagIds = array();
// Loop over the requested tags to find their respective IDs
$tags = $this->getProperty('tags');
$tags = explode(',', $tags);
if (count($tags) > 0) {
$this->debug('Adding conditions for tags: ' . implode(', ', $tags));
foreach ($tags as $tag) {
$exclude = strpos($tag, '-') === 0;
$tag = ($exclude) ? substr($tag, 1) : $tag;
if (is_numeric($tag)) {
if (!$exclude) {
$tagIds[] = (int)$tag;
} else {
$excludedTagIds[] = (int)$tag;
}
} else {
$search = array_search($tag, $allTagIds, true);
if (is_numeric($search) && $search > 0) {
if (!$exclude) {
$tagIds[] = $search;
} else {
$excludedTagIds[] = $search;
}
}
}
}
// If we have tags to include, add 'm to the query
if (!empty($tagIds)) {
$this->debug('Including tag IDs: ' . implode(', ', $tagIds));
$c->leftJoin('mgImageTag', 'Tags');
$c->where(array(
'Tags.tag:IN' => $tagIds,
));
}
// If we have tags to exclude, get rid of 'm
if (!empty($excludedTagIds)) {
$this->debug('Excluding tag IDs: ' . implode(', ', $excludedTagIds));
$excludedTagIds = implode(',', $excludedTagIds);
$c->where(array(
"NOT EXISTS (SELECT 1 FROM {$this->modx->getTableName('mgImageTag')} Tags WHERE `mgImage`.`id` = `Tags`.`image` AND `Tags`.`tag` IN ({$excludedTagIds}))"
));
}
}
}
/**
* Parses collection data into markup.
*
* @param $data
* @param $total
* @return string
*/
public function parseCollection($data, $total)
{
$formattedArr = array();
foreach ($data as $phs) {
$tpl = $this->determineTpl($phs);
$formattedArr[] = $this->service->getChunk($tpl, $phs);
}
$formatted = implode($this->getProperty('imageSeparator'), $formattedArr);
// Apply the wrapper template
$wrapperTpl = $this->getProperty('wrapperTpl');
if (!empty($wrapperTpl)) {
$wrapperIfEmpty = $this->getProperty('wrapperIfEmpty');
if (!empty($formatted) || $wrapperIfEmpty) {
$phs = array_merge(array(
'output' => $formatted,
'image_count' => $total,
), $this->getResourceFields($this->getProperty('resource')));
$formatted = $this->service->getChunk($wrapperTpl, $phs);
}
}
return $formatted;
}
/**
* Returns the template (chunk name) to use for this item.
*
* @param $data
* @return string
*/
public function determineTpl($data)
{
switch ($data['class_key']) {
case 'mgYouTubeVideo':
case 'mgVideo':
return $this->getProperty('youtubeTpl');
case 'mgVimeoVideo':
return $this->getProperty('vimeoTpl');
case 'mgImage':
default:
return $this->getProperty('imageTpl');
}
}
/**
* Returns the template (chunk name) to use for a single item being displayed.
*
* @param $data
* @return string
*/
public function determineSingleTpl($data)
{
switch ($data['class_key']) {
case 'mgYouTubeVideo':
case 'mgVideo':
return $this->getProperty('singleYoutubeTpl');
case 'mgVimeoVideo':
return $this->getProperty('singleVimeoTpl');
case 'mgImage':
default:
return $this->getProperty('singleImageTpl');
}
}
/**
* Generates a sha1 hash for the (unparsed) content of all chunks used by this snippet call. This is used for
* automatically busting the relevant caches when a chunk is changed.
*
* @return string
*/
public function getChunkHash()
{
if (empty($this->chunkHash)) {
$props = array(
'tagTpl',
'imageTpl',
'youtubeTpl',
'vimeoTpl',
'singleImageTpl',
'singleYoutubeTpl',
'singleVimeoTpl'
);
$chunks = array();
foreach ($props as $tplProp) {
$chunks[] = $this->getProperty($tplProp);
}
$chunks = array_unique($chunks);
$this->debug('Calculating chunkHash (for automatic cache-busting) for chunks: ' . implode(',', $chunks));
$c = $this->modx->newQuery('modChunk');
$c->where(array('name:IN' => $chunks));
$c->select($this->modx->getSelectColumns('modChunk', 'modChunk', '', array('id', 'name', 'snippet')));
$c->prepare();
$sql = $c->toSQL();
$chunkContents = array();
$result = $this->modx->query($sql);
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
$chunkContents[] = $row['snippet'];
}
$chunkContents = implode(',', $chunkContents);
$this->chunkHash = sha1(sha1($chunkContents));
$chunkContents = str_replace(array('[', ']'), array('[', ']'), htmlentities($chunkContents, ENT_QUOTES, 'utf-8'));
$this->debug('chunkHash calculated as ' . $this->chunkHash . ' based on raw content ' . $chunkContents);
}
return $this->chunkHash;
}
}