mirror of
https://github.com/processwire/processwire.git
synced 2025-08-09 00:06:55 +02:00
Add support for custom fields in file/image fields. Details and instructions coming on Friday, but if you want to preview, create a template with name "field-images" where the "images" part is the name of your file/image field. Add the fields to it that you want to represent your custom fields, and it's ready to use. Supports most core Fieldtypes (including multi-language) with the following exceptions that are not supported as custom fields in an File/Image field: CKEditor, Repeaters, PageTable, Files (nested), Images (nested), Comments, Cache, Selector.
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
* Pagefile objects are contained by a `Pagefiles` object.
|
* Pagefile objects are contained by a `Pagefiles` object.
|
||||||
* #pw-body
|
* #pw-body
|
||||||
*
|
*
|
||||||
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
|
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
|
||||||
* https://processwire.com
|
* https://processwire.com
|
||||||
*
|
*
|
||||||
* @property-read string $url URL to the file on the server.
|
* @property-read string $url URL to the file on the server.
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
* @property-read string $name Returns the filename without the path, same as the "basename" property.
|
* @property-read string $name Returns the filename without the path, same as the "basename" property.
|
||||||
* @property-read string $hash Get a unique hash (for the page) representing this Pagefile.
|
* @property-read string $hash Get a unique hash (for the page) representing this Pagefile.
|
||||||
* @property-read array $tagsArray Get file tags as an array. #pw-group-tags @since 3.0.17
|
* @property-read array $tagsArray Get file tags as an array. #pw-group-tags @since 3.0.17
|
||||||
|
* @property-read array $fieldValues Custom field values. #pw-internal @since 3.0.142
|
||||||
* @property int $sort Sort order in database. #pw-group-other
|
* @property int $sort Sort order in database. #pw-group-other
|
||||||
* @property string $basename Returns the filename without the path.
|
* @property string $basename Returns the filename without the path.
|
||||||
* @property string $description Value of the file’s description field (string), if enabled. Note you can also set this property directly.
|
* @property string $description Value of the file’s description field (string), if enabled. Note you can also set this property directly.
|
||||||
@@ -77,6 +78,17 @@ class Pagefile extends WireData {
|
|||||||
*/
|
*/
|
||||||
protected $filedata = array();
|
protected $filedata = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom field values indexed by field name, loaded on request
|
||||||
|
*
|
||||||
|
* Values here have been run through wakeupValue and sanitizeValue already.
|
||||||
|
* Prior to that they are stored in $filedata (above).
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected $fieldValues = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new Pagefile
|
* Construct a new Pagefile
|
||||||
*
|
*
|
||||||
@@ -227,6 +239,8 @@ class Pagefile extends WireData {
|
|||||||
$language = $languages->get((int) $matches[1]);
|
$language = $languages->get((int) $matches[1]);
|
||||||
if($language && $language->id) return $this->setDescription($value, $language);
|
if($language && $language->id) return $this->setDescription($value, $language);
|
||||||
}
|
}
|
||||||
|
} else if($this->setFieldValue($key, $value)) {
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::set($key, $value);
|
return parent::set($key, $value);
|
||||||
@@ -237,7 +251,6 @@ class Pagefile extends WireData {
|
|||||||
*
|
*
|
||||||
* Filedata is any additional data that you want to store with the file’s database record.
|
* Filedata is any additional data that you want to store with the file’s database record.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* - To get a value, specify just the $key argument. Null is returned if request value is not present.
|
* - To get a value, specify just the $key argument. Null is returned if request value is not present.
|
||||||
* - To get all values, omit all arguments. An associative array will be returned.
|
* - To get all values, omit all arguments. An associative array will be returned.
|
||||||
* - To set a value, specify the $key and the $value to set.
|
* - To set a value, specify the $key and the $value to set.
|
||||||
@@ -558,11 +571,120 @@ class Pagefile extends WireData {
|
|||||||
$value = filemtime($this->filename());
|
$value = filemtime($this->filename());
|
||||||
if(strpos($key, 'Str')) $value = wireDate($this->wire('config')->dateFormat, $value);
|
if(strpos($key, 'Str')) $value = wireDate($this->wire('config')->dateFormat, $value);
|
||||||
break;
|
break;
|
||||||
|
case 'fieldValues':
|
||||||
|
return $this->fieldValues;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$value = $this->getFieldValue($key);
|
||||||
|
|
||||||
}
|
}
|
||||||
if(is_null($value)) return parent::get($key);
|
if(is_null($value)) return parent::get($key);
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a custom field value
|
||||||
|
*
|
||||||
|
* #pw-internal Most non-core cases should just use get() or direct access rather than this method
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param bool|null Get as formatted value? true=yes, false=no, null=use page output formatting setting (default=null)
|
||||||
|
* @return mixed|null Returns value or null if not present
|
||||||
|
* @since 3.0.142
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getFieldValue($name, $formatted = null) {
|
||||||
|
|
||||||
|
$field = $this->wire('fields')->get($name);
|
||||||
|
if(!$field) return null;
|
||||||
|
|
||||||
|
$template = $this->pagefiles->getFieldsTemplate();
|
||||||
|
if(!$template) return null;
|
||||||
|
|
||||||
|
$fieldgroup = $template->fieldgroup;
|
||||||
|
if(!$fieldgroup->hasField($field)) return null;
|
||||||
|
|
||||||
|
$field = $fieldgroup->getFieldContext($field); // get in context
|
||||||
|
$fieldtype = $field->type; /** @var Fieldtype $fieldtype */
|
||||||
|
$fileField = $this->pagefiles->getField(); /** @var Field $fileField */
|
||||||
|
$fileFieldtype = $fileField->type; /** @var FieldtypeFile|FieldtypeImage $fileFieldtype */
|
||||||
|
$page = $fileFieldtype->getFieldsPage($fileField);
|
||||||
|
|
||||||
|
if(array_key_exists($name, $this->fieldValues)) {
|
||||||
|
$value = $this->fieldValues[$name];
|
||||||
|
} else {
|
||||||
|
$idKey = "_$field->id";
|
||||||
|
$value = $this->filedata($idKey);
|
||||||
|
if($value !== null) {
|
||||||
|
$value = $fieldtype->wakeupValue($page, $field, $value);
|
||||||
|
$value = $fieldtype->sanitizeValue($page, $field, $value);
|
||||||
|
}
|
||||||
|
$this->fieldValues[$name] = $value;
|
||||||
|
unset($this->filedata[$idKey]); // avoid storing double copies
|
||||||
|
}
|
||||||
|
|
||||||
|
if($value === null) {
|
||||||
|
$value = $fieldtype->getBlankValue($page, $field);
|
||||||
|
$value = $fieldtype->sanitizeValue($page, $field, $value);
|
||||||
|
$this->fieldValues[$name] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($formatted === null) $formatted = $this->page->of();
|
||||||
|
if($formatted) $value = $fieldtype->formatValue($page, $field, $value);
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a custom field value
|
||||||
|
*
|
||||||
|
* #pw-internal Most non-core cases should use set() instead
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param mixed $value
|
||||||
|
* @param bool|null $changed Specify true to force track change, false to force no change, or null to auto-detect (default=null)
|
||||||
|
* @return bool Returns true if value set, or false if not (like if there’s no template defined for the purpose)
|
||||||
|
* @since 3.0.142
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function setFieldValue($name, $value, $changed = null) {
|
||||||
|
|
||||||
|
$template = $this->pagefiles->getFieldsTemplate();
|
||||||
|
if(!$template) return false;
|
||||||
|
|
||||||
|
$fieldgroup = $template->fieldgroup;
|
||||||
|
if(!$fieldgroup) return false;
|
||||||
|
|
||||||
|
$field = $fieldgroup->getFieldContext($name);
|
||||||
|
if(!$field) return false;
|
||||||
|
|
||||||
|
$page = $this->pagefiles->getFieldsPage();
|
||||||
|
|
||||||
|
/** @var Fieldtype $fieldtype */
|
||||||
|
$fieldtype = $field->type;
|
||||||
|
$value = $fieldtype->sanitizeValue($page, $field, $value);
|
||||||
|
|
||||||
|
if($changed === null && $this->page->trackChanges()) {
|
||||||
|
// detect if a change has taken place
|
||||||
|
$oldValue = $this->getFieldValue($field->name, false);
|
||||||
|
if(is_object($oldValue) && $oldValue instanceof Wire && $oldValue === $value) {
|
||||||
|
// $oldValue and new $value are the same object instance, so ask it if anything has changed
|
||||||
|
$changed = $oldValue->isChanged();
|
||||||
|
if($changed) $this->trackChange($field->name);
|
||||||
|
} else if($oldValue != $value) {
|
||||||
|
// $oldValue and new $value differ, record change
|
||||||
|
$this->trackChange($field->name, $oldValue, $value);
|
||||||
|
}
|
||||||
|
} else if($changed === true) {
|
||||||
|
$this->trackChange($field->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->fieldValues[$field->name] = $value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hookable no-cache URL
|
* Hookable no-cache URL
|
||||||
*
|
*
|
||||||
|
@@ -109,6 +109,12 @@ class Pagefiles extends WireArray implements PageFieldValueInterface {
|
|||||||
*/
|
*/
|
||||||
protected $formatted = false;
|
protected $formatted = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Template|null
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected $fieldsTemplate = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a Pagefiles object
|
* Construct a Pagefiles object
|
||||||
*
|
*
|
||||||
@@ -117,6 +123,7 @@ class Pagefiles extends WireArray implements PageFieldValueInterface {
|
|||||||
*/
|
*/
|
||||||
public function __construct(Page $page) {
|
public function __construct(Page $page) {
|
||||||
$this->setPage($page);
|
$this->setPage($page);
|
||||||
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -917,6 +924,44 @@ class Pagefiles extends WireArray implements PageFieldValueInterface {
|
|||||||
return $this->formatted;
|
return $this->formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Template object used for Pagefile custom fields, if available (false if not)
|
||||||
|
*
|
||||||
|
* #pw-internal
|
||||||
|
*
|
||||||
|
* @return bool|Template
|
||||||
|
* @since 3.0.142
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getFieldsTemplate() {
|
||||||
|
if($this->fieldsTemplate === null) {
|
||||||
|
/** @var Field $field */
|
||||||
|
$field = $this->getField();
|
||||||
|
if($field) {
|
||||||
|
$this->fieldsTemplate = false;
|
||||||
|
/** @var FieldtypeFile $fieldtype */
|
||||||
|
$fieldtype = $field->type;
|
||||||
|
$template = $fieldtype ? $fieldtype->getFieldsTemplate($field) : null;
|
||||||
|
if($template) $this->fieldsTemplate = $template;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->fieldsTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get mock/placeholder Page object used for Pagefile custom fields
|
||||||
|
*
|
||||||
|
* @return Page
|
||||||
|
* @since 3.0.142
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getFieldsPage() {
|
||||||
|
$field = $this->getField();
|
||||||
|
/** @var FieldtypeFile $fieldtype */
|
||||||
|
$fieldtype = $field->type;
|
||||||
|
return $fieldtype->getFieldsPage($field);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debug info
|
* Debug info
|
||||||
*
|
*
|
||||||
|
@@ -9,19 +9,20 @@
|
|||||||
* /wire/core/Fieldtype.php
|
* /wire/core/Fieldtype.php
|
||||||
* /wire/core/FieldtypeMulti.php
|
* /wire/core/FieldtypeMulti.php
|
||||||
*
|
*
|
||||||
* ProcessWire 3.x, Copyright 2017 by Ryan Cramer
|
* ProcessWire 3.x, Copyright 2019 by Ryan Cramer
|
||||||
* https://processwire.com
|
* https://processwire.com
|
||||||
*
|
*
|
||||||
|
* @property array $allowFieldtypes Allowed Fieldtype types for custom fields
|
||||||
* @method string formatValueString(Page $page, Field $field, $value)
|
* @method string formatValueString(Page $page, Field $field, $value)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class FieldtypeFile extends FieldtypeMulti {
|
class FieldtypeFile extends FieldtypeMulti implements ConfigurableModule {
|
||||||
|
|
||||||
public static function getModuleInfo() {
|
public static function getModuleInfo() {
|
||||||
return array(
|
return array(
|
||||||
'title' => __('Files', __FILE__),
|
'title' => __('Files', __FILE__),
|
||||||
'version' => 105,
|
'version' => 106,
|
||||||
'summary' => __('Field that stores one or more files', __FILE__),
|
'summary' => __('Field that stores one or more files', __FILE__),
|
||||||
'permanent' => true,
|
'permanent' => true,
|
||||||
);
|
);
|
||||||
@@ -95,12 +96,40 @@ class FieldtypeFile extends FieldtypeMulti {
|
|||||||
*/
|
*/
|
||||||
protected $defaultInputfieldClass = '';
|
protected $defaultInputfieldClass = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default fieldtypes allowed for custom fields
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected $defaultAllowFieldtypes = array(
|
||||||
|
'Checkbox',
|
||||||
|
'Datetime',
|
||||||
|
'Email',
|
||||||
|
'FieldsetClose',
|
||||||
|
'FieldsetOpen',
|
||||||
|
'Float',
|
||||||
|
'Integer',
|
||||||
|
'Page',
|
||||||
|
'PageTitle',
|
||||||
|
'PageTitleLanguage',
|
||||||
|
'Text',
|
||||||
|
'TextLanguage',
|
||||||
|
'Textarea',
|
||||||
|
'TextareaLanguage',
|
||||||
|
'Toggle',
|
||||||
|
'URL',
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct
|
* Construct
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->defaultInputfieldClass = str_replace('Fieldtype', 'Inputfield', $this->className);
|
$this->defaultInputfieldClass = str_replace('Fieldtype', 'Inputfield', $this->className);
|
||||||
|
if($this->className() === 'FieldtypeFile') $this->allowFieldtypes = $this->defaultAllowFieldtypes;
|
||||||
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,7 +243,11 @@ class FieldtypeFile extends FieldtypeMulti {
|
|||||||
if(isset($v['modified'])) $pagefile->modified = $v['modified'];
|
if(isset($v['modified'])) $pagefile->modified = $v['modified'];
|
||||||
if(isset($v['created'])) $pagefile->created = $v['created'];
|
if(isset($v['created'])) $pagefile->created = $v['created'];
|
||||||
if(isset($v['tags'])) $pagefile->tags = $v['tags'];
|
if(isset($v['tags'])) $pagefile->tags = $v['tags'];
|
||||||
if(!empty($v['filedata'])) $pagefile->filedata = json_decode($v['filedata'], true);
|
if(!empty($v['filedata'])) {
|
||||||
|
$filedata = json_decode($v['filedata'], true);
|
||||||
|
unset($filedata['ix']);
|
||||||
|
$pagefile->filedata = $filedata;
|
||||||
|
}
|
||||||
$pagefile->setTrackChanges(true);
|
$pagefile->setTrackChanges(true);
|
||||||
$pagefiles->add($pagefile);
|
$pagefiles->add($pagefile);
|
||||||
}
|
}
|
||||||
@@ -238,6 +271,8 @@ class FieldtypeFile extends FieldtypeMulti {
|
|||||||
if(!$value instanceof Pagefiles) return $sleepValue;
|
if(!$value instanceof Pagefiles) return $sleepValue;
|
||||||
|
|
||||||
foreach($value as $pagefile) {
|
foreach($value as $pagefile) {
|
||||||
|
/** @var Pagefile $pagefile */
|
||||||
|
|
||||||
$item = array(
|
$item = array(
|
||||||
'data' => $pagefile->basename,
|
'data' => $pagefile->basename,
|
||||||
'description' => $pagefile->description(true),
|
'description' => $pagefile->description(true),
|
||||||
@@ -255,9 +290,12 @@ class FieldtypeFile extends FieldtypeMulti {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if($fileSchema & self::fileSchemaFiledata) {
|
if($fileSchema & self::fileSchemaFiledata) {
|
||||||
|
$filedata = $this->sleepFiledata($field, $pagefile);
|
||||||
|
if(empty($filedata)) {
|
||||||
$item['filedata'] = null;
|
$item['filedata'] = null;
|
||||||
$filedata = $pagefile->filedata;
|
} else {
|
||||||
if(!empty($filedata)) $item['filedata'] = json_encode($filedata);
|
$item['filedata'] = json_encode($filedata);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$sleepValue[] = $item;
|
$sleepValue[] = $item;
|
||||||
@@ -265,8 +303,121 @@ class FieldtypeFile extends FieldtypeMulti {
|
|||||||
return $sleepValue;
|
return $sleepValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the filedata from given $pagefile ready for placement in a sleep value
|
||||||
|
*
|
||||||
|
* @param Field $fileField Field having type FieldtypeFile or FieldtypeImage (or derivative)
|
||||||
|
* @param Pagefile|Pageimage $pagefile
|
||||||
|
* @return array Sleep value array
|
||||||
|
* @since 3.0.142
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function sleepFiledata(Field $fileField, Pagefile $pagefile) {
|
||||||
|
|
||||||
|
$filedata = $pagefile->filedata;
|
||||||
|
$template = $this->getFieldsTemplate($fileField);
|
||||||
|
|
||||||
|
if(!$template) return $filedata; // custom field template not in use, return filedata as-is
|
||||||
|
if(!is_array($filedata)) $filedata = array();
|
||||||
|
|
||||||
|
$fieldValues = $pagefile->fieldValues; // field values that were loaded
|
||||||
|
$fieldgroup = $template->fieldgroup;
|
||||||
|
$idKeys = array(); // _123 type field ID keys that are populated
|
||||||
|
$mockPage = $this->getFieldsPage($fileField);
|
||||||
|
|
||||||
|
// sleep values from pagefile->fieldValues and place back into filedata
|
||||||
|
foreach($fieldValues as $key => $value) {
|
||||||
|
$field = $fieldgroup->getFieldContext($key);
|
||||||
|
if(!$field) continue;
|
||||||
|
$idKey = "_$field->id";
|
||||||
|
if($value === null) {
|
||||||
|
// null to remove value
|
||||||
|
unset($filedata[$idKey]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$sleepValue = $field->type->sleepValue($mockPage, $field, $value);
|
||||||
|
if($sleepValue === null) {
|
||||||
|
unset($filedata[$idKey]);
|
||||||
|
} else {
|
||||||
|
$filedata[$idKey] = $sleepValue;
|
||||||
|
$idKeys[$idKey] = $idKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for data that should no longer be here
|
||||||
|
// validate that all field ID keys resolve to fields in the fieldgroup
|
||||||
|
foreach($filedata as $key => $value) {
|
||||||
|
if(isset($idKeys[$key])) continue; // valid, skip
|
||||||
|
if(strpos($key, '_') !== 0) continue; // some other filedata, skip
|
||||||
|
$fieldID = ltrim($key, '_');
|
||||||
|
if(!ctype_digit($fieldID)) continue; // not a custom field, skip
|
||||||
|
if($fieldgroup->hasField((int) $fieldID)) continue; // valid, skip
|
||||||
|
unset($filedata[$key]); // at this point, it can be removed
|
||||||
|
}
|
||||||
|
|
||||||
|
// build fieldValues index
|
||||||
|
$index = array();
|
||||||
|
foreach($filedata as $idKey => $value) {
|
||||||
|
$id = ltrim($idKey, '_');
|
||||||
|
if(!ctype_digit($id)) continue;
|
||||||
|
$key = "{$id}_";
|
||||||
|
$indexValue = $this->filedataIndexValue($key, $value);
|
||||||
|
if($indexValue !== null) $index[] = $indexValue;
|
||||||
|
}
|
||||||
|
unset($filedata['ix']);
|
||||||
|
if(count($index)) $filedata['ix'] = implode(' ', $index);
|
||||||
|
|
||||||
|
return $filedata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get indexable value for filedata or null if not one that will be indexed
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @param mixed $value
|
||||||
|
* @return null|string
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function filedataIndexValue($key, $value) {
|
||||||
|
|
||||||
|
$indexValue = null;
|
||||||
|
|
||||||
|
if(is_array($value) && count($value) && !is_array(reset($value))) {
|
||||||
|
$a = array();
|
||||||
|
foreach($value as $k => $v) {
|
||||||
|
if(is_array($v) || !strlen("$v")) continue;
|
||||||
|
$kk = is_int($k) ? $key : "{$key}{$k}_";
|
||||||
|
$v = $this->filedataIndexValue($kk, $v);
|
||||||
|
if($v !== null) $a[] = $v;
|
||||||
|
}
|
||||||
|
if(count($a)) $indexValue = implode(' ', $a);
|
||||||
|
|
||||||
|
} else if(is_int($value)) {
|
||||||
|
// integer index
|
||||||
|
$indexValue = "$key$value";
|
||||||
|
|
||||||
|
} else if(is_string($value) && ctype_alnum($value) && strlen($value) <= 128) {
|
||||||
|
// one word string
|
||||||
|
$indexValue = "$key$value";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $indexValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export value
|
||||||
|
*
|
||||||
|
* @param Page $page
|
||||||
|
* @param Field $field
|
||||||
|
* @param array|float|int|null|object|string $value
|
||||||
|
* @param array $options
|
||||||
|
* @return array|float|int|string
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function ___exportValue(Page $page, Field $field, $value, array $options = array()) {
|
public function ___exportValue(Page $page, Field $field, $value, array $options = array()) {
|
||||||
|
|
||||||
|
/** @var Pagefiles $pagefiles */
|
||||||
$pagefiles = $value;
|
$pagefiles = $value;
|
||||||
$value = $this->sleepValue($page, $field, $value);
|
$value = $this->sleepValue($page, $field, $value);
|
||||||
$exportValue = array();
|
$exportValue = array();
|
||||||
@@ -500,9 +651,32 @@ class FieldtypeFile extends FieldtypeMulti {
|
|||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get match query
|
||||||
|
*
|
||||||
|
* @param DatabaseQuerySelect $query
|
||||||
|
* @param string $table
|
||||||
|
* @param string $subfield
|
||||||
|
* @param string $operator
|
||||||
|
* @param mixed $value
|
||||||
|
* @return DatabaseQuery|DatabaseQuerySelect
|
||||||
|
* @throws PageFinderSyntaxException
|
||||||
|
* @throws WireException
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function getMatchQuery($query, $table, $subfield, $operator, $value) {
|
public function getMatchQuery($query, $table, $subfield, $operator, $value) {
|
||||||
|
|
||||||
if(in_array($operator, array("*=", "~=", "%=", "^=", "$="))) {
|
if($subfield && !in_array($subfield, array('data', 'count', 'filedata', 'created', 'modified', 'description', 'tags'))) {
|
||||||
|
// match custom fields
|
||||||
|
if($this->getMatchQuerySubfield($query, $subfield, $operator, $value)) {
|
||||||
|
// successfully handled
|
||||||
|
} else {
|
||||||
|
// requested subfield does not match what's available
|
||||||
|
throw new PageFinderSyntaxException("Property '$subfield' not recognized in selector: $query->selector");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(in_array($operator, array("*=", "~=", "!~=", "%=", "^=", "$="))) {
|
||||||
// fulltext match filename or description
|
// fulltext match filename or description
|
||||||
/** @var DatabaseQuerySelectFulltext $ft */
|
/** @var DatabaseQuerySelectFulltext $ft */
|
||||||
$ft = $this->wire(new DatabaseQuerySelectFulltext($query));
|
$ft = $this->wire(new DatabaseQuerySelectFulltext($query));
|
||||||
@@ -511,15 +685,117 @@ class FieldtypeFile extends FieldtypeMulti {
|
|||||||
} else {
|
} else {
|
||||||
$query = parent::getMatchQuery($query, $table, $subfield, $operator, $value);
|
$query = parent::getMatchQuery($query, $table, $subfield, $operator, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get match query for custom field selector
|
||||||
|
*
|
||||||
|
* @param DatabaseQuerySelect $query
|
||||||
|
* @param string $subfield
|
||||||
|
* @param string $operator
|
||||||
|
* @param mixed $value
|
||||||
|
* @return bool
|
||||||
|
* @throws PageFinderSyntaxException
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function getMatchQuerySubfield($query, &$subfield, &$operator, &$value) {
|
||||||
|
|
||||||
|
/** @var Sanitizer $sanitizer */
|
||||||
|
$sanitizer = $this->wire('sanitizer');
|
||||||
|
$selector = $query->selector;
|
||||||
|
$property = '';
|
||||||
|
|
||||||
|
if($selector && substr_count($selector->field(), '.') > 1) {
|
||||||
|
// field in format field.subfield.property
|
||||||
|
$parts = explode('.', $selector->field());
|
||||||
|
while(count($parts) > 2) $property = array_pop($parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$field = $this->wire('fields')->get($subfield);
|
||||||
|
$fileField = $query->field;
|
||||||
|
|
||||||
|
if($fileField && !$fileField->type instanceof FieldtypeFile) $fileField = null;
|
||||||
|
if(!$field || !$fileField) return false;
|
||||||
|
|
||||||
|
$template = $this->getFieldsTemplate($fileField);
|
||||||
|
if(!$template || !$template->fieldgroup->hasField($field)) {
|
||||||
|
throw new PageFinderSyntaxException("Field '$fileField->name' does not have subfield '$field->name'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if($field->type instanceof FieldtypePage) {
|
||||||
|
if($property) {
|
||||||
|
throw new PageFinderSyntaxException("Property '$property' not supported in field '" . $selector->field() . "'");
|
||||||
|
} else if(!ctype_digit("$value") && $sanitizer->pagePathName($value) === $value) {
|
||||||
|
// page path to ID
|
||||||
|
$p = $this->wire('pages')->get($value);
|
||||||
|
if($p->id && $p->viewable(false)) $value = $p->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(($operator === '=' || $operator === '!=') && ctype_alnum("$value") && strlen("$value") <= 128) {
|
||||||
|
// we can match our index value in filedata[ix]
|
||||||
|
$operator = $operator === '=' ? '~=' : '!~=';
|
||||||
|
$value = $property ? "{$field->id}_{$property}_$value" : "{$field->id}_$value";;
|
||||||
|
|
||||||
|
} else if($operator === '=') {
|
||||||
|
$operator = ctype_alnum("$value") ? '~=' : '*='; // ok
|
||||||
|
|
||||||
|
} else if($operator === '!=' && ctype_alnum("$value")) {
|
||||||
|
$operator = '!~='; // ok
|
||||||
|
|
||||||
|
} else if($operator === '~=' || $operator === '!~=' || $operator === '%=' || $operator === '*=') {
|
||||||
|
// ok
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new PageFinderSyntaxException("Operator $operator is not supported by $this in selector: $selector");
|
||||||
|
}
|
||||||
|
|
||||||
|
$subfield = 'filedata';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get selector info
|
||||||
|
*
|
||||||
|
* @param Field $field
|
||||||
|
* @param array $data
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function ___getSelectorInfo(Field $field, array $data = array()) {
|
public function ___getSelectorInfo(Field $field, array $data = array()) {
|
||||||
$info = parent::___getSelectorInfo($field, $data);
|
$info = parent::___getSelectorInfo($field, $data);
|
||||||
$info['subfields']['data']['label'] = $this->_('filename');
|
$info['subfields']['data']['label'] = $this->_('filename');
|
||||||
|
$template = $this->getFieldsTemplate($field);
|
||||||
|
if($template) {
|
||||||
|
foreach($template->fieldgroup as $f) {
|
||||||
|
$f = $template->fieldgroup->getFieldContext($f);
|
||||||
|
if($f->type instanceof FieldtypePage) {
|
||||||
|
$info['subfields'][$f->name] = array(
|
||||||
|
'name' => $f->name,
|
||||||
|
'label' => $f->getLabel(),
|
||||||
|
'operators' => array('=', '!='),
|
||||||
|
'input' => 'page',
|
||||||
|
'options' => array(),
|
||||||
|
);
|
||||||
|
} else if($f->type instanceof FieldtypeCheckbox || $f->type->className() === 'FieldtypeToggle') {
|
||||||
|
$info['subfields'][$f->name] = $f->type->getSelectorInfo($f, $data);
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return $info;
|
return $info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database schema
|
||||||
|
*
|
||||||
|
* @param Field $field
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function getDatabaseSchema(Field $field) {
|
public function getDatabaseSchema(Field $field) {
|
||||||
|
|
||||||
$database = $this->wire('database');
|
$database = $this->wire('database');
|
||||||
@@ -747,7 +1023,58 @@ class FieldtypeFile extends FieldtypeMulti {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return Template used to manage fields for given file field
|
||||||
|
*
|
||||||
|
* #pw-internal
|
||||||
|
*
|
||||||
|
* @param Field $field Field having type FieldtypeFile or FieldtypeImage
|
||||||
|
* @return Template|null Returns Template or null if it does not exist
|
||||||
|
* @since 3.0.142
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getFieldsTemplate(Field $field) {
|
||||||
|
$template = $this->wire('templates')->get('field-' . $field->name);
|
||||||
|
if(!$template) return null;
|
||||||
|
// prepare fieldgroup used by template
|
||||||
|
$fieldgroup = $template->fieldgroup;
|
||||||
|
if($this->className() === 'FieldtypeFile') {
|
||||||
|
$allowFieldtypes = $this->allowFieldtypes;
|
||||||
|
} else {
|
||||||
|
$allowFieldtypes = $this->wire('fieldtypes')->get('FieldtypeFile')->get('allowFieldtypes');
|
||||||
|
}
|
||||||
|
$allowFieldtypes = array_flip($allowFieldtypes);
|
||||||
|
foreach($fieldgroup as $f) {
|
||||||
|
$name = str_replace('Fieldtype', '', $f->type->className());
|
||||||
|
if(!isset($allowFieldtypes[$name])) $fieldgroup->softRemove($f);
|
||||||
|
}
|
||||||
|
return $template;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a mock/placeholder page for using custom fields in files
|
||||||
|
*
|
||||||
|
* #pw-internal
|
||||||
|
*
|
||||||
|
* @param Field $field
|
||||||
|
* @return Page
|
||||||
|
* @since 3.0.142
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getFieldsPage(Field $field) {
|
||||||
|
$page = new Page();
|
||||||
|
$page->template = $this->getFieldsTemplate($field);
|
||||||
|
$page->status = Page::statusOn | Page::statusCorrupted; // corrupted status prevents saving
|
||||||
|
return $page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field config
|
||||||
|
*
|
||||||
|
* @param Field $field
|
||||||
|
* @return InputfieldWrapper
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function ___getConfigInputfields(Field $field) {
|
public function ___getConfigInputfields(Field $field) {
|
||||||
|
|
||||||
$inputfields = parent::___getConfigInputfields($field);
|
$inputfields = parent::___getConfigInputfields($field);
|
||||||
@@ -891,6 +1218,83 @@ class FieldtypeFile extends FieldtypeMulti {
|
|||||||
return $inputfields;
|
return $inputfields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module config
|
||||||
|
*
|
||||||
|
* @param InputfieldWrapper $inputfields
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($names as $key => $name) {
|
||||||
|
$f->addOption($key, $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
$f->val($this->allowFieldtypes);
|
||||||
|
$inputfields->add($f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -338,7 +338,15 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
var $inputfields = $markup.find('.Inputfield');
|
||||||
|
if($inputfields.length) {
|
||||||
|
InputfieldsInit($markup.find('.Inputfields'));
|
||||||
|
$inputfields.trigger('reloaded', ['InputfieldFileUpload']);
|
||||||
}
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
} // for
|
||||||
|
|
||||||
$progressItem.remove();
|
$progressItem.remove();
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -30,6 +30,7 @@
|
|||||||
* @method void processInputAddFile($filename)
|
* @method void processInputAddFile($filename)
|
||||||
* @method void processInputDeleteFile(Pagefile $pagefile)
|
* @method void processInputDeleteFile(Pagefile $pagefile)
|
||||||
* @method bool processInputFile(WireInputData $input, Pagefile $pagefile, $n)
|
* @method bool processInputFile(WireInputData $input, Pagefile $pagefile, $n)
|
||||||
|
* @method bool processItemInputfields(Pagefile $pagefile, InputfieldWrapper $inputfields, $id, WireInputData $input)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class InputfieldFile extends Inputfield implements InputfieldItemList, InputfieldHasSortableValue {
|
class InputfieldFile extends Inputfield implements InputfieldItemList, InputfieldHasSortableValue {
|
||||||
@@ -38,7 +39,7 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
|
|||||||
return array(
|
return array(
|
||||||
'title' => __('Files', __FILE__), // Module Title
|
'title' => __('Files', __FILE__), // Module Title
|
||||||
'summary' => __('One or more file uploads (sortable)', __FILE__), // Module Summary
|
'summary' => __('One or more file uploads (sortable)', __FILE__), // Module Summary
|
||||||
'version' => 125,
|
'version' => 126,
|
||||||
'permanent' => true,
|
'permanent' => true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -109,6 +110,14 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
|
|||||||
*/
|
*/
|
||||||
protected $labels = array();
|
protected $labels = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cached value of Fieldgroup used for Pagefile custom fields, as used by getItemInputfields() method
|
||||||
|
*
|
||||||
|
* @var Fieldgroup|null|bool Null when not yet known, false when known not applicable, Fieldgroup when known and in use
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected $itemFieldgroup = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the InputfieldFile
|
* Initialize the InputfieldFile
|
||||||
*
|
*
|
||||||
@@ -477,16 +486,20 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
|
|||||||
"<input type='checkbox' name='delete_$id' value='1' title='$deleteLabel' />" .
|
"<input type='checkbox' name='delete_$id' value='1' title='$deleteLabel' />" .
|
||||||
"<i class='fa fa-fw fa-trash'></i></label>";
|
"<i class='fa fa-fw fa-trash'></i></label>";
|
||||||
|
|
||||||
$out .=
|
$description = $this->renderItemDescriptionField($pagefile, $id, $n);
|
||||||
"</p>" .
|
$class = 'InputfieldFileData ';
|
||||||
"<div class='InputfieldFileData description ui-widget-content'>" .
|
$class .= $description ? 'description ui-widget-content' : 'InputfieldFileFields';
|
||||||
$this->renderItemDescriptionField($pagefile, $id, $n);
|
|
||||||
|
|
||||||
if(!$this->renderValueMode) $out .=
|
$out .= "</p><div class='$class'>" . $description;
|
||||||
"<input class='InputfieldFileSort' type='text' name='sort_$id' value='$n' />";
|
|
||||||
|
|
||||||
$out .=
|
$inputfields = $this->getItemInputfields($pagefile);
|
||||||
"</div>";
|
if($inputfields) $out .= $inputfields->render();
|
||||||
|
|
||||||
|
if(!$this->renderValueMode) {
|
||||||
|
$out .= "<input class='InputfieldFileSort' type='text' name='sort_$id' value='$n' />";
|
||||||
|
}
|
||||||
|
|
||||||
|
$out .= "</div>";
|
||||||
|
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
@@ -661,6 +674,8 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->getItemInputfields(); // custom fields ready
|
||||||
|
|
||||||
return parent::renderReady($parent, $renderValueMode);
|
return parent::renderReady($parent, $renderValueMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,6 +763,8 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Process input to add a file
|
||||||
|
*
|
||||||
* @param string $filename
|
* @param string $filename
|
||||||
* @throws WireException
|
* @throws WireException
|
||||||
*
|
*
|
||||||
@@ -822,6 +839,12 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process input to delete a Pagefile item
|
||||||
|
*
|
||||||
|
* @param Pagefile $pagefile
|
||||||
|
*
|
||||||
|
*/
|
||||||
protected function ___processInputDeleteFile(Pagefile $pagefile) {
|
protected function ___processInputDeleteFile(Pagefile $pagefile) {
|
||||||
$fileLabel = $this->wire('config')->debug ? $pagefile->url() : $pagefile->name;
|
$fileLabel = $this->wire('config')->debug ? $pagefile->url() : $pagefile->name;
|
||||||
$this->message($this->_("Deleted file:") . " $fileLabel"); // Label that precedes a deleted filename
|
$this->message($this->_("Deleted file:") . " $fileLabel"); // Label that precedes a deleted filename
|
||||||
@@ -829,8 +852,19 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
|
|||||||
$this->trackChange('value');
|
$this->trackChange('value');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process input for one Pagefile
|
||||||
|
*
|
||||||
|
* @param WireInputData $input
|
||||||
|
* @param Pagefile $pagefile
|
||||||
|
* @param int $n
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
*/
|
||||||
protected function ___processInputFile(WireInputData $input, Pagefile $pagefile, $n) {
|
protected function ___processInputFile(WireInputData $input, Pagefile $pagefile, $n) {
|
||||||
|
|
||||||
|
$saveFields = false; // allow custom Inputfields to be saved?
|
||||||
|
$changed = false; // are there any changes to this file?
|
||||||
$id = $this->name . '_' . $pagefile->hash;
|
$id = $this->name . '_' . $pagefile->hash;
|
||||||
|
|
||||||
if($this->uploadOnlyMode) {
|
if($this->uploadOnlyMode) {
|
||||||
@@ -839,8 +873,6 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
|
|||||||
if($input->$key === null) return false;
|
if($input->$key === null) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$changed = false;
|
|
||||||
|
|
||||||
// replace (currently only used by InputfieldImage)
|
// replace (currently only used by InputfieldImage)
|
||||||
$key = "replace_$id";
|
$key = "replace_$id";
|
||||||
$replace = $input->$key;
|
$replace = $input->$key;
|
||||||
@@ -918,9 +950,16 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
|
|||||||
$sort = (int) $sort;
|
$sort = (int) $sort;
|
||||||
$pagefile->sort = $sort;
|
$pagefile->sort = $sort;
|
||||||
if($n !== $sort) $changed = true;
|
if($n !== $sort) $changed = true;
|
||||||
|
$saveFields = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($saveFields) {
|
||||||
|
// save custom Inputfields
|
||||||
|
$inputfields = $this->getItemInputfields($pagefile);
|
||||||
|
if($inputfields && $this->processItemInputfields($pagefile, $inputfields, $id, $input)) $changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if(isset($input['delete_' . $id])) {
|
if(isset($input['delete_' . $id])) {
|
||||||
$this->processInputDeleteFile($pagefile);
|
$this->processInputDeleteFile($pagefile);
|
||||||
$changed = true;
|
$changed = true;
|
||||||
@@ -938,6 +977,48 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
|
|||||||
return $changed;
|
return $changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process custom Inputfields for Pagefile item
|
||||||
|
*
|
||||||
|
* @param Pagefile $pagefile
|
||||||
|
* @param InputfieldWrapper $inputfields
|
||||||
|
* @param string $id Pagefile ID string
|
||||||
|
* @param WireInputData $input
|
||||||
|
* @return bool True if changes detected, false if not
|
||||||
|
* @since 3.0.142
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected function ___processItemInputfields(Pagefile $pagefile, InputfieldWrapper $inputfields, $id, WireInputData $input) {
|
||||||
|
|
||||||
|
$changed = false;
|
||||||
|
|
||||||
|
foreach($inputfields->getAll() as $f) {
|
||||||
|
$f->resetTrackChanges(true);
|
||||||
|
$f->processInput($input);
|
||||||
|
if(!$f->isChanged() && !$pagefile->isTemp()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = str_replace("_$id", '', $f->attr('name'));
|
||||||
|
if($f->getSetting('useLanguages')) {
|
||||||
|
$value = $pagefile->getFieldValue($name);
|
||||||
|
if(is_object($value)) $value->setFromInputfield($f);
|
||||||
|
} else {
|
||||||
|
$value = $f->val();
|
||||||
|
}
|
||||||
|
$pagefile->setFieldValue($name, $value, true);
|
||||||
|
$changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process input
|
||||||
|
*
|
||||||
|
* @param WireInputData $input
|
||||||
|
* @return self
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function ___processInput(WireInputData $input) {
|
public function ___processInput(WireInputData $input) {
|
||||||
|
|
||||||
if(is_null($this->value)) $this->value = $this->wire(new Pagefiles($this->wire('page')));
|
if(is_null($this->value)) $this->value = $this->wire(new Pagefiles($this->wire('page')));
|
||||||
@@ -1080,7 +1161,13 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
protected function allowCollapsedItems() {
|
protected function allowCollapsedItems() {
|
||||||
return $this->descriptionRows == 0 && !$this->useTags && !$this->noCollapseItem;
|
$allow = $this->descriptionRows == 0 && !$this->useTags && !$this->noCollapseItem;
|
||||||
|
if($allow && $this->hasField) {
|
||||||
|
/** @var FieldtypeFile $fieldtype */
|
||||||
|
$fieldtype = $this->hasField->type;
|
||||||
|
if($fieldtype->getFieldsTemplate($this->hasField)) $allow = false;
|
||||||
|
}
|
||||||
|
return $allow;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1094,6 +1181,90 @@ class InputfieldFile extends Inputfield implements InputfieldItemList, Inputfiel
|
|||||||
return $this->wire('sanitizer')->entities(str_replace(' ', ', ', trim($extensions)));
|
return $this->wire('sanitizer')->entities(str_replace(' ', ', ', trim($extensions)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom Inputfields for editing given Pagefile
|
||||||
|
*
|
||||||
|
* @param Pagefile|null $item Specify Pagefile item, or omit to prepare for render ready
|
||||||
|
* @return bool|InputfieldWrapper
|
||||||
|
* @since 3.0.142
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function getItemInputfields(Pagefile $item = null) {
|
||||||
|
|
||||||
|
/** @var Pagefiles $pagefiles */
|
||||||
|
$value = $this->val();
|
||||||
|
$pagefiles = $value instanceof Pagefile ? $value->pagefiles : $value;
|
||||||
|
|
||||||
|
if(!$pagefiles instanceof Pagefiles) {
|
||||||
|
// no value present on this Inputfield
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->itemFieldgroup === false) {
|
||||||
|
// item fieldgroup already determined not in use
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->itemFieldgroup === null) {
|
||||||
|
// item fieldgroup not yet determined
|
||||||
|
$this->itemFieldgroup = false;
|
||||||
|
$template = $pagefiles->getFieldsTemplate();
|
||||||
|
if(!$template) return false;
|
||||||
|
$this->itemFieldgroup = $template->fieldgroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Page $page */
|
||||||
|
$page = $pagefiles->getFieldsPage();
|
||||||
|
$id = $item ? ('_' . $this->pagefileId($item)) : '';
|
||||||
|
|
||||||
|
$inputfields = $this->itemFieldgroup->getPageInputfields($page, $id, '', false);
|
||||||
|
if(!$inputfields) return false;
|
||||||
|
|
||||||
|
/** @var Languages|null $languages */
|
||||||
|
$languages = $this->wire('languages');
|
||||||
|
|
||||||
|
foreach($inputfields->getAll() as $f) {
|
||||||
|
|
||||||
|
if(!$item) {
|
||||||
|
// prepare inputfields for render rather than populating them
|
||||||
|
$f->renderReady();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Inputfield $f */
|
||||||
|
$name = str_replace($id, '', $f->name);
|
||||||
|
$value = $item ? $item->getFieldValue($name) : null;
|
||||||
|
if($value === null) continue;
|
||||||
|
|
||||||
|
if($languages && $f->getSetting('useLanguages') && $value instanceof LanguagesValueInterface) {
|
||||||
|
foreach($languages as $language) {
|
||||||
|
$f->set($language->isDefault() ? "value" : "value$language->id", $value->getLanguageValue($language->id));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$f->val($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($f->className() === 'InputfieldCKEditor') {
|
||||||
|
// CKE does not like being placed in file/image fields.
|
||||||
|
// I'm sure it's possible, but needs more work and debugging, so it's disabled for now.
|
||||||
|
$allow = false;
|
||||||
|
} else {
|
||||||
|
$allow = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$allow) {
|
||||||
|
$inputfields->remove($f);
|
||||||
|
$this->prependMarkup =
|
||||||
|
"<p class='ui-state-error-text'>" .
|
||||||
|
sprintf($this->_('Field “%1$s” type “%2$s” is not supported in field “%3$s”'), $f->label, $f->className(), $this->label) .
|
||||||
|
'</p>';
|
||||||
|
$f->getParent()->remove($f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $inputfields;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration settings for InputfieldFile
|
* Configuration settings for InputfieldFile
|
||||||
*
|
*
|
||||||
|
@@ -87,12 +87,18 @@ ul.InputfieldFileList li .ui-icon {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.InputfieldFileList li .InputfieldFileData {
|
ul.InputfieldFileList li {
|
||||||
|
.InputfieldFileData {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
}
|
||||||
|
.InputfieldFileData.InputfieldFileFields {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.InputfieldFile.InputfieldItemListCollapse ul.InputfieldFileList li .InputfieldFileData {
|
.InputfieldFile.InputfieldItemListCollapse ul.InputfieldFileList li .InputfieldFileData {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -1330,10 +1330,10 @@ function InputfieldImage($) {
|
|||||||
$grid.click(toggleClick);
|
$grid.click(toggleClick);
|
||||||
|
|
||||||
if($target.hasClass('InputfieldImage')) {
|
if($target.hasClass('InputfieldImage')) {
|
||||||
$target.find('.InputfieldHeader').append($list).append($left).append($grid);
|
$target.children('.InputfieldHeader').append($list).append($left).append($grid);
|
||||||
defaultMode = getCookieData($target, 'mode');
|
defaultMode = getCookieData($target, 'mode');
|
||||||
} else {
|
} else {
|
||||||
$(".InputfieldImage .InputfieldHeader", $target).append($list).append($left).append($grid);
|
$(".InputfieldImage > .InputfieldHeader", $target).append($list).append($left).append($grid);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(defaultMode == 'list') {
|
if(defaultMode == 'list') {
|
||||||
@@ -2041,6 +2041,13 @@ function InputfieldImage($) {
|
|||||||
} else {
|
} else {
|
||||||
setGridSize($inputfield, size);
|
setGridSize($inputfield, size);
|
||||||
}
|
}
|
||||||
|
setTimeout(function() {
|
||||||
|
var $inputfields = $markup.find('.Inputfield');
|
||||||
|
if($inputfields.length) {
|
||||||
|
InputfieldsInit($markup.find('.Inputfields'));
|
||||||
|
$inputfields.trigger('reloaded', ['InputfieldImageUpload']);
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
}).css("display", "");
|
}).css("display", "");
|
||||||
$markup.addClass('InputfieldFileItemExisting');
|
$markup.addClass('InputfieldFileItemExisting');
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -53,7 +53,7 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
|
|||||||
return array(
|
return array(
|
||||||
'title' => __('Images', __FILE__), // Module Title
|
'title' => __('Images', __FILE__), // Module Title
|
||||||
'summary' => __('One or more image uploads (sortable)', __FILE__), // Module Summary
|
'summary' => __('One or more image uploads (sortable)', __FILE__), // Module Summary
|
||||||
'version' => 122,
|
'version' => 123,
|
||||||
'permanent' => true,
|
'permanent' => true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -672,11 +672,13 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
|
|||||||
</div>
|
</div>
|
||||||
";
|
";
|
||||||
|
|
||||||
// @todo move the following to separate method shared by the renderSingle
|
|
||||||
$ext = $pagefile->ext();
|
$ext = $pagefile->ext();
|
||||||
$basename = $pagefile->basename(false);
|
$basename = $pagefile->basename(false);
|
||||||
$focus = $pagefile->focus();
|
$focus = $pagefile->focus();
|
||||||
|
|
||||||
|
$inputfields = $this->getItemInputfields($pagefile);
|
||||||
|
if($inputfields) $additional .= $inputfields->render();
|
||||||
|
|
||||||
$out .= "
|
$out .= "
|
||||||
<div class='ImageData'>
|
<div class='ImageData'>
|
||||||
<h2 class='InputfieldImageEdit__name'><span contenteditable='true'>$basename</span>.$ext</h2>
|
<h2 class='InputfieldImageEdit__name'><span contenteditable='true'>$basename</span>.$ext</h2>
|
||||||
@@ -1063,6 +1065,7 @@ class InputfieldImage extends InputfieldFile implements InputfieldItemList, Inpu
|
|||||||
$field->columnWidth = 50;
|
$field->columnWidth = 50;
|
||||||
$fieldset->add($field);
|
$fieldset->add($field);
|
||||||
|
|
||||||
|
/** @var InputfieldInteger $field */
|
||||||
$field = $this->modules->get('InputfieldInteger');
|
$field = $this->modules->get('InputfieldInteger');
|
||||||
$field->attr('name', 'clientQuality');
|
$field->attr('name', 'clientQuality');
|
||||||
$field->label = $this->_('Client-side resize quality percent for JPEGs');
|
$field->label = $this->_('Client-side resize quality percent for JPEGs');
|
||||||
|
@@ -459,7 +459,7 @@ $focusPointCircleSize: 40px;
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
display: flex;
|
display: flex;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
align-items: center;
|
// align-items: center;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
border-right: 1px solid #eee;
|
border-right: 1px solid #eee;
|
||||||
|
Reference in New Issue
Block a user