1349 lines
42 KiB
Text
1349 lines
42 KiB
Text
<?php namespace ProcessWire;
|
||
|
||
/**
|
||
* Class InputfieldImage
|
||
*
|
||
* Inputfield for FieldtypeImage fields
|
||
*
|
||
*
|
||
* Accessible Properties
|
||
*
|
||
* @property string $extensions Space separated list of allowed image extensions (default="JPG JPEG GIF PNG")
|
||
* @property array $okExtensions Array of manually whitelisted extensions, for instance [ 'SVG' ] must be manually whitelisted if allowed. (default=[])
|
||
* @property int|string $maxWidth Max width for uploaded images, larger will be sized down (default='')
|
||
* @property int|string $maxHeight Max height for uploaded images, larger will be sized down (default='')
|
||
* @property float $maxSize Maximum number of megapixels for client-side resize, i.e. 1.7 is ~1600x1000, alt. to maxWidth/maxHeight (default=0).
|
||
* @property bool|int $maxReject Reject images that exceed max allowed size? (default=false)
|
||
* @property int|string $minWidth Min width for uploaded images, smaller will be refused (default='')
|
||
* @property int|string $minHeight Min height for uploaded images, smaller will be refused (default='')
|
||
* @property bool|int $dimensionsByAspectRatio Switch min-/maxWidth and min-/maxHeight restriction for portrait images
|
||
* @property string $itemClass Space separated CSS classes for items rendered by this Inputfield. Generally you should append rather than replace.
|
||
* @property int|bool $useImageEditor Whether or not the modal image editor is allowed for this field (default=true)
|
||
* @property int $adminThumbScale for backwards compatibility only
|
||
* @property int|bool $resizeServer Resize to max width/height at server? 1=Server-only, 0=Use client-side resize when possible (default=0).
|
||
* @property int $clientQuality Quality setting to use for client-side resize. 60=60%, 90=90%, etc. (default=90).
|
||
*
|
||
* The following properties default values are pulled from $config->adminThumbOptions and can be overridden
|
||
* by setting directly to an instance of this Inputfield:
|
||
*
|
||
* @property int $gridSize squared size of the admin thumbnails (default=130)
|
||
* @property string $gridMode Default grid mode in admin, one of "grid", "left" or "list" (default="grid")
|
||
* @property string $focusMode May be 'on', 'off', or 'zoom'
|
||
* @property array $imageSizerOptions Options to pass along to the ImageSizer class. See /wire/config.php $imageSizerOptions for details.
|
||
*
|
||
*
|
||
* Hookable Methods
|
||
*
|
||
* @method string render()
|
||
* @method string renderItem(Pageimage $pagefile, $id, $n)
|
||
* @method string renderList(Pageimages $value)
|
||
* @method string renderUpload(Pageimages $value)
|
||
* @method string renderSingleItem(Pageimage $pagefile, $id, $n)
|
||
* @method string renderButtons(Pageimage $pagefile, $id, $n)
|
||
* @method string renderAdditionalFields(Pageimage $pagefile, $id, $n)
|
||
* @method array buildTooltipData(Pageimage $pagefile)
|
||
* @method array getFileActions(Pagefile $pagefile)
|
||
* @method bool|null processUnknownFileAction(Pageimage $pagefile, $action, $label)
|
||
*
|
||
*
|
||
*/
|
||
|
||
class InputfieldImage extends InputfieldFile implements InputfieldItemList, InputfieldHasSortableValue {
|
||
|
||
public static function getModuleInfo() {
|
||
return array(
|
||
'title' => __('Images', __FILE__), // Module Title
|
||
'summary' => __('One or more image uploads (sortable)', __FILE__), // Module Summary
|
||
'version' => 124,
|
||
'permanent' => true,
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Default square grid item size
|
||
*
|
||
*/
|
||
const defaultGridSize = 130;
|
||
|
||
/**
|
||
* Force render value mode for dev/debug purposes
|
||
*
|
||
*/
|
||
const debugRenderValue = false;
|
||
|
||
/**
|
||
* Cached list of all image variations
|
||
*
|
||
* @var array
|
||
*
|
||
*/
|
||
protected $variations = array();
|
||
|
||
/**
|
||
* Class used for modal editor windows
|
||
*
|
||
* @var string
|
||
*
|
||
*/
|
||
protected $modalClass = 'pw-modal-large';
|
||
|
||
|
||
public function init() {
|
||
parent::init();
|
||
|
||
$this->set('extensions', 'JPG JPEG GIF PNG');
|
||
$this->set('maxWidth', '');
|
||
$this->set('maxHeight', '');
|
||
$this->set('maxSize', 0.0);
|
||
$this->set('maxReject', 0);
|
||
$this->set('minWidth', '');
|
||
$this->set('minHeight', '');
|
||
$this->set('resizeServer', 0); // 0=allow client resize, 1=resize at server only
|
||
$this->set('clientQuality', 90);
|
||
$this->set('dimensionsByAspectRatio', 0);
|
||
$this->set('itemClass', 'gridImage ui-widget');
|
||
|
||
$options = $this->wire('config')->adminThumbOptions;
|
||
if(!is_array($options)) $options = array();
|
||
$gridSize = empty($options['gridSize']) ? self::defaultGridSize : (int) $options['gridSize'];
|
||
if($gridSize < 100) $gridSize = self::defaultGridSize; // establish min of 100
|
||
if($gridSize >= (self::defaultGridSize * 2)) $gridSize = self::defaultGridSize; // establish max of 259
|
||
$this->set('gridSize', $gridSize);
|
||
$this->set('gridMode', 'grid'); // one of "grid", "left" or "list"
|
||
$this->set('focusMode', 'on'); // One of "on", "zoom" or "off"
|
||
|
||
// adminThumbScale is no longer in use (here in case descending module using it)
|
||
$this->set('adminThumbScale', empty($options['scale']) ? 1.0 : (float) $options['scale']);
|
||
|
||
if(empty($options['imageSizerOptions'])) {
|
||
// properties specified in $options rather than $options['imageSizerOptions'], so we copy them
|
||
$options['imageSizerOptions'] = array();
|
||
foreach($options as $key => $value) {
|
||
if($key == 'height' || $key == 'width' || $key == 'scale' || $key == 'gridSize') continue;
|
||
$options['imageSizerOptions'][$key] = $value;
|
||
}
|
||
}
|
||
$this->set('imageSizerOptions', empty($options['imageSizerOptions']) ? array() : $options['imageSizerOptions']);
|
||
$this->set('useImageEditor', 1);
|
||
|
||
$this->labels = array_merge($this->labels, array(
|
||
'crop' => $this->_('Crop'),
|
||
'focus' => $this->_('Focus'),
|
||
'variations' => $this->_('Variations'),
|
||
'dimensions' => $this->_('Dimensions'),
|
||
'filesize' => $this->_('Filesize'),
|
||
'edit' => $this->_('Edit'),
|
||
'drag-drop-in' => $this->_('drag and drop in new images above'),
|
||
'na' => $this->_('N/A'), // for JS
|
||
'changes' => $this->_('This images field may have unsaved changes that could be lost after this action. Please save before cropping, or double-click the button proceed anyway.'),
|
||
));
|
||
|
||
$themeDefaults = array(
|
||
// 'error' => "<span class='ui-state-error-text'>{out}</span>", // provided by InputfieldFile
|
||
'buttonClass' => "ui-button ui-corner-all ui-state-default",
|
||
'buttonText' => "<span class='ui-button-text'>{out}</span>",
|
||
'selectClass' => '',
|
||
);
|
||
$themeSettings = $this->wire('config')->InputfieldImage;
|
||
$themeSettings = is_array($themeSettings) ? array_merge($themeDefaults, $themeSettings) : $themeDefaults;
|
||
$this->themeSettings = array_merge($this->themeSettings, $themeSettings);
|
||
}
|
||
|
||
public function get($key) {
|
||
if($key == 'themeSettings') return $this->themeSettings;
|
||
return parent::get($key);
|
||
}
|
||
|
||
/**
|
||
* Called right before Inputfield render
|
||
*
|
||
* @param Inputfield $parent Parent Inputfield
|
||
* @param bool $renderValueMode Whether or not we are in renderValue mode
|
||
* @return bool
|
||
*
|
||
*/
|
||
public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
|
||
|
||
if(self::debugRenderValue) {
|
||
// force render value mode for dev/debugging purposes
|
||
$renderValueMode = true;
|
||
$this->renderValueMode = true;
|
||
$this->addClass('InputfieldRenderValueMode', 'wrapClass');
|
||
}
|
||
|
||
$config = $this->wire('config');
|
||
$modules = $this->wire('modules');
|
||
$jqueryCore = $modules->get('JqueryCore');
|
||
$jqueryCore->use('simulate');
|
||
$jqueryCore->use('cookie');
|
||
$modules->loadModuleFileAssets('InputfieldFile');
|
||
$modules->getInstall("JqueryMagnific");
|
||
|
||
if(!$renderValueMode && $this->focusMode == 'zoom') {
|
||
$this->addClass('InputfieldImageFocusZoom', 'wrapClass');
|
||
}
|
||
|
||
$settings = $config->get('InputfieldImage');
|
||
if(!is_array($settings)) $settings = array();
|
||
if(empty($settings['ready'])) {
|
||
$settings['labels'] = $this->labels;
|
||
$settings['ready'] = true;
|
||
$config->js('InputfieldImage', $settings);
|
||
}
|
||
|
||
// client side image resize
|
||
if(!$this->resizeServer && ($this->maxWidth || $this->maxHeight || $this->maxSize)) {
|
||
$moduleInfo = self::getModuleInfo();
|
||
$thisURL = $config->urls->InputfieldImage;
|
||
$jsExt = $config->debug ? "js" : "min.js";
|
||
$config->scripts->add($thisURL . "piexif.$jsExt");
|
||
$config->scripts->add($thisURL . "PWImageResizer.$jsExt?v=$moduleInfo[version]");
|
||
$maxSize = str_replace(',', '.', $this->maxSize);
|
||
$quality = str_replace(',', '.', (float) ($this->clientQuality / 100));
|
||
$this->wrapAttr('data-resize', "$this->maxWidth;$this->maxHeight;$maxSize;$quality");
|
||
}
|
||
|
||
if(!$renderValueMode && $this->value instanceof Pageimages) {
|
||
$page = $this->getRootHasPage();
|
||
if($page->id && $this->wire('user')->hasPermission('page-edit-images', $page)) {
|
||
$modules->get('JqueryUI')->use('modal');
|
||
} else {
|
||
$this->useImageEditor = 0;
|
||
}
|
||
}
|
||
|
||
if($this->value instanceof Pageimages) $this->variations = $this->value->getAllVariations();
|
||
|
||
return parent::renderReady($parent, $renderValueMode);
|
||
}
|
||
|
||
/**
|
||
* Render Inputfield
|
||
*
|
||
* @return string
|
||
*
|
||
*/
|
||
public function ___render() {
|
||
if($this->isAjax) clearstatcache();
|
||
$out = parent::___render();
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* Render list of images
|
||
*
|
||
* @param Pageimages|array $value
|
||
* @return string
|
||
* @throws WireException
|
||
*
|
||
*/
|
||
protected function ___renderList($value) {
|
||
|
||
//if(!$value) return '';
|
||
$out = '';
|
||
$n = 0;
|
||
|
||
$this->renderListReady($value);
|
||
|
||
if(!$this->uploadOnlyMode && WireArray::iterable($value)) {
|
||
|
||
foreach($value as $k => $pagefile) {
|
||
$id = $this->pagefileId($pagefile);
|
||
$this->currentItem = $pagefile;
|
||
|
||
$out .= $this->renderItemWrap($this->renderItem($pagefile, $id, $n++));
|
||
/*
|
||
if($this->maxFiles != 1) {
|
||
$out .= $this->renderItemWrap($this->renderItem($pagefile, $id, $n++));
|
||
} else {
|
||
$out .= $this->renderSingleItem($pagefile, $id, $n++);
|
||
}
|
||
*/
|
||
}
|
||
|
||
if(!$this->renderValueMode) {
|
||
$dropNew = $this->wire('sanitizer')->entities1($this->_('drop in new image file to replace'));
|
||
$focus = $this->wire('sanitizer')->entities1($this->_('drag circle to center of focus'));
|
||
$out .= "
|
||
<div class='InputfieldImageEdit'>
|
||
<div class='InputfieldImageEdit__inner'>
|
||
<div class='InputfieldImageEdit__arrow'></div>
|
||
<div class='InputfieldImageEdit__close'><span class='fa fa-times'></span></div>
|
||
<div class='InputfieldImageEdit__imagewrapper'>
|
||
<div>
|
||
<img class='InputfieldImageEdit__image' src='' alt=''>
|
||
<small class='detail detail-upload'>$dropNew</small>
|
||
<small class='detail detail-focus'>$focus</small>
|
||
</div>
|
||
</div>
|
||
<div class='InputfieldImageEdit__edit'></div>
|
||
</div>
|
||
</div>
|
||
";
|
||
}
|
||
}
|
||
|
||
$class = 'InputfieldImageList gridImages ui-helper-clearfix';
|
||
if($this->uploadOnlyMode) $class .= " InputfieldImageUploadOnly";
|
||
if($this->overwrite && !$this->renderValueMode) $class .= " InputfieldFileOverwrite";
|
||
$out = "<ul class='$class' data-gridSize='$this->gridSize' data-gridMode='$this->gridMode'>$out</ul>";
|
||
$out = "<ul class='InputfieldImageErrors'></ul>$out";
|
||
|
||
return $out;
|
||
}
|
||
|
||
protected function renderItemWrap($out) {
|
||
$item = $this->currentItem;
|
||
$id = $item && !$this->renderValueMode ? " id='file_$item->hash'" : "";
|
||
return "<li$id class='ImageOuter {$this->itemClass}'>$out</li>";
|
||
}
|
||
|
||
protected function ___renderUpload($value) {
|
||
|
||
if($this->noUpload || $this->renderValueMode) return '';
|
||
|
||
// enables user to choose more than one file
|
||
if($this->maxFiles != 1) $this->setAttribute('multiple', 'multiple');
|
||
|
||
$attrs = $this->getAttributes();
|
||
unset($attrs['value']);
|
||
if(substr($attrs['name'], -1) != ']') $attrs['name'] .= '[]';
|
||
$attrStr = $this->getAttributesString($attrs);
|
||
|
||
$extensions = $this->getAllowedExtensions();
|
||
$formatExtensions = $this->formatExtensions($extensions);
|
||
$chooseLabel = $this->labels['choose-file'];
|
||
|
||
$out =
|
||
"<div " .
|
||
"data-maxfilesize='{$this->maxFilesize}' " .
|
||
"data-extensions='{$extensions}' " .
|
||
"data-fieldname='$attrs[name]' " .
|
||
"class='InputfieldImageUpload'" .
|
||
">";
|
||
|
||
$out .= "
|
||
<div class='InputMask ui-button ui-state-default'>
|
||
<span class='ui-button-text'>
|
||
<i class='fa fa-fw fa-folder-open-o'></i>$chooseLabel
|
||
</span>
|
||
<input $attrStr>
|
||
</div>
|
||
<span class='InputfieldImageValidExtensions detail'>$formatExtensions</span>
|
||
<input type='hidden' class='InputfieldImageMaxFiles' value='{$this->maxFiles}' />
|
||
";
|
||
|
||
if(!$this->noAjax) {
|
||
|
||
$dropLabel = $this->uploadOnlyMode ? $this->labels['drag-drop'] : $this->labels['drag-drop-in'];
|
||
// $refreshLabel = $this->('legacy thumbnails will be re-created on save');
|
||
|
||
$out .= "
|
||
<span class='AjaxUploadDropHere description'>
|
||
<span>
|
||
<i class='fa fa-cloud-upload'></i> $dropLabel
|
||
</span>
|
||
</span>";
|
||
}
|
||
|
||
if($this->get('_hasLegacyThumbs')) {
|
||
$label = $this->_('There are older/low quality thumbnail preview images above – check this box to re-create them.');
|
||
$out .= "
|
||
<p class='InputfieldImageRefresh detail'>
|
||
<label>
|
||
<input type='checkbox' name='_refresh_thumbnails_$this->name' value='1' />
|
||
$label
|
||
</label>
|
||
</p>
|
||
";
|
||
}
|
||
|
||
$out .= "</div>";
|
||
|
||
return $out;
|
||
}
|
||
|
||
|
||
/**
|
||
* Resize images to max width/height if specified in field config and image is larger than max
|
||
*
|
||
* #pw-hooker
|
||
*
|
||
* @param Pagefile $pagefile
|
||
* @throws WireException
|
||
*
|
||
*/
|
||
protected function ___fileAdded(Pagefile $pagefile) {
|
||
|
||
/** @var Pageimage $pagefile */
|
||
|
||
if($pagefile->ext() === 'svg') {
|
||
parent::___fileAdded($pagefile);
|
||
return;
|
||
}
|
||
|
||
$pagefile2 = null;
|
||
|
||
if(!$pagefile->width) {
|
||
$pagefile->unlink();
|
||
throw new WireException($this->_('Invalid image'));
|
||
}
|
||
|
||
$minWidth = $this->minWidth;
|
||
$minHeight = $this->minHeight;
|
||
|
||
if($this->dimensionsByAspectRatio && $pagefile->width < $pagefile->height){
|
||
$minWidth = $this->minHeight;
|
||
$minHeight = $this->minWidth;
|
||
}
|
||
|
||
if(
|
||
($minWidth && $pagefile->width < $minWidth) ||
|
||
($minHeight && $pagefile->height < $minHeight)
|
||
) {
|
||
$actualDimensions = $pagefile->width . 'x' . $pagefile->height;
|
||
$requiredDimensions = $minWidth . 'x' . $minHeight;
|
||
throw new WireException(
|
||
sprintf($this->_('Image of %s does not meet minimum size requirements'), $actualDimensions) . " ($requiredDimensions)"
|
||
);
|
||
}
|
||
|
||
$maxWidth = $this->maxWidth;
|
||
$maxHeight = $this->maxHeight;
|
||
|
||
if($this->dimensionsByAspectRatio && $pagefile->width < $pagefile->height){
|
||
$maxWidth = $this->maxHeight;
|
||
$maxHeight = $this->maxWidth;
|
||
}
|
||
|
||
if(
|
||
($maxWidth && $pagefile->width > $maxWidth) ||
|
||
($maxHeight && $pagefile->height > $maxHeight)
|
||
) {
|
||
if($this->maxReject) {
|
||
$actualDimensions = $pagefile->width . '×' . $pagefile->height;
|
||
$requiredDimensions = $maxWidth . '×' . $maxHeight;
|
||
throw new WireException(
|
||
sprintf($this->_('Image of %s exceeds maximum allowed size'), $actualDimensions) . " ($requiredDimensions)"
|
||
);
|
||
}
|
||
$pagefile2 = $pagefile->size($maxWidth, $maxHeight, array('cropping' => false));
|
||
if($pagefile->filename != $pagefile2->filename) {
|
||
$this->wire('files')->unlink($pagefile->filename);
|
||
$this->wire('files')->rename($pagefile2->filename, $pagefile->filename);
|
||
}
|
||
$pagefile->getImageInfo(true); // force it to reload its dimensions
|
||
}
|
||
|
||
if($pagefile2) {
|
||
$this->message($this->_("Image resized to fit maximum allowed dimensions") . " ({$maxWidth}x{$maxHeight}");
|
||
}
|
||
|
||
parent::___fileAdded($pagefile);
|
||
}
|
||
|
||
protected function fileAddedGetMarkup(Pagefile $pagefile, $n) {
|
||
/** @var Pageimage $pagefile */
|
||
/*
|
||
$markup = $this->maxFiles == 1
|
||
? $this->renderSingleItem($pagefile, $this->pagefileId($pagefile), $n)
|
||
: $this->renderItemWrap($this->renderItem($pagefile, $this->pagefileId($pagefile), $n));
|
||
*/
|
||
$markup = $this->renderItemWrap($this->renderItem($pagefile, $this->pagefileId($pagefile), $n));
|
||
return $markup;
|
||
}
|
||
|
||
/**
|
||
* Get thumbnail image info
|
||
*
|
||
* @param Pageimage $img Image to get thumb for
|
||
* @param bool $useSizeAttributes Whether width and or height size attributes should be included in the <img> tag
|
||
* @param bool $remove Specify true to remove legacy thumbnail file
|
||
*
|
||
* @return array of(
|
||
* 'thumb' => Pageimage object,
|
||
* 'attr' => associative array of image attributes
|
||
* 'markup' => string of markup for <img>,
|
||
* 'amarkup' => same as above but wrapped in <a> tag
|
||
* 'error' => error message if applicable
|
||
* 'title' => potential title attribute for <a> tag with image info
|
||
* );
|
||
*
|
||
*/
|
||
public function getAdminThumb(Pageimage $img, $useSizeAttributes = true, $remove = false) {
|
||
|
||
$thumb = $img;
|
||
$error = '';
|
||
$attr = array();
|
||
|
||
$_thumbHeight = $thumb->height;
|
||
$thumbHeight = $_thumbHeight;
|
||
$_thumbWidth = $thumb->width;
|
||
$thumbWidth = $_thumbWidth;
|
||
$useResize = ($img->ext == 'svg' && $thumbHeight == '100%')
|
||
|| ($this->gridSize && $thumbHeight > $this->gridSize)
|
||
|| ($this->gridSize && $thumbWidth > $this->gridSize);
|
||
|
||
if($useResize) {
|
||
|
||
$imageSizerOptions = $this->imageSizerOptions;
|
||
$imageSizerOptions['upscaling'] = true;
|
||
$imageSizerOptions['focus'] = false; // disable focus since we show focus from JS/CSS in admin thumbs
|
||
$adminThumbOptions = $this->wire('config')->adminThumbOptions;
|
||
$gridSize2x = $this->gridSize * 2;
|
||
// if($adminThumbOptions['scale'] === 1.0) $gridSize2x = $this->gridSize; // force non-HiDPI
|
||
|
||
// check if there is an existing thumbnail using pre-gridSize legacy settings
|
||
$h = (int) $adminThumbOptions['height'];
|
||
$w = (int) $adminThumbOptions['width'];
|
||
$f = $img->pagefiles->path() . basename($img->basename(), '.' . $img->ext()) . ".{$w}x{$h}." . $img->ext();
|
||
$exists = is_file($f);
|
||
|
||
if($exists && $remove) {
|
||
$this->wire('files')->unlink($f);
|
||
$exists = false;
|
||
}
|
||
|
||
if($exists) {
|
||
// use existing legacy thumbnail (upscaled in browser to gridSize)
|
||
$thumb = $thumb->size($w, $h, $imageSizerOptions);
|
||
if($thumbWidth > $thumbHeight) {
|
||
$thumbHeight = $this->gridSize;
|
||
$thumbWidth = 0;
|
||
} else if($thumbWidth < $thumbHeight) {
|
||
$thumbWidth = $this->gridSize;
|
||
$thumbHeight = 0;
|
||
} else {
|
||
$thumbWidth = $this->gridSize;
|
||
$thumbHeight = $this->gridSize;
|
||
}
|
||
$this->set('_hasLegacyThumbs', true);
|
||
} else {
|
||
// use new thumbnail size, 260px (scaled to 130px in output)
|
||
if($thumbWidth >= $thumbHeight) {
|
||
if($thumbHeight > $gridSize2x) {
|
||
$thumb = $thumb->height($gridSize2x, $imageSizerOptions);
|
||
$thumbHeight = $this->gridSize;
|
||
$thumbWidth = 0;
|
||
}
|
||
} else if($thumbWidth > $gridSize2x) {
|
||
$thumb = $thumb->width($gridSize2x, $imageSizerOptions);
|
||
$thumbWidth = $this->gridSize;
|
||
$thumbHeight = 0;
|
||
}
|
||
}
|
||
|
||
if($thumb->error) $error = $thumb->error;
|
||
}
|
||
|
||
if($useSizeAttributes) {
|
||
if($thumb->get('_requireHeight')) {
|
||
// _requireHeight set by InputfieldPageEditImageSelect
|
||
if(!$thumbHeight || $thumbHeight > $this->gridSize) $thumbHeight = $this->gridSize;
|
||
$attr['height'] = $thumbHeight;
|
||
} else if($thumbHeight && $thumbWidth) {
|
||
$attr['width'] = $thumbWidth;
|
||
$attr['height'] = $thumbHeight;
|
||
} else if($thumbHeight) {
|
||
if(!$thumbHeight) $thumbHeight = $this->gridSize;
|
||
$attr['height'] = $thumbHeight;
|
||
} else if($thumbWidth) {
|
||
$attr['width'] = $thumbWidth;
|
||
}
|
||
}
|
||
|
||
$attr['src'] = $thumb->URL;
|
||
$attr['alt'] = $this->wire('sanitizer')->entities1($img->description);
|
||
$attr['data-w'] = $_thumbWidth;
|
||
$attr['data-h'] = $_thumbHeight;
|
||
$attr["data-original"] = $img->URL;
|
||
|
||
$focus = $img->focus();
|
||
$attr['data-focus'] = $focus['str'];
|
||
|
||
$markup = "<img ";
|
||
foreach($attr as $key => $value) $markup .= "$key=\"$value\" ";
|
||
$markup .= " />";
|
||
|
||
$title = $img->basename() . " ({$img->width}x{$img->height}) $img->filesizeStr";
|
||
if($attr['alt']) $title .= ": $attr[alt]";
|
||
|
||
$amarkup = "<a href='$img->url' title='$title'>$markup</a>";
|
||
|
||
$a = array(
|
||
'thumb' => $thumb,
|
||
'attr' => $attr,
|
||
'markup' => $markup,
|
||
'amarkup' => $amarkup,
|
||
'error' => $error,
|
||
'title' => $title,
|
||
);
|
||
|
||
return $a;
|
||
}
|
||
|
||
/**
|
||
* Get Pagefile to pull description and tags from
|
||
*
|
||
* @param Pagefile $pagefile
|
||
* @return Pageimage|Pagefile
|
||
*
|
||
*/
|
||
protected function getMetaPagefile(Pagefile $pagefile) {
|
||
if(!$this->isAjax || !isset($_SERVER['HTTP_X_REPLACENAME'])) return $pagefile;
|
||
$metaFilename = $_SERVER['HTTP_X_REPLACENAME'];
|
||
if(strpos($metaFilename, '?')) list($metaFilename,) = explode('?', $metaFilename);
|
||
$metaFilename = $this->wire('sanitizer')->name($metaFilename);
|
||
$metaPagefile = $this->attr('value')->get($metaFilename);
|
||
if(!$metaPagefile instanceof Pagefile) $metaPagefile = $pagefile;
|
||
return $metaPagefile;
|
||
}
|
||
|
||
/**
|
||
* Render a Pageimage item
|
||
*
|
||
* @param Pagefile|Pageimage $pagefile
|
||
* @param string $id
|
||
* @param int $n
|
||
*
|
||
* @return string
|
||
*
|
||
*/
|
||
protected function ___renderItem($pagefile, $id, $n) {
|
||
|
||
$sanitizer = $this->wire('sanitizer');
|
||
$thumb = $this->getAdminThumb($pagefile, false);
|
||
$fileStats = str_replace(' ', ' ', $pagefile->filesizeStr) . ", {$pagefile->width}×{$pagefile->height} ";
|
||
|
||
foreach($pagefile->extras() as $name => $extra) {
|
||
if($extra->exists()) $fileStats .= " • $extra->filesizeStr $name ($extra->savingsPct)";
|
||
}
|
||
|
||
// $gridSize = $this->gridSize;
|
||
|
||
// <div class='gridImage__overflow' style='width: {$gridSize}px; height: {$gridSize}px'>
|
||
$out = $this->getTooltip($pagefile) . "
|
||
<div class='gridImage__overflow'>
|
||
$thumb[markup]
|
||
</div>
|
||
";
|
||
|
||
if(!$this->isEditableInRendering($pagefile)) return $out;
|
||
|
||
if($this->uploadOnlyMode) {
|
||
$out .= "
|
||
<div class='ImageData'>
|
||
<input class='InputfieldFileSort' type='text' name='sort_$id' value='$n' />
|
||
</div>
|
||
";
|
||
} else {
|
||
$buttons = $pagefile->ext() == 'svg' ? '' : $this->renderButtons($pagefile, $id, $n);
|
||
$metaPagefile = $this->getMetaPagefile($pagefile);
|
||
$description = $this->renderItemDescriptionField($metaPagefile, $id, $n);
|
||
$additional = $this->renderAdditionalFields($metaPagefile, $id, $n);
|
||
$actions = $this->renderFileActionSelect($metaPagefile, $id);
|
||
$error = '';
|
||
if($thumb['error']) {
|
||
$error = str_replace('{out}', $sanitizer->entities($thumb['error']), $this->themeSettings['error']);
|
||
}
|
||
$labels = $this->labels;
|
||
$out .= "
|
||
<div class='gridImage__hover'>
|
||
<div class='gridImage__inner'>
|
||
<label for='' class='gridImage__trash'>
|
||
<input class='gridImage__deletebox' type='checkbox' name='delete_$id' value='1' title='$labels[delete]' />
|
||
<span class='fa fa-trash-o'></span>
|
||
</label>
|
||
<a class='gridImage__edit'>
|
||
<span>$labels[edit]</span>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
";
|
||
|
||
$ext = $pagefile->ext();
|
||
$basename = $pagefile->basename(false);
|
||
$focus = $pagefile->focus();
|
||
|
||
$inputfields = $this->getItemInputfields($pagefile);
|
||
if($inputfields) $additional .= $inputfields->render();
|
||
|
||
$out .= "
|
||
<div class='ImageData'>
|
||
<h2 class='InputfieldImageEdit__name'><span contenteditable='true'>$basename</span>.$ext</h2>
|
||
<span class='InputfieldImageEdit__info'>$fileStats</span>
|
||
<div class='InputfieldImageEdit__errors'>$error</div>
|
||
<div class='InputfieldImageEdit__buttons'><small>$buttons</small> $actions</div>
|
||
<div class='InputfieldImageEdit__core'>$description</div>
|
||
<div class='InputfieldImageEdit__additional'>$additional</div>
|
||
<input class='InputfieldFileSort' type='text' name='sort_$id' value='$n' />
|
||
<input class='InputfieldFileReplace' type='hidden' name='replace_$id' />
|
||
<input class='InputfieldFileRename' type='hidden' name='rename_$id' />
|
||
<input class='InputfieldImageFocus' type='hidden' name='focus_$id' value='$focus[str]' />
|
||
</div>
|
||
";
|
||
}
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* Render a Pageimage item
|
||
*
|
||
* @deprecated No longer used by core. Left for a little while longer in case any extending module uses it.
|
||
* @param Pagefile|Pageimage $pagefile
|
||
* @param string $id
|
||
* @param int $n
|
||
* @return string
|
||
*
|
||
*/
|
||
protected function ___renderSingleItem($pagefile, $id, $n) {
|
||
|
||
$editable = $this->isEditableInRendering($pagefile);
|
||
$fileStats = str_replace(' ', ' ', $pagefile->filesizeStr) . ", {$pagefile->width}×{$pagefile->height} ";
|
||
$description = $this->wire('sanitizer')->entities($pagefile->description);
|
||
$deleteLabel = $this->labels['delete'];
|
||
|
||
if($editable) {
|
||
$buttons = $this->renderButtons($pagefile, $id, $n);
|
||
$metaPagefile = $this->getMetaPagefile($pagefile);
|
||
$descriptionField = $this->renderItemDescriptionField($metaPagefile, $id, $n);
|
||
$additional = $this->renderAdditionalFields($metaPagefile, $id, $n);
|
||
$editableOut = "
|
||
<div class='InputfieldImageEdit__buttons'>$buttons</div>
|
||
<div class='InputfieldImageEdit__core'>$descriptionField</div>
|
||
<div class='InputfieldImageEdit__additional'>$additional</div>
|
||
<input class='InputfieldFileSort' type='hidden' name='sort_$id' value='$n' />
|
||
<input class='InputfieldFileReplace' type='hidden' name='replace_$id' />
|
||
<input class='InputfieldFileRename' type='hidden' name='rename_$id' />
|
||
";
|
||
} else {
|
||
$editableOut = '';
|
||
//$editableOut = "<p>" . $this->_("Not editable.") . "</p>";
|
||
}
|
||
|
||
$trashOut = '';
|
||
if($editable && !$this->renderValueMode) $trashOut = "
|
||
<div class='InputfieldImageEdit__trash-single'>
|
||
<label for='' class='gridImage__trash gridImage__trash--single'>
|
||
<input class='gridImage__deletebox' type='checkbox' name='delete_$id' value='1' title='$deleteLabel' />
|
||
<span class='fa fa-trash-o'></span>
|
||
</label>
|
||
</div>
|
||
";
|
||
|
||
$out = "
|
||
<div class='ImageOuter InputfieldImageEdit InputfieldImageEditSingle' id='file_$pagefile->hash'>
|
||
<div class='InputfieldImageEdit__inner'>
|
||
$trashOut
|
||
<div class='InputfieldImageEdit__imagewrapper'>
|
||
<div>
|
||
<img class='InputfieldImageEdit__image' src='$pagefile->URL' alt='$description'>
|
||
</div>
|
||
</div>
|
||
<div class='InputfieldImageEdit__edit'>
|
||
<h2 class='InputfieldImageEdit__name'>$pagefile->name</h2>
|
||
<span class='InputfieldImageEdit__info'>$fileStats</span>
|
||
$editableOut
|
||
</div>
|
||
</div>
|
||
</div>
|
||
";
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* Render buttons for image edit mode
|
||
*
|
||
* #pw-hooker
|
||
*
|
||
* @param Pagefile|Pageimage $pagefile
|
||
* @param string $id
|
||
* @param int $n
|
||
* @return string
|
||
*
|
||
*/
|
||
protected function ___renderButtons($pagefile, $id, $n) {
|
||
|
||
if(!$this->useImageEditor) return '';
|
||
|
||
if($n) {} // ignore, $n is for hooks
|
||
$pageID = $pagefile->pagefiles->page->id;
|
||
$variationCount = $pagefile->variations()->count();
|
||
// if($pagefile->webp()->exists()) $variationCount++;
|
||
$editUrl = $this->getEditUrl($pagefile, $pageID);
|
||
$variationUrl = $this->getVariationUrl($pagefile, $id);
|
||
$buttonClass = $this->themeSettings['buttonClass'];
|
||
$modalButtonClass = trim("$buttonClass $this->modalClass pw-modal");
|
||
$modalAttrs = "data-buttons='#non_rte_dialog_buttons button' data-autoclose='1' data-close='#non_rte_cancel'";
|
||
$labels = $this->labels;
|
||
$out = '';
|
||
|
||
|
||
// Crop
|
||
$buttonText = str_replace('{out}', "<i class='fa fa-crop'></i> $labels[crop]", $this->themeSettings['buttonText']);
|
||
$out .= "<button type='button' data-href='$editUrl' class='InputfieldImageButtonCrop $modalButtonClass' $modalAttrs>$buttonText</button>";
|
||
|
||
// Focus
|
||
if($this->focusMode && $this->focusMode != 'off') {
|
||
$iconA = $pagefile->hasFocus ? 'fa-check-circle-o' : 'fa-circle-o';
|
||
$iconB = $pagefile->hasFocus ? 'fa-check-circle' : 'fa-dot-circle-o';
|
||
$buttonText = str_replace('{out}', "<i class='fa $iconA' data-toggle='$iconA $iconB'></i> $labels[focus]", $this->themeSettings['buttonText']);
|
||
$out .= "<button type='button' class='InputfieldImageButtonFocus $buttonClass'>$buttonText</button>";
|
||
}
|
||
|
||
// Variations
|
||
$buttonText = "<i class='fa fa-files-o'></i> $labels[variations] <span class='ui-priority-secondary'>($variationCount)</span>";
|
||
$buttonText = str_replace('{out}', $buttonText, $this->themeSettings['buttonText']);
|
||
$out .= "<button type='button' data-href='$variationUrl' class='$modalButtonClass' data-buttons='button'>$buttonText</button>";
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* Render an image action select for given Pageimage
|
||
*
|
||
* @param Pagefile $pagefile
|
||
* @param string $id
|
||
* @return string
|
||
*
|
||
*/
|
||
protected function renderFileActionSelect(Pagefile $pagefile, $id) {
|
||
|
||
if(!$this->useImageEditor) return '';
|
||
|
||
static $hooked = null;
|
||
|
||
if($hooked === null) $hooked =
|
||
$this->wire('hooks')->isHooked('InputfieldImage::getFileActions()') ||
|
||
$this->wire('hooks')->isHooked('InputfieldFile::getFileActions()');
|
||
|
||
$actions = $hooked ? $this->getFileActions($pagefile) : $this->___getFileActions($pagefile);
|
||
|
||
if(empty($actions)) return '';
|
||
|
||
$selectClass = trim($this->themeSettings['selectClass'] . ' InputfieldFileActionSelect');
|
||
/** @var Sanitizer $sanitizer */
|
||
$sanitizer = $this->wire('sanitizer');
|
||
|
||
$out =
|
||
"<select class='$selectClass' name='act_$id'>" .
|
||
"<option value=''>" . $this->_('Actions') . "</option>";
|
||
|
||
foreach($actions as $name => $label) {
|
||
$out .= "<option value='$name'>" . $sanitizer->entities1($label) . "</option>";
|
||
}
|
||
|
||
$out .= "</select> ";
|
||
$out .= "<span class='InputfieldFileActionNote detail'>" . $this->_('Action applied at save.') . "</span>";
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* Get array of actions available for given Pagefile
|
||
*
|
||
* @param Pagefile|Pageimage $pagefile
|
||
* @return array Associative array of ('action_name' => 'Action Label')
|
||
*
|
||
*/
|
||
public function ___getFileActions(Pagefile $pagefile) {
|
||
|
||
static $labels = null;
|
||
static $hasIMagick = null;
|
||
|
||
if($hasIMagick === null) {
|
||
$hasIMagick = $this->wire('modules')->isInstalled('ImageSizerEngineIMagick');
|
||
}
|
||
|
||
if($labels === null) $labels = array(
|
||
'flip' => $this->_('Flip'),
|
||
'rotate' => $this->_('Rotate'),
|
||
'dup' => $this->_('Duplicate'),
|
||
'rmv' => $this->_('Remove variations'),
|
||
'rbv' => $this->_('Rebuild variations'),
|
||
'rmf' => $this->_('Remove focus'),
|
||
'vertical' => $this->_('vert'),
|
||
'horizontal' => $this->_('horiz'),
|
||
'both' => $this->_('both'),
|
||
'cop' => $this->_('Copy'),
|
||
'pas' => $this->_('Paste'),
|
||
'x50' => $this->_('Reduce 50%'),
|
||
'bw' => $this->_('B&W'), // Black and White
|
||
'sep' => $this->_('Sepia'),
|
||
);
|
||
|
||
$actions = array(
|
||
'dup' => $labels['dup'],
|
||
);
|
||
|
||
if($this->maxFiles && count($pagefile->pagefiles) >= $this->maxFiles) {
|
||
unset($actions['dup']);
|
||
}
|
||
|
||
if($pagefile->ext() != 'svg') {
|
||
|
||
// $actions['rmv'] = $labels['rmv'];
|
||
// $actions['rbv'] = $labels['rbv'];
|
||
|
||
$actions['fv'] = "$labels[flip] $labels[vertical]";
|
||
$actions['fh'] = "$labels[flip] $labels[horizontal]";
|
||
$actions['fb'] = "$labels[flip] $labels[both]";
|
||
|
||
foreach(array(90, 180, 270, -90, -180, -270) as $degrees) {
|
||
$actions["r$degrees"] = "$labels[rotate] {$degrees}°";
|
||
}
|
||
|
||
if($hasIMagick) {
|
||
$actions['x50'] = $labels['x50'];
|
||
}
|
||
|
||
$actions['bw'] = $labels['bw'];
|
||
$actions['sep'] = $labels['sep'];
|
||
|
||
if($pagefile->hasFocus) {
|
||
$actions['rmf'] = $labels['rmf'];
|
||
}
|
||
}
|
||
|
||
return $actions;
|
||
}
|
||
|
||
/**
|
||
* Render non-editable value
|
||
*
|
||
* @return string
|
||
*
|
||
public function ___renderValue() {
|
||
$value = $this->value;
|
||
if(!$value instanceof Pageimages) return '';
|
||
$out = '';
|
||
foreach($value as $img) {
|
||
$info = $this->getAdminThumb($img);
|
||
$out .= $info['amarkup'];
|
||
|
||
}
|
||
return $out;
|
||
}
|
||
*/
|
||
|
||
/**
|
||
* Render any additional fields (for hooks)
|
||
*
|
||
* #pw-hooker
|
||
*
|
||
* @param Pageimage|Pagefile $pagefile
|
||
* @param string $id
|
||
* @param int $n
|
||
*
|
||
*/
|
||
protected function ___renderAdditionalFields($pagefile, $id, $n) { }
|
||
|
||
/*
|
||
protected function ___renderClipboard() {
|
||
$clipboard = $this->wire('session')->getFor('Pagefiles', 'clipboard');
|
||
if(!is_array($clipboard)) return '';
|
||
foreach($clipboard as $key) {
|
||
list($type, $pageID, $fieldName, $file) = explode(':', $key);
|
||
$page = $this->wire('pages')->get((int) $pageID);
|
||
$field = $this->wire('fields')->get($fieldName);
|
||
}
|
||
}
|
||
*/
|
||
|
||
/**
|
||
* Template method: allow items to be collapsed? Override default from InputfieldFile
|
||
*
|
||
* @return bool
|
||
*
|
||
*/
|
||
protected function allowCollapsedItems() {
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Configure field
|
||
*
|
||
* @return InputfieldWrapper
|
||
*
|
||
*/
|
||
public function ___getConfigInputfields() {
|
||
$inputfields = parent::___getConfigInputfields();
|
||
require_once(__DIR__ . '/config.php');
|
||
$configuration = new InputfieldImageConfiguration();
|
||
$this->wire($configuration);
|
||
$configuration->getConfigInputfields($this, $inputfields);
|
||
return $inputfields;
|
||
}
|
||
|
||
/**
|
||
* Is the given image editable during rendering?
|
||
*
|
||
* @param Pagefile|Pageimage $pagefile
|
||
* @return bool|int
|
||
*
|
||
*/
|
||
protected function isEditableInRendering($pagefile) {
|
||
//$editable = (int) $this->useImageEditor;
|
||
//if($editable) {
|
||
if($this->renderValueMode) {
|
||
$editable = false;
|
||
} else if($pagefile->ext == 'svg') {
|
||
$editable = true;
|
||
} else {
|
||
$editable = true;
|
||
}
|
||
// if(strpos($this->name, '_repeater') && preg_match('/_repeater\d+$/', $this->name)) {
|
||
// $editable = false;
|
||
// }
|
||
return $editable;
|
||
}
|
||
|
||
/**
|
||
* Get URL for viewing image variations
|
||
*
|
||
* @param Pageimage $pagefile
|
||
* @param string $id
|
||
* @return string
|
||
*
|
||
*/
|
||
protected function getVariationUrl($pagefile, $id) {
|
||
return $this->wire('config')->urls->admin . "page/image/variations/" .
|
||
"?id={$pagefile->page->id}" .
|
||
"&file=$pagefile->name" .
|
||
"&modal=1" .
|
||
"&varcnt=varcnt_$id";
|
||
}
|
||
|
||
/**
|
||
* Get variations for the given Pagefile
|
||
*
|
||
* @param Pagefile|Pageimage $pagefile
|
||
* @return array
|
||
*
|
||
*/
|
||
protected function getPagefileVariations(Pagefile $pagefile) {
|
||
return isset($this->variations[$pagefile->name]) ? $this->variations[$pagefile->name] : array();
|
||
}
|
||
|
||
/**
|
||
* Get the image editor URL
|
||
*
|
||
* @param Pagefile|Pageimage $pagefile
|
||
* @param int $pageID
|
||
* @return string
|
||
*
|
||
*/
|
||
protected function getEditUrl(Pagefile $pagefile, $pageID) {
|
||
return $this->wire('config')->urls->admin . "page/image/edit/" .
|
||
"?id=$pageID" .
|
||
"&file=$pageID,$pagefile->name" .
|
||
"&rte=0" .
|
||
"&field=$this->name";
|
||
}
|
||
|
||
/**
|
||
* Render the description field input
|
||
*
|
||
* @param Pagefile|Pageimage $pagefile
|
||
* @param string $id
|
||
* @param int $n
|
||
* @return string
|
||
*
|
||
*/
|
||
protected function renderItemDescriptionField(Pagefile $pagefile, $id, $n) {
|
||
return parent::renderItemDescriptionField($pagefile, $id, $n); // TODO: Change the autogenerated stub
|
||
}
|
||
|
||
/**
|
||
* Get the hover tooltip that appears above thumbnails
|
||
*
|
||
* @param Pageimage $pagefile
|
||
* @return string
|
||
*
|
||
*/
|
||
protected function getTooltip($pagefile) {
|
||
|
||
$data = $this->buildTooltipData($pagefile);
|
||
$rows = "";
|
||
|
||
foreach($data as $row) {
|
||
$rows .= "<tr><th>$row[0]</th><td>$row[1]</td></tr>";
|
||
}
|
||
|
||
$tooltip = "<div class='gridImage__tooltip'><table>$rows</table></div>";
|
||
|
||
return $tooltip;
|
||
}
|
||
|
||
/**
|
||
* Get the root "hasPage" being edited
|
||
*
|
||
* @return NullPage|Page
|
||
* @since 3.0.168
|
||
*
|
||
*/
|
||
protected function getRootHasPage() {
|
||
$page = $this->hasPage;
|
||
if(!$page || !$page->id) {
|
||
$process = $this->wire()->process;
|
||
$page = $process instanceof WirePageEditor ? $process->getPage() : new NullPage();
|
||
}
|
||
if(wireClassExists('RepeaterPage')) { /** @var RepeaterPage $page */
|
||
while(wireInstanceOf($page, 'RepeaterPage')) $page = $page->getForPage();
|
||
}
|
||
return $page;
|
||
}
|
||
|
||
/**
|
||
* Build data for the tooltip that appears above the thumbnails
|
||
*
|
||
* #pw-hooker
|
||
*
|
||
* @param Pagefile|Pageimage $pagefile
|
||
* @return array
|
||
*
|
||
*/
|
||
protected function ___buildTooltipData($pagefile) {
|
||
|
||
$data = array(
|
||
array(
|
||
$this->labels['dimensions'],
|
||
"{$pagefile->width}x{$pagefile->height}"
|
||
),
|
||
array(
|
||
$this->labels['filesize'],
|
||
str_replace(' ', ' ', $pagefile->filesizeStr)
|
||
),
|
||
array(
|
||
$this->labels['variations'],
|
||
count($this->getPagefileVariations($pagefile))
|
||
)
|
||
);
|
||
|
||
if(strlen($pagefile->description)) {
|
||
$data[] = array(
|
||
$this->labels['description'],
|
||
"<span class='fa fa-check'></span>"
|
||
);
|
||
}
|
||
|
||
if($this->useTags && strlen($pagefile->tags)) {
|
||
$data[] = array(
|
||
$this->labels['tags'],
|
||
"<span class='fa fa-check'></span>"
|
||
);
|
||
}
|
||
|
||
return $data;
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* Return whether or not admin thumbs should be scaled
|
||
*
|
||
* @return bool
|
||
* @deprecated
|
||
*
|
||
*/
|
||
protected function getAdminThumbScale() {
|
||
return $this->adminThumbScale > 0 && ((float) $this->adminThumbScale) != 1.0;
|
||
}
|
||
|
||
/**
|
||
* Process input
|
||
*
|
||
* @param WireInputData $input
|
||
* @return $this
|
||
*
|
||
*/
|
||
public function ___processInput(WireInputData $input) {
|
||
|
||
/** @var Sanitizer $sanitizer */
|
||
$sanitizer = $this->wire('sanitizer');
|
||
$page = $this->getRootHasPage();
|
||
|
||
if($page && $page->id) {
|
||
if(!$this->wire()->user->hasPermission('page-edit-images', $page)) $this->useImageEditor = 0;
|
||
}
|
||
|
||
parent::___processInput($input);
|
||
|
||
if((int) $this->wire('input')->post("_refresh_thumbnails_$this->name")) {
|
||
foreach($this->value as $img) {
|
||
$this->getAdminThumb($img, false, true);
|
||
}
|
||
$this->message($this->_('Recreated all legacy thumbnails') . " - $this->name");
|
||
}
|
||
|
||
if(!$this->isAjax && !$this->wire('config')->ajax) {
|
||
// process actions, but only on non-ajax save requests
|
||
foreach($this->value as $k => $pagefile) {
|
||
$id = $this->pagefileId($pagefile);
|
||
$action = $input->{"act_$id"};
|
||
if(empty($action)) continue;
|
||
$action = $sanitizer->pageName($action);
|
||
$actions = $this->getFileActions($pagefile);
|
||
if(!isset($actions[$action])) continue; // action not available for this file
|
||
$success = $this->processFileAction($pagefile, $action, $actions[$action]);
|
||
if($success === null) {
|
||
// action was not handled
|
||
}
|
||
}
|
||
}
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* Process input for a given Pageimage
|
||
*
|
||
* @param WireInputData $input
|
||
* @param Pagefile|Pageimage $pagefile
|
||
* @param int $n
|
||
* @return bool
|
||
*
|
||
*/
|
||
protected function ___processInputFile(WireInputData $input, Pagefile $pagefile, $n) {
|
||
|
||
$changed = false;
|
||
|
||
$id = $this->name . '_' . $pagefile->hash;
|
||
$key = "focus_$id";
|
||
$val = $input->$key;
|
||
|
||
if($val !== null) {
|
||
if(!strlen($val)) $val = '50 50 0';
|
||
$focus = $pagefile->focus();
|
||
if($focus['str'] !== $val) {
|
||
$pagefile->focus($val);
|
||
$changed = true;
|
||
$focus = $pagefile->focus();
|
||
$rebuild = $pagefile->rebuildVariations();
|
||
// @todo rebuild variations only for images that specify both width and height
|
||
$this->message(
|
||
"Updated focus for $pagefile to: top=$focus[top]%, left=$focus[left]%, zoom=$focus[zoom] " .
|
||
"and rebuilt " . count($rebuild['rebuilt']) . " variations",
|
||
Notice::debug
|
||
);
|
||
}
|
||
}
|
||
|
||
if(parent::___processInputFile($input, $pagefile, $n)) $changed = true;
|
||
|
||
return $changed;
|
||
}
|
||
|
||
/**
|
||
* Process an action on a Pagefile/Pageimage
|
||
*
|
||
* @param Pageimage $pagefile Image file to process
|
||
* @param string $action Action to execute
|
||
* @param string $label Label that was provided to describe action
|
||
* @return bool|null Returns true on success, false on fail, or null if action was not handled or recognized
|
||
*
|
||
*/
|
||
protected function processFileAction(Pageimage $pagefile, $action, $label) {
|
||
|
||
if(!$this->useImageEditor) return null;
|
||
|
||
$success = null;
|
||
$showSuccess = true;
|
||
$rebuildVariations = false;
|
||
|
||
if($action == 'dup') {
|
||
// duplicate image file
|
||
$_pagefile = $pagefile->pagefiles->clone($pagefile);
|
||
$success = $_pagefile ? true : false;
|
||
if($success) {
|
||
$this->wire('session')->message(
|
||
sprintf($this->_('Duplicated file %1$s => %2$s'), $pagefile->basename(), $_pagefile->basename())
|
||
);
|
||
$showSuccess = false;
|
||
}
|
||
} else if($action == 'cop') {
|
||
// copy to another page and/or field
|
||
/*
|
||
$key = 'cop:' . $pagefile->page->id . ':' . $pagefile->field->name . ':' . $pagefile->basename();
|
||
$clipboard = $this->wire('session')->getFor('Pagefiles', 'clipboard');
|
||
if(!is_array($clipboard)) $clipboard = array();
|
||
if(!in_array($key, $clipboard)) $clipboard[] = $key;
|
||
$this->wire('session')->setFor('Pagefiles', 'clipboard', $clipboard);
|
||
*/
|
||
} else if($action == 'rbv') {
|
||
// rebuild variations
|
||
} else if($action == 'rmv') {
|
||
// remove variations
|
||
} else if($action == 'rmf') {
|
||
// remove focus
|
||
$pagefile->focus(false);
|
||
$success = true;
|
||
} else {
|
||
/** @var ImageSizer $sizer Image sizer actions */
|
||
$sizer = $this->wire(new ImageSizer($pagefile->filename()));
|
||
$rebuildVariations = true;
|
||
|
||
if($action == 'fv') {
|
||
$success = $sizer->flipVertical();
|
||
} else if($action == 'fh') {
|
||
$success = $sizer->flipHorizontal();
|
||
} else if($action == 'fb') {
|
||
$success = $sizer->flipBoth();
|
||
} else if($action == 'bw') {
|
||
$success = $sizer->convertToGreyscale();
|
||
} else if($action == 'sep') {
|
||
$success = $sizer->convertToSepia();
|
||
} else if($action == 'x50') {
|
||
/** @var ImageSizerEngineIMagick $engine */
|
||
$engine = $sizer->getEngine();
|
||
if(method_exists($engine, 'reduceByHalf')) {
|
||
$success = $engine->reduceByHalf($pagefile->filename());
|
||
$rebuildVariations = false;
|
||
}
|
||
} else if(strpos($action, 'r') === 0 && preg_match('/^r(-?\d+)$/', $action, $matches)) {
|
||
$deg = (int) $matches[1];
|
||
$success = $sizer->rotate($deg);
|
||
}
|
||
|
||
}
|
||
|
||
if($success && $rebuildVariations) $pagefile->rebuildVariations();
|
||
|
||
if($success === null) {
|
||
// for hooks
|
||
$success = $this->processUnknownFileAction($pagefile, $action, $label);
|
||
}
|
||
|
||
if($success) {
|
||
$pagefile->trackChange("action-$action");
|
||
$this->trackChange('value');
|
||
}
|
||
|
||
if($success && $showSuccess) {
|
||
$this->message(sprintf($this->_('Executed action “%1$s” on file %2$s'), $label, $pagefile->basename));
|
||
} else if($success === false) {
|
||
$this->error(sprintf($this->_('Failed action “%1$s” on file %2$s'), $label, $pagefile->basename));
|
||
} else if($success === null) {
|
||
$this->error(sprintf($this->_('No handler found for action “%1$s” on file %2$s'), $label, $pagefile->basename));
|
||
}
|
||
|
||
return $success;
|
||
}
|
||
|
||
/**
|
||
* Called when an action was received that InputfieldImage does not recognize (for hooking purposes)
|
||
*
|
||
* @param Pageimage $pagefile Image file to process
|
||
* @param string $action Action to execute
|
||
* @param string $label Label that was provided to describe action
|
||
* @return bool|null Returns true on success, false on fail, or null if action was not handled or recognized
|
||
*
|
||
*/
|
||
protected function ___processUnknownFileAction(Pageimage $pagefile, $action, $label) {
|
||
if($pagefile && $action && $label) {}
|
||
return null;
|
||
}
|
||
|
||
}
|