From 0e9cc868b76890c5272128f9a0491abba98ea625 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Fri, 2 Aug 2019 10:30:57 -0400 Subject: [PATCH] Minor refactoring in ProcessPageEditImageSelect, plus update the executeVariations() method to also show webp extras when applicable. --- wire/core/Pageimage.php | 415 +----------- wire/core/PageimageVariations.php | 621 ++++++++++++++++++ wire/core/Sanitizer.php | 9 +- .../ProcessPageEditImageSelect.min.js | 2 +- .../ProcessPageEditImageSelect.module | 285 +++++--- 5 files changed, 849 insertions(+), 483 deletions(-) create mode 100644 wire/core/PageimageVariations.php diff --git a/wire/core/Pageimage.php b/wire/core/Pageimage.php index d6654b6b..70ae4acb 100644 --- a/wire/core/Pageimage.php +++ b/wire/core/Pageimage.php @@ -101,9 +101,11 @@ class Pageimage extends Pagefile { protected $original = null; /** - * Cached result of the getVariations() method + * Cached result of the variations() method * - * Don't reference this directly, because it won't be loaded unless requested, instead use the getVariations() method + * Don't reference this directly, because it won't be loaded unless requested, instead use the variations() method + * + * @var PageimageVariations * */ private $variations = null; @@ -1188,6 +1190,19 @@ class Pageimage extends Pagefile { return $this->size($adjustedWidth, $adjustedHeight, $options); } + /** + * Get the PageimageVariations helper instancd + * + * #pw-internal + * + * @return PageimageVariations + * + */ + public function variations() { + if($this->variations === null) $this->variations = new PageimageVariations($this); + return $this->variations; + } + /** * Get all size variations of this image * @@ -1198,9 +1213,11 @@ class Pageimage extends Pagefile { * * @param array $options Optional, one or more options in an associative array of the following: * - `info` (bool): when true, method returns variation info arrays rather than Pageimage objects (default=false). - * - `verbose` (bool): Return verbose array of info. If false, returns only filenames (default=true). + * - `verbose` (bool|int): Return verbose array of info. If false, returns only filenames (default=true). * This option does nothing unless the `info` option is true. Also note that if verbose is false, then all options - * following this one no longer apply (since it is no longer returning width/height info). + * following this one no longer apply (since it is no longer returning width/height info). + * When integer 1, returned info array also includes Pageimage variation options in 'pageimage' index of + * returned arrays (since 3.0.137). * - `width` (int): only variations with given width will be returned * - `height` (int): only variations with given height will be returned * - `width>=` (int): only variations with width greater than or equal to given will be returned @@ -1219,97 +1236,7 @@ class Pageimage extends Pagefile { * */ public function getVariations(array $options = array()) { - - if(!is_null($this->variations) && empty($options)) return $this->variations; - - $defaults = array( - 'info' => false, - 'verbose' => true, - ); - - $options = array_merge($defaults, $options); - if(!$options['verbose'] && !$options['info']) $options['verbose'] = true; // non-verbose only allowed if info==true - $variations = $options['info'] ? null : $this->wire(new Pageimages($this->pagefiles->page)); - $dir = new \DirectoryIterator($this->pagefiles->path); - $infos = array(); - - // if suffix or noSuffix option contains space, convert it to suffixes or noSuffixes array option - foreach(array('suffix', 'noSuffix') as $key) { - if(!isset($options[$key])) continue; - if(strpos(trim($options['suffix']), ' ') === false) continue; - $keyPlural = $key . 'es'; - $value = isset($options[$keyPlural]) ? $options[$keyPlural] : array(); - $options[$keyPlural] = array_merge($value, explode(' ', trim($options[$key]))); - unset($options[$key]); - } - - foreach($dir as $file) { - - if($file->isDir() || $file->isDot()) continue; - - $info = $this->isVariation($file->getFilename(), array('verbose' => $options['verbose'])); - if(!$info) continue; - - if($options['info'] && !$options['verbose']) { - $infos[] = $info; - continue; - } - - $allow = true; - - foreach($options as $option => $value) { - switch($option) { - case 'width': $allow = $info['width'] == $value; break; - case 'width>=': $allow = $info['width'] >= $value; break; - case 'width<=': $allow = $info['width'] <= $value; break; - case 'height': $allow = $info['height'] == $value; break; - case 'height>=': $allow = $info['height'] >= $value; break; - case 'height<=': $allow = $info['height'] <= $value; break; - case 'name': $allow = stripos($file->getBasename(), $value) !== false; break; - case 'noName': $allow = stripos($file->getBasename(), $value) === false; break; - case 'regexName': $allow = preg_match($value, $file->getBasename()); break; - case 'suffix': $allow = in_array($value, $info['suffix']); break; - case 'noSuffix': $allow = !in_array($value, $info['suffix']); break; - case 'suffixes': - // any one of given suffixes will allow the variation - $allow = false; - foreach($value as $suffix) { - $allow = in_array($suffix, $info['suffix']); - if($allow) break; - } - break; - case 'noSuffixes': - // any one of the given suffixes will disallow the variation - $allow = true; - foreach($value as $noSuffix) { - if(!in_array($noSuffix, $info['suffix'])) continue; - $allow = false; - break; - } - break; - } - if(!$allow) break; - } - - if(!$allow) continue; - - if(!empty($options['info'])) { - $infos[$file->getBasename()] = $info; - } else { - $pageimage = clone $this; - $pathname = $file->getPathname(); - if(DIRECTORY_SEPARATOR != '/') $pathname = str_replace(DIRECTORY_SEPARATOR, '/', $pathname); - $pageimage->setFilename($pathname); - $pageimage->setOriginal($this); - $variations->add($pageimage); - } - } - - if(!empty($options['info'])) return $infos; - - if(empty($options)) $this->variations = $variations; - - return $variations; + return $this->variations()->find($options); } /** @@ -1343,118 +1270,7 @@ class Pageimage extends Pagefile { * */ public function ___rebuildVariations($mode = 0, array $suffix = array(), array $options = array()) { - - $skipped = array(); - $rebuilt = array(); - $errors = array(); - $reasons = array(); - $options['forceNew'] = true; - - foreach($this->getVariations(array('info' => true)) as $info) { - - $o = $options; - unset($o['cropping']); - $skip = false; - $name = $info['name']; - - if($info['crop'] && !$mode) { - // skip crops when mode is 0 - $reasons[$name] = "$name: Crop is $info[crop] and mode is 0"; - $skip = true; - - } else if(count($info['suffix'])) { - // check suffixes - foreach($info['suffix'] as $k => $s) { - if($s === 'hidpi') { - // allow hidpi to passthru - $o['hidpi'] = true; - } else if($s == 'is') { - // this is a known core suffix that we allow - } else if(strpos($s, 'cropx') === 0) { - // skip cropx suffix (already known from $info[crop]) - unset($info['suffix'][$k]); - continue; - } else if(strpos($s, 'pid') === 0 && preg_match('/^pid\d+$/', $s)) { - // allow pid123 to pass through - } else if(in_array($s, $suffix)) { - // suffix is one provided in $suffix argument - if($mode == 2) { - // mode 2 where $suffix is an exclusion list - $skip = true; - $reasons[$name] = "$name: Suffix '$s' is one provided in exclusion list (mode==true)"; - } else { - // allowed suffix - } - } else { - // image has suffix not specified in $suffix argument - if($mode == 0 || $mode == 1 || $mode == 3) { - $skip = true; - $reasons[$name] = "$name: Image has suffix '$s' not provided in allowed list: " . implode(', ', $suffix); - } - } - } - } - - if($mode == 4 && ($info['width'] == 0 || $info['height'] == 0)) { - // skip images that don't specify both width and height - $skip = true; - } - - if($skip) { - $skipped[] = $name; - continue; - } - - // rebuild the variation - $o['forceNew'] = true; - $o['suffix'] = $info['suffix']; - if(is_file($info['path'])) $this->wire('files')->unlink($info['path'], true); - - /* - if(!$info['width'] && $info['actualWidth']) { - $info['width'] = $info['actualWidth']; - $options['nameWidth'] = 0; - } - if(!$info['height'] && $info['actualHeight']) { - $info['height'] = $info['actualHeight']; - $options['nameHeight'] = 0; - } - */ - - if($info['crop'] && preg_match('/^x(\d+)y(\d+)$/', $info['crop'], $matches)) { - // dimensional cropping info contained in filename - $cropX = (int) $matches[1]; - $cropY = (int) $matches[2]; - $variation = $this->crop($cropX, $cropY, $info['width'], $info['height'], $options); - - } else if($info['crop']) { - // direct cropping info contained in filename - $options['cropping'] = $info['crop']; - $variation = $this->size($info['width'], $info['height'], $options); - - } else if($this->hasFocus) { - // crop to focus area, which the size() method will determine on its own - $variation = $this->size($info['width'], $info['height'], $options); - - } else { - // no crop, no focus, just resize - $variation = $this->size($info['width'], $info['height'], $options); - } - - if($variation) { - if($variation->name != $name) rename($variation->filename(), $info['path']); - $rebuilt[] = $name; - } else { - $errors[] = $name; - } - } - - return array( - 'rebuilt' => $rebuilt, - 'skipped' => $skipped, - 'reasons' => $reasons, - 'errors' => $errors - ); + return $this->variations()->rebuild($mode, $suffix, $options); } /** @@ -1488,153 +1304,7 @@ class Pageimage extends Pagefile { * */ public function ___isVariation($basename, $options = array()) { - - $defaults = array( - 'allowSelf' => false, - 'verbose' => true, - ); - - if(!is_array($options)) $options = array('allowSelf' => (bool) $options); - $options = array_merge($defaults, $options); - - static $level = 0; - $variationName = basename($basename); - $originalName = $this->basename; - $info = array(); - - // that that everything from the beginning up to the first period is exactly the same - // otherwise, they are different source files - $test1 = substr($variationName, 0, strpos($variationName, '.')); - $test2 = substr($originalName, 0, strpos($originalName, '.')); - if($test1 !== $test2) return false; - - // remove extension from originalName - $originalName = basename($originalName, "." . $this->ext()); - - // if originalName is already a variation filename, remove the variation info from it. - // reduce to original name, i.e. all info after (and including) a period - if(strpos($originalName, '.') && preg_match('/^([^.]+)\.(?:\d+x\d+|-[_a-z0-9]+)/', $originalName, $matches)) { - $originalName = $matches[1]; - } - - // if file is the same as the original, then it's not a variation - if(!$options['allowSelf'] && $variationName == $this->basename) return false; - - // if file doesn't start with the original name then it's not a variation - if(strpos($variationName, $originalName) !== 0) return false; - - // get down to the meat and the base - // meat is the part of the filename containing variation info like dimensions, crop, suffix, etc. - // base is the part before that, which may include parent meat - $pos = strrpos($variationName, '.'); // get extension - $ext = substr($variationName, $pos); - $base = substr($variationName, 0, $pos); // get without extension - $rpos = strrpos($base, '.'); // get last data chunk after dot - if($rpos !== false) { - $meat = substr($base, $rpos+1) . $ext; // the part of the filename we're interested in - $base = substr($base, 0, $rpos); // the rest of the filename - $parent = "$base." . $this->ext(); - } else { - $meat = $variationName; - $parent = null; - } - - // identify parent and any parent suffixes - $suffixAll = array(); - if($options['verbose']) { - while(($pos = strrpos($base, '.')) !== false) { - $part = substr($base, $pos + 1); - $base = substr($base, 0, $pos); - while(($rpos = strrpos($part, '-')) !== false) { - $suffixAll[] = substr($part, $rpos + 1); - $part = substr($part, 0, $rpos); - } - } - } - - // variation name with size dimensions and optionally suffix - $re1 = '/^' . - '(\d+)x(\d+)' . // 50x50 - '([pd]\d+x\d+|[a-z]{1,2})?' . // nw or p30x40 or d30x40 - '(?:-([-_a-z0-9]+))?' . // -suffix1 or -suffix1-suffix2, etc. - '\.' . $this->ext() . // .jpg - '$/'; - - // variation name with suffix only - $re2 = '/^' . - '-([-_a-z0-9]+)' . // suffix1 or suffix1-suffix2, etc. - '(?:\.' . // optional extras for dimensions/crop, starts with period - '(\d+)x(\d+)' . // optional 50x50 - '([pd]\d+x\d+|[a-z]{1,2})?' . // nw or p30x40 or d30x40 - ')?' . - '\.' . $this->ext() . // .jpg - '$/'; - - // if regex does not match, return false - if(preg_match($re1, $meat, $matches)) { - // this is a variation with dimensions, return array of info - if($options['verbose']) $info = array( - 'name' => $basename, - 'url' => $this->pagefiles->url . $basename, - 'path' => $this->pagefiles->path . $basename, - 'original' => $originalName . '.' . $this->ext(), - 'width' => (int) $matches[1], - 'height' => (int) $matches[2], - 'crop' => (isset($matches[3]) ? $matches[3] : ''), - 'suffix' => (isset($matches[4]) ? explode('-', $matches[4]) : array()), - ); - - } else if(preg_match($re2, $meat, $matches)) { - - // this is a variation only with suffix - if($options['verbose']) $info = array( - 'name' => $basename, - 'url' => $this->pagefiles->url . $basename, - 'path' => $this->pagefiles->path . $basename, - 'original' => $originalName . '.' . $this->ext(), - 'width' => (isset($matches[2]) ? (int) $matches[2] : 0), - 'height' => (isset($matches[3]) ? (int) $matches[3] : 0), - 'crop' => (isset($matches[4]) ? $matches[4] : ''), - 'suffix' => explode('-', $matches[1]), - ); - - } else { - return false; - } - - // if not in verbose mode, just return variation basename - if(!$options['verbose']) return $variationName; - - $actualInfo = $this->getImageInfo($info['path']); - $info['actualWidth'] = $actualInfo['width']; - $info['actualHeight'] = $actualInfo['height']; - $info['hidpiWidth'] = $this->hidpiWidth(0, $info['actualWidth']); - $info['hidpiHeight'] = $this->hidpiWidth(0, $info['actualHeight']); - - if(empty($info['crop'])) { - // attempt to extract crop info from suffix - foreach($info['suffix'] as $key => $suffix) { - if(strpos($suffix, 'cropx') === 0) { - $info['crop'] = ltrim($suffix, 'crop'); // i.e. x123y456 - } - } - } - - if($parent) { - // suffixAll includes all parent suffix in addition to current suffix - if(!$level) $info['suffixAll'] = array_unique(array_merge($info['suffix'], $suffixAll)); - // parent property is set with more variation info, when available - $level++; - $info['parentName'] = $parent; - $info['parent'] = $this->isVariation($parent); - $level--; - } - - if(!$this->original) { - $this->original = $this->pagefiles->get($info['original']); - } - - return $info; + return $this->variations()->getInfo($basename, $options); } /** @@ -1649,42 +1319,7 @@ class Pageimage extends Pagefile { * */ public function removeVariations(array $options = array()) { - - $defaults = array( - 'dryRun' => false, - 'getFiles' => false - ); - - $variations = $this->getVariations($options); - if(!empty($options['dryrun'])) $defaults['dryRun'] = $options['dryrun']; // case insurance - $options = array_merge($defaults, $options); // placement after getVariations() intended - $deletedFiles = array(); - - /** @var WireFileTools $files */ - $files = $this->wire('files'); - - foreach($variations as $variation) { - $filename = $variation->filename; - if(!is_file($filename)) continue; - if($options['dryRun']) { - $success = true; - } else { - $success = $files->unlink($filename, true); - } - if($success) $deletedFiles[] = $filename; - - foreach($this->extras() as $extra) { - if($options['dryRun']) { - $deletedFiles[] = $extra->filename(); - } else if($extra->unlink()) { - $deletedFiles[] = $extra->filename(); - } - } - } - - if(!$options['dryRun']) $this->variations = null; - - return ($options['dryRun'] || $options['getFiles'] ? $deletedFiles : $this); + return $this->variations()->remove($options); } /** diff --git a/wire/core/PageimageVariations.php b/wire/core/PageimageVariations.php new file mode 100644 index 00000000..d00927c6 --- /dev/null +++ b/wire/core/PageimageVariations.php @@ -0,0 +1,621 @@ +pageimage = $pageimage; + $this->pagefiles = $pageimage->pagefiles; + $pageimage->wire($this); + parent::__construct(); + } + + public function getIterator() { + return $this->find(); + } + + /** + * Return a total or filtered count of variations + * + * This method is also here to implement the Countable interface. + * + * @param array $options See options for find() emthod + * @return int + * + */ + public function count($options = array()) { + if($this->variations) { + $count = $this->variations->count(); + } else { + $options['count'] = true; + $count = $this->find($options); + } + return $count; + } + + /** + * Given a file name (basename), return array of info if this is a variation for this instance’s file, or false if not. + * + * Returned array includes the following indexes: + * + * - `original` (string): Original basename + * - `url` (string): URL to image + * - `path` (string): Full path + filename to image + * - `width` (int): Specified width in filename + * - `height` (int): Specified height in filename + * - `actualWidth` (int): Actual width when checked manually + * - `actualHeight` (int): Acual height when checked manually + * - `crop` (string): Cropping info string or blank if none + * - `suffix` (array): Array of suffixes + * + * The following are only present if variation is based on another variation, and thus has a parent variation + * image between it and the original: + * + * - `suffixAll` (array): Contains all suffixes including among parent variations + * - `parent` (array): Variation info array of direct parent variation file + * + * @param string $basename Filename to check (basename, which excludes path) + * @param array|bool $options Array of options to modify behavior, or boolean to only specify `allowSelf` option. + * - `allowSelf` (bool): When true, it will return variation info even if same as current Pageimage. (default=false) + * - `verbose` (bool): Return verbose array of info? If false, just returns basename (string) or false. (default=true) + * @return bool|string|array Returns false if not a variation, or array (verbose) or string (non-verbose) of info if it is. + * + */ + public function getInfo($basename, $options = array()) { + + $defaults = array( + 'allowSelf' => false, + 'verbose' => true, + ); + + if(!is_array($options)) $options = array('allowSelf' => (bool) $options); + $options = array_merge($defaults, $options); + + static $level = 0; + $variationName = basename($basename); + $originalName = $this->pageimage->basename; + + // that that everything from the beginning up to the first period is exactly the same + // otherwise, they are different source files + $test1 = substr($variationName, 0, strpos($variationName, '.')); + $test2 = substr($originalName, 0, strpos($originalName, '.')); + if($test1 !== $test2) return false; + + // remove extension from originalName + $originalName = basename($originalName, "." . $this->pageimage->ext()); + + // if originalName is already a variation filename, remove the variation info from it. + // reduce to original name, i.e. all info after (and including) a period + if(strpos($originalName, '.') && preg_match('/^([^.]+)\.(?:\d+x\d+|-[_a-z0-9]+)/', $originalName, $matches)) { + $originalName = $matches[1]; + } + + // if file is the same as the original, then it's not a variation + if(!$options['allowSelf'] && $variationName == $this->pageimage->basename) return false; + + // if file doesn't start with the original name then it's not a variation + if(strpos($variationName, $originalName) !== 0) return false; + + // get down to the meat and the base + // meat is the part of the filename containing variation info like dimensions, crop, suffix, etc. + // base is the part before that, which may include parent meat + $pos = strrpos($variationName, '.'); // get extension + $ext = substr($variationName, $pos); + $base = substr($variationName, 0, $pos); // get without extension + $rpos = strrpos($base, '.'); // get last data chunk after dot + + if($rpos !== false) { + $meat = substr($base, $rpos+1) . $ext; // the part of the filename we're interested in + $base = substr($base, 0, $rpos); // the rest of the filename + $parent = "$base." . $this->pageimage->ext(); + } else { + $meat = $variationName; + $parent = null; + } + + // identify parent and any parent suffixes + $suffixAll = array(); + if($options['verbose']) { + while(($pos = strrpos($base, '.')) !== false) { + $part = substr($base, $pos + 1); + $base = substr($base, 0, $pos); + while(($rpos = strrpos($part, '-')) !== false) { + $suffixAll[] = substr($part, $rpos + 1); + $part = substr($part, 0, $rpos); + } + } + } + + // variation name with size dimensions and optionally suffix + $re1 = '/^' . + '(\d+)x(\d+)' . // 50x50 + '([pd]\d+x\d+|[a-z]{1,2})?' . // nw or p30x40 or d30x40 + '(?:-([-_a-z0-9]+))?' . // -suffix1 or -suffix1-suffix2, etc. + '\.' . $this->pageimage->ext() . + '$/'; + + // variation name with suffix only + $re2 = '/^' . + '-([-_a-z0-9]+)' . // suffix1 or suffix1-suffix2, etc. + '(?:\.' . // optional extras for dimensions/crop, starts with period + '(\d+)x(\d+)' . // optional 50x50 + '([pd]\d+x\d+|[a-z]{1,2})?' . // nw or p30x40 or d30x40 + ')?' . + '\.' . $this->pageimage->ext() . + '$/'; + + // if regex does not match, return false + if(preg_match($re1, $meat, $matches)) { + + // this is a variation with dimensions, return array of info + $width = (int) $matches[1]; + $height = (int) $matches[2]; + $crop = isset($matches[3]) ? $matches[3] : ''; + $suffix = isset($matches[4]) ? explode('-', $matches[4]) : array(); + + } else if(preg_match($re2, $meat, $matches)) { + + // this is a variation only with suffix + $width = isset($matches[2]) ? (int) $matches[2] : 0; + $height = isset($matches[3]) ? (int) $matches[3] : 0; + $crop = isset($matches[4]) ? $matches[4] : ''; + $suffix = explode('-', $matches[1]); + + } else { + return false; + } + + // if not in verbose mode, just return variation basename + if(!$options['verbose']) return $variationName; + + $path = $this->pagefiles->path . $basename; + $actualInfo = $this->pageimage->getImageInfo($path); + + $info = array( + 'name' => $basename, + 'url' => $this->pagefiles->url . $basename, + 'path' => $path, + 'original' => $originalName . '.' . $this->pageimage->ext(), + 'width' => $width, + 'height' => $height, + 'crop' => $crop, + 'suffix' => $suffix, + 'suffixAll' => array(), // present only when image has a parent variation + 'actualWidth' => $actualInfo['width'], + 'actualHeight' => $actualInfo['height'], + 'hidpiWidth' => $this->pageimage->hidpiWidth(0, $actualInfo['width']), + 'hidpiHeight' => $this->pageimage->hidpiWidth(0, $actualInfo['height']), + 'parentName' => '', // present only when image has a parent variation + 'parent' => null, // present only when image has a parent variation + 'webpUrl' => '', + 'webpPath' => '', + ); + + foreach($this->pageimage->extras() as $name => $extra) { + if(!$extra->exists()) continue; + $info["{$name}Url"] = $extra->url(false); + $info["{$name}Path"] = $extra->filename(); + } + + if(empty($info['crop'])) { + // attempt to extract crop info from suffix + foreach($info['suffix'] as $key => $suffix) { + if(strpos($suffix, 'cropx') === 0) { + $info['crop'] = ltrim($suffix, 'crop'); // i.e. x123y456 + } + } + } + + if($parent) { + // suffixAll includes all parent suffix in addition to current suffix + if(!$level) $info['suffixAll'] = array_unique(array_merge($info['suffix'], $suffixAll)); + // parent property is set with more variation info, when available + $level++; + $info['parentName'] = $parent; + $info['parent'] = $this->getInfo($parent); + $level--; + } else { + unset($info['parent'], $info['parentName'], $info['suffixAll']); + } + + if(!$this->pageimage->original) { + $this->pageimage->setOriginal($this->pagefiles->get($info['original'])); + } + + return $info; + } + + + /** + * Get all size variations of this image + * + * This is useful after a delete of an image (for example). This method can be used to track down all the + * child files that also need to be deleted. + * + * @param array $options Optional, one or more options in an associative array of the following: + * - `info` (bool): when true, method returns variation info arrays rather than Pageimage objects (default=false). + * - `count` (bool): when true, only a count of variations is returned (default=false). + * - `verbose` (bool|int): Return verbose array of info. If false, returns only filenames (default=true). + * This option does nothing unless the `info` option is true. Also note that if verbose is false, then all options + * following this one no longer apply (since it is no longer returning width/height info). + * When integer 1, returned info array also includes Pageimage variation options in 'pageimage' index of + * returned arrays (since 3.0.137). + * - `width` (int): only variations with given width will be returned + * - `height` (int): only variations with given height will be returned + * - `width>=` (int): only variations with width greater than or equal to given will be returned + * - `height>=` (int): only variations with height greater than or equal to given will be returned + * - `width<=` (int): only variations with width less than or equal to given will be returned + * - `height<=` (int): only variations with height less than or equal to given will be returned + * - `suffix` (string): only variations having the given suffix will be returned + * - `suffixes` (array): only variations having one of the given suffixes will be returned + * - `noSuffix` (string): exclude variations having this suffix + * - `noSuffixes` (array): exclude variations having any of these suffixes + * - `name` (string): only variations containing this text in filename will be returned (case insensitive) + * - `noName` (string): only variations NOT containing this text in filename will be returned (case insensitive) + * - `regexName` (string): only variations that match this PCRE regex will be returned + * @return Pageimages|array|int Returns Pageimages array of Pageimage instances. + * Only returns regular array if provided `$options['info']` is true. + * Returns integer if count option is specified. + * + */ + public function find(array $options = array()) { + + if(!is_null($this->variations) && empty($options)) return $this->variations; + + $defaults = array( + 'info' => false, + 'verbose' => true, + 'count' => false, + ); + + $options = array_merge($defaults, $options); + if($options['count']) { + $options['verbose'] = false; + $options['info'] = false; + } else if(!$options['verbose'] && !$options['info']) { + $options['verbose'] = true; // non-verbose only allowed if info==true + } + + $variations = null; + $dir = new \DirectoryIterator($this->pagefiles->path); + $infos = array(); + $count = 0; + + if(!$options['info'] && $options['count']) { + $variations = $this->wire(new Pageimages($this->pagefiles->page)); + } + + // if suffix or noSuffix option contains space, convert it to suffixes or noSuffixes array option + foreach(array('suffix', 'noSuffix') as $key) { + if(!isset($options[$key])) continue; + if(strpos(trim($options['suffix']), ' ') === false) continue; + $keyPlural = $key . 'es'; + $value = isset($options[$keyPlural]) ? $options[$keyPlural] : array(); + $options[$keyPlural] = array_merge($value, explode(' ', trim($options[$key]))); + unset($options[$key]); + } + + foreach($dir as $file) { + + if($file->isDir() || $file->isDot()) continue; + + $info = $this->getInfo($file->getFilename(), array('verbose' => $options['verbose'])); + if(!$info) continue; + + if($options['info'] && !$options['verbose']) { + $infos[] = $info; + continue; + } + + $allow = true; + + foreach($options as $option => $value) { + switch($option) { + case 'width': $allow = $info['width'] == $value; break; + case 'width>=': $allow = $info['width'] >= $value; break; + case 'width<=': $allow = $info['width'] <= $value; break; + case 'height': $allow = $info['height'] == $value; break; + case 'height>=': $allow = $info['height'] >= $value; break; + case 'height<=': $allow = $info['height'] <= $value; break; + case 'name': $allow = stripos($file->getBasename(), $value) !== false; break; + case 'noName': $allow = stripos($file->getBasename(), $value) === false; break; + case 'regexName': $allow = preg_match($value, $file->getBasename()); break; + case 'suffix': $allow = in_array($value, $info['suffix']); break; + case 'noSuffix': $allow = !in_array($value, $info['suffix']); break; + case 'suffixes': + // any one of given suffixes will allow the variation + $allow = false; + foreach($value as $suffix) { + $allow = in_array($suffix, $info['suffix']); + if($allow) break; + } + break; + case 'noSuffixes': + // any one of the given suffixes will disallow the variation + $allow = true; + foreach($value as $noSuffix) { + if(!in_array($noSuffix, $info['suffix'])) continue; + $allow = false; + break; + } + break; + } + if(!$allow) break; + } + + if(!$allow) continue; + + $basename = $file->getBasename(); + + if($options['count']) { + $count++; + continue; + } + + if(empty($options['info']) || $options['verbose'] === 1) { + $pageimage = clone $this->pageimage; + $pathname = $file->getPathname(); + if(DIRECTORY_SEPARATOR != '/') $pathname = str_replace(DIRECTORY_SEPARATOR, '/', $pathname); + $pageimage->setFilename($pathname); + $pageimage->setOriginal($this->pageimage); + if($options['verbose'] === 1) { + $info['pageimage'] = $pageimage; + } else { + $variations->add($pageimage); + } + } + if(!empty($options['info'])) { + $infos[$basename] = $info; + } + } + + if($options['count']) return $count; + if(!empty($options['info'])) return $infos; + if(empty($options)) $this->variations = $variations; + + return $variations; + } + + /** + * Rebuilds variations of this image + * + * By default, this excludes crops and images with suffixes, but can be overridden with the `$mode` and `$suffix` arguments. + * + * **Options for $mode argument** + * + * - `0` (int): Rebuild only non-suffix, non-crop variations, and those w/suffix specified in $suffix argument. ($suffix is INCLUSION list) + * - `1` (int): Rebuild all non-suffix variations, and those w/suffix specifed in $suffix argument. ($suffix is INCLUSION list) + * - `2` (int): Rebuild all variations, except those with suffix specified in $suffix argument. ($suffix is EXCLUSION list) + * - `3` (int): Rebuild only variations specified in the $suffix argument. ($suffix is ONLY-INCLUSION list) + * - `4` (int): Rebuild only non-proportional, non-crop variations (variations that specify both width and height) + * + * Mode 0 is the only truly safe mode, as in any other mode there are possibilities that the resulting + * rebuild of the variation may not be exactly what was intended. The issues with other modes primarily + * arise when the suffix means something about the technical details of the produced image, or when + * rebuilding variations that include crops from an original image that has since changed dimensions or crops. + * + * @param int $mode See the options for $mode argument above (default=0). + * @param array $suffix Optional argument to specify suffixes to include or exclude (according to $mode). + * @param array $options See $options for `Pageimage::size()` for details. + * @return array Returns an associative array with with the following indexes: + * - `rebuilt` (array): Names of files that were rebuilt. + * - `skipped` (array): Names of files that were skipped. + * - `errors` (array): Names of files that had errors. + * - `reasons` (array): Reasons why files were skipped or had errors, associative array indexed by file name. + * + */ + public function rebuild($mode = 0, array $suffix = array(), array $options = array()) { + + $skipped = array(); + $rebuilt = array(); + $errors = array(); + $reasons = array(); + $options['forceNew'] = true; + + foreach($this->find(array('info' => true)) as $info) { + + $o = $options; + unset($o['cropping']); + $skip = false; + $name = $info['name']; + $hadWebp = false; + + if($info['crop'] && !$mode) { + // skip crops when mode is 0 + $reasons[$name] = "$name: Crop is $info[crop] and mode is 0"; + $skip = true; + + } else if(count($info['suffix'])) { + // check suffixes + foreach($info['suffix'] as $k => $s) { + if($s === 'hidpi') { + // allow hidpi to passthru + $o['hidpi'] = true; + } else if($s == 'is') { + // this is a known core suffix that we allow + } else if(strpos($s, 'cropx') === 0) { + // skip cropx suffix (already known from $info[crop]) + unset($info['suffix'][$k]); + continue; + } else if(strpos($s, 'pid') === 0 && preg_match('/^pid\d+$/', $s)) { + // allow pid123 to pass through + } else if(in_array($s, $suffix)) { + // suffix is one provided in $suffix argument + if($mode == 2) { + // mode 2 where $suffix is an exclusion list + $skip = true; + $reasons[$name] = "$name: Suffix '$s' is one provided in exclusion list (mode==true)"; + } else { + // allowed suffix + } + } else { + // image has suffix not specified in $suffix argument + if($mode == 0 || $mode == 1 || $mode == 3) { + $skip = true; + $reasons[$name] = "$name: Image has suffix '$s' not provided in allowed list: " . implode(', ', $suffix); + } + } + } + } + + if($mode == 4 && ($info['width'] == 0 || $info['height'] == 0)) { + // skip images that don't specify both width and height + $skip = true; + } + + if($skip) { + $skipped[] = $name; + continue; + } + + // rebuild the variation + $o['forceNew'] = true; + $o['suffix'] = $info['suffix']; + + if(is_file($info['path'])) { + $this->wire('files')->unlink($info['path'], true); + if(!empty($info['webpPath']) && is_file($info['webpPath'])) { + $this->wire('files')->unlink($info['webpPath'], true); + $hadWebp = true; + } + } + + /* + if(!$info['width'] && $info['actualWidth']) { + $info['width'] = $info['actualWidth']; + $options['nameWidth'] = 0; + } + if(!$info['height'] && $info['actualHeight']) { + $info['height'] = $info['actualHeight']; + $options['nameHeight'] = 0; + } + */ + + if($info['crop'] && preg_match('/^x(\d+)y(\d+)$/', $info['crop'], $matches)) { + // dimensional cropping info contained in filename + $cropX = (int) $matches[1]; + $cropY = (int) $matches[2]; + $variation = $this->pageimage->crop($cropX, $cropY, $info['width'], $info['height'], $options); + + } else if($info['crop']) { + // direct cropping info contained in filename + $options['cropping'] = $info['crop']; + $variation = $this->pageimage->size($info['width'], $info['height'], $options); + + } else if($this->pageimage->hasFocus) { + // crop to focus area, which the size() method will determine on its own + $variation = $this->pageimage->size($info['width'], $info['height'], $options); + + } else { + // no crop, no focus, just resize + $variation = $this->pageimage->size($info['width'], $info['height'], $options); + } + + if($variation) { + if($variation->name != $name) { + rename($variation->filename(), $info['path']); + $variation->data('basename', $name); + } + $rebuilt[] = $name; + if($hadWebp) { + // forces create of webp version + $webpName = basename($variation->webp()->url()); + if($webpName) $rebuilt[] = $webpName; + } + } else { + $errors[] = $name; + } + } + + return array( + 'rebuilt' => $rebuilt, + 'skipped' => $skipped, + 'reasons' => $reasons, + 'errors' => $errors + ); + } + + /** + * Delete all the alternate sizes associated with this Pageimage + * + * @param array $options See options for getVariations() method to limit what variations are removed, plus these: + * - `dryRun` (bool): Do not remove now and instead only return the filenames of variations that would be deleted (default=false). + * - `getFiles` (bool): Return deleted filenames? Also assumed if the test option is used (default=false). + * @return $this|array Returns $this by default, or array of deleted filenames if the `getFiles` option is specified + * + */ + public function remove(array $options = array()) { + + $defaults = array( + 'dryRun' => false, + 'getFiles' => false + ); + + $variations = $this->find($options); + if(!empty($options['dryrun'])) $defaults['dryRun'] = $options['dryrun']; // case insurance + $options = array_merge($defaults, $options); // placement after getVariations() intended + $deletedFiles = array(); + + /** @var WireFileTools $files */ + $files = $this->wire('files'); + + foreach($variations as $variation) { + $filename = $variation->filename; + if(!is_file($filename)) continue; + if($options['dryRun']) { + $success = true; + } else { + $success = $files->unlink($filename, true); + } + if($success) $deletedFiles[] = $filename; + + foreach($this->pageimage->extras() as $extra) { + if(!$extra->exists()) continue; + if($options['dryRun']) { + $deletedFiles[] = $extra->filename(); + } else if($extra->unlink()) { + $deletedFiles[] = $extra->filename(); + } + } + } + + if(!$options['dryRun']) $this->variations = null; + + return ($options['dryRun'] || $options['getFiles'] ? $deletedFiles : $this); + } +} \ No newline at end of file diff --git a/wire/core/Sanitizer.php b/wire/core/Sanitizer.php index 9d5eeb58..23ec6773 100644 --- a/wire/core/Sanitizer.php +++ b/wire/core/Sanitizer.php @@ -923,10 +923,13 @@ class Sanitizer extends Wire { $value = "$basename.$pathinfo[extension]"; } - return $this->name($value, $beautify, $maxLength, '_', array( + $value = $this->name($value, $beautify, $maxLength, '_', array( 'allowAdjacentExtras' => true, // language translation filenames require doubled "--" chars, others may too - ) - ); + )); + + while(strpos($value, '..') !== false) $value = str_replace('..', '', $value); + + return $value; } /** diff --git a/wire/modules/Process/ProcessPageEditImageSelect/ProcessPageEditImageSelect.min.js b/wire/modules/Process/ProcessPageEditImageSelect/ProcessPageEditImageSelect.min.js index 46fcc6ad..64580005 100644 --- a/wire/modules/Process/ProcessPageEditImageSelect/ProcessPageEditImageSelect.min.js +++ b/wire/modules/Process/ProcessPageEditImageSelect/ProcessPageEditImageSelect.min.js @@ -1 +1 @@ -function enablePWImageDialogButtons(){var a=parent.jQuery(".ui-dialog-buttonpane");a.find("button").button("enable");return}function disablePWImageDialogButtons(){var a=parent.jQuery(".ui-dialog-buttonpane");a.find("button").button("disable");return}function closePWImageDialog(){parent.jQuery("iframe.ui-dialog-content").dialog("close")}function setupProcessSaveReloaded(b,a){if(a){var c=parent.jQuery("#"+b).offset().top-20;parent.jQuery("html, body").animate({scrollTop:c},1000,"swing");parent.jQuery("#"+b).hide();setTimeout(function(){parent.jQuery("#"+b).slideDown()},900)}else{parent.jQuery("#"+b).find("img").hide();setTimeout(function(){parent.jQuery("#"+b).find("img").fadeIn("normal",function(){parent.jQuery("#"+b).find(".gridImage__edit").click()})},500)}closePWImageDialog()}function setupProcessSave(e,b,a){var d=false;var c=parent.jQuery("#wrap_Inputfield_"+e);if(!c.length){c=parent.jQuery("#"+b).closest(".Inputfield")}c.trigger("reload");parent.jQuery(".Inputfield").on("reloaded",function(){if(d){return}d=true;if(b.length>0){setTimeout(function(){setupProcessSaveReloaded(b,a)},250)}})}function refreshPageEditField(a){parent.jQuery("#wrap_Inputfield_"+a).trigger("reload")}function setupExecuteVariations(){$(document).on("click","input#delete_all",function(e){if($(this).is(":checked")){$("input.delete").attr("checked","checked")}else{$("input.delete").removeAttr("checked")}e.stopPropagation()});var d={type:"image",closeOnContentClick:true,closeBtnInside:true};$("a.preview").magnificPopup(d);var c=$("#varcnt_id");var a=c.val();var b=c.attr("data-cnt");window.parent.jQuery("#"+a).text(b)}function setupSelectedImage(){var g=false;var d=false;var j=$("#selected_image_settings");var i=$("#selected_image_container");var b=$("#selected_image");var e=$("#selected_image_hidpi");var f;var c=0;var h=0;function a(l){var n=l.width();var w=9999;var x=9999;function v(y){if(y<(f-(f*0.2))){if(!e.is(":visible")){e.closest("label").fadeIn()}e.removeAttr("disabled")}else{e.attr("disabled","disabled");if(e.is(":visible")){e.closest("label").fadeOut()}}}function t(){var z=l.width();var B=l.height();var A=$("#wrap_link_original");if((B>=x||z>=w)&&j.hasClass("croppable")){z=w;B=x;$("#selected_image_link").removeAttr("checked");A.hide()}else{if(!A.is(":visible")){A.fadeIn();if(A.attr("data-was-checked")==1){A.attr("checked","checked")}}}$("#input_width").val(z);$("#input_height").val(B);l.attr("width",z);l.attr("height",B);v(z);var y=$("#latin");if(y.is(":visible")){y.height(B)}if(!j.hasClass("rte")){var C=$("#selected_image_resize");if(n<=z){C.hide()}else{if(!C.is(":visible")){C.fadeIn()}}}}function u(){l.resizable({aspectRatio:true,handles:"n, ne, e, se, s, sw, w",alsoResize:"#selected_image_container",maxWidth:w,maxHeight:x,minWidth:10,minHeight:10,start:function(){j.addClass("resizing_active")},stop:function(){l.attr("width",l.width()).attr("height",l.height());if(n!=l.width()){l.addClass("resized");if(!j.hasClass("rte")){var y=$("#selected_image_resize_yes");if(!y.is(":checked")){y.attr("checked","checked");$("#selected_image_resize_no").removeAttr("checked")}}}j.removeClass("resizing_active");if($("#resize_action").hasClass("on")){$("#resize_action").click().mouseout()}},resize:t});l.addClass("resizable_setup")}var m=null;function r(){var y=[{html:$("#button_crop").html(),click:function(){$("#button_crop").click()}},{html:$("#button_cancel_crop").html(),click:function(){$("#button_cancel_crop").click()},"class":"ui-priority-secondary"}];$(".show_when_crop").hide();$("#crop_action, .crop_trigger").click(function(E){var C=$(this).attr("data-recrop");if(C&&C.length>0){window.location.assign(C);return true}if(!j.hasClass("croppable")){return}if(g){return false}g=true;$("#selected_image_settings").addClass("cropping_active");$(".hide_when_crop").hide();$(".show_when_crop").show();if(l.hasClass("resizable_setup")){l.resizable("destroy")}var B={autoCrop:true,autoCropArea:0.35,zoomable:false,rotatable:false,maxWidth:l.attr("data-origwidth"),maxHeight:l.attr("data-origheight"),minCropBoxWidth:(c<2?0:c),minCropBoxHeight:(h<2?0:h),minWidth:(c<2?0:c),minHeight:(h<2?0:h),done:function(F){$("#crop_x").val(Math.floor(F.x));$("#crop_y").val(Math.floor(F.y));$("#crop_w").val(Math.floor(F.width));$("#crop_h").val(Math.floor(F.height));m=F}};var D=l.attr("data-crop");if(D&&D.length>0){D=D.split(",");B.data={x:D[0],y:D[1],width:D[2],height:D[3]};setTimeout(function(){disablePWImageDialogButtons(y)},1000)}else{disablePWImageDialogButtons(y)}l.cropper(B);setTimeout(function(){$(".cropper-canvas").width($(".cropper-container").width()).height($(".cropper-container").height())},500);var A=function(){var F={x:parseInt($("#crop_x").val()),y:parseInt($("#crop_y").val()),width:parseInt($("#crop_w").val()),height:parseInt($("#crop_h").val()),rotate:0};l.cropper("setData",F)};$("#crop_coordinates input").change(A)});function z(){l.cropper("destroy");$(".show_when_crop").hide();$(".hide_when_crop").show();g=false;$("#selected_image_settings").removeClass("cropping_active");u();enablePWImageDialogButtons()}$("#button_cancel_crop").click(function(){z()});$("#button_crop").click(function(){if(j.hasClass("processing")){return false}j.addClass("processing");return true});if(l.attr("data-crop")){$("#crop_action").click()}}function o(y){if(d){return}if($(this).parents("#crop_coordinates").length){return}d=true;var F,E,C=false,D=false,A=l.attr("width"),G=l.attr("height"),H=parseInt(l.attr("data-origwidth")),I=parseInt(l.attr("data-origheight"));A=typeof A=="undefined"?l.width():parseInt(A);G=typeof G=="undefined"?l.height():parseInt(G);if($(this).attr("id")=="input_width"){F=parseInt($(this).val());E=(I/(H/F));if(F==A){D=true}}else{E=parseInt($(this).val());F=Math.round((E/G)*A);F=(H/(I/E));if(E==G){D=true}}if(F<1||E<1||D){C=1}else{if(w>0&&F>w){C=2}else{if((c>1&&F1&&Ew){z=w}if(z>$(window).width()){$("#content").css("overflow-x","auto")}$("#input_width").val(z).change()});$("#min_action").click(function(){var B=l.width();var z=l.height();var C=$(window).width()-30;var D=$(window).height()-$("#wrap_info").height()-60;var A=false;if(z>D){$("#input_height").val(D).change();A=true}if(B>C){$("#input_width").val(C).change();A=true}if(!A){$("#input_width").val(Math.ceil(B/2)).change()}});$("#align_left_action, #align_center_action, #align_right_action").click(function(){var A=$("#selected_image_class");var z=$(this).attr("data-label");if($(this).hasClass("on")){A.children("option").removeAttr("selected");$(this).removeClass("on")}else{$(this).siblings(".on").removeClass("on");A.children("option").removeAttr("selected");A.find("option[data-label="+z+"]").attr("selected","selected");$(this).addClass("on")}A.change()});var y=$("#selected_image_class").find("option[selected=selected]").attr("data-label");if(y){$("#action_icons").find("span[data-label="+y+"]").addClass("on")}$("#resize_action").hover(function(){if($(this).hasClass("on")){return}$("#resize_tips").show();$("#input_width, #input_height").addClass("ui-state-highlight")},function(){if($(this).hasClass("on")){return}$("#resize_tips").hide();$("#input_width, #input_height").removeClass("ui-state-highlight")}).click(function(){if($(this).hasClass("on")){$(this).removeClass("on");$("#input_width, #input_height").removeClass("ui-state-highlight")}else{$(this).addClass("on");$("#input_width, #input_height").addClass("ui-state-highlight")}});$("#description_action").click(function(){if($(this).hasClass("on")){$(this).removeClass("on");$("#wrap_description").slideUp("fast")}else{$(this).addClass("on");$("#wrap_description").slideDown("fast")}})}function k(){$("#selected_image_caption").change(function(){if(j.hasClass("cropping_active")){return}var y=$("#caption_preview");if($(this).is(":checked")){y.fadeIn()}else{if(y.is(":visible")){y.fadeOut()}}}).change()}function s(){var z=$(window).width()-30;var y=$(window).height()-($("#wrap_info").height()+60);if(l.width()>z){l.width(z).css("height","auto").removeAttr("height");l.removeAttr("height")}if(l.height()>y){l.removeAttr("width").css("width","auto").height(y)}i.width(l.width()).height(l.height())}$("#loading_button").hide();if(l.attr("data-fit")){s()}else{i.width(l.width()).height(l.height())}$("#selected_image_settings .input_pixels").change(o);$("#selected_image_class").change(p).change();f=l.attr("data-origwidth");t();r();q();k();$("button.submit_save_copy, button.submit_save_replace").click(function(){j.addClass("processing");disablePWImageDialogButtons()})}if(b.length>0){b=b.first();if(b.width()>0&&b.height()>0){a(b)}else{b.load(function(){b=$(this);a(b)})}}}$(document).ready(function(){var b=$("#page_id");if(b.length>0){var a=b.val();b.bind("pageSelected",function(c,d){if(d.id==a){return}window.location="./?id="+d.id+"&modal=1"})}if($("#selected_image").length>0){setTimeout(function(){setupSelectedImage()},250)}else{if($("#ImageVariations").length>0){setupExecuteVariations()}}enablePWImageDialogButtons();$(window).keydown(function(c){if(c.keyCode==13){c.preventDefault();return false}})}); \ No newline at end of file +function enablePWImageDialogButtons(){var $buttonPane=parent.jQuery(".ui-dialog-buttonpane");$buttonPane.find("button").button("enable");return}function disablePWImageDialogButtons(){var $buttonPane=parent.jQuery(".ui-dialog-buttonpane");$buttonPane.find("button").button("disable");return}function closePWImageDialog(){parent.jQuery("iframe.ui-dialog-content").dialog("close")}function setupProcessSaveReloaded(fileID,isNew){if(isNew){var offsetTop=parent.jQuery("#"+fileID).offset().top-20;parent.jQuery("html, body").animate({scrollTop:offsetTop},1e3,"swing");parent.jQuery("#"+fileID).hide();setTimeout(function(){parent.jQuery("#"+fileID).slideDown()},900)}else{parent.jQuery("#"+fileID).find("img").hide();setTimeout(function(){parent.jQuery("#"+fileID).find("img").fadeIn("normal",function(){parent.jQuery("#"+fileID).find(".gridImage__edit").click()})},500)}closePWImageDialog()}function setupProcessSave(fieldName,fileID,isNew){var finished=false;var $inputfield=parent.jQuery("#wrap_Inputfield_"+fieldName);if(!$inputfield.length){$inputfield=parent.jQuery("#"+fileID).closest(".Inputfield")}$inputfield.trigger("reload");parent.jQuery(".Inputfield").on("reloaded",function(){if(finished)return;finished=true;if(fileID.length>0){setTimeout(function(){setupProcessSaveReloaded(fileID,isNew)},250)}})}function refreshPageEditField(fieldName){parent.jQuery("#wrap_Inputfield_"+fieldName).trigger("reload")}function setupExecuteVariations(){$(document).on("click","input#delete_all",function(event){if($(this).is(":checked")){$("input.delete").attr("checked","checked")}else{$("input.delete").removeAttr("checked")}event.stopPropagation()});var magnificOptions={type:"image",closeOnContentClick:true,closeBtnInside:true};$("a.preview").magnificPopup(magnificOptions);var $varcnt=$("#varcnt_id");var varcntID=$varcnt.val();var varcnt=$varcnt.attr("data-cnt");window.parent.jQuery("#"+varcntID).text(varcnt)}function setupSelectedImage(){var croppingActive=false;var inputPixelsActive=false;var $form=$("#selected_image_settings");var $container=$("#selected_image_container");var $img=$("#selected_image");var $hidpi=$("#selected_image_hidpi");var fullWidth;var minWidth=0;var minHeight=0;function setupImage($img){var originalWidth=$img.width();var maxWidth=9999;var maxHeight=9999;function updateHidpiCheckbox(w){if(w=maxHeight||w>=maxWidth)&&$form.hasClass("croppable")){w=maxWidth;h=maxHeight;$("#selected_image_link").removeAttr("checked");$link.hide()}else{if(!$link.is(":visible")){$link.fadeIn();if($link.attr("data-was-checked")==1){$link.attr("checked","checked")}}}$("#input_width").val(w);$("#input_height").val(h);$img.attr("width",w);$img.attr("height",h);updateHidpiCheckbox(w);var $latin=$("#latin");if($latin.is(":visible"))$latin.height(h);if(!$form.hasClass("rte")){var $useResize=$("#selected_image_resize");if(originalWidth<=w){$useResize.hide()}else{if(!$useResize.is(":visible"))$useResize.fadeIn()}}}function setupImageResizable(){$img.resizable({aspectRatio:true,handles:"n, ne, e, se, s, sw, w",alsoResize:"#selected_image_container",maxWidth:maxWidth,maxHeight:maxHeight,minWidth:10,minHeight:10,start:function(){$form.addClass("resizing_active")},stop:function(){$img.attr("width",$img.width()).attr("height",$img.height());if(originalWidth!=$img.width()){$img.addClass("resized");if(!$form.hasClass("rte")){var $resizeYes=$("#selected_image_resize_yes");if(!$resizeYes.is(":checked")){$resizeYes.attr("checked","checked");$("#selected_image_resize_no").removeAttr("checked")}}}$form.removeClass("resizing_active");if($("#resize_action").hasClass("on"))$("#resize_action").click().mouseout()},resize:populateResizeDimensions});$img.addClass("resizable_setup")}var cropData=null;function setupImageCroppable(){var cropButtons=[{html:$("#button_crop").html(),click:function(){$("#button_crop").click()}},{html:$("#button_cancel_crop").html(),click:function(){$("#button_cancel_crop").click()},class:"ui-priority-secondary"}];$(".show_when_crop").hide();$("#crop_action, .crop_trigger").click(function(e){var recrop=$(this).attr("data-recrop");if(recrop&&recrop.length>0){window.location.assign(recrop);return true}if(!$form.hasClass("croppable"))return;if(croppingActive)return false;croppingActive=true;$("#selected_image_settings").addClass("cropping_active");$(".hide_when_crop").hide();$(".show_when_crop").show();if($img.hasClass("resizable_setup"))$img.resizable("destroy");var cropSettings={autoCrop:true,autoCropArea:.35,zoomable:false,rotatable:false,maxWidth:$img.attr("data-origwidth"),maxHeight:$img.attr("data-origheight"),minCropBoxWidth:minWidth<2?0:minWidth,minCropBoxHeight:minHeight<2?0:minHeight,minWidth:minWidth<2?0:minWidth,minHeight:minHeight<2?0:minHeight,done:function(data){$("#crop_x").val(Math.floor(data.x));$("#crop_y").val(Math.floor(data.y));$("#crop_w").val(Math.floor(data.width));$("#crop_h").val(Math.floor(data.height));cropData=data}};var crop=$img.attr("data-crop");if(crop&&crop.length>0){crop=crop.split(",");cropSettings.data={x:crop[0],y:crop[1],width:crop[2],height:crop[3]};setTimeout(function(){disablePWImageDialogButtons(cropButtons)},1e3)}else{disablePWImageDialogButtons(cropButtons)}$img.cropper(cropSettings);setTimeout(function(){$(".cropper-canvas").width($(".cropper-container").width()).height($(".cropper-container").height())},500);var cropCoordinatesChange=function(){var data={x:parseInt($("#crop_x").val()),y:parseInt($("#crop_y").val()),width:parseInt($("#crop_w").val()),height:parseInt($("#crop_h").val()),rotate:0};$img.cropper("setData",data)};$("#crop_coordinates input").change(cropCoordinatesChange)});function stopCrop(){$img.cropper("destroy");$(".show_when_crop").hide();$(".hide_when_crop").show();croppingActive=false;$("#selected_image_settings").removeClass("cropping_active");setupImageResizable();enablePWImageDialogButtons()}$("#button_cancel_crop").click(function(){stopCrop()});$("#button_crop").click(function(){if($form.hasClass("processing"))return false;$form.addClass("processing");return true});if($img.attr("data-crop")){$("#crop_action").click()}}function inputPixelsChange(event){if(inputPixelsActive)return;if($(this).parents("#crop_coordinates").length)return;inputPixelsActive=true;var w,h,abort=false,noChange=false,oldWidth=$img.attr("width"),oldHeight=$img.attr("height"),origWidth=parseInt($img.attr("data-origwidth")),origHeight=parseInt($img.attr("data-origheight"));oldWidth=typeof oldWidth=="undefined"?$img.width():parseInt(oldWidth);oldHeight=typeof oldHeight=="undefined"?$img.height():parseInt(oldHeight);if($(this).attr("id")=="input_width"){w=parseInt($(this).val());h=origHeight/(origWidth/w);if(w==oldWidth)noChange=true}else{h=parseInt($(this).val());w=Math.round(h/oldHeight*oldWidth);w=origWidth/(origHeight/h);if(h==oldHeight)noChange=true}if(w<1||h<1||noChange){abort=1}else if(maxWidth>0&&w>maxWidth){abort=2}else if(minWidth>1&&w1&&hmaxWidth)origWidth=maxWidth;if(origWidth>$(window).width()){$("#content").css("overflow-x","auto")}$("#input_width").val(origWidth).change()});$("#min_action").click(function(){var imgWidth=$img.width();var imgHeight=$img.height();var windowWidth=$(window).width()-30;var windowHeight=$(window).height()-$("#wrap_info").height()-60;var updated=false;if(imgHeight>windowHeight){$("#input_height").val(windowHeight).change();updated=true}if(imgWidth>windowWidth){$("#input_width").val(windowWidth).change();updated=true}if(!updated){$("#input_width").val(Math.ceil(imgWidth/2)).change()}});$("#align_left_action, #align_center_action, #align_right_action").click(function(){var $select=$("#selected_image_class");var labelKey=$(this).attr("data-label");if($(this).hasClass("on")){$select.children("option").removeAttr("selected");$(this).removeClass("on")}else{$(this).siblings(".on").removeClass("on");$select.children("option").removeAttr("selected");$select.find("option[data-label="+labelKey+"]").attr("selected","selected");$(this).addClass("on")}$select.change()});var labelKey=$("#selected_image_class").find("option[selected=selected]").attr("data-label");if(labelKey)$("#action_icons").find("span[data-label="+labelKey+"]").addClass("on");$("#resize_action").hover(function(){if($(this).hasClass("on"))return;$("#resize_tips").show();$("#input_width, #input_height").addClass("ui-state-highlight")},function(){if($(this).hasClass("on"))return;$("#resize_tips").hide();$("#input_width, #input_height").removeClass("ui-state-highlight")}).click(function(){if($(this).hasClass("on")){$(this).removeClass("on");$("#input_width, #input_height").removeClass("ui-state-highlight")}else{$(this).addClass("on");$("#input_width, #input_height").addClass("ui-state-highlight")}});$("#description_action").click(function(){if($(this).hasClass("on")){$(this).removeClass("on");$("#wrap_description").slideUp("fast")}else{$(this).addClass("on");$("#wrap_description").slideDown("fast")}})}function setupImageCaption(){$("#selected_image_caption").change(function(){if($form.hasClass("cropping_active"))return;var $caption=$("#caption_preview");if($(this).is(":checked")){$caption.fadeIn()}else if($caption.is(":visible")){$caption.fadeOut()}}).change()}function fitImageToWindow(){var winwidth=$(window).width()-30;var winheight=$(window).height()-($("#wrap_info").height()+60);if($img.width()>winwidth){$img.width(winwidth).css("height","auto").removeAttr("height");$img.removeAttr("height")}if($img.height()>winheight){$img.removeAttr("width").css("width","auto").height(winheight)}$container.width($img.width()).height($img.height())}$("#loading_button").hide();if($img.attr("data-fit")){fitImageToWindow()}else{$container.width($img.width()).height($img.height())}$("#selected_image_settings .input_pixels").change(inputPixelsChange);$("#selected_image_class").change(alignClassChange).change();fullWidth=$img.attr("data-origwidth");populateResizeDimensions();setupImageCroppable();setupImageActions();setupImageCaption();$("button.submit_save_copy, button.submit_save_replace").click(function(){$form.addClass("processing");disablePWImageDialogButtons()})}if($img.length>0){$img=$img.first();if($img.width()>0&&$img.height()>0){setupImage($img)}else{$img.load(function(){$img=$(this);setupImage($img)})}}}$(document).ready(function(){var $page_id=$("#page_id");if($page_id.length>0){var page_id=$page_id.val();$page_id.bind("pageSelected",function(event,data){if(data.id==page_id)return;window.location="./?id="+data.id+"&modal=1"})}if($("#selected_image").length>0){setTimeout(function(){setupSelectedImage()},250)}else if($("#ImageVariations").length>0){setupExecuteVariations()}enablePWImageDialogButtons();$(window).keydown(function(event){if(event.keyCode==13){event.preventDefault();return false}})}); \ No newline at end of file diff --git a/wire/modules/Process/ProcessPageEditImageSelect/ProcessPageEditImageSelect.module b/wire/modules/Process/ProcessPageEditImageSelect/ProcessPageEditImageSelect.module index 9751e7bf..1d8e7f01 100644 --- a/wire/modules/Process/ProcessPageEditImageSelect/ProcessPageEditImageSelect.module +++ b/wire/modules/Process/ProcessPageEditImageSelect/ProcessPageEditImageSelect.module @@ -212,35 +212,45 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { * */ public function init() { + + /** @var Config $config */ + $config = $this->wire('config'); + /** @var WireInput $input */ + $input = $this->wire('input'); + /** @var Session $session */ + $session = $this->wire('session'); + /** @var Sanitizer $sanitizer */ + $sanitizer = $this->wire('sanitizer'); + // throw new WireException($this->labels['demoMode']); - if($this->config->demo) { - if($this->wire('input')->urlSegmentStr != 'variations') { + if($config->demo) { + if($input->urlSegmentStr != 'variations') { throw new WireException($this->labels['demoMode']); } } $this->modules->get("ProcessPageList"); - $this->rte = $this->input->get('rte') !== null && $this->input->get('rte') == "0" ? false : true; - $id = (int) $this->input->get->id; - $editID = (int) $this->input->get->edit_page_id; + $this->rte = $input->get('rte') !== null && $input->get('rte') == "0" ? false : true; + $id = (int) $input->get('id'); + $editID = (int) $input->get('edit_page_id'); if($editID) { - $this->wire('session')->set($this, 'edit_page_id', $editID); + $session->set($this, 'edit_page_id', $editID); } else { - $editID = (int) $this->wire('session')->get($this, 'edit_page_id'); + $editID = (int) $session->get($this, 'edit_page_id'); } if($editID) { $this->editorPage = $this->wire('pages')->get($editID); if(!$this->editorPage->editable()) { $this->editorPage = null; - $this->wire('session')->remove($this, 'edit_page_id'); + $session->remove($this, 'edit_page_id'); } } - $fieldName = $this->wire('sanitizer')->fieldName($this->wire('input')->get('field')); + $fieldName = $sanitizer->fieldName($input->get('field')); if($fieldName) { $this->fieldName = $fieldName; // original if(strpos($fieldName, '_repeater') && preg_match('/_repeater\d+$/', $fieldName, $matches)) { @@ -255,20 +265,21 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { } // if no ID was specified, then retrieive ID from filename path, if it's there - if($this->input->get->file && preg_match('{[/,]}', $this->input->get->file)) { + $file = $input->get('file'); + if($file && preg_match('{[/,]}', $file)) { - if(preg_match('{(\d+)[/,][^/,]+\.(' . $this->extensions . ')$}iD', $this->input->get->file, $matches)) { + if(preg_match('{(\d+)[/,][^/,]+\.(' . $this->extensions . ')$}iD', $file, $matches)) { // ..........ID.../,filename.ext // format: 123/filename.jpg OR 123,filename.jpg // also covers new pagefileSecure URLs $id = (int) $matches[1]; - } else if(preg_match('{(/[\d/]+)/([-_.a-z0-9]+)\.(' . $this->extensions . ')$}iD', $this->input->get->file, $matches)) { + } else if(preg_match('{(/[\d/]+)/([-_.a-z0-9]+)\.(' . $this->extensions . ')$}iD', $file, $matches)) { // .................ID........filename........ext // extended asset path format: 1/2/3/filename.jpg $id = PagefilesManager::dirToPageID($matches[1]); - } else if(preg_match('{^' . $this->wire('config')->urls->root . '([-_./a-zA-Z0-9]*/)' . $this->wire('config')->pagefileUrlPrefix . '[^/]+\.(' . $this->extensions . ')$}iD', $this->input->get->file, $matches)) { + } else if(preg_match('{^' . $config->urls->root . '([-_./a-zA-Z0-9]*/)' . $config->pagefileUrlPrefix . '[^/]+\.(' . $this->extensions . ')$}iD', $file, $matches)) { // .............................../....................path/to/page.........................-?....................filename..........ext // legacy pagefileSecure URL format: /path/to/page/filename.jpg // @todo: does this still need to be here or can it be dropped? @@ -310,21 +321,20 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { } } - if($this->input->get->winwidth) $this->maxImageWidth = ((int) $this->input->get->winwidth) - 70; - if($this->maxImageWidth < 400) $this->maxImageWidth = 400; - - $this->hidpi = ((int) $this->wire('input')->get('hidpi')) ? true : false; - $hidpi = $this->wire('input')->get('hidpi'); + if($input->get('winwidth')) $this->maxImageWidth = ((int) $input->get('winwidth')) - 70; + if($this->maxImageWidth < 400) $this->maxImageWidth = 400; + + $hidpi = $input->get('hidpi'); if($hidpi === null && $this->hidpiDefault) $hidpi = true; if(!$this->rte) $hidpi = false; // hidpi not applicable outside of RTE mode $this->hidpi = $hidpi ? true : false; - if($this->rte) $this->caption = $this->wire('input')->get('caption') ? true : false; + if($this->rte) $this->caption = $input->get('caption') ? true : false; // original used by InputfieldImage - $original = $this->wire('input')->get('original'); + $original = $input->get('original'); if($original) { - $original = $this->wire('sanitizer')->filename($original); + $original = $sanitizer->filename($original); if(is_file($this->page->filesManager()->path . $original)) { $this->original = $original; } @@ -344,7 +354,7 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { public function getPageimage($getVariation = false) { $images = $this->getImages($this->page); - $file = basename($this->input->get->file); + $file = basename($this->input->get('file')); $variationFilename = ''; if(strpos($file, ',') === false) { @@ -501,14 +511,14 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { return "

$error

"; } - if($this->input->get->file) return $this->executeEdit(); + if($this->input->get('file')) return $this->executeEdit(); $images = $this->getImages($this->page, $this->page->fields); $out = ''; if(count($images)) { - $winwidth = (int) $this->input->get->winwidth; + $winwidth = (int) $this->input->get('winwidth'); $in = $this->wire('modules')->get('InputfieldImage'); $in->adminThumbs = true; @@ -722,22 +732,28 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { */ public function ___executeEdit() { + + /** @var WireInput $input */ + $input = $this->wire('input'); + /** @var Config $config */ + $config = $this->wire('config'); + $this->checkImageEditPermission(); - if($this->wire('input')->post('submit_crop')) { + if($input->post('submit_crop')) { $crop = $this->processCrop(); $parts = $this->hidpi ? array('width' => $crop->hidpiWidth()) : array(); $this->wire('session')->redirect($this->makeEditURL($crop->basename, $parts)); return ''; - } else if($this->wire('input')->post('submit_save_replace')) { + } else if($input->post('submit_save_replace')) { return $this->processSave(true); - } else if($this->wire('input')->post('submit_save_copy')) { + } else if($input->post('submit_save_copy')) { return $this->processSave(false); } - $path = $this->wire('config')->urls->ProcessPageEditImageSelect; - $this->wire('config')->styles->add($path . 'cropper/cropper.min.css'); - $this->wire('config')->scripts->add($path . 'cropper/cropper.min.js'); + $path = $config->urls('ProcessPageEditImageSelect'); + $config->styles->add($path . 'cropper/cropper.min.css'); + $config->scripts->add($path . 'cropper/cropper.min.js'); $labels =& $this->labels; $formClasses = array(); @@ -751,7 +767,7 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { foreach($icons as $key => $value) { $icons[$key] = ""; } - + $optionalClasses = array( $this->alignLeftClass => $labels['alignLeft'], $this->alignRightClass => $labels['alignRight'], @@ -762,7 +778,7 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { $cropY = null; $cropWidth = null; $cropHeight = null; - $isCropped = preg_match_all('/\.(\d+)x(\d+).*?-crop[xy](\d+)[xy](\d+)[-.]/', $this->wire('input')->get('file'), $matches); + $isCropped = preg_match_all('/\.(\d+)x(\d+).*?-crop[xy](\d+)[xy](\d+)[-.]/', $input->get('file'), $matches); if($isCropped) { // get last coordinates present $cropWidth = (int) array_pop($matches[1]); @@ -825,25 +841,25 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { ); if(!$this->rte && $this->field) { - $minWidth = $this->field->minWidth ? (int) $this->field->minWidth : 1; - $minHeight = $this->field->minHeight ? (int) $this->field->minHeight : 1; + $minWidth = $this->field->get('minWidth') ? (int) $this->field->get('minWidth') : 1; + $minHeight = $this->field->get('minHeight') ? (int) $this->field->get('minHeight') : 1; } else { $minWidth = 1; $minHeight = 1; } // bundle in crop information to image attributes, if specified - if($this->wire('input')->get('crop_x')) { + if($input->get('crop_x')) { $attrs['data-crop'] = - ((int) $this->wire('input')->get('crop_x')) . ',' . - ((int) $this->wire('input')->get('crop_y')) . ',' . - ((int) $this->wire('input')->get('crop_w')) . ',' . - ((int) $this->wire('input')->get('crop_h')); + ((int) $input->get('crop_x')) . ',' . + ((int) $input->get('crop_y')) . ',' . + ((int) $input->get('crop_w')) . ',' . + ((int) $input->get('crop_h')); } // determine width/height we will display image at in editor, so that it fits - $width = (int) $this->input->get('width'); - $height = (int) $this->input->get('height'); + $width = (int) $input->get('width'); + $height = (int) $input->get('height'); if(!$width) $width = $this->editWidth; if(!$width) $width = ''; if(!$height) $height = $this->editHeight; @@ -860,7 +876,7 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { // if they aren't already working with a resized image, and it's being scaled down, // then add the 'resized' class to ensure that our RTE 'pwimage' plugin knows to perform the resize // by checking for the 'resized' classname on the image - if(basename($this->input->get->file) == $fullname && $originalWidth > $width) $attrs['class'] .= " resized"; + if(basename($input->get('file')) == $fullname && $originalWidth > $width) $attrs['class'] .= " resized"; // if hidpi specified keep it checed $hidpiChecked = $this->hidpi ? " checked='checked'" : ""; @@ -870,7 +886,7 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { $classOptions = ''; foreach($optionalClasses as $class => $label) { $labelKey = array_search($label, $labels); - $selected = strpos($this->input->get('class'), $class) !== false ? " selected='selected'" : ''; + $selected = strpos($input->get('class'), $class) !== false ? " selected='selected'" : ''; if($selected) $attrs['class'] .= " $class"; $classOptions .= "$label"; } @@ -883,16 +899,16 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { } // prepare description (alt) - $description = isset($_GET['description']) ? $this->input->get('description') : ''; // $image->description; + $description = isset($_GET['description']) ? $input->get('description') : ''; // $image->description; if(strlen($description) > 8192) $description = substr($description, 0, 8192); $description = $this->wire('sanitizer')->entities($description); // if dealing with a variation size or crop provide the option to link to the original (larger) $linkOriginalChecked = ''; - if($image->name != $original->name || $this->wire('input')->get('link')) { + if($image->name != $original->name || $input->get('link')) { if($image->name != $original->name) $formClasses[] = 'not-original'; - $imgURL = str_replace($this->wire('config')->urls->root, '/', $original->url); - $imgLinkURL = trim($this->wire('input')->get('link')); + $imgURL = str_replace($config->urls->root, '/', $original->url); + $imgLinkURL = trim($input->get('link')); if($imgLinkURL) { if($imgLinkURL === "1") $imgLinkURL = $imgURL; // if in toggle mode, substitute URL // determine if 'link to original' checkbox should be checked @@ -907,7 +923,7 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { $resizeYesChecked = $this->rte ? " checked='checked'" : ""; $resizeNoChecked = !$this->rte ? " checked='checked'" : ""; - if($this->field && $this->field->maxFiles == 1) $formClasses[] = 'maxfiles1'; + if($this->field && $this->field->get('maxFiles') == 1) $formClasses[] = 'maxfiles1'; // form attributes $formClasses[] = $isCroppable ? 'croppable' : 'not-croppable'; @@ -1145,7 +1161,7 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { if($this->original && $this->original != $image2->basename() && $original = $image->pagefiles->get($this->original)) { $fileID = 'file_' . $original->hash; - if($rebuildVariations && $this->field->adminThumbs) { + if($rebuildVariations && $this->field->get('adminThumbs')) { // remove original thumbnail /** @var InputfieldImage $inputfield */ $inputfield = $this->field->getInputfield($this->page); @@ -1252,13 +1268,16 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { public function ___executeResize() { $this->checkImageEditPermission(); + + /** @var WireInput $input */ + $input = $this->wire('input'); - $width = (int) $this->input->get('width'); - $class = $this->sanitizer->name($this->input->get->class); + $width = (int) $input->get('width'); + $class = $this->sanitizer->name($input->get('class')); $hidpi = $this->hidpi; - $json = (int) $this->input->get('json'); // 1 or 0 (for json output mode on or off) - $rotate = (int) $this->wire('input')->get('rotate'); - $flip = $this->wire('input')->get('flip'); + $json = (int) $input->get('json'); // 1 or 0 (for json output mode on or off) + $rotate = (int) $input->get('rotate'); + $flip = $input->get('flip'); if($flip != 'v' && $flip != 'h') $flip = ''; if(strpos($class, 'hidpi') !== false) { @@ -1355,12 +1374,21 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { if(!$this->page || !$pageimage) throw new WireException("No file provided"); if(!$this->masterPage->editable()) throw new WireException($this->labels['noAccess']); + + $cnt = 0; + $rows = array(); + $name = $pageimage->basename(); + $filesize = $pageimage->filesize(); + $filesizeStr = wireBytesStr($filesize); + $mtime = filemtime($pageimage->filename); + $modified = date('Y-m-d H:i:s', $mtime); + $url = $pageimage->url() . "?nc=$mtime"; + $originalLabel = $this->_('Original'); $hasEditPermission = $this->wire('user')->hasPermission('page-edit-images', $this->masterPage); - - $variations = $pageimage->getVariations(array('info' => true)); - $cnt = count($variations); - + $variations = $pageimage->getVariations(array('info' => true, 'verbose' => 1)); + $adminThumbOptions = $this->wire('config')->adminThumbOptions; $delete = $this->wire('input')->post('delete'); + if(is_array($delete) && count($delete) && $hasEditPermission) { foreach($delete as $name) { if(!isset($variations[$name])) continue; @@ -1374,33 +1402,43 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { } } - $headline = sprintf( - $this->_n('%1$d variation for image %2$s', '%1$d variations for image %2$s', $cnt), - //$cnt, "$pageimage->basename" - $cnt, $pageimage->basename + $rows[] = array( + 'cnt' => $cnt, + 'url' => $url, + 'name' => $name, + 'notes' => array($originalLabel), + 'width' => $pageimage->width(), + 'height' => $pageimage->height(), + 'modified' => $modified, + 'filesize' => $filesize, + 'filesizeStr' => $filesizeStr, + 'deletable' => $hasEditPermission, + ); + + foreach($pageimage->extras() as $extra) { + if(!file_exists($extra->filename)) continue; + $name = $extra->basename(); + $filesize = $extra->filesize(); + $filesizeStr = wireBytesStr($filesize); + $mtime = filemtime($extra->filename); + $modified = date('Y-m-d H:i:s', $mtime); + $url = $extra->url() . "?nc=$mtime"; + $rows[] = array( + 'cnt' => ++$cnt, + 'url' => $url, + 'name' => $name, + 'notes' => array("$originalLabel ($extra->ext $extra->savingsPct)"), + 'width' => $pageimage->width(), + 'height' => $pageimage->height(), + 'modified' => $modified, + 'filesize' => $filesize, + 'filesizeStr' => $filesizeStr, + 'deletable' => false, ); - $this->headline($headline); - - if(!$cnt) return "

$headline

"; - - $adminThumbOptions = $this->wire('config')->adminThumbOptions; - - $table = $this->wire('modules')->get('MarkupAdminDataTable'); - $table->setEncodeEntities(false); - $table->headerRow(array( - '#', - $this->_x('Image', 'th'), - $this->_x('File', 'th'), - $this->_x('Size', 'th'), - $this->_x('Modified', 'th'), - $this->_x('Notes', 'th'), - ($hasEditPermission ? "" : " ") - )); - - $cnt = 0; + } foreach($variations as $name => $info) { - + $notes = array(); if(in_array('is', $info['suffix'])) $notes[] = $this->_x('Created for placement in textarea', 'notes'); if(in_array('hidpi', $info['suffix'])) $notes[] = $this->_x('HiDPI/Retina', 'notes'); @@ -1432,18 +1470,87 @@ class ProcessPageEditImageSelect extends Process implements ConfigurableModule { $mtime = filemtime($info['path']); $modified = date('Y-m-d H:i:s', $mtime); $url = "$info[url]?nc=$mtime"; + + $rows[] = array( + 'cnt' => ++$cnt, + 'url' => $url, + 'name' => $name, + 'notes' => $notes, + 'width' => $width, + 'height' => $height, + 'modified' => $modified, + 'filesize' => $filesize, + 'filesizeStr' => $filesizeStr, + 'deletable' => $hasEditPermission, + ); + /** @var Pageimage $pi */ + $pi = $info['pageimage']; + foreach($pi->extras() as $extra) { + if(!file_exists($extra->filename)) continue; + $name = $extra->basename(); + $filesize = $extra->filesize(); + $filesizeStr = wireBytesStr($filesize); + $mtime = filemtime($extra->filename()); + $modified = date('Y-m-d H:i:s', $mtime); + $url = $extra->url() . "?nc=$mtime"; + + $rows[] = array( + 'cnt' => ++$cnt, + 'url' => $url, + 'name' => $name, + 'notes' => $notes, + 'width' => $width, + 'height' => $height, + 'modified' => $modified, + 'filesize' => $filesize, + 'filesizeStr' => $filesizeStr, + 'deletable' => false, + ); + } + } + + /** @var InputfieldCheckbox $checkbox */ + $checkbox = $this->wire('modules')->get('InputfieldCheckbox'); + $checkbox->label = ' '; + $checkbox->addClass('delete'); + $checkbox->attr('id+name', 'delete_all'); + $checkbox->val(1); + + /** @var MarkupAdminDataTable $table */ + $table = $this->wire('modules')->get('MarkupAdminDataTable'); + $table->setEncodeEntities(false); + $table->headerRow(array( + '#', + $this->_x('Image', 'th'), + $this->_x('File', 'th'), + $this->_x('Size', 'th'), + $this->_x('Modified', 'th'), + $this->_x('Notes', 'th'), + ($hasEditPermission ? $checkbox->render() : " ") + )); + + foreach($rows as $row) { + $checkbox->attr('name', 'delete[]'); + $checkbox->val($row['name']); + $checkbox->attr('id', "delete_$row[cnt]"); $table->row(array( - ++$cnt, - "$name", - "$name
{$width}x{$height}", - "$filesize$filesizeStr", - $modified, - implode('
', $notes), - ($hasEditPermission ? "" : " ") + ($row['cnt'] ? $row['cnt'] : ' '), + "$row[name]", + "$row[name]
$row[width]x$row[height]", + "$row[filesize]$row[filesizeStr]", + $row['modified'], + implode('
', $row['notes']), + //($hasEditPermission ? "" : " ") + ($row['cnt'] && $row['deletable'] ? $checkbox->render() : " ") )); } + $this->headline(sprintf( + $this->_n('%1$d variation for image %2$s', '%1$d variations for image %2$s', $cnt), + $cnt, $pageimage->basename + )); + $varcnt = $this->wire('sanitizer')->entities($this->wire('input')->get('varcnt')); $form = $this->wire('modules')->get('InputfieldForm');