'Page Edit Image', 'summary' => 'Provides image manipulation functions for image fields and rich text editors.', 'version' => 121, 'permanent' => true, 'permission' => 'page-edit', ); } /** * Max image width when outputting tags, derives value from $_GET[winwidth] * * @var int * */ protected $maxImageWidth = 835; /** * Page that the image lives on * * @var Page|null * */ protected $page = null; /** * If $page is a repeater item, then $masterPage is the Page the repeater lives on * * @var Page|null * */ protected $masterPage = null; /** * The page being edited, if different from $page * * @var Page|null * */ protected $editorPage = null; /** * If editing a filename that is a variation, this is the width determined from the filename (123x456) * * @var int * */ protected $editWidth = 0; /** * If editing a filename that is a variation, this is the height determined from the filename (123x456) * * @var int * */ protected $editHeight = 0; /** * Whether or not HiDPI mode will be used or resizes * * @var bool * */ protected $hidpi = false; /** * Extensions to match in a regex for files in $_GET[file] * * @var string * */ protected $extensions = 'jpg|jpeg|gif|png|svg'; /** * Common translation labels (see init) * * @var array * */ protected $labels = array(); /** * Are we in Rich Text editor mode? Determined from $_GET[rte] * * @var bool * */ protected $rte = true; /** * Field of type FieldtypeImage that edited image is part of * * @var Field|null * */ protected $field = null; /** * Name of field of type FieldtypeImage that edited image is part of * * @var string * */ protected $fieldName = ''; /** * If file being edited is a variation, $original is basename of the file it originated from * * @var string * */ protected $original = ''; /** * Caption text for image when in RTE mode, can be provided in $_GET[caption] * * @var string * */ protected $caption = ''; /** * Default module config settings * * @var array * */ protected static $defaultConfig = array( 'hidpiDefault' => 0, 'skipFields' => '', 'noSizeAttrs' => 0, 'noThumbs' => 0, 'alignLeftClass' => 'align_left', 'alignRightClass' => 'align_right', 'alignCenterClass' => 'align_center', ); /** * Construct and set default configuration * */ public function __construct() { parent::__construct(); foreach(self::$defaultConfig as $key => $value) $this->set($key, $value); $this->labels = array( 'width' => $this->_('Width:'), 'height' => $this->_('Height:'), 'top' => $this->_('Top:'), 'left' => $this->_('Left:'), 'hidpi' => $this->_('HiDPI/Retina'), 'description' => $this->_('Image description (alt attribute)'), 'linkOriginal' => $this->_('Link to larger/original version'), 'resize' => $this->_('Resize'), 'useResize' => $this->_('Save at this size?'), 'crop' => $this->_('Crop'), 'saveCrop' => $this->_('Apply'), 'max' => $this->_('Maximize/full width'), 'min' => $this->_('Minimize/fit to screen'), 'cancel' => $this->_('Cancel'), 'alignLeft' => $this->_('Align Left'), 'alignRight' => $this->_('Align Right'), 'alignCenter' => $this->_('Align Center'), 'saveReplace' => $this->_('Save and Replace'), 'saveCopy' => $this->_('Save as Copy'), 'saving' => $this->_('Saving...'), 'updating' => $this->_('Updating...'), 'yes' => $this->_('Yes'), 'noUse' => $this->_('No, use %s'), 'caption' => $this->_('Caption?'), 'captionTip' => $this->_('Caption text is entered in the editor after you insert the image.'), 'captionText' => $this->_('Caption text here'), 'rotateRight' => $this->_('Rotate to the right'), 'rotateLeft' => $this->_('Rotate to the left'), 'flipHorizontal' => $this->_('Flip horizontal'), 'flipVertical' => $this->_('Flip vertical'), 'noAccess' => $this->_('You do not have access to edit images on this page.'), 'demoMode' => "Image editing functions are disabled in demo mode", ); } /** * Initialize and populate required variables from GET variables * * @throws WireException * */ public function init() { $config = $this->wire()->config; $input = $this->wire()->input; $session = $this->wire()->session; $sanitizer = $this->wire()->sanitizer; $modules = $this->wire()->modules; $pages = $this->wire()->pages; $fields = $this->wire()->fields; $user = $this->wire()->user; // throw new WireException($this->labels['demoMode']); if($config->demo) { if($input->urlSegmentStr != 'variations') { throw new WireException($this->labels['demoMode']); } } $modules->get("ProcessPageList"); $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) { $session->set($this, 'edit_page_id', $editID); } else { $editID = (int) $session->get($this, 'edit_page_id'); } if($editID) { $this->editorPage = $pages->get($editID); if(!$this->editorPage->editable()) { $this->editorPage = null; $session->remove($this, 'edit_page_id'); } } $fieldName = $sanitizer->fieldName($input->get('field')); if($fieldName) { $this->fieldName = $fieldName; // original if(strpos($fieldName, '_repeater') && preg_match('/_repeater\d+$/', $fieldName, $matches)) { $fieldName = str_replace($matches[0], '', $fieldName); } else if(strpos($fieldName, '_LPID')) { list($fieldName, $lpid) = explode('_LPID', $fieldName); if($lpid) {} // ignore } $this->field = $fields->get($fieldName); if(!$this->field) throw new WireException("Unknown field $fieldName"); if(!$this->field->type instanceof FieldtypeImage) { if(!method_exists($this->field->type, 'getPageimages')) { throw new WireException("Field $fieldName is not an instance of FieldtypeImage or FieldtypeHasPageimages"); } } } // if no ID was specified, then retrieive ID from filename path, if it's there $file = $input->get('file'); if($file && preg_match('{[/,]}', $file)) { 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', $file, $matches)) { // .................ID........filename........ext // extended asset path format: 1/2/3/filename.jpg $id = PagefilesManager::dirToPageID($matches[1]); } 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? $this->page = $pages->get('/' . $matches[1]); $id = $this->page->id; } } if(!$id) throw new WireException("No page specified"); if(!$this->page) $this->page = $this->pages->get($id); if(!$this->page) throw new WireException("No page specified"); if(!$this->page->id) throw new WireException("Unknown page"); if(!$this->editorPage) $this->editorPage = $this->page; // if $this->page is a repeater item (for example), $this->masterPage is page it lives on $p = null; if(wireInstanceOf($this->page, 'RepeaterPage')) { /** @var RepeaterPage $p */ $p = $this->page; while(wireInstanceOf($p, 'RepeaterPage')) { $p = $p->getForPage(); } } $this->masterPage = $p && $p->id ? $p : $this->page; // note we use hasPermission('page-view') rather than viewable() here because // we want to allow pages without template files if(!$user->hasPermission('page-view', $this->page)) { /** @var PagePermissions $pagePermissions */ $pagePermissions = $modules->get('PagePermissions'); if($this->page->id === $user->id && $fieldName && $pagePermissions->userFieldEditable($fieldName)) { // user editing allowed images field in their profile } else if(wireInstanceOf($this->page, 'RepeaterPage')) { if(!$this->masterPage->editable()) { throw new WireException($this->labels['noAccess']); } } else { throw new WireException($this->labels['noAccess']); } } 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 = $input->get('caption') ? true : false; // original used by InputfieldImage $original = $input->get('original'); if($original) { $original = $sanitizer->filename($original); if(is_file($this->page->filesManager()->path . $original)) { $this->original = $original; } } parent::init(); } /** * Return the Pageimage object being edited * * @param bool $getVariation Returns the variation specified in the URL. Otherwise returns original (default). * @return Pageimage * @throws WireException * */ public function getPageimage($getVariation = false) { $images = $this->getImages($this->page); $file = basename($this->input->get('file')); $variationFilename = ''; if(strpos($file, ',') === false) { // prepend ID if it's not there, needed for ajax in-editor resize $originalFilename = $file; $file = $this->page->id . ',' . $file; } else { // already has a "123," at beginning list($pageID, $originalFilename) = explode(',', $file); if($pageID) {} // ignore } $originalFilename = $this->wire()->sanitizer->filename($originalFilename, false, 1024); // if requested file does not match one of our allowed extensions, abort if(!preg_match('/\.(' . $this->extensions . ')$/iD', $file, $matches)) { throw new WireException("Unknown image file"); } // get the original, non resized version, if present // format: w x h crop -suffix if(preg_match('/(\.(\d+)x(\d+)([a-z0-9]*)(-[-_.a-z0-9]+)?)\.' . $matches[1] . '$/', $file, $matches)) { // filename referenced in $_GET['file'] IS a variation // Follows format: original.600x400-suffix1-suffix2.ext $this->editWidth = (int) $matches[2]; $this->editHeight = (int) $matches[3]; $variationFilename = $originalFilename; $originalFilename = str_replace($matches[1], '', $originalFilename); // remove dimensions and optional suffix } else { // filename referenced in $_GET['file'] is NOT a variation $getVariation = false; } // update $file as sanitized version and with original filename only $file = "{$this->page->id},$originalFilename"; // if requested file is not one that we have, abort if(!array_key_exists($file, $images)) { throw new WireException("Cannot find image file '$originalFilename' on page: {$this->page->path}"); } // return original if(!$getVariation) return $images[$file]; // get variation $original = $images[$file]; $variationPathname = $original->pagefiles->path() . $variationFilename; $pageimage = null; if(is_file($variationPathname)) { $pageimage = $this->wire(new Pageimage($original->pagefiles, $variationPathname)); } if(!$pageimage) { throw new WireException("Unrecognized variation file: $file"); } return $pageimage; } /** * Get all Pageimage objects on page * * @param Page $page * @param array|WireArray $fields * @param int $level Recursion level (internal use) * @return array * */ public function getImages(Page $page, $fields = array(), $level = 0) { $allImages = array(); if(!$page->id) return $allImages; $numImages = 0; $numImageFields = 0; $skipFields = $this->wire()->input->urlSegment1 ? array() : explode(' ', $this->skipFields); if(empty($fields)) { if($this->field) { $fields = array($this->field); $skipFields = array(); } else { $fields = $page->fields; } } foreach($fields as $field) { $fieldtype = $field->type; if(in_array($field->name, $skipFields)) continue; if(wireInstanceOf($fieldtype, 'FieldtypeRepeater')) { // get images that are possibly in a repeater $repeaterValue = $page->get($field->name); if($repeaterValue instanceof Page) $repeaterValue = array($repeaterValue); if($repeaterValue) { foreach($repeaterValue as $p) { $images = $this->getImages($p, $p->fields, $level + 1); if(!wireCount($images)) continue; foreach($images as $image) { $parentFields = $image->get('_parentFields'); if(!is_array($parentFields)) $parentFields = array(); array_unshift($parentFields, $field); $image->setQuietly('_parentFields', $parentFields); } $allImages = array_merge($allImages, $images); $numImages += wireCount($images); $numImageFields++; } } continue; } if($fieldtype instanceof FieldtypeImage) { $numImageFields++; $images = $page->getUnformatted($field->name); } else if(method_exists($fieldtype, 'getPageimages')) { /** @var FieldtypeHasPageimages $images */ $numImageFields++; $images = $fieldtype->getPageimages($page, $field); } else { continue; } if(!wireCount($images)) continue; foreach($images as $image) { $numImages++; $key = $page->id . ',' . $image->basename; // page_id,basename for repeater support $allImages[$key] = $image; } } if(!$level) { if(!$numImageFields) { $this->message($this->_("There are no image fields on this page. Choose another page to select images from.")); // Message when page has no image fields } else if(!$numImages) { $this->message($this->_("There are no images present on this page. Upload an image, or select images from another page.")); // Message when page has no images } } return $allImages; } /** * Get all editable image fields on the page * * @param Page $page * @param bool $excludeFullFields Exclude fields that are already full? (i.e. can't add more images to them) * @return array of Field objects for image fields * */ public function getImageFields(Page $page, $excludeFullFields = true) { $skipFields = explode(' ', $this->skipFields); $imageFields = array(); foreach($page->fields as $field) { /** @var Field $field */ if(!$field->type instanceof FieldtypeImage) continue; if(in_array($field->name, $skipFields)) continue; if(!$page->editable($field->name)) continue; if($excludeFullFields && $field->get('maxFiles') > 0) { $value = $page->get($field->name); if(wireCount($value) >= $field->get('maxFiles')) continue; } $imageFields[$field->name] = $field; } return $imageFields; } /** * Default execute: display list of images on page for selection * * @return string * @throws WireException * */ public function ___execute() { if($this->config->demo) throw new WireException("Sorry, image editing functions are disabled in demo mode"); if(!$this->page) { $error = "No page provided"; $this->error($error); return "

$error

"; } if($this->input->get('file')) return $this->executeEdit(); $sanitizer = $this->wire()->sanitizer; $modules = $this->wire()->modules; $input = $this->wire()->input; $images = $this->getImages($this->page, $this->page->fields); $out = ''; if(wireCount($images)) { $winwidth = (int) $input->get('winwidth'); $in = $modules->get('InputfieldImage'); /** @var InputfieldImage $in */ $in->set('adminThumbs', true); $lastFieldLabel = ''; $numImageFields = 0; foreach($images as $image) { /** @var PageImage $image */ $fieldLabels = array(); $parentFields = $image->get('_parentFields'); if(!is_array($parentFields)) $parentFields = array(); foreach($parentFields as $parentField) { $fieldLabels[] = $parentField->getLabel(); } $fieldLabels[] = $image->field->getLabel(); $fieldLabel = implode(' > ', $fieldLabels); if($fieldLabel != $lastFieldLabel) { $numImageFields++; $out .= "\n\t
  • " . $sanitizer->entities($fieldLabel) . "
  • "; } $lastFieldLabel = $fieldLabel; if($this->noThumbs) { $width = $image->width(); $alt = $sanitizer->entities1($image->description); if($width > $this->maxImageWidth) $width = $this->maxImageWidth; $img = "\"$alt\""; } else { $image->set('_requireHeight', true); // recognized by InputfieldImage $info = $in->getAdminThumb($image); $img = $info['markup']; } $out .= "\n\t
  • $img
  • "; } $class = $this->noThumbs ? "" : "thumbs"; if($numImageFields > 1) $class = trim("$class multifield"); $out = "\n"; } /** @var InputfieldForm $form */ $form = $modules->get("InputfieldForm"); $form->action = "./"; $form->method = "get"; /** @var InputfieldPageListSelect $field */ $field = $modules->get("InputfieldPageListSelect"); $field->label = $this->_("Images on Page:") . ' ' . $this->page->get("title") . " (" . $this->page->path . ")"; // Headline for page selection, precedes current page title/url $field->description = $this->_("If you would like to select images from another page, select the page below."); // Instruction on how to select another page $field->attr('id+name', 'page_id'); $field->value = $this->page->id; $field->parent_id = 0; $field->collapsed = wireCount($images) ? Inputfield::collapsedYes : Inputfield::collapsedNo; $field->required = true; $form->append($field); // locate any image fields $imageFields = $this->getImageFields($this->page); if(wireCount($imageFields)) { $imageFieldNames = implode(',', array_keys($imageFields)); /** @var InputfieldButton $btn */ $btn = $modules->get('InputfieldButton'); $uploadOnlyMode = "$this->page" === "$this->editorPage" ? 1 : 2; $btn->href = "../edit/?modal=1&id={$this->page->id}&fields=$imageFieldNames&uploadOnlyMode=$uploadOnlyMode"; $btn->value = $this->_('Upload Image'); $btn->addClass('upload pw-modal-button pw-modal-button-visible'); $btn->icon = 'upload'; $changes = $input->get('changes'); if($changes) { foreach(explode(',', $changes) as $name) { $name = $sanitizer->fieldName($name); $field = $this->wire()->fields->get($name); if(!$field) continue; $out .= ""; } } } else $btn = null; $out = $form->render() . $out; if($btn) $out .= $btn->render(); return "
    " . $out . "\n
    "; } /** * Given a Pageimage, return the closest/last cropped version of it * * This is so that resizes can use the highest quality version to resize from. * * @param Pageimage $image * @return Pageimage * */ protected function getLastCrop(Pageimage $image) { $info = $image->isVariation($image->name, true); if(!$info || !empty($info['crop'])) { return $image; } $cropName = null; $parentInfo = $info; while(!empty($parentInfo['parent'])) { $parentInfo = $parentInfo['parent']; if(!empty($parentInfo['crop'])) { $cropName = $parentInfo['name']; break; } } $original = $image->getOriginal(); if(!$original) return $image; if(filemtime($original->filename) > filemtime($image->filename)) return $image; // if no last crop found, return original if(!$cropName) return $original; $variations = $original->getVariations(); // return last found crop $cropImage = $variations->get($cropName); if($cropImage && filemtime($cropImage->filename) > filemtime($image->filename)) return $image; return $cropImage ? $cropImage : $original; } /** * Make a image edit URL * * @param string $file * @param array $parts Additional variables you want to add * @return string * */ protected function makeEditURL($file, $parts = array()) { $input = $this->wire()->input; $file = basename($file); $id = isset($parts['id']) ? (int) $parts['id'] : $this->page->id; if(!isset($parts['modal'])) $parts['modal'] = 1; if(!isset($parts['edit_page_id']) && $this->editorPage) $parts['edit_page_id'] = $this->editorPage->id; if(!isset($parts['hidpi'])) $parts['hidpi'] = (int) $this->hidpi; if(!isset($parts['original'])) { if($this->original) { $parts['original'] = $this->original; } else { $parts['original'] = $file; } } if(!isset($parts['class']) && $input->get('class')) { $class = $input->get('class'); if($class) { $validClasses = array_merge( explode(' ', $this->alignLeftClass), explode(' ', $this->alignCenterClass), explode(' ', $this->alignRightClass) ); $classes = array(); foreach(explode(' ', $class) as $c) { if(empty($c)) continue; if(in_array($c, $validClasses)) $classes[] = $c; } if(count($classes)) $parts['class'] = urlencode(implode(' ' , $classes)); } } if(!$this->rte) $parts['rte'] = '0'; if($this->field) $parts['field'] = $this->fieldName; if(!isset($parts['winwidth'])) { $winwidth = (int) $input->get('winwidth'); if($winwidth) $parts['winwidth'] = $winwidth; } if(!isset($parts['caption']) && $this->rte && $this->caption) { $parts['caption'] = 1; } // @todo should we check for 'class' ? unset($parts['id'], $parts['file']); // in case they are set here $url = $this->wire()->config->urls->admin . "page/image/edit/?id=$id&file=$id,$file"; foreach($parts as $key => $value) $url .= "&$key=$value"; return $url; } /** * Check if user has image edit permission and throw exception if they don't * * @param bool $throw Specify false if you only want this method to return a true|false rather than throw exception * @throws WirePermissionException * @return bool if the $throw argument was false * */ public function checkImageEditPermission($throw = true) { if(!$this->rte && !$this->wire()->user->hasPermission('page-edit-images', $this->masterPage)) { if($throw) { throw new WirePermissionException($this->labels['noAccess']); } else { return false; } } return true; } /** * Edit a selected image * * Required GET variables: * - file (string): URL of image, or preferably "123,basename.jpg" where 123 is ID. * - id (int): ID of page images is from, optional if specified in 'file'. * * Optional GET variables: * - edit_page_id (int): ID of the page being edited, where image will be placed. * - width (int): Current width of image in the editor * - height (int): Current height of image in the editor * - winwidth (int): Current width of client-side window in pixels * - id (int): ID of the page the image lives on (if omitted, we attempt to determine from 'file' path). * - class (string): Class name to pass along to the editor (i.e. align_left, align_right, etc.) * - hidpi (int): Whether hidpi mode is used or not (0=no, 1=yes) * - link: (string): URL that image is linking to in the editor. * - description: (string): Text for description "alt" tag. * - rte (int): Rich text editor mode (0=off, 1 or omit=on) * - field (string): Name of field (for non-rte mode) * * Optional GET variables that must come together as a group. When present, the image editor * will start in crop mode and show the original image with this portion selected for crop: * - crop_x (int): Predefined crop left position * - crop_y (int): Predefined crop top position * - crop_w (int): Predefined crop width * - crop_h (int): Predefined crop height * * @return string * @throws \Exception|WireException * */ public function ___executeEdit() { $input = $this->wire()->input; $config = $this->wire()->config; $session = $this->wire()->session; $sanitizer = $this->wire()->sanitizer; $adminTheme = $this->wire()->adminTheme; $checkboxClass = $adminTheme instanceof AdminThemeFramework ? $adminTheme->getClass('input-checkbox') : ''; $radioClass = $adminTheme instanceof AdminThemeFramework ? $adminTheme->getClass('input-radio') : ''; $this->checkImageEditPermission(); if($input->post('submit_crop')) { $crop = $this->processCrop(); $parts = $this->hidpi ? array('width' => $crop->hidpiWidth()) : array(); $session->location($this->makeEditURL($crop->basename, $parts)); return ''; } else if($input->post('submit_save_replace')) { return $this->processSave(true); } else if($input->post('submit_save_copy')) { return $this->processSave(false); } $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(); $icons = array( 'top' => 'long-arrow-up', 'left' => 'long-arrow-left', 'width' => 'arrows-h', 'height' => 'arrows-v', ); foreach($icons as $key => $value) { $icons[$key] = ""; } $optionalClasses = array( $this->alignLeftClass => $labels['alignLeft'], $this->alignRightClass => $labels['alignRight'], $this->alignCenterClass => $labels['alignCenter'], ); $cropX = null; $cropY = null; $cropWidth = null; $cropHeight = null; $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]); $cropHeight = (int) array_pop($matches[2]); $cropX = (int) array_pop($matches[3]); $cropY = (int) array_pop($matches[4]); } $isCroppable = !$isCropped; try { $original = $this->getPageimage(false); } catch(\Exception $e) { if($this->rte) { // when RTE mode, go to image selection again when invalid image $this->error($e->getMessage()); $session->location("./?id={$this->page->id}"); return ''; } else { throw $e; } } if($isCropped) { $image = $this->getPageimage(true); $recropURL = $this->makeEditURL($original->basename, array( 'crop_x' => $cropX, 'crop_y' => $cropY, 'crop_w' => $cropWidth, 'crop_h' => $cropHeight )); } else { $recropURL = ""; $image = $original; } if(!is_file($image->filename)) throw new WireException("Image file does not exist"); if($this->field) { $found = false; if($this->field->type instanceof FieldtypeImage) { $pageimages = $this->page->get($this->field->name); if($pageimages && $pageimages->get($original->name)) $found = true; } else if(method_exists($this->field->type, 'getPageimages')) { /** @var FieldtypeHasPageimages $fieldtype */ $fieldtype = $this->field->type; $pageimages = $fieldtype->getPageimages($this->page, $this->field); foreach($pageimages as $pageimage) { if($pageimage->name === $original->name) $found = true; if($found) break; } } if(!$found) { throw new WireException("Please save the page before editing newly uploaded images."); } } $basename = $image->basename; $fullname = $image->page->id . ',' . $basename; // "123,basename.jpg" $originalWidth = $image->width(); $originalHeight = $image->height(); // attributes for #selected_image $attrs = array( 'class' => '', 'width' => 0, 'height' => 0, 'data-origwidth' => $originalWidth, 'data-origheight' => $originalHeight, 'data-nosize' => ($this->noSizeAttrs ? '1' : ''), ); if(!$this->rte && $this->field) { $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($input->get('crop_x')) { $attrs['data-crop'] = ((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) $input->get('width'); $height = (int) $input->get('height'); if(!$width) $width = $this->editWidth; if(!$width) $width = ''; if(!$height) $height = $this->editHeight; if(!$height) $height = ''; // if width not specified in edit URL and image exceeds the window width, then scale to fit window if(!$width) { $width = $image->width; } $attrs['width'] = $width; $attrs['data-fit'] = '1'; $this->wire('processBrowserTitle', $basename); // 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($input->get('file')) == $fullname && $originalWidth > $width) $attrs['class'] .= " resized"; // if hidpi specified keep it checed $hidpiChecked = $this->hidpi ? " checked='checked'" : ""; $captionChecked = $this->caption ? " checked='checked'" : ""; // alignment class options $classOptions = ''; foreach($optionalClasses as $class => $label) { $labelKey = array_search($label, $labels); $selected = strpos((string) $input->get('class'), $class) !== false ? " selected='selected'" : ''; if($selected) $attrs['class'] .= " $class"; $classOptions .= "$label"; } // convert $attrs to an attribute string for placement in #selected_image markup $attrStr = ''; foreach($attrs as $key => $value) { if(!$value && $key != 'data-nosize') continue; // skip most empty attributes $attrStr .= "$key='" . trim($value) . "' "; } // prepare description (alt) $description = isset($_GET['description']) ? $input->get('description') : ''; // $image->description; if(strlen($description) > 8192) $description = substr($description, 0, 8192); $description = $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 || $input->get('link')) { if($image->name != $original->name) $formClasses[] = 'not-original'; $imgURL = str_replace($config->urls->root, '/', $original->url); $imgLinkURL = trim((string) $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 $test = substr($imgLinkURL, -1 * strlen($imgURL)); $linkOriginalChecked = $test == $imgURL ? " checked='checked' data-was-checked='1'" : ""; unset($imgLinkURL); } } else { $formClasses[] = 'original'; } $resizeYesChecked = $this->rte ? " checked='checked'" : ""; $resizeNoChecked = !$this->rte ? " checked='checked'" : ""; if($this->field && $this->field->get('maxFiles') == 1) $formClasses[] = 'maxfiles1'; // form attributes $formClasses[] = $isCroppable ? 'croppable' : 'not-croppable'; $formClasses[] = $this->rte ? 'rte' : 'not-rte'; $formClass = implode(' ', $formClasses); $formActionURL = $this->makeEditURL($basename); $originalDimension = $originalWidth . 'x' . $originalHeight; $imageURL = $this->rte ? $image->url : $image->URL; // form header and inputs // @todo move all this markup to a separate template file $out = "
    " . "" . "

    " . "" . "" . "" . "" . //"" . //"" . //"" . //"" . "" . "" . "" . "" . "" . "" . "" . "" . "" . "  " . "" . " " . "" . "" . "" . "" . "" . "" . "" . "" . "" . "" . "" . // selected_image_checkboxes "" . " " . "$labels[useResize]  " . "  " . "" . "" . " " . "" . " " . "   " . "" . "  " . "  " . "  " . "  " . "" . "" . "" . "

    " . "

    " . "" . "" . "

    " . "
    " . //"

    Please note: this feature is still in development.

    " . "
    " . "" . "" . "" . "" . "" . "" . "" . "" . "" . "" . "" . "
    " . "$labels[captionText]" . "
    " . "
    " . "

    " . "" . $this->getLatin(2) . "

    " . "$basename" . "" . "" . "

    " . (!in_array('maxfiles1', $formClasses) ? ( "" ) : "") . "" . "" . "

    " . "
    "; // if($this->wire('config')->debug) $out .= "

    $basename

    "; return $out; } /** * Get a bunch of latin text * * @param int $n How many blocks of latin to get (default=1) * @return string * */ protected function getLatin($n = 1) { $latin = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In malesuada magna ex. Donec sed consectetur felis, ac molestie massa. Duis fermentum rutrum vehicula. Praesent consequat efficitur dolor quis vestibulum. Sed vulputate, nisi et efficitur semper, erat odio scelerisque massa, nec convallis leo ipsum ac eros. Donec a augue eleifend, pellentesque sapien a, tristique orci. Sed ac leo vel libero iaculis facilisis in at mauris. Etiam vitae mi cursus nisl gravida vulputate. Nam in magna risus. Nullam vel suscipit lacus. Ut sit amet malesuada massa. Etiam rhoncus, tellus vel porta dignissim, elit massa cursus tortor, vitae fermentum dolor augue ac tortor. Aliquam eleifend mi at dictum pulvinar. Sed bibendum dolor non mi placerat, euismod euismod est commodo. Vivamus ultrices orci arcu, sed pulvinar tellus molestie quis. Quisque pharetra velit a metus mattis, vitae fermentum orci hendrerit. Fusce id libero ac urna ultrices consequat. Donec lorem lacus, dignissim vitae faucibus eget, tincidunt sit amet felis. Aenean tempor quis sapien vitae euismod. Donec mattis mi at sem egestas interdum. Integer sed diam finibus, volutpat lectus ac, ornare dui. Suspendisse potenti. Suspendisse sagittis interdum elit sodales commodo. Maecenas porttitor mi sit amet velit porta, sit amet eleifend massa malesuada. Phasellus luctus nibh id sem venenatis, dictum fermentum turpis hendrerit. Nam tempor, ex eu sagittis gravida, mi lorem maximus ex, aliquam commodo nunc leo et lacus. Nunc vel faucibus lacus, non pellentesque elit. Nulla non rutrum magna, nec porta augue. "; $out = ''; for($c = 1; $c <= $n; $c++) $out .= $latin; return $out; } /** * Process an image crop from submitted POST vars * * @return Pageimage * @throws WireException * */ protected function processCrop() { $input = $this->wire()->input; $cropX = (int) $input->post('crop_x'); $cropY = (int) $input->post('crop_y'); $cropW = (int) $input->post('crop_w'); $cropH = (int) $input->post('crop_h'); $image = $this->getPageimage(); if(!$image) throw new WireException("Unable to load image"); $suffix = $this->rte ? array('is') : array(); $options = array('cleanFilename' => true, 'suffix' => $suffix); $crop = $image->crop($cropX, $cropY, $cropW, $cropH, $options); return $crop; } /** * Save resized image and existing eisting or create a copy * * @param bool $replace Replace existing image? (default=false) * @return string Returns status output which may appear momentarily while dialog updating * @throws WireException * */ protected function processSave($replace = false) { if(!$this->field) throw new WireException("Field is not defined"); if(!$this->original) throw new WireException("Original is not set"); if($this->page->hasStatus(Page::statusLocked)) throw new WireException("Page is locked for edits"); $pages = $this->wire()->pages; $input = $this->wire()->input; $fieldtype = $this->field->type; $image = $this->getPageimage(false); if($input->get('file') != "$this->page,$image->name") $image = $this->getPageimage(true); $width = (int) $input->post('width'); if(!$width) throw new WireException("Width not specified"); $rebuildVariations = preg_match('/-cropx\d+y\d+/', $image->name); $useResize = ((int) $input->post('use_resize')) == 1; // image2 = resized version if($useResize) { $image2 = $image->width($width); if(!$image2 || !$image2->width) throw new WireException('Unable to complete resize'); } else { $image2 = $image; } /** @var Pageimages $pageimages */ if($fieldtype instanceof FieldtypeImage) { $pageimages = $this->page->getUnformatted($this->field->name); } else { $pageimages = $image->pagefiles; } $path = $pageimages->path(); $fileID = ''; $isNew = 0; $body = ''; if($replace) { // replace original image if($this->original && $this->original != $image2->basename() && $original = $image->pagefiles->get($this->original)) { /** @var Pageimage $original */ $fileID = 'file_' . $original->hash; if($rebuildVariations && $this->field->get('adminThumbs')) { // remove original thumbnail /** @var InputfieldImage $inputfield */ $inputfield = $this->field->getInputfield($this->page); if($inputfield) { $thumb = $inputfield->getAdminThumb($original); $thumb = $thumb['thumb']; if($thumb->url != $original->url) { // there is a thumbnail, distinct from the original image $this->wire()->files->unlink($thumb->filename); } } } // replace original image if($original->replaceFile($image2->filename())) { $original->modified = time(); $original->modifiedUser = $this->wire()->user; /** @var FieldtypeFile $fieldtype */ if($fieldtype instanceof FieldtypeFile) { $fieldtype->saveFileCols($this->page, $this->field, $original, array( 'filesize' => $original->filesize(), 'modified' => $original->modified, 'modified_users_id' => $original->modified_users_id, 'width' => $original->width(), 'height' => $original->height(), 'ratio' => $original->ratio(), )); } else { $this->page->trackChange($this->field->name); } } $pages->uncacheAll(); $page = $pages->get($this->page->id); /** @var Pageimages $value */ $value = $page->getUnformatted($this->field->name); if(!$value instanceof Pageimages) $value = $pageimages; if($rebuildVariations) { /** @var Pageimage $finalImage */ $finalImage = $value->get($this->original); $variationInfo = $finalImage->rebuildVariations(0, array('is', 'hidpi')); foreach($variationInfo as $type => $files) { if(!wireCount($files)) continue; $body .= "

    " . ucfirst($type) . "

    "; $body .= "

    " . implode('
    ', $files) . "

    "; // $this->wire('log')->save('images', "$type: [ " . implode(' ], [ ', $files) . ' ]'); } } else { $body .= "

    " . $this->_('No changes necessary to image variations.') . "

    "; } $headline = $this->labels['updating']; } else { $headline = $this->_('No changes made'); } } else { // save a new copy $n = 0; do { $n++; $basename = basename($this->original); preg_match('/^([^.]+)(\..+)$/', $basename, $matches); $basename = $matches[1]; $aftername = $matches[2]; if(strpos($basename, '-v')) $basename = preg_replace('/-v[0-9]+$/', '', $basename); $basename .= "-v$n"; $basename .= $aftername; $pathname = $path . $basename; } while(is_file($pathname)); if(!$useResize && !$rebuildVariations) { $this->wire()->files->copy($image2->filename(), $pathname); } else { $this->wire()->files->rename($image2->filename(), $pathname); } $pageimages->add($pathname); $pageimage = $pageimages->last(); /** @var Pageimage $pageimage */ if(!$this->field->get('overwrite')) $pageimage->isTemp(true); $this->page->save($this->field->name); $fileID = "file_$pageimage->hash"; $isNew = 1; $headline = $this->labels['updating']; } $js = 'script'; $out = "

    $headline

    $body <$js>$(document).ready(function() { setupProcessSave('$this->fieldName', '$fileID', $isNew); }); "; return $out; } /** * Resize image and output basic markup with information about the resize * * The output is intended to be read by the rich text editor for insertion into the text. * * Required GET variables: * - file (string): URL of image * - width (int): Target resize width * - edit_page_id (int): ID of the page being edited, where image will be placed. * * Optional GET variables: * - id (int): ID of the page the image lives on (if omitted, we attempt to determine from 'file' path). * - class (string): Class name to pass along to the editor (i.e. align_left, align_right, etc.) * - hidpi (int): Whether hidpi mode is used or not (0=no, 1=yes) * - json (int): Specify 1 to provide output in JSON (if not specified, output is a paragraph of markup) * * @return string * @throws WireException * */ public function ___executeResize() { $this->checkImageEditPermission(); $input = $this->wire()->input; $adminTheme = $this->wire()->adminTheme; $checkboxClass = $adminTheme instanceof AdminThemeFramework ? $adminTheme->getClass('input-checkbox') : ''; $width = (int) $input->get('width'); $class = $this->sanitizer->name($input->get('class')); $hidpi = $this->hidpi; $json = (int) $input->get('json'); // 1 or 0 (for json output mode on or off) $rotate = (int) $input->get('rotate'); $flip = $input->get('flip'); $crop = $input->get('crop'); if($flip != 'v' && $flip != 'h') $flip = ''; if($crop !== null && (!ctype_alnum($crop) || strlen($crop) > 20)) $crop = ''; if(strpos($class, 'hidpi') !== false) { if(!$hidpi) $hidpi = true; } else { if($hidpi) $class = trim("$class hidpi"); } $image = $this->getPageimage(true); if(!$crop && strpos($image->basename, '-cropx') !== false) { $image = $this->getLastCrop($image); } if( (!$hidpi && $width < $image->width) || ($hidpi && $width < $image->hidpiWidth()) || $flip || $rotate) { $suffix = array('is'); // is=image select if($this->editorPage && $this->editorPage->id != $this->page->id) { $suffix[] = "pid$this->editorPage"; // identify page that is using the variation } $options = array( 'suffix' => $suffix, 'hidpi' => $hidpi, ); if($rotate) $options['rotate'] = $rotate; if($flip) $options['flip'] = $flip; if($crop) $options['cropping'] = $crop; //$this->log(print_r($options, true)); $resized = $image->width($width, $options); $height = $resized->height(); } else { $width = $image->width; $height = $image->height; $resized = $image; } if($hidpi) { $width = $resized->hidpiWidth(); $height = $resized->hidpiHeight(); $hidpiChecked = " checked='checked'"; } else { $hidpiChecked = ''; } $nosize = $this->noSizeAttrs ? 1 : ''; if($json) { $data = array( 'src' => $resized->url, 'name' => $image->basename, 'width' => $width, 'height' => $height, 'class' => $class, 'page' => $image->page->id, 'hidpi' => (bool) $hidpi, 'nosize' => (bool) $nosize, ); header("Content-Type: application/json"); $out = json_encode($data); } else { // note IE8 won't properly read the width/height attrs via ajax // so we provide the width/height in separate fields $out = "

    " . "$widthx" . "$height " . "
    " . "" . "

    "; } //$this->log($out); return $out; } /** * Show all variations for the image provided in GET var 'file' * * @return string * @throws WireException * */ public function ___executeVariations() { $files = $this->wire()->files; $modules = $this->wire()->modules; $user = $this->wire()->user; $input = $this->wire()->input; $sanitizer = $this->wire()->sanitizer; $pages = $this->wire()->pages; $config = $this->wire()->config; $pageimage = $this->getPageimage(); if(!$this->page || !$pageimage) throw new WireException("No file provided"); if(!$this->masterPage->editable()) throw new WireException($this->labels['noAccess']); $cnt = 0; // for id purposes $num = 0; // for display purposes $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'); $extraLabel = $this->_('%s of above'); $hasEditPermission = $user->hasPermission('page-edit-images', $this->masterPage); $variations = $pageimage->getVariations(array('info' => true, 'verbose' => 1)); $adminThumbOptions = $config->adminThumbOptions; $delete = $input->post('delete'); if(is_array($delete) && count($delete) && $hasEditPermission) { $deleteUrls = array(); $deleteErrors = array(); foreach($delete as $name) { if(!isset($variations[$name])) continue; $info = $variations[$name]; if($files->exists($info['path']) && $files->unlink($info['path'])) { $deleteUrls[] = $info['url']; if(!empty($info['webpPath']) && $files->exists($info['webpPath'])) { if($files->unlink($info['webpPath'])) { $deleteUrls[] = $info['webpUrl']; } else { $deleteErrors[] = $info['webpUrl']; } } unset($variations[$name]); } else { $deleteErrors[] = $info['url']; } } foreach($deleteUrls as $url) { $this->message($this->_('Deleted image variation') . " - $url"); } foreach($deleteErrors as $url) { $this->error($this->_('Error deleting image variation') . " - $url"); } $this->wire()->session->location("./?id={$this->page->id}&file=$pageimage->basename"); } $rows[] = array( 'cnt' => $cnt, 'num' => $num, '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"; $ext = strtoupper($extra->ext); $rows[] = array( 'cnt' => ++$cnt, 'num' => "$num $ext", 'url' => $url, 'name' => $name, 'notes' => array(sprintf($extraLabel, $ext) . " ($extra->savingsPct)"), 'width' => $pageimage->width(), 'height' => $pageimage->height(), 'modified' => $modified, 'filesize' => $filesize, 'filesizeStr' => $filesizeStr, 'deletable' => false, ); } 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'); if(!count($notes) && $info['width'] == $adminThumbOptions['width'] && $info['height'] == $adminThumbOptions['height']) { $notes[] = $this->_x('Auto-generated admin thumbnail', 'notes'); } if(!count($notes)) $notes[] = $this->_x('API-generated variation', 'notes'); if($info['crop']) $notes[] = $this->_x('Cropped version', 'notes'); // identify pid suffixes foreach($info['suffix'] as $suffix) { if(strpos($suffix, 'pid') === 0) { $suffix = ltrim($suffix, 'pid'); $refpage = null; if(ctype_digit($suffix)) $refpage = $pages->get((int) $suffix); if($refpage && $refpage->id && $user->hasPermission('page-view', $refpage)) { $notes[] = $this->_x('Inserted from page:', 'notes') . " $refpage->path"; } } } $width = (int) $info['width']; $height = (int) $info['height']; if(!$width || !$height) list($width, $height) = getimagesize($info['path']); $filesize = filesize($info['path']); $filesizeStr = wireBytesStr($filesize); $mtime = filemtime($info['path']); $modified = date('Y-m-d H:i:s', $mtime); $url = "$info[url]?nc=$mtime"; $rows[] = array( 'cnt' => ++$cnt, 'num' => ++$num, '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"; $ext = strtoupper($extra->ext); $rows[] = array( 'cnt' => ++$cnt, 'num' => "$num $ext", 'url' => $url, 'name' => $name, 'notes' => array(sprintf($extraLabel, $ext) . " ($extra->savingsPct)"), 'width' => $width, 'height' => $height, 'modified' => $modified, 'filesize' => $filesize, 'filesizeStr' => $filesizeStr, 'deletable' => false, ); } } /** @var InputfieldCheckbox $checkbox */ $checkbox = $modules->get('InputfieldCheckbox'); $checkbox->label = ' '; $checkbox->addClass('delete'); $checkbox->attr('id+name', 'delete_all'); $checkbox->val(1); /** @var MarkupAdminDataTable $table */ $table = $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( (strlen($row['num']) ? $row['num'] : ' '), "$row[name]", "$row[name]
    $row[width]x$row[height]", "$row[filesize] $row[filesizeStr]", $row['modified'], implode('
    ', $row['notes']), ($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', $num), $num, $pageimage->basename )); $varcnt = $sanitizer->entities($input->get('varcnt')); /** @var InputfieldForm $form */ $form = $modules->get('InputfieldForm'); $form->attr('id', 'ImageVariations'); $form->action = "./?id={$this->page->id}&file=$pageimage->basename&varcnt=$varcnt"; $form->prependMarkup = $table->render(); if($hasEditPermission) { /** @var InputfieldSubmit $submit */ $submit = $modules->get('InputfieldSubmit'); $submit->attr('value', $this->_('Delete Checked')); $submit->addClass('delete-checked'); $submit->icon = 'trash'; $form->add($submit); } /** @var InputfieldButton $button */ $button = $modules->get('InputfieldButton'); $button->attr('value', $this->_('Close')); $button->addClass('pw-modal-cancel'); $button->icon = 'times-circle'; $form->add($button); /** @var InputfieldHidden $hidden */ $hidden = $modules->get('InputfieldHidden'); $hidden->attr('id+name', 'varcnt_id'); $hidden->attr('value', $varcnt); $hidden->attr('data-cnt', wireCount($variations)); $form->add($hidden); $modules->get('JqueryMagnific'); $out = $form->render(); if($config->demo) { $out = "

    Note: " . $this->labels['demoMode'] . "

    " . $out; } else { $out = "
    " . $out; } return $out; } /** * Module configuration * * @param array $data * @return InputfieldWrapper * */ public function getModuleConfigInputfields(array $data) { $inputfields = $this->wire(new InputfieldWrapper()); $data = array_merge(self::$defaultConfig, $data); $modules = $this->wire()->modules; /** @var InputfieldCheckbox $f */ $f = $modules->get('InputfieldCheckbox'); $f->attr('name', 'hidpiDefault'); $f->label = $this->_('HiDPI/Retina checkbox default checked?'); $f->description = $this->_('Check this box to have the HiDPI/Retina checkbox checked by default for newly inserted images.'); if(!empty($data['hidpiDefault'])) $f->attr('checked', 'checked'); $f->columnWidth = 50; $inputfields->add($f); /** @var InputfieldCheckbox $f */ $f = $modules->get('InputfieldCheckbox'); $f->attr('name', 'noThumbs'); $f->label = $this->_('Do not generate thumbnail images'); $f->description = $this->_('When checked, image selection will use full-size images rather than thumbnail images.'); if(!empty($data['noThumbs'])) $f->attr('checked', 'checked'); $f->columnWidth = 50; $inputfields->add($f); /** @var InputfieldText $f */ $f = $modules->get('InputfieldText'); $f->attr('name', 'skipFields'); $f->attr('value', isset($data['skipFields']) ? $data['skipFields'] : ''); $f->label = $this->_('Field names to skip for selection'); $f->description = $this->_('Enter the names of any image fields (separated by a space) that you do not want to allow for selection with this module.'); $inputfields->add($f); /** @var InputfieldCheckbox $f */ $f = $modules->get('InputfieldCheckbox'); $f->attr('name', 'noSizeAttrs'); $f->attr('value', 1); if(!empty($data['noSizeAttrs'])) $f->attr('checked', 'checked'); $f->label = $this->_('Skip width attributes on image tags?'); // noSizeAttr label $f->description = $this->_('By default, this module will include width attributes in the img tag. If you are using responsive images, you might want to disable this behavior. Check the box to disable width and height attributes.'); // noSizeAttr description $f->notes = $this->_('We do not recommend checking this box as it will interfere with some features (like use of HiDPI/retina images).'); $inputfields->add($f); $notes = $this->_('Recommended value:') . ' '; /** @var InputfieldText $f */ $f = $modules->get('InputfieldText'); $f->attr('name', 'alignLeftClass'); $f->attr('value', $data['alignLeftClass']); $f->label = $this->_('Align Image Left Class'); $f->notes = $notes . '**align_left**'; $f->columnWidth = 33; $inputfields->add($f); /** @var InputfieldText $f */ $f = $modules->get('InputfieldText'); $f->attr('name', 'alignCenterClass'); $f->attr('value', $data['alignCenterClass']); $f->label = $this->_('Align Image Center Class'); $f->notes = $notes . '**align_center**'; $f->columnWidth = 34; $inputfields->add($f); /** @var InputfieldText $f */ $f = $modules->get('InputfieldText'); $f->attr('name', 'alignRightClass'); $f->attr('value', $data['alignRightClass']); $f->label = $this->_('Align Image Right Class'); $f->notes = $notes . '**align_right**'; $f->columnWidth = 33; $inputfields->add($f); return $inputfields; } }