1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-15 03:05:26 +02:00

Lots of improvements to configuration of File and Image fields (as used in ProcessField).

This commit is contained in:
Ryan Cramer
2021-01-01 13:59:06 -05:00
parent 43cfdd9dfe
commit d5d4e8049b
8 changed files with 1063 additions and 485 deletions

View File

@@ -9,7 +9,7 @@
* /wire/core/Fieldtype.php
* /wire/core/FieldtypeMulti.php
*
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer
* https://processwire.com
*
* @property array $allowFieldtypes Allowed Fieldtype types for custom fields
@@ -22,7 +22,7 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
public static function getModuleInfo() {
return array(
'title' => __('Files', __FILE__),
'version' => 106,
'version' => 107,
'summary' => __('Field that stores one or more files', __FILE__),
'permanent' => true,
);
@@ -181,9 +181,16 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
return $inputfield;
}
/**
* Get compatible Fieldtypes
*
* @param Field $field
* @return Fieldtypes
*
*/
public function ___getCompatibleFieldtypes(Field $field) {
$fieldtypes = $this->wire(new Fieldtypes());
foreach($this->wire('fieldtypes') as $fieldtype) {
foreach($this->wire()->fieldtypes as $fieldtype) {
if($fieldtype instanceof FieldtypeFile) $fieldtypes->add($fieldtype);
}
return $fieldtypes;
@@ -521,7 +528,6 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
return $indexValue;
}
/**
* Export value
*
@@ -623,7 +629,16 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
return $description;
}
/**
* Get blank value
*
* @param Page $page
* @param Field $field
* @return Pagefiles
*
*/
public function getBlankValue(Page $page, Field $field) {
/** @var Pagefiles $pagefiles */
$pagefiles = $this->wire(new Pagefiles($page));
$pagefiles->setField($field);
$pagefiles->setTrackChanges(true);
@@ -644,6 +659,15 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
return $this->wire(new Pagefile($pagefiles, $filename));
}
/**
* Sanitize value
*
* @param Page $page
* @param Field $field
* @param mixed $value
* @return Pagefiles
*
*/
public function sanitizeValue(Page $page, Field $field, $value) {
if($value instanceof Pagefiles) return $value;
$pagefiles = $page->getUnformatted($field->name);
@@ -751,6 +775,15 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
return $value;
}
/**
* Format value when output format is string
*
* @param Page $page
* @param Field $field
* @param Pagefiles $value
* @return string
*
*/
protected function ___formatValueString(Page $page, Field $field, $value) {
if($page) {} // ignore
$out = '';
@@ -1160,7 +1193,14 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
return $result;
}
/**
* Delete field from page
*
* @param Page $page
* @param Field $field
* @return bool
*
*/
public function ___deletePageField(Page $page, Field $field) {
// if($this->config->debug) $this->message("deletePageField, Page:$page, Field:$field");
@@ -1194,6 +1234,13 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
}
/**
* Delete field
*
* @param Field $field
* @return bool
*
*/
public function ___deleteField(Field $field) {
// delete files not necessary since deletePageField would have been called for all instances before this could be called
return parent::___deleteField($field);
@@ -1206,10 +1253,38 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
* Subclasses can override with their own string of file extensions
*
*/
protected function getDefaultFileExtensions() {
public function getDefaultFileExtensions() {
return "pdf doc docx xls xlsx gif jpg jpeg png";
}
/**
* Get default Inputfield class name
*
* #pw-internal
*
* @return string
* @since 3.0.170
*
*/
public function getDefaultInputfieldClass() {
return $this->defaultInputfieldClass;
}
/**
* Get allowable Fieldtypes for custom fields
*
* #pw-internal
*
* @param bool $getDefaults Get the default setting instead?
* @return array
* @since 3.0.170
*
*/
public function getAllowFieldtypes($getDefaults = false) {
if($getDefaults) return $this->defaultAllowFieldtypes;
return $this->allowFieldtypes;
}
/**
* Disable autojoin for files
*
@@ -1506,181 +1581,20 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
*
*/
public function ___getConfigInputfields(Field $field) {
$inputfields = parent::___getConfigInputfields($field);
$extensionInfo = $this->getValidFileExtensions($field);
$fileValidatorsUrl = 'https://modules.processwire.com/categories/file-validator/';
// extensions
/** @var InputfieldTextarea $f */
$f = $this->modules->get('InputfieldTextarea');
$f->attr('name', 'extensions');
$f->attr('value', $field->get('extensions') ? $field->get('extensions') : $this->getDefaultFileExtensions());
$f->attr('rows', 3);
$f->label = $this->_('Valid File Extensions');
$f->description = $this->_('Enter all file extensions allowed by this upload field. Separate each extension by a space. No periods or commas. This field is not case sensitive.'); // Valid file extensions description
if(count($extensionInfo['invalid']) && !$this->wire()->input->is('POST')) {
foreach($extensionInfo['invalid'] as $ext) {
$error = sprintf(
$this->_('File extension %s must be removed, whitelisted, or have a [file validator](%s) module installed.'),
strtoupper($ext),
$fileValidatorsUrl
);
$this->error($error, Notice::allowMarkdown | Notice::noGroup);
}
}
$inputfields->append($f);
if(count($extensionInfo['invalid']) || count($extensionInfo['whitelist'])) {
/** @var array $okExtensions */
$badExtensions = array_merge($extensionInfo['invalid'], $extensionInfo['whitelist']);
ksort($badExtensions);
/** @var InputfieldCheckboxes $f Whitelisted file extensions */
$f = $this->modules->get('InputfieldCheckboxes');
$f->attr('name', 'okExtensions');
$f->label = $this->_('File extensions to allow without validation (whitelist)');
$f->icon = 'warning';
foreach($badExtensions as $ext) $f->addOption($ext);
$f->description =
sprintf(
$this->_('These file extensions need a [file validator module](%s) installed. Unchecked extensions have been disabled for safety.'),
$fileValidatorsUrl
) . ' ' .
$this->_('To ignore and allow the file extension without file validation, check the box next to it (not recommended).') . ' ' .
$this->_('If you dont need an extension, please remove it from your valid file extensions list.');
$f->attr('value', $extensionInfo['whitelist']);
$inputfields->add($f);
} else {
$field->set('okExtensions', array());
return $this->fieldtypeConfiguration()->getConfigInputfields($field, $inputfields);
}
// max files
/** @var InputfieldInteger $f */
$f = $this->modules->get('InputfieldInteger');
$f->attr('name', 'maxFiles');
$f->attr('value', (int) $field->get('maxFiles'));
$f->attr('size', 4);
$f->label = $this->_('Maximum files allowed');
$f->description = $this->_('0=No limit');
$f->collapsed = Inputfield::collapsedBlank;
$inputfields->append($f);
// output format
/** @var InputfieldRadios $f */
$f = $this->modules->get('InputfieldRadios');
$f->attr('name', 'outputFormat');
$f->label = $this->_('Formatted value');
$f->description = $this->_('Select the type of value you want this field to provide when accessed from the API on the front-end of your site.');
$f->notes = $this->_('When output formatting is off, the value is always an array (WireArray).');
$f->addOption(self::outputFormatAuto, $this->_('Automatic (single item or null when max files set to 1, array of items otherwise)'));
$f->addOption(self::outputFormatArray, $this->_('Array of items'));
$f->addOption(self::outputFormatSingle, $this->_('Single item (null if empty)'));
$f->addOption(self::outputFormatString, $this->_('Rendered string of text (that you provide)'));
$f->attr('value', (int) $field->get('outputFormat'));
$f->collapsed = Inputfield::collapsedBlank;
$inputfields->add($f);
// output string
/** @var InputfieldText $f */
$f = $this->modules->get('InputfieldText');
$f->attr('name', 'outputString');
$f->label = $this->_('Rendered string of text');
$f->attr('placeholder', "i.e. <a href='{url}'>{description}</a>");
$f->attr('value', $field->get('outputString') ? $field->get('outputString') : '');
$f->description = $this->_('Provide the rendered string of text you want to output as the value of this field. If the field contains multiple items, this string will be rendered multiple times. If the field contains no items, a blank string will be used.');
$f->notes = $this->_('You may use any of the following tags:') . ' {url}, {description}, {tags}';
$f->showIf = "outputFormat=" . self::outputFormatString;
$inputfields->add($f);
// default value page
/** @var InputfieldPageListSelect $f */
$f = $this->modules->get('InputfieldPageListSelect');
$f->attr('name', 'defaultValuePage');
$f->label = $this->_('Default value (when empty)');
$f->description = $this->_('Optionally select a page that will contain the default value (in this same field). You may wish to create a page specifically for this purpose.');
$f->attr('value', (int) $field->get('defaultValuePage'));
$f->collapsed = $field->get('defaultValuePage') ? Inputfield::collapsedNo : Inputfield::collapsedYes;
$inputfields->add($f);
// textformatters
/** @var InputfieldAsmSelect $f */
$f = $this->modules->get('InputfieldAsmSelect');
$f->setAttribute('name', 'textformatters');
$f->label = $this->_('Text formatters (for file descriptions)');
$f->description = $this->_('Select one or more text formatters (and their order) that will be applied to the file description when output formatting is active. The HTML Entity Encoder is recommended as a minimum.');
foreach($this->wire('modules') as $module) {
$className = $module->className();
if(strpos($className, 'Textformatter') !== 0) continue;
$info = $this->wire('modules')->getModuleInfo($module);
$f->addOption($className, "$info[title]");
}
if(!is_array($field->get('textformatters'))) {
$field->set('textformatters', $field->get('entityEncode') ? array('TextformatterEntities') : array());
}
$f->attr('value', $field->get('textformatters'));
$inputfields->add($f);
// entity encode (deprecated)
/** @var InputfieldHidden $f */
$f = $this->modules->get("InputfieldHidden");
$f->attr('name', 'entityEncode');
$f->attr('value', '');
if($field->get('entityEncode')) $f->attr('checked', 'checked');
$inputfields->append($f);
$field->set('entityEncode', null);
// use tags
/** @var InputfieldRadios $f */
$f = $this->modules->get("InputfieldRadios");
$f->attr('name', 'useTags');
$f->label = $this->_('Use Tags?');
$f->description = $this->_('When enabled, the field will also contain an option for tags in addition to the description.'); // Use tags description
$f->icon = 'tags';
$predefinedLabel = $this->_('User selects from list of predefined tags');
$f->addOption(self::useTagsOff, $this->_('Tags disabled'));
$f->addOption(self::useTagsNormal, $this->_('User enters tags by text input'));
$f->addOption(self::useTagsPredefined, $predefinedLabel);
$f->addOption(self::useTagsNormal | self::useTagsPredefined, $predefinedLabel . ' + ' . $this->_('can input their own'));
$f->attr('value', (int) $field->get('useTags'));
if(!$f->attr('value')) $f->collapsed = Inputfield::collapsedYes;
$inputfields->append($f);
/** @var InputfieldTextarea $f */
$f = $this->modules->get('InputfieldText');
$f->attr('name', 'tagsList');
$f->label = $this->_('Predefined tags');
$f->description = $this->_('Enter tags separated by a space. Tags may contain letters, digits, underscores or hyphens.');
$f->icon = 'tags';
$f->attr('value', $field->get('tagsList'));
$f->showIf = 'useTags>1';
$inputfields->append($f);
// inputfield class
/** @var InputfieldSelect $f */
$f = $this->modules->get('InputfieldSelect');
$f->attr('name', 'inputfieldClass');
$f->label = $this->_('Inputfield Type');
$f->description = $this->_('The type of field that will be used to collect input.');
$f->notes = $this->_('Change this only if instructed to do so by 3rd party Inputfield module instructions.');
$f->required = true;
$baseClass = $this->defaultInputfieldClass;
foreach($this->wire('modules')->findByPrefix('Inputfield') as $fm) {
if($baseClass == 'InputfieldFile' && strpos($fm, 'InputfieldImage') === 0) continue;
if("$fm" == $baseClass || is_subclass_of(__NAMESPACE__ . "\\$fm", __NAMESPACE__ . "\\$baseClass")) {
$f->addOption("$fm", str_replace("Inputfield", '', "$fm"));
}
}
$inputfieldClass = $field->get('inputfieldClass');
$f->attr('value', $inputfieldClass ? $inputfieldClass : $this->defaultInputfieldClass);
$f->collapsed = $inputfieldClass && $inputfieldClass != $this->defaultInputfieldClass ? Inputfield::collapsedNo : Inputfield::collapsedYes;
$inputfields->append($f);
return $inputfields;
/**
* Field advanced config
*
* @param Field $field
* @return InputfieldWrapper
*
*/
public function ___getConfigAdvancedInputfields(Field $field) {
$inputfields = parent::___getConfigAdvancedInputfields($field);
return $this->fieldtypeConfiguration()->getConfigAdvancedInputfields($field, $inputfields);
}
/**
@@ -1690,77 +1604,17 @@ class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
*
*/
public function ___getModuleConfigInputfields(InputfieldWrapper $inputfields) {
if($this->className() != 'FieldtypeFile') return;
/** @var InputfieldCheckboxes $f */
$f = $this->wire('modules')->get('InputfieldCheckboxes');
$f->attr('name', 'allowFieldtypes');
$f->label = $this->_('Allowed Fieldtype modules for custom fields');
$f->description = $this->_('Types with strikethrough are not likely to be 100% compatible.');
$f->optionColumns = 3;
$f->entityEncodeText = false;
$moduleNames = $this->wire('modules')->findByPrefix('Fieldtype');
ksort($moduleNames);
$names = array();
// types always disallowed
$blacklist = array(
'File',
'Image',
'Repeater',
'RepeaterMatrix',
'PageTable',
'Options',
'Comments',
'Table'
);
foreach($moduleNames as $key => $moduleName) {
list(,$name) = explode('Fieldtype', $moduleName, 2);
$names[$name] = $name;
if(in_array($name, $blacklist)) {
unset($names[$name]);
continue;
$this->fieldtypeConfiguration()->getModuleConfigInputfields($inputfields);
}
if(in_array($name, $this->defaultAllowFieldtypes)) continue; // these are fine
// check schema of field by finding an example of one
$allow = false;
foreach($this->wire('fields') as $field) {
// @var Field $field
if($field->type instanceof FieldtypeFile) continue;
if(!wireInstanceOf($field->type, $moduleName)) continue;
// verify that field DB table is responsible for all data created by the field
$schema = $field->type->getDatabaseSchema($field);
if(isset($schema['xtra']['all']) && $schema['xtra']['all'] !== true) continue;
unset($schema['data'], $schema['pages_id'], $schema['keys'], $schema['xtra']);
// if there's not any other schema required by the Fieldtype, it can be supported here
if(!count($schema)) $allow = true;
break;
}
if(!$allow) {
// indicate with strikethrough potential issue with this type
$names[$name] = "<s>$name</s>";
/**
* @return FieldtypeFileConfiguration
*
*/
protected function fieldtypeConfiguration() {
require_once(__DIR__ . '/config.php');
return new FieldtypeFileConfiguration($this);
}
}
foreach($names as $key => $name) {
$f->addOption($key, $name);
}
$f->val($this->allowFieldtypes);
$inputfields->add($f);
}
}

View File

@@ -0,0 +1,612 @@
<?php namespace ProcessWire;
/**
* ProcessWire File Fieldtype (configuration)
*
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer
* https://processwire.com
*
*/
class FieldtypeFileConfiguration extends Wire {
/**
* @var FieldtypeFile
*
*/
protected $fieldtype;
/**
* Construct
*
* @param FieldtypeFile $fieldtype
*
*/
public function __construct(FieldtypeFile $fieldtype) {
$this->fieldtype = $fieldtype;
$fieldtype->wire($this);
parent::__construct();
}
/**
* Fieldtype file field configuration
*
* @param Field $field
* @param InputfieldWrapper $inputfields
*
* @return InputfieldWrapper
*
*/
public function getConfigInputfields(Field $field, InputfieldWrapper $inputfields) {
$modules = $this->wire()->modules;
$input = $this->wire()->input;
/** @var FieldtypeFile $fieldtype */
$extensionInfo = $this->fieldtype->getValidFileExtensions($field);
$fileValidatorsUrl = 'https://processwire.com/modules/category/file-validator/';
/** @var InputfieldFieldset $fs */
$fs = $modules->get('InputfieldFieldset');
$fs->attr('name', '_files_fieldset');
$fs->label = $this->_('Files');
$fs->icon = 'files-o';
$inputfields->add($fs);
// extensions
/** @var InputfieldTextarea $f */
$f = $modules->get('InputfieldTextarea');
$f->attr('name', 'extensions');
$value = $field->get('extensions');
if(!$value) $value = $fieldtype->getDefaultFileExtensions();
$f->attr('value', $value);
$f->attr('rows', 3);
$f->label = $this->_('Allowed file extensions');
$f->description = $this->_('Enter all file extensions allowed by this upload field. Separate each extension by a space. No periods or commas. This field is not case sensitive.'); // Valid file extensions description
$f->icon = 'files-o';
if(count($extensionInfo['invalid']) && !$input->is('POST')) {
foreach($extensionInfo['invalid'] as $ext) {
$error = sprintf(
$this->_('File extension %s must be removed, whitelisted, or have a [file validator](%s) module installed.'),
strtoupper($ext),
$fileValidatorsUrl
);
$this->fieldtype->error($error, Notice::allowMarkdown | Notice::noGroup);
}
}
$fs->add($f);
if(count($extensionInfo['invalid']) || count($extensionInfo['whitelist'])) {
/** @var array $okExtensions */
$badExtensions = array_merge($extensionInfo['invalid'], $extensionInfo['whitelist']);
ksort($badExtensions);
/** @var InputfieldCheckboxes $f Whitelisted file extensions */
$f = $modules->get('InputfieldCheckboxes');
$f->attr('name', 'okExtensions');
$f->label = $this->_('File extensions to allow without validation (whitelist)');
$f->icon = 'warning';
foreach($badExtensions as $ext) $f->addOption($ext);
$f->description =
sprintf(
$this->_('These file extensions need a [file validator module](%s) installed. Unchecked extensions have been disabled for safety.'),
$fileValidatorsUrl
) . ' ' .
$this->_('To ignore and allow the file extension without file validation, check the box next to it (not recommended).') . ' ' .
$this->_('If you dont need an extension, please remove it from your valid file extensions list.');
$f->attr('value', $extensionInfo['whitelist']);
$fs->add($f);
} else {
$field->set('okExtensions', array());
}
$maxFiles = (int) $field->get('maxFiles');
// max files
/** @var InputfieldInteger $f */
$f = $modules->get('InputfieldInteger');
$f->attr('name', 'maxFiles');
$f->attr('value', $maxFiles);
$f->label = $this->_('Maximum number of files');
$f->icon = 'step-forward';
$f->appendMarkup = "&nbsp; <span class='description'>" . $this->_('(a value of 0 means no limit)') . "</span>";
$fs->append($f);
$this->getConfigInputfieldsOutputFormat($field, $fs);
require_once($this->wire()->config->paths->modules . 'Inputfield/InputfieldFile/config.php');
$configuration = new InputfieldFileConfiguration();
$this->wire($configuration);
$descriptionFieldset = $configuration->getConfigInputfieldsDescription($field);
$descriptionFieldset->themeOffset = 1;
$descriptionFieldset->description =
$this->_('Each file may have optional description text that is input in the page editor and can be output on the front-end.') . ' ' .
$this->_('For instance, its common to use the description text as the “alt” attribute when rendering `<img>` tags for image files.');
$inputfields->add($descriptionFieldset);
// textformatters
/** @var InputfieldAsmSelect $f */
$f = $modules->get('InputfieldAsmSelect');
$f->setAttribute('name', 'textformatters');
$f->label = $this->_('Text formatters (for file descriptions)');
$f->description = $this->_('Select one or more text formatters (and their order) that will be applied to the file description when output formatting is active. The HTML Entity Encoder is recommended as a minimum.');
$f->icon = 'text-width';
foreach($modules->findbyPrefix('Textformatter', 1) as $moduleName => $moduleInfo) {
$f->addOption($moduleName, "$moduleInfo[title]");
}
if(!is_array($field->get('textformatters'))) {
$field->set('textformatters', $field->get('entityEncode') ? array('TextformatterEntities') : array());
}
$f->attr('value', $field->get('textformatters'));
$descriptionFieldset->add($f);
// entity encode (deprecated)
/** @var InputfieldHidden $f */
$f = $modules->get("InputfieldHidden");
$f->attr('name', 'entityEncode');
$f->attr('value', '');
if($field->get('entityEncode')) $f->attr('checked', 'checked');
$descriptionFieldset->add($f);
$field->set('entityEncode', null);
$this->getConfigInputfieldsCustomFields($field, $inputfields);
/** @var InputfieldFieldset $fs */
$fs = $modules->get('InputfieldFieldset');
$fs->attr('name', '_file_tags');
$fs->label = $this->_('File tags');
$fs->description =
$this->_('Tags enable you to have predefined or custom 1-word tags to accompany each file.') . ' ' .
$this->_('When used, files can have any number of tags.') . ' ' .
$this->_('Tags can be useful for categorizing a file.') . ' ' .
$this->_('For instance, a tag of `sidebar` might indicate an image you want to automatically display in a sidebar.');
$fs->icon = 'tags';
$inputfields->add($fs);
// use tags
/** @var InputfieldRadios $f */
$f = $modules->get("InputfieldRadios");
$f->attr('name', 'useTags');
$f->label = $this->_('Use file tags?');
$f->description = $this->_('When enabled, the field will also contain an option for user selected (or entered) tags for each file.'); // Use tags description
$f->icon = 'tags';
$predefinedLabel = $this->_('User selects from list of predefined tags');
$f->addOption(FieldtypeFile::useTagsOff, $this->_('Tags disabled'));
$f->addOption(FieldtypeFile::useTagsNormal, $this->_('User enters tags by text input'));
$f->addOption(FieldtypeFile::useTagsPredefined, $predefinedLabel);
$f->addOption(FieldtypeFile::useTagsNormal | FieldtypeFile::useTagsPredefined, $predefinedLabel . ' + ' . $this->_('can input their own'));
$f->attr('value', (int) $field->get('useTags'));
if(!$f->attr('value')) $f->collapsed = Inputfield::collapsedYes;
$fs->add($f);
/** @var InputfieldTextarea $f */
$f = $modules->get('InputfieldText');
$f->attr('name', 'tagsList');
$f->label = $this->_('Predefined file tags');
$f->description = $this->_('Enter tags separated by a space. Tags may contain letters, digits, underscores or hyphens.');
$f->icon = 'tags';
$f->attr('value', $field->get('tagsList'));
$f->showIf = 'useTags>1';
$fs->add($f);
/** @var InputfieldMarkup $f */
$f = $modules->get('InputfieldMarkup');
$f->attr('name', '_file_tags_example');
$f->label = $this->_('Examples of using tags');
$f->icon = 'code';
$f->collapsed = Inputfield::collapsedYes;
$f->entityEncodeText = false;
$example = array(
'// ' . $this->_('Display all files with tag “sidebar”'),
'foreach($page->images as $file) {',
' if($file->hasTag("sidebar") {',
' echo "<img src=\'$file->url\' alt=\'$file->description\'>";',
' }',
'}',
'',
'// ' . $this->_('Same as above using another method'),
'foreach($page->images->findTag("sidebar") as $file) {',
' echo "<img src=\'$file->url\' alt=\'$file->description\'>";',
'}',
'',
'// ' . $this->_('Get one file having tag'),
'$file = $page->images->getTag("hero");',
'if($file) {',
' // ... ',
'}',
'',
'// ' . $this->_('Display tags for all files'),
'foreach($page->images as $file) {',
' echo "<li>$file->name: $file->tags</li>";',
'}',
);
$docs = array(
'Pagefiles::findTag()' => 'pagefiles/find-tag/',
'Pagefiles::getTag()' => 'pagefiles/get-tag/',
'Pagefile::tags()' => 'pagefile/tags/',
'Pagefile::hasTag()' => 'pagefile/has-tag/',
);
foreach($example as $k => $v) {
$example[$k] = htmlspecialchars($v);
}
$f->value = "<pre style='margin:0'><code>" . implode("\n", $example) . "</code></pre>";
foreach($docs as $label => $path) {
$docs[$label] = "<a target='_blank' href='https://processwire.com/api/ref/$path'>$label</a>";
}
$f->notes = $this->_('Documentation:') . ' ' . implode(', ', $docs);
$fs->add($f);
return $inputfields;
}
/**
* @param Field $field
* @param InputfieldWrapper $fs
*
*/
protected function getConfigInputfieldsOutputFormat(Field $field, InputfieldWrapper $fs) {
$modules = $this->wire()->modules;
// output format
$typeMulti = $this->fieldtype instanceof FieldtypeImage ? 'Pageimages' : 'Pagefiles';
$typeSingle = $this->fieldtype instanceof FieldtypeImage ? 'Pageimage' : 'Pagefile';
/** @var InputfieldRadios $f */
$f = $modules->get('InputfieldRadios');
$f->attr('name', 'outputFormat');
$f->label = $this->_('Formatted value');
$f->description = $this->_('Select the type of value you want this field to provide when accessed from the API on the front-end of your site.');
$f->description .= sprintf($this->_('When output formatting is off, the value is always a %s array.'), $typeMulti);
$f->addOption(FieldtypeFile::outputFormatAuto,
$this->_('Automatic') . ' ' .
'[span.detail] ' . $this->_('(single file or null when max files set to 1, multi-file array of items otherwise)') . ' [/span]'
);
$f->addOption(FieldtypeFile::outputFormatArray,
$this->_('Multi-file array of items') . ' ' .
"[span.detail] ($typeMulti) [/span]"
);
$f->addOption(FieldtypeFile::outputFormatSingle,
$this->_('Single-file when populated, null when empty') . ' ' .
"[span.detail] ($typeSingle) [/span]"
);
$f->addOption(FieldtypeFile::outputFormatString,
$this->_('Rendered string of markup/text') . ' ' .
"[span.detail] " . $this->_('(configurable)') . " [/span]"
);
$f->attr('value', (int) $field->get('outputFormat'));
$f->collapsed = Inputfield::collapsedBlank;
$f->icon = 'magic';
$f->notes =
$this->_('Documentation:') . ' ' .
'[Pagefiles](https://processwire.com/api/ref/pagefiles/), [Pagefile](https://processwire.com/api/ref/pagefile/)';
if($this->fieldtype instanceof FieldtypeImage) {
$f->notes .= ", [Pageimages](https://processwire.com/api/ref/pageimages/), [Pageimage](https://processwire.com/api/ref/pageimage/)";
}
$fs->add($f);
$examples = array(
'Pagefiles' => array(
'foreach($page->field_name as $file) {',
' echo "<li><a href=\'$file->url\'>$file->name</a> - $file->description</li>";',
'}'
),
'Pagefile' => array(
'$file = $page->field_name;',
'if($file) {',
' echo "<p><a href=\'$file->url\'>$file->name</a> - $file->description</p>',
'}'
),
'Pageimages' => array(
'foreach($page->field_name as $image) {',
' $thumb = $image->size(100, 100);',
' echo "<a href=\'$image->url\'><img src=\'$thumb->url\' alt=\'$image->description\'></a>";',
'}'
),
'Pageimage' => array(
'$image = $page->field_name;',
'if($image) {',
' $thumb = $image->size(100, 100);',
' echo "<a href=\'$image->url\'><img src=\'$thumb->url\' alt=\'$image->description\'></a>";',
'}'
),
);
foreach($examples as $key => $example) {
foreach($example as $k => $v) {
$v = str_replace('field_name', $field->name, $v);
$v = htmlspecialchars($v);
$example[$k] = $v;
}
$examples[$key] = '<pre style="margin:0"><code>' . implode("\n", $example) . '</code></pre>';
}
/** @var InputfieldMarkup $f */
$f = $modules->get('InputfieldMarkup');
$f->attr('name', '_exampleMulti');
$f->label = $this->_('Multi-file usage example');
$f->showIf = 'outputFormat=' . FieldtypeFile::outputFormatArray . '|' . FieldtypeFile::outputFormatAuto;
$f->label .= " ($typeMulti)";
$f->icon = 'code';
$f->collapsed = Inputfield::collapsedYes;
if($typeMulti === 'Pageimages') {
$f->value = $examples['Pageimages'];
} else {
$f->value = $examples['Pagefiles'];
}
$fs->add($f);
/** @var InputfieldMarkup $f */
$f = $modules->get('InputfieldMarkup');
$f->attr('name', '_exampleSingle');
$f->label = $this->_('Single file usage example');
$f->showIf = 'outputFormat=' . FieldtypeFile::outputFormatSingle . '|' . FieldtypeFile::outputFormatAuto;
$f->label .= " ($typeSingle)";
$f->icon = 'code';
$f->collapsed = Inputfield::collapsedYes;
if($typeSingle === 'Pageimage') {
$f->value = $examples['Pageimage'];
} else {
$f->value = $examples['Pagefile'];
}
$fs->add($f);
// output string
$placeholders = array('{url}', '{description}', '{tags}');
$ie = $this->_('i.e.'); // i.e. as in “for example”
/** @var InputfieldText $f */
$f = $modules->get('InputfieldText');
$f->attr('name', 'outputString');
$f->label = $this->_('Rendered string of markup/text');
if($typeSingle === 'Pageimage') {
$f->attr('placeholder', "$ie <img src='{url}' alt='{description}' />");
$placeholders[] = '{width}';
$placeholders[] = '{height}';
} else {
$f->attr('placeholder', "$ie <a href='{url}'>{description}</a>");
}
$f->attr('value', $field->get('outputString') ? $field->get('outputString') : '');
$f->description = $this->_('Provide the rendered string of text you want to output as the value of this field. If the field contains multiple items, this string will be rendered multiple times. If the field contains no items, a blank string will be used.');
$f->notes = $this->_('You may use any of the following placeholder tags:') . ' ' . implode(', ', $placeholders);
$f->showIf = "outputFormat=" . FieldtypeFile::outputFormatString;
$f->icon = 'magic';
$fs->add($f);
}
/**
* @param Field $field
* @param InputfieldWrapper $inputfields
*
*/
protected function getConfigInputfieldsCustomFields(Field $field, InputfieldWrapper $inputfields) {
$templates = $this->wire()->templates;
$modules = $this->wire()->modules;
$input = $this->wire()->input;
/** @var InputfieldFieldset $fs */
$fs = $modules->get('InputfieldFieldset');
$fs->attr('name', '_custom_fields');
$fs->label = $this->_('Custom fields');
$fs->icon = 'newspaper-o';
$fs->themeOffset = 1;
$fs->entityEncodeText = false;
$fs->description =
$this->_('Custom fields enable you to define additional text, number and selection fields to accompany each file as metadata.') . ' ' .
$this->_('Use this when you need something more than just a file description and/or tags.') . ' ' .
sprintf($this->_('For more information, see: %s.'),
"<a target='_blank' href='https://processwire.com/blog/posts/pw-3.0.142/'>" .
$this->_('How custom fields for files/images work') .
"</a>"
);
$inputfields->add($fs);
// Custom fields
$customTplEnabledName = 'field-' . $field->name;
$customTplDisabledName = 'field-x-' . $field->name;
$customTpl = $templates->get($customTplEnabledName);
if(!$customTpl) $customTpl = $templates->get($customTplDisabledName);
$customVal = $input->is('POST') ? $input->post('_use_custom_fields') : null;
$editLink = $customTpl ? "<a class='pw-modal' href='$customTpl->editUrl'>$customTpl->name</a>" : "";
/** @var InputfieldToggle $f */
$f = $modules->get('InputfieldToggle');
$f->name('_use_custom_fields');
$f->label = $this->_('Use custom fields for each file?');
//$f->labelType = InputfieldToggle::labelTypeEnabled;
$f->entityEncodeText = false;
$f->icon = 'toggle-on';
$f->useReverse = true;
$fs->add($f);
if($customVal === "1") {
// enabled
$f->val(1);
if($customTpl && $customTpl->name === $customTplEnabledName) {
// already enabled
} else if($customTpl && $customTpl->name === $customTplDisabledName) {
// rename template field-x-name to field-name
$templates->rename($customTpl, $customTplEnabledName);
$f->val(0);
} else if(!$customTpl) {
// create custom template
$customTpl = $templates->add($customTplEnabledName);
$this->message(sprintf($this->_('Created custom fields template: %s'), $customTpl->name));
}
} else if($customVal === "0") {
// disabled
$f->val(0);
if($customTpl) {
// rename custom template to field-x-name
$templates->rename($customTpl, $customTplDisabledName);
}
} else if($customTpl) {
if($customTpl->name === $customTplEnabledName) {
$f->description =
$this->_('Custom fields are enabled and managed from a template.') . ' ' .
$this->_('Use the “Edit custom fields” button below to add or modify the fields you want to maintain for each file.') . ' ' .
$this->_('Most text, number and selection fields will work here, but some field types may not.');
$f->val(1);
/** @var InputfieldButton $btn */
$btn = $modules->get('InputfieldButton');
$btn->attr('name', '_edit_custom_fields');
$btn->href = $customTpl->editUrl;
$btn->addClass('pw-modal');
$btn->value = $this->_('Edit custom fields');
$btn->icon = 'pencil';
$btn->showIf = "$f->name=1";
$btn->detail = sprintf($this->_('Custom fields template is: %s'), '**' . $customTpl->name . '**');
$fs->add($btn);
} else if($customTpl->name === $customTplDisabledName) {
$f->notes = sprintf($this->_('To permanently disable custom fields for this field, delete template %s.'), $editLink);
$f->val(0);
} else {
$f->val($customTpl ? 1 : 0);
}
} else {
$f->val(0);
}
if(!$customVal) {
$f->description = trim(
$f->description . ' ' .
$this->_('When you enable custom fields, save and then return here to configure them.')
);
}
}
/**
* Fieldtype file field advanced configuration
*
* @param Field $field
* @param InputfieldWrapper $inputfields
* @return InputfieldWrapper
*
*/
public function getConfigAdvancedInputfields(Field $field, InputfieldWrapper $inputfields) {
$modules = $this->wire()->modules;
// default value page
/** @var InputfieldPageListSelect $f */
$f = $modules->get('InputfieldPageListSelect');
$f->attr('name', 'defaultValuePage');
$f->label = $this->_('Page containing default/fallback value for this field');
$f->description =
$this->_('Optionally select a page that will contain the default/fallback value, in this same field.') . ' ' .
$this->_('You may wish to create a page specifically for this purpose.') . ' ' .
sprintf($this->_('The selected page must have a `%s` field populated with one or more files.'), $field->name);
$f->attr('value', (int) $field->get('defaultValuePage'));
$inputfields->add($f);
// inputfield class
/** @var InputfieldSelect $f */
$f = $modules->get('InputfieldSelect');
$f->attr('name', 'inputfieldClass');
$f->label = $this->_('Inputfield type for files field');
$f->description = $this->_('The type of field that will be used to collect input for this files field.');
$f->notes = $this->_('Change this only if instructed to do so by 3rd party Inputfield module instructions.');
$f->required = true;
$defaultClass = $this->fieldtype->getDefaultInputfieldClass();
foreach($modules->findByPrefix('Inputfield') as $fm) {
if($defaultClass == 'InputfieldFile' && strpos($fm, 'InputfieldImage') === 0) continue;
if("$fm" == $defaultClass || is_subclass_of(__NAMESPACE__ . "\\$fm", __NAMESPACE__ . "\\$defaultClass")) {
$f->addOption("$fm", str_replace("Inputfield", '', "$fm"));
}
}
$inputfieldClass = $field->get('inputfieldClass');
$f->attr('value', $inputfieldClass ? $inputfieldClass : $defaultClass);
// $f->collapsed = $inputfieldClass && $inputfieldClass != $defaultClass ? Inputfield::collapsedNo : Inputfield::collapsedYes;
$inputfields->add($f);
return $inputfields;
}
/**
* Module config
*
* @param InputfieldWrapper $inputfields
*
*/
public function getModuleConfigInputfields(InputfieldWrapper $inputfields) {
$modules = $this->wire()->modules;
$fields = $this->wire()->fields;
$moduleNames = $modules->findByPrefix('Fieldtype');
$defaultAllowFieldtypes = $this->fieldtype->getAllowFieldtypes(true);
$names = array();
$blacklist = array( // types always disallowed
'File',
'Image',
'Repeater',
'RepeaterMatrix',
'PageTable',
'Options',
'Comments',
'Table'
);
ksort($moduleNames);
/** @var InputfieldCheckboxes $f */
$f = $modules->get('InputfieldCheckboxes');
$f->attr('name', 'allowFieldtypes');
$f->label = $this->_('Allowed Fieldtype modules for custom fields');
$f->description = $this->_('Types with strikethrough are not likely to be 100% compatible.');
$f->optionColumns = 3;
$f->entityEncodeText = false;
foreach($moduleNames as $key => $moduleName) {
list(,$name) = explode('Fieldtype', $moduleName, 2);
$names[$name] = $name;
if(in_array($name, $blacklist)) {
unset($names[$name]);
continue;
}
if(in_array($name, $defaultAllowFieldtypes)) continue; // these are fine
// check schema of field by finding an example of one
$allow = false;
foreach($fields as $field) {
// @var Field $field
if($field->type instanceof FieldtypeFile) continue;
if(!wireInstanceOf($field->type, $moduleName)) continue;
// verify that field DB table is responsible for all data created by the field
$schema = $field->type->getDatabaseSchema($field);
if(isset($schema['xtra']['all']) && $schema['xtra']['all'] !== true) continue;
unset($schema['data'], $schema['pages_id'], $schema['keys'], $schema['xtra']);
// if there's not any other schema required by the Fieldtype, it can be supported here
if(!count($schema)) $allow = true;
break;
}
if(!$allow) {
// indicate with strikethrough potential issue with this type
$names[$name] = "<s>$name</s>";
}
}
foreach($names as $key => $name) {
$f->addOption($key, $name);
}
$f->val($this->fieldtype->getAllowFieldtypes());
$inputfields->add($f);
}
}

View File

@@ -236,12 +236,19 @@ class FieldtypeImage extends FieldtypeFile {
*
*/
public function ___getConfigInputfields(Field $field) {
$inputfields = parent::___getConfigInputfields($field);
$f = $inputfields->get('outputString');
$f->attr('placeholder', "i.e. <img src='{url}' alt='{description}' />");
$f->notes .= ", {width}, {height}";
return $inputfields;
}
/**
* Get Inputfields to advanced configure fields using this Fieldtype
*
* @param Field $field
* @return InputfieldWrapper
*
*/
public function ___getConfigAdvancedInputfields(Field $field) {
$inputfields = parent::___getConfigAdvancedInputfields($field);
return $inputfields;
}

View File

@@ -1387,72 +1387,10 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
*/
public function ___getConfigInputfields() {
$inputfields = parent::___getConfigInputfields();
if($this->hasFieldtype === false) {
/** @var InputfieldText $f */
$f = $this->modules->get('InputfieldText');
$f->attr('name', 'extensions');
$f->label = $this->_('Allowed file extensions for upload');
$f->description = $this->_('One or more file extensions separated by a space.');
$value = $this->getSetting('extensions');
if(empty($value)) $value = 'pdf jpg jpeg png gif doc docx csv xls xlsx ppt pptx';
$value = explode(' ', $value);
foreach($value as $k => $v) $value[$k] = trim($v, '. ');
$f->val(implode(' ', $value));
$inputfields->add($f);
/** @var InputfieldInteger $f */
$f = $this->modules->get('InputfieldInteger');
$f->attr('name', 'maxFiles');
$f->attr('value', (int) $this->getSetting('maxFiles'));
$f->attr('size', 4);
$f->label = $this->_('Maximum files allowed');
$f->description = $this->_('0=No limit');
$f->collapsed = Inputfield::collapsedBlank;
$inputfields->add($f);
}
/** @var InputfieldCheckbox $f */
$f = $this->modules->get("InputfieldCheckbox");
$f->attr('name', 'unzip');
$f->attr('value', 1);
$f->setAttribute('checked', $this->unzip ? 'checked' : '');
$f->label = $this->_('Decompress ZIP files?');
$f->description = $this->_("If checked, ZIP archives will be decompressed and all valid files added as uploads (if supported by the hosting environment). Max files must be set to 0 (no max) in order for ZIP uploads to be functional."); // Decompress ZIP files description
$f->collapsed = Inputfield::collapsedBlank;
$inputfields->append($f);
$f = $this->modules->get("InputfieldCheckbox");
$f->attr('name', 'overwrite');
$f->label = $this->_('Overwrite existing files?');
$f->description = $this->_('If checked, a file uploaded with the same name as an existing file will replace the existing file (description and tags will remain). If not checked, uploaded filenames will be renamed to be unique.'); // Overwrite description
$f->notes = $this->_('Please note that when this option is enabled, AJAX-uploaded files are saved with the page immediately at upload, rather than when you click "save". As a result, you may wish to leave this option unchecked unless you have a specific need for it.'); // Overwrite notes
if($this->overwrite) $f->attr('checked', 'checked');
$f->collapsed = Inputfield::collapsedBlank;
$inputfields->append($f);
$f = $this->modules->get("InputfieldInteger");
$f->attr('name', 'descriptionRows');
$f->attr('value', $this->descriptionRows !== null ? (int) $this->descriptionRows : 1);
//$f->minValue = 0;
//$f->maxValue = 30;
$f->label = $this->_('Number of rows for description field?');
$f->description = $this->_("Enter the number of rows available for the file description field, or enter 0 to not have a description field."); // Number of rows description
$inputfields->append($f);
if($this->wire('languages') && $this->descriptionRows >= 1) {
$f = $this->modules->get("InputfieldCheckbox");
$f->attr('name', 'noLang');
$f->attr('value', 1);
$f->setAttribute('checked', $this->noLang ? 'checked' : '');
$f->label = $this->_('Disable multi-language descriptions?');
$f->description = $this->_('By default, descriptions are multi-language when you have Language Support installed. If you want to disable multi-language descriptions, check this box.'); // Disable multi-language description
$inputfields->append($f);
}
require_once(__DIR__ . '/config.php');
$configuration = new InputfieldFileConfiguration();
$this->wire($configuration);
$configuration->getConfigInputfields($this, $inputfields);
return $inputfields;
}
}

View File

@@ -0,0 +1,126 @@
<?php namespace ProcessWire;
/**
* ProcessWire File Inputfield (configuration)
*
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer
* https://processwire.com
*
*/
class InputfieldFileConfiguration extends Wire {
/**
* Configuration settings for InputfieldFile
*
* @param InputfieldFile|Field $field
* @param InputfieldWrapper $inputfields
*
*/
public function getConfigInputfields($field, $inputfields) {
$inputfield = $field instanceof Inputfield ? $field : null;
if($inputfield && $inputfield->hasFieldtype === false) {
// usage when Inputfield is used independently of any Field
/** @var InputfieldText $f */
$f = $this->modules->get('InputfieldText');
$f->attr('name', 'extensions');
$f->label = $this->_('Allowed file extensions for upload');
$f->description = $this->_('One or more file extensions separated by a space.');
$value = $field->get('extensions');
if(empty($value)) $value = 'pdf jpg jpeg png gif doc docx csv xls xlsx ppt pptx';
$value = explode(' ', $value);
foreach($value as $k => $v) $value[$k] = trim($v, '. ');
$f->val(implode(' ', $value));
$inputfields->add($f);
/** @var InputfieldInteger $f */
$f = $this->modules->get('InputfieldInteger');
$f->attr('name', 'maxFiles');
$f->attr('value', (int) $field->get('maxFiles'));
$f->label = $this->_('Maximum files allowed');
$f->description = $this->_('0=No limit');
$f->collapsed = Inputfield::collapsedBlank;
$inputfields->add($f);
}
/** @var InputfieldFieldset $fs */
$fs = $this->modules->get('InputfieldFieldset');
$fs->attr('name', '_file_uploads');
$fs->label = $this->_('File uploads');
$fs->icon = 'cloud-upload';
$fs->themeOffset = 1;
$inputfields->add($fs);
/** @var InputfieldCheckbox $f */
$f = $this->modules->get("InputfieldCheckbox");
$f->attr('name', 'unzip');
$f->attr('value', 1);
$f->setAttribute('checked', $field->get('unzip') ? 'checked' : '');
$f->label = $this->_('Decompress ZIP files?');
$f->icon = 'file-zip-o';
$f->description = $this->_("If checked, ZIP archives will be decompressed and all valid files added as uploads (if supported by the hosting environment). Max files must be set to 0 (no max) in order for ZIP uploads to be functional."); // Decompress ZIP files description
$f->collapsed = Inputfield::collapsedBlank;
$fs->add($f);
/** @var InputfieldCheckbox $f */
$f = $this->modules->get("InputfieldCheckbox");
$f->attr('name', 'overwrite');
$f->label = $this->_('Overwrite existing files?');
$f->icon = 'exchange';
$f->description = $this->_('If checked, a file uploaded with the same name as an existing file will replace the existing file (description and tags will remain). If not checked, uploaded filenames will be renamed to be unique.'); // Overwrite description
$f->notes = $this->_('Please note that when this option is enabled, AJAX-uploaded files are saved with the page immediately at upload, rather than when you click "save". As a result, you may wish to leave this option unchecked unless you have a specific need for it.'); // Overwrite notes
if($field->get('overwrite')) $f->attr('checked', 'checked');
$f->collapsed = Inputfield::collapsedBlank;
$fs->add($f);
if($inputfield && $inputfield->hasFieldtype === false) {
$inputfields->add($this->getConfigInputfieldsDescription($field));
}
}
/**
* @param Field|InputfieldFile $field
* @return InputfieldFieldset
*
*/
public function getConfigInputfieldsDescription($field) {
$rows = $field->get('descriptionRows');
$rows = $rows === null ? 1 : (int) $rows;
/** @var InputfieldFieldset $fs */
$fs = $this->modules->get('InputfieldFieldset');
$fs->attr('name', '_file_descriptions');
$fs->label = $this->_('File descriptions');
$fs->icon = 'align-left';
/** @var InputfieldInteger $f */
$f = $this->modules->get('InputfieldInteger');
$f->attr('name', 'descriptionRows');
$f->attr('value', $rows);
$f->label = $this->_('Number of rows for description field?');
$f->description = $this->_("Enter the number of rows available for the file description field, or enter 0 to not have a description field."); // Number of rows description
$f->icon = 'text-height';
$fs->add($f);
if($this->wire()->languages && $rows >= 1) {
/** @var InputfieldCheckbox $f */
$f = $this->modules->get("InputfieldCheckbox");
$f->attr('name', 'noLang');
$f->label = $this->_('Disable multi-language descriptions?');
$f->icon = 'language';
$f->description =
$this->_('By default, descriptions are multi-language when you have Language Support installed.') . ' ' .
$this->_('If you do not need multi-language descriptions for files, you can disable them here.');
$f->checked((bool) $field->get('noLang'));
$f->collapsed = Inputfield::collapsedBlank;
$fs->add($f);
}
return $fs;
}
}

View File

@@ -968,166 +968,11 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
*
*/
public function ___getConfigInputfields() {
$inputfields = parent::___getConfigInputfields();
/** @var InputfieldRadios $f */
$f = $this->wire('modules')->get('InputfieldRadios');
$f->attr('name', 'gridMode');
$f->label = $this->_('Default image grid mode');
$f->description = $this->_('In the admin, the list of images will appear in this mode by default. The user can change it at any time by clicking the icons in the top right corner of the field.');
$f->notes = $this->_('If you have recently used this images field, you will have to clear your cookies before seeing any changes to this setting.');
$f->icon = 'photo';
$f->addOption('grid', '[i.fa.fa-th][/i] ' . $this->_('Square grid images'));
$f->addOption('left', '[i.fa.fa-tasks][/i] ' . $this->_('Proportional grid images'));
$f->addOption('list', '[i.fa.fa-th-list][/i] ' . $this->_('Vertical list (verbose)'));
$f->attr('value', $this->gridMode);
$f->columnWidth = 50;
$inputfields->add($f);
/** @var InputfieldRadios $f */
$f = $this->wire('modules')->get('InputfieldRadios');
$f->attr('name', 'focusMode');
$f->label = $this->_('Focus point selection');
$f->description = $this->_('Enables a draggable focus point to select the subject of an image. This helps to generate non-proportional crops.') . ' ' .
$this->_('A preview of the focus point is also shown when images are in the “Square grid images” mode.');
$f->addOption('on', $this->_('Focus point'));
$f->addOption('zoom', $this->_('Focus point and zoom'));
$f->addOption('off', $this->_('Disabled'));
$f->attr('value', $this->focusMode);
$f->icon = 'crosshairs';
$f->columnWidth = 50;
$inputfields->add($f);
/** @var InputfieldFieldset $fieldset */
$fieldset = $this->modules->get('InputfieldFieldset');
$fieldset->label = $this->_("Maximum image dimensions");
$fieldset->icon = 'expand';
//$fieldset->collapsed = $this->maxWidth || $this->maxHeight ? Inputfield::collapsedNo : Inputfield::collapsedYes;
$fieldset->description = $this->_("Optionally enter the max width and/or height of uploaded images. If specified, images will be resized at upload time when they exceed either the max width or height. The resize is performed at upload time, and thus does not affect any images in the system, or images added via the API."); // Max image dimensions description
$fieldset->description .= ' ' . $this->_('Applies to JPG, PNG and GIF images.');
$description = $this->_("Enter the value in number of pixels or leave blank for no limit."); // Min/Max width/height description
/** @var InputfieldInteger $field */
$field = $this->modules->get("InputfieldInteger");
$field->attr('name', 'maxWidth');
$field->attr('value', $this->maxWidth ? (int) $this->maxWidth : '');
$field->label = $this->_("Max width for uploaded images");
$field->icon = 'arrows-h';
$field->description = $description;
$field->columnWidth = 50;
$field->appendMarkup = '&nbsp;px';
$fieldset->add($field);
$field = $this->modules->get("InputfieldInteger");
$field->attr('name', 'maxHeight');
$field->attr('value', $this->maxHeight ? (int) $this->maxHeight : '');
$field->label = $this->_("Max height for uploaded images");
$field->icon = 'arrows-v';
$field->description = $description;
$field->columnWidth = 50;
$field->appendMarkup = '&nbsp;px';
$fieldset->add($field);
/** @var InputfieldRadios $field */
$field = $this->modules->get('InputfieldRadios');
$field->attr('name', 'resizeServer');
$field->label = $this->_('How to resize to max dimensions');
$field->description = $this->_('Using client-side resize enables you to reduce the file size and dimensions before uploading.');
$field->notes = $this->_('When using client-side resize, please specify both max width *and* max height in the fields above, or max megapixels in the field below.');
$field->icon = 'object-group';
$field->addOption(0, $this->_('Use client-side resize when possible'));
$field->addOption(1, $this->_('Use only server-side resize'));
$field->attr('value', (int) $this->resizeServer);
$fieldset->add($field);
$field = $this->modules->get('InputfieldFloat');
$field->attr('name', 'maxSize');
$field->label = $this->_('Max megapixels for uploaded images');
$field->description = $this->_('This can be used as an alternative to max width/height. Specify a floating point value.');
$field->description .= ' ' . $this->_('Applicable to client-side resize only.');
$field->notes = $this->_('A good value for websites is 1.7 which is roughly 1600x1000 pixels, where 1600 and 1000 can be either width or height.');
$field->notes .= ' ' . $this->_('Other examples:') . ' 0.2=516x387, 2.0=1633x1225, 3.0=2000x1500, 12.0=4000x3000';
$field->icon = 'camera';
$field->attr('value', (float) $this->maxSize > 0 ? (float) $this->maxSize : '');
$field->showIf = 'resizeServer=0';
//$field->collapsed = Inputfield::collapsedBlank;
$field->columnWidth = 50;
$fieldset->add($field);
/** @var InputfieldInteger $field */
$field = $this->modules->get('InputfieldInteger');
$field->attr('name', 'clientQuality');
$field->label = $this->_('Client-side resize quality percent for JPEGs');
$field->description = $this->_('Specify a number between 10 (lowest quality/smallest file size) and 100 (highest quality/largest file size). Default is 90.');
$field->icon = 'signal';
$field->min = 10;
$field->max = 100;
$field->attr('size', 4);
$field->attr('value', (int) $this->clientQuality);
$field->showIf = 'resizeServer=0';
$field->columnWidth = 50;
$field->appendMarkup = '&nbsp;%';
$fieldset->add($field);
// maxReject option comes from @JanRomero PR #1051
/** @var InputfieldCheckbox $field */
$field = $this->modules->get("InputfieldCheckbox");
$field->attr('name', 'maxReject');
$field->attr('value', (int) $this->maxReject);
$field->attr('checked', ((int) $this->maxReject) ? 'checked' : '');
$field->label = $this->_('Refuse images exceeding max dimensions?');
$field->showIf = 'maxWidth|maxHeight>0';
$field->icon = 'ban';
$field->description = $this->_('If checked, images that exceed max width/height (that cannot be resized client-side) will be refused rather than resized.');
if(!$this->maxReject) $field->collapsed = Inputfield::collapsedYes;
$fieldset->add($field);
$inputfields->add($fieldset);
// min image dimensions
/** @var InputfieldFieldset $fieldset */
$fieldset = $this->modules->get('InputfieldFieldset');
$fieldset->label = $this->_("Minimum image dimensions");
$fieldset->icon = 'compress';
$fieldset->collapsed = $this->minWidth || $this->minHeight ? Inputfield::collapsedNo : Inputfield::collapsedYes;
$fieldset->description = $this->_("Optionally enter the minimum width and/or height of uploaded images. If specified, images that don't meet these minimums will be refused."); // Max image dimensions description
/** @var InputfieldInteger $field */
$field = $this->modules->get("InputfieldInteger");
$field->attr('name', 'minWidth');
$field->attr('value', $this->minWidth ? (int) $this->minWidth : '');
$field->label = $this->_("Min width for uploaded images");
$field->description = $description;
$field->columnWidth = 50;
$field->icon = 'arrows-h';
$field->appendMarkup = '&nbsp;px';
$fieldset->add($field);
$field = $this->modules->get("InputfieldInteger");
$field->attr('name', 'minHeight');
$field->attr('value', $this->minHeight ? (int) $this->minHeight : '');
$field->label = $this->_("Min height for uploaded images");
$field->description = $description;
$field->columnWidth = 50;
$field->icon = 'arrows-v';
$field->appendMarkup = '&nbsp;px';
$fieldset->add($field);
$inputfields->add($fieldset);
$field = $this->modules->get("InputfieldCheckbox");
$field->attr('name', 'dimensionsByAspectRatio');
$field->attr('value', (int) $this->dimensionsByAspectRatio);
$field->attr('checked', ((int) $this->dimensionsByAspectRatio) ? 'checked' : '');
$field->label = $this->_("Swap min/max dimensions for portrait images?");
$field->showIf = 'minWidth|minHeight|maxWidth|maxHeight>0';
$field->description = $this->_('If checked, minimum width/height and maximum width/height dimensions will be swapped for portrait images to accommodate for the different aspect ratio.');
$field->description .= ' ' . $this->_('Applies to server-side resizes only.');
$field->collapsed = $this->dimensionsByAspectRatio ? Inputfield::collapsedNo : Inputfield::collapsedYes;
$field->icon = 'exchange';
$inputfields->add($field);
require_once(__DIR__ . '/config.php');
$configuration = new InputfieldImageConfiguration();
$this->wire($configuration);
$configuration->getConfigInputfields($this, $inputfields);
return $inputfields;
}

View File

@@ -0,0 +1,182 @@
<?php namespace ProcessWire;
/**
* ProcessWire Image Inputfield (configuration)
*
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer
* https://processwire.com
*
*/
class InputfieldImageConfiguration extends Wire {
public function getConfigInputfields(InputfieldImage $inputfield, InputfieldWrapper $inputfields) {
$modules = $this->wire()->modules;
$fs = $modules->get('InputfieldFieldset');
$fs->attr('name', '_image_features');
$fs->label = $this->_('Input features');
$fs->themeOffset = 1;
$fs->icon = 'sliders';
$inputfields->add($fs);
/** @var InputfieldRadios $f */
$f = $modules->get('InputfieldRadios');
$f->attr('name', 'gridMode');
$f->label = $this->_('Default image grid mode');
$f->description = $this->_('In the admin, the list of images will appear in this mode by default. The user can change it at any time by clicking the icons in the top right corner of the field.');
$f->notes = $this->_('If you have recently used this images field, you will have to clear your cookies before seeing any changes to this setting.');
$f->icon = 'photo';
$f->addOption('grid', '[i.fa.fa-th][/i] ' . $this->_('Square grid images'));
$f->addOption('left', '[i.fa.fa-tasks][/i] ' . $this->_('Proportional grid images'));
$f->addOption('list', '[i.fa.fa-th-list][/i] ' . $this->_('Vertical list (verbose)'));
$f->attr('value', $inputfield->gridMode);
$f->columnWidth = 50;
$fs->add($f);
/** @var InputfieldRadios $f */
$f = $modules->get('InputfieldRadios');
$f->attr('name', 'focusMode');
$f->label = $this->_('Focus point selection');
$f->description = $this->_('Enables a draggable focus point to select the subject of an image. This helps to generate non-proportional crops.') . ' ' .
$this->_('A preview of the focus point is also shown when images are in the “Square grid images” mode.');
$f->addOption('on', $this->_('Focus point'));
$f->addOption('zoom', $this->_('Focus point and zoom'));
$f->addOption('off', $this->_('Disabled'));
$f->attr('value', $inputfield->focusMode);
$f->icon = 'crosshairs';
$f->columnWidth = 50;
$fs->add($f);
/** @var InputfieldFieldset $fieldset */
$fieldset = $modules->get('InputfieldFieldset');
$fieldset->label = $this->_("Maximum image dimensions");
$fieldset->icon = 'expand';
$fieldset->description = $this->_("Optionally enter the max width and/or height of uploaded images. If specified, images will be resized at upload time when they exceed either the max width or height. The resize is performed at upload time, and thus does not affect any images in the system, or images added via the API."); // Max image dimensions description
$fieldset->description .= ' ' . $this->_('Applies to JPG, PNG and GIF images.');
$fieldset->themeOffset = 1;
$inputfields->add($fieldset);
$description = $this->_("Enter the value in number of pixels or leave blank for no limit."); // Min/Max width/height description
/** @var InputfieldInteger $field */
$field = $modules->get("InputfieldInteger");
$field->attr('name', 'maxWidth');
$field->attr('value', $inputfield->maxWidth ? (int) $inputfield->maxWidth : '');
$field->label = $this->_("Max width for uploaded images");
$field->icon = 'arrows-h';
$field->description = $description;
$field->columnWidth = 50;
$field->appendMarkup = '&nbsp;px';
$fieldset->add($field);
$field = $modules->get("InputfieldInteger");
$field->attr('name', 'maxHeight');
$field->attr('value', $inputfield->maxHeight ? (int) $inputfield->maxHeight : '');
$field->label = $this->_("Max height for uploaded images");
$field->icon = 'arrows-v';
$field->description = $description;
$field->columnWidth = 50;
$field->appendMarkup = '&nbsp;px';
$fieldset->add($field);
/** @var InputfieldRadios $field */
$field = $modules->get('InputfieldRadios');
$field->attr('name', 'resizeServer');
$field->label = $this->_('How to resize to max dimensions');
$field->description = $this->_('Using client-side resize enables you to reduce the file size and dimensions before uploading.');
$field->notes = $this->_('When using client-side resize, please specify both max width *and* max height in the fields above, or max megapixels in the field below.');
$field->icon = 'object-group';
$field->addOption(0, $this->_('Use client-side resize when possible'));
$field->addOption(1, $this->_('Use only server-side resize'));
$field->attr('value', (int) $inputfield->resizeServer);
$fieldset->add($field);
$field = $modules->get('InputfieldFloat');
$field->attr('name', 'maxSize');
$field->label = $this->_('Max megapixels for uploaded images');
$field->description = $this->_('This can be used as an alternative to max width/height. Specify a floating point value.');
$field->description .= ' ' . $this->_('Applicable to client-side resize only.');
$field->notes = $this->_('A good value for websites is 1.7 which is roughly 1600x1000 pixels, where 1600 and 1000 can be either width or height.');
$field->notes .= ' ' . $this->_('Other examples:') . ' 0.2=516x387, 2.0=1633x1225, 3.0=2000x1500, 12.0=4000x3000';
$field->icon = 'camera';
$field->attr('value', (float) $inputfield->maxSize > 0 ? (float) $inputfield->maxSize : '');
$field->showIf = 'resizeServer=0';
$field->columnWidth = 50;
$fieldset->add($field);
/** @var InputfieldInteger $field */
$field = $modules->get('InputfieldInteger');
$field->attr('name', 'clientQuality');
$field->label = $this->_('Client-side resize quality percent for JPEGs');
$field->description = $this->_('Specify a number between 10 (lowest quality/smallest file size) and 100 (highest quality/largest file size). Default is 90.');
$field->icon = 'signal';
$field->min = 10;
$field->max = 100;
$field->attr('size', 4);
$field->attr('value', (int) $inputfield->clientQuality);
$field->showIf = 'resizeServer=0';
$field->columnWidth = 50;
$field->appendMarkup = '&nbsp;%';
$fieldset->add($field);
// maxReject option comes from @JanRomero PR #1051
/** @var InputfieldCheckbox $field */
$field = $modules->get("InputfieldCheckbox");
$field->attr('name', 'maxReject');
$field->attr('value', (int) $inputfield->maxReject);
$field->attr('checked', ((int) $inputfield->maxReject) ? 'checked' : '');
$field->label = $this->_('Refuse images exceeding max dimensions?');
$field->showIf = 'maxWidth|maxHeight>0';
$field->icon = 'ban';
$field->description = $this->_('If checked, images that exceed max width/height (that cannot be resized client-side) will be refused rather than resized.');
if(!$inputfield->maxReject) $field->collapsed = Inputfield::collapsedYes;
$fieldset->add($field);
// min image dimensions
/** @var InputfieldFieldset $fieldset */
$fieldset = $modules->get('InputfieldFieldset');
$fieldset->label = $this->_("Minimum image dimensions");
$fieldset->icon = 'compress';
$fieldset->collapsed = $inputfield->minWidth || $inputfield->minHeight ? Inputfield::collapsedNo : Inputfield::collapsedYes;
$fieldset->description = $this->_("Optionally enter the minimum width and/or height of uploaded images. If specified, images that don't meet these minimums will be refused."); // Max image dimensions description
$fieldset->themeOffset = 1;
$inputfields->add($fieldset);
/** @var InputfieldInteger $field */
$field = $modules->get("InputfieldInteger");
$field->attr('name', 'minWidth');
$field->attr('value', $inputfield->minWidth ? (int) $inputfield->minWidth : '');
$field->label = $this->_("Min width for uploaded images");
$field->description = $description;
$field->columnWidth = 50;
$field->icon = 'arrows-h';
$field->appendMarkup = '&nbsp;px';
$fieldset->add($field);
/** @var InputfieldInteger $field */
$field = $modules->get("InputfieldInteger");
$field->attr('name', 'minHeight');
$field->attr('value', $inputfield->minHeight ? (int) $inputfield->minHeight : '');
$field->label = $this->_("Min height for uploaded images");
$field->description = $description;
$field->columnWidth = 50;
$field->icon = 'arrows-v';
$field->appendMarkup = '&nbsp;px';
$fieldset->add($field);
/** @var InputfieldCheckbox $field */
$field = $modules->get("InputfieldCheckbox");
$field->attr('name', 'dimensionsByAspectRatio');
$field->attr('value', (int) $inputfield->dimensionsByAspectRatio);
$field->attr('checked', ((int) $inputfield->dimensionsByAspectRatio) ? 'checked' : '');
$field->label = $this->_("Swap min/max dimensions for portrait images?");
$field->showIf = 'minWidth|minHeight|maxWidth|maxHeight>0';
$field->description = $this->_('If checked, minimum width/height and maximum width/height dimensions will be swapped for portrait images to accommodate for the different aspect ratio.');
$field->description .= ' ' . $this->_('Applies to server-side resizes only.');
$field->collapsed = $inputfield->dimensionsByAspectRatio ? Inputfield::collapsedNo : Inputfield::collapsedYes;
$field->icon = 'exchange';
$field->themeOffset = 1;
$inputfields->add($field);
}
}

View File

@@ -659,13 +659,15 @@ class ProcessTemplate extends Process {
*/
protected function ___buildEditForm(Template $template) {
$input = $this->wire()->input;
/** @var InputfieldForm $form */
$form = $this->modules->get('InputfieldForm');
$form->attr('id', 'ProcessTemplateEdit');
$form->attr('action', "save");
$form->attr('method', 'post');
if(!is_file($template->filename) && !count($this->input->post)) {
if(!is_file($template->filename) && !count($input->post) && !$input->get('modal')) {
$this->message(sprintf($this->_('Pages using this template are not viewable because the template file (%s) does not exist [no-file].'), $template->name . '.' . $this->config->templateExtension));
}
@@ -986,10 +988,22 @@ class ProcessTemplate extends Process {
// if this template isn't defining it's fields, then just show what it's using
if($template->fieldgroup->name != $template->name) return $this->buildEditFormShowFields($template);
if(strpos($template->name, 'field-') === 0) {
list(,$fieldName) = explode('-', $template->name, 2);
$forField = $this->wire()->fields->get($fieldName);
} else {
$forField = null;
}
/** @var InputfieldAsmSelect $select */
$select = $this->modules->get('InputfieldAsmSelect');
$select->label = $this->_x('Fields', 'field-label');
if($forField) {
$select->description = sprintf($this->_('Define the custom fields for “%s”. It is not necessary to configure anything else here.'), $forField->name);
} else {
$select->description = $this->_('Define the fields that are used by this template. You may also drag and drop fields to the desired order or [create a new field](../field/add).'); // Fields definition, description
}
$select->icon = 'cube';
$select->attr('name', 'fieldgroup_fields');
$select->attr('id', 'fieldgroup_fields');