1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-09 16:26:59 +02:00

Improve support for Field tags by adding a new "Manage Tags" button at the bottom of the fields list screen, enabling you to add or remove fields to/from tags. In addition tags can now be used in $pages->find() searches, i.e. $pages->find("my_tag%=something"); would search all fields in the "my_tag" collection.

This commit is contained in:
Ryan Cramer
2018-06-13 15:31:55 -04:00
parent a465fab672
commit bd35c02e81
8 changed files with 558 additions and 102 deletions

View File

@@ -657,6 +657,7 @@ abstract class AdminThemeFramework extends AdminTheme {
// unencode + re-encode entities, just in case module already entity some or all of output
if(strpos($text, '&') !== false) $text = $this->sanitizer->unentities($text);
$text = $this->sanitizer->entities($text);
$text = nl2br($text);
}
if($notice instanceof NoticeError) {

View File

@@ -12,7 +12,7 @@
* #pw-body Field objects are managed by the `$fields` API variable.
* #pw-use-constants
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
* https://processwire.com
*
* @property int $id Numeric ID of field in the database #pw-group-properties
@@ -26,6 +26,8 @@
* @property string $description Longer description text for the field #pw-group-properties
* @property string $notes Additional notes text about the field #pw-group-properties
* @property string $icon Icon name used by the field, if applicable #pw-group-properties
* @property string $tags Tags that represent this field, if applicable (space separated string). #pw-group-properties
* @property-read array $tagList Same as $tags property, but as an array. #pw-group-properties
* @property bool $useRoles Whether or not access control is enabled #pw-group-access
* @property array $editRoles Role IDs with edit access, applicable only if access control is enabled. #pw-group-access
* @property array $viewRoles Role IDs with view access, applicable only if access control is enabled. #pw-group-access
@@ -203,6 +205,14 @@ class Field extends WireData implements Saveable, Exportable {
*/
protected $inputfieldSettings = array();
/**
* Tags assigned to this field, keys are lowercase version of tag, values can possibly contain mixed case
*
* @var null|array
*
*/
protected $tagList = null;
/**
* True if lowercase tables should be enforce, false if not (null = unset). Cached from $config
*
@@ -341,6 +351,7 @@ class Field extends WireData implements Saveable, Exportable {
*
*/
public function get($key) {
if($key === 'type' && isset($this->settings['type'])) {
$value = $this->settings['type'];
if($value) $value->setLastAccessField($this);
@@ -355,6 +366,8 @@ class Field extends WireData implements Saveable, Exportable {
else if($key == 'icon') return $this->getIcon(true);
else if($key == 'useRoles') return ($this->settings['flags'] & self::flagAccess) ? true : false;
else if($key == 'flags') return $this->settings['flags'];
else if($key == 'tagList') return $this->getTags();
else if($key == 'tags') return $this->getTags(true);
$value = parent::get($key);
if($key === 'allowContexts' && !is_array($value)) $value = array();
@@ -1255,6 +1268,104 @@ class Field extends WireData implements Saveable, Exportable {
return $this;
}
/**
* Get tags
*
* @param bool|string $getString Optionally specify true for space-separated string, or delimiter string (default=false)
* @return array|string Returns array of tags unless $getString option is requested
* @since 3.0.106
*
*/
public function getTags($getString = false) {
if($this->tagList === null) {
$tagList = $this->setTags(parent::get('tags'));
} else {
$tagList = $this->tagList;
}
if($getString !== false) {
$delimiter = $getString === true ? ' ' : $getString;
return implode($delimiter, $tagList);
}
return $tagList;
}
/**
* Set all tags
*
* #pw-internal
*
* @param array $tagList Array of tags to add
* @param bool $reindex Set to false to set given $tagsList exactly as-is (assumes it's already in correct format)
* @return array Array of tags that were set
* @since 3.0.106
*
*/
public function setTags($tagList, $reindex = true) {
if($tagList === null || $tagList === '') {
$tagList = array();
} else if(!is_array($tagList)) {
$tagList = explode(' ', $tagList);
}
if($reindex && count($tagList)) {
$tags = array();
foreach($tagList as $tag) {
$tag = trim($tag);
if(strlen($tag)) $tags[strtolower($tag)] = $tag;
}
$tagList = $tags;
}
if($this->tagList !== $tagList) {
$this->tagList = $tagList;
parent::set('tags', implode(' ', $tagList));
$this->wire('fields')->getTags('reset');
}
return $tagList;
}
/**
* Add one or more tags
*
* @param string $tag
* @return array Returns current tag list
* @since 3.0.106
*
*/
public function addTag($tag) {
$tagList = $this->getTags();
$tagList[strtolower($tag)] = $tag;
$this->setTags($tagList, false);
return $tagList;
}
/**
* Return true if this field has the given tag or false if not
*
* @param string $tag
* @return bool
* @since 3.0.106
*
*/
public function hasTag($tag) {
$tagList = $this->getTags();
return isset($tagList[strtolower(trim(ltrim($tag, '-')))]);
}
/**
* Remove a tag
*
* @param string $tag
* @return array Returns current tag list
* @since 3.0.106
*
*/
public function removeTag($tag) {
$tagList = $this->getTags();
$tag = strtolower($tag);
if(!isset($tagList[$tag])) return $tagList;
unset($tagList[$tag]);
return $this->setTags($tagList, false);
}
/**
* debugInfo PHP 5.6+ magic method
*

View File

@@ -5,7 +5,7 @@
*
* Manages collection of ALL Field instances, not specific to any particular Fieldgroup
*
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
* ProcessWire 3.x, Copyright 2018 by Ryan Cramer
* https://processwire.com
*
* #pw-summary Manages all custom fields in ProcessWire
@@ -37,6 +37,8 @@ class Fields extends WireSaveableItems {
static protected $nativeNamesSystem = array(
'child',
'children',
'count',
'check_access',
'created_users_id',
'created',
'createdUser',
@@ -44,11 +46,16 @@ class Fields extends WireSaveableItems {
'createdUsersID',
'data',
'description',
'editUrl',
'end',
'fieldgroup',
'fields',
'find',
'flags',
'get',
'has_parent',
'hasParent',
'httpUrl',
'id',
'include',
'isNew',
@@ -76,20 +83,33 @@ class Fields extends WireSaveableItems {
'templatePrevious',
'templates_id',
'url',
'_custom',
);
/**
* Field names that are native/permanent to this instance of ProcessWire (configurable at runtime)
*
* Array indexes are the names and values are all boolean true.
*
*/
protected $nativeNamesLocal = array();
/**
* Cache of all tags for all fields, populated to array when asked for the first time
*
* @var array|null
*
*/
protected $tagList = null;
/**
* Construct
*
*/
public function __construct() {
$this->fieldsArray = new FieldsArray();
// convert so that keys are names so that isset() can be used rather than in_array()
if(isset(self::$nativeNamesSystem[0])) self::$nativeNamesSystem = array_flip(self::$nativeNamesSystem);
}
/**
@@ -208,6 +228,8 @@ class Fields extends WireSaveableItems {
}
}
$this->getTags('reset');
return true;
}
@@ -806,8 +828,8 @@ class Fields extends WireSaveableItems {
*
*/
public function isNative($name) {
if(in_array($name, self::$nativeNamesSystem)) return true;
if(in_array($name, $this->nativeNamesLocal)) return true;
if(isset(self::$nativeNamesSystem[$name])) return true;
if(isset($this->nativeNamesLocal[$name])) return true;
return false;
}
@@ -820,7 +842,76 @@ class Fields extends WireSaveableItems {
*
*/
public function setNative($name) {
$this->nativeNamesLocal[] = $name;
$this->nativeNamesLocal[$name] = true;
}
/**
* Get list of all tags used by fields
*
* - By default it returns an array of tag names where both keys and values are the tag names.
* - If you specify true for the `$getFields` argument, it returns an array where the keys are
* tag names and the values are arrays of field names in the tag.
* - If you specify "reset" for the `$getFields` argument it returns a blank array and resets
* internal tags cache.
*
* @param bool|string $getFieldNames Specify true to return associative array where keys are tags and values are field names
* …or specify the string "reset" to force getTags() to reset its cache, forcing it to reload on the next call.
* @return array
* @since 3.0.106
*
*/
public function getTags($getFieldNames = false) {
if($getFieldNames === 'reset') {
$this->tagList = null;
return array();
}
if($this->tagList === null) {
$tagList = array();
foreach($this as $field) {
/** @var Field $field */
$fieldTags = $field->getTags();
foreach($fieldTags as $tag) {
if(!isset($tagList[$tag])) $tagList[$tag] = array();
$tagList[$tag][] = $field->name;
}
}
ksort($tagList);
$this->tagList = $tagList;
}
if($getFieldNames) return $this->tagList;
$tagList = array();
foreach($this->tagList as $tag => $fieldNames) {
$tagList[$tag] = $tag;
}
return $tagList;
}
/**
* Return all fields that have the given $tag
*
* Returns an associative array of `['field_name' => 'field_name']` if `$getFieldNames` argument is true,
* or `['field_name => Field instance]` if not (which is the default).
*
* @param string $tag Tag to find fields for
* @param bool $getFieldNames If true, returns array of field names rather than Field objects (default=false).
* @return array Array of Field objects, or array of field names if requested. Array keys are always field names.
* @since 3.0.106
*
*/
public function findByTag($tag, $getFieldNames = false) {
$tags = $this->getTags(true);
$items = array();
if(!isset($tags[$tag])) return $items;
foreach($tags[$tag] as $fieldName) {
$items[$fieldName] = ($getFieldNames ? $fieldName : $this->get($fieldName));
}
ksort($items);
return $items;
}
/**

View File

@@ -784,16 +784,41 @@ class PageFinder extends Wire {
*/
protected function preProcessSelector(Selector $selector, Selectors $selectors, array $options, $level = 0) {
/** @var Fields $fields */
$fields = $this->wire('fields');
$quote = $selector->quote;
$fields = $selector->fields;
$fieldsArray = $selector->fields;
$hasDoubleDot = false;
$tags = null;
foreach($fieldsArray as $key => $fn) {
foreach($fields as $fn) {
$dot = strpos($fn, '.');
$parts = $dot ? explode('.', $fn) : array($fn);
// determine if it is a double-dot field (a.b.c)
if($dot && strrpos($fn, '.') !== $dot) {
if(strpos($fn, '__owner.') !== false) continue;
$hasDoubleDot = true;
break;
}
// determine if it is referencing any tags that should be coverted to field1|field2|field3
foreach($parts as $partKey => $part) {
if($tags !== null && empty($tags)) continue;
if($fields->get($part)) continue; // maps to Field object
if($fields->isNative($part)) continue; // maps to native property
if($tags === null) $tags = $fields->getTags(true); // determine tags
if(!isset($tags[$part])) continue; // not a tag
$tagFields = $tags[$part];
foreach($tagFields as $k => $fieldName) {
$_parts = $parts;
$_parts[$partKey] = $fieldName;
$tagFields[$k] = implode('.', $_parts);
}
if(count($tagFields)) {
unset($fieldsArray[$key]);
$selector->fields = array_merge($fieldsArray, $tagFields);
}
}
}
@@ -831,11 +856,11 @@ class PageFinder extends Wire {
} else if($hasDoubleDot) {
// has an "a.b.c" type string in the field, convert to a sub-selector
if(count($fields) > 1) {
if(count($fieldsArray) > 1) {
throw new PageFinderSyntaxException("Multi-dot 'a.b.c' type selectors may not be used with OR '|' fields");
}
$fn = reset($fields);
$fn = reset($fieldsArray);
$parts = explode('.', $fn);
$fieldName = array_shift($parts);
$field = $this->isPageField($fieldName);

View File

@@ -5,7 +5,7 @@ $(document).ready(function() {
};
$("#templates_id").change(fieldFilterFormChange);
$("#fieldtype").change(fieldFilterFormChange);
$("#show_system").click(fieldFilterFormChange);
$("#wrap_show_system input").click(fieldFilterFormChange);
var $asmListItemStatus = $("#asmListItemStatus");
@@ -65,7 +65,7 @@ $(document).ready(function() {
// instantiate the WireTabs
var $fieldEdit = $("#ProcessFieldEdit");
if($fieldEdit.size() > 0 && $('li.WireTab').size() > 1) {
if($fieldEdit.length > 0 && $('li.WireTab').length > 1) {
$fieldEdit.find('script').remove();
$fieldEdit.WireTabs({
items: $(".Inputfields li.WireTab"),

View File

@@ -1 +1 @@
$(document).ready(function(){var e=function(){$("#field_filter_form").submit()};$("#templates_id").change(e);$("#fieldtype").change(e);$("#show_system").click(e);var b=$("#asmListItemStatus");var d=$("#columnWidth");function a(){var i=b.attr("data-tpl");if(!i){return}var k=$("#Inputfield_showIf").val();var j=$("#Inputfield_required").is(":checked")?true:false;if(k&&k.length>0){i="<i class='fa fa-question-circle'></i>"+i}if(j){i="<i class='fa fa-asterisk'></i>"+i}var h=parseInt(d.val());if(h==100){h=0}if(h>0){h=h+"%"}else{h=""}i=i.replace("%",h);b.val(i)}$("#Inputfield_showIf").change(a);$("#Inputfield_required").change(a);a();if(d.length>0){var f=$("<div class='InputfieldColumnWidthSlider'></div>");var c=parseInt($("#columnWidth").val());d.val(c+"%");d.after(f);f.slider({range:"min",min:10,max:100,value:parseInt(d.val()),slide:function(i,h){var j=h.value+"%";d.val(j).trigger("change");a()}});d.change(function(){var h=parseInt($(this).val());if(h>100){h=100}if(h<10){h=10}$(this).val(h+"%");f.slider("option","value",h)})}var g=$("#ProcessFieldEdit");if(g.size()>0&&$("li.WireTab").size()>1){g.find("script").remove();g.WireTabs({items:$(".Inputfields li.WireTab"),id:"FieldEditTabs",skipRememberTabIDs:["delete"]})}$("#fieldgroupContextSelect").change(function(){var i=$("#Inputfield_id").val();var j=$(this).val();var h="./edit?id="+i;if(j>0){h+="&fieldgroup_id="+j}window.location=h});$("a.fieldFlag").click(function(){return false});$("#export_data").click(function(){$(this).select()});$(".import_toggle input[type=radio]").change(function(){var h=$(this).parents("p.import_toggle").next("table");var i=$(this).closest(".InputfieldFieldset");if($(this).is(":checked")&&$(this).val()==0){h.hide();i.addClass("ui-priority-secondary")}else{h.show();i.removeClass("ui-priority-secondary")}}).change();$("#wrap_Inputfield_send_templates").find(":input").change(function(){$("#_send_templates_changed").val("changed")});$("#viewRoles_37").click(function(){if($(this).is(":checked")){$("input.viewRoles").attr("checked","checked")}});$("input.viewRoles:not(#viewRoles_37)").click(function(){if($("#viewRoles_37").is(":checked")){return false}return true});$("input.editRoles:not(:disabled)").click(function(){if($(this).is(":checked")){$(this).closest("tr").find("input.viewRoles").attr("checked","checked")}});$(".override-select-all").click(function(){var h=$(this).closest("table").find("input[type=checkbox]");if($(this).hasClass("override-checked")){h.removeAttr("checked");$(this).removeClass("override-checked")}else{h.attr("checked","checked");$(this).addClass("override-checked")}return false})});
$(document).ready(function(){var e=function(){$("#field_filter_form").submit()};$("#templates_id").change(e);$("#fieldtype").change(e);$("#wrap_show_system input").click(e);var b=$("#asmListItemStatus");var d=$("#columnWidth");function a(){var i=b.attr("data-tpl");if(!i){return}var k=$("#Inputfield_showIf").val();var j=$("#Inputfield_required").is(":checked")?true:false;if(k&&k.length>0){i="<i class='fa fa-question-circle'></i>"+i}if(j){i="<i class='fa fa-asterisk'></i>"+i}var h=parseInt(d.val());if(h==100){h=0}if(h>0){h=h+"%"}else{h=""}i=i.replace("%",h);b.val(i)}$("#Inputfield_showIf").change(a);$("#Inputfield_required").change(a);a();if(d.length>0){var f=$("<div class='InputfieldColumnWidthSlider'></div>");var c=parseInt($("#columnWidth").val());d.val(c+"%");d.after(f);f.slider({range:"min",min:10,max:100,value:parseInt(d.val()),slide:function(i,h){var j=h.value+"%";d.val(j).trigger("change");a()}});d.change(function(){var h=parseInt($(this).val());if(h>100){h=100}if(h<10){h=10}$(this).val(h+"%");f.slider("option","value",h)})}var g=$("#ProcessFieldEdit");if(g.length>0&&$("li.WireTab").length>1){g.find("script").remove();g.WireTabs({items:$(".Inputfields li.WireTab"),id:"FieldEditTabs",skipRememberTabIDs:["delete"]})}$("#fieldgroupContextSelect").change(function(){var i=$("#Inputfield_id").val();var j=$(this).val();var h="./edit?id="+i;if(j>0){h+="&fieldgroup_id="+j}window.location=h});$("a.fieldFlag").click(function(){return false});$("#export_data").click(function(){$(this).select()});$(".import_toggle input[type=radio]").change(function(){var h=$(this).parents("p.import_toggle").next("table");var i=$(this).closest(".InputfieldFieldset");if($(this).is(":checked")&&$(this).val()==0){h.hide();i.addClass("ui-priority-secondary")}else{h.show();i.removeClass("ui-priority-secondary")}}).change();$("#wrap_Inputfield_send_templates").find(":input").change(function(){$("#_send_templates_changed").val("changed")});$("#viewRoles_37").click(function(){if($(this).is(":checked")){$("input.viewRoles").attr("checked","checked")}});$("input.viewRoles:not(#viewRoles_37)").click(function(){if($("#viewRoles_37").is(":checked")){return false}return true});$("input.editRoles:not(:disabled)").click(function(){if($(this).is(":checked")){$(this).closest("tr").find("input.viewRoles").attr("checked","checked")}});$(".override-select-all").click(function(){var h=$(this).closest("table").find("input[type=checkbox]");if($(this).hasClass("override-checked")){h.removeAttr("checked");$(this).removeClass("override-checked")}else{h.attr("checked","checked");$(this).addClass("override-checked")}return false})});

View File

@@ -48,7 +48,7 @@ class ProcessField extends Process implements ConfigurableModule {
return array(
'title' => __('Fields', __FILE__),
'summary' => __('Edit individual fields that hold page data', __FILE__),
'version' => 112,
'version' => 113,
'permanent' => true,
'permission' => 'field-admin', // add this permission if you want this Process available for roles other than Superuser
'icon' => 'cube',
@@ -128,19 +128,32 @@ class ProcessField extends Process implements ConfigurableModule {
*
*/
public function init() {
if($this->input->urlSegment1 == 'edit') $this->modules->get("JqueryWireTabs");
$this->moduleInfo = self::getModuleInfo();
$this->headline($this->moduleInfo['title']);
$this->labels = array(
'save' => $this->_('Save'), // Save button label
'import' => $this->_('Import'),
'export' => $this->_('Export'),
'ok' => $this->_('Ok')
);
if($this->input->post->id) $this->id = (int) $this->input->post->id;
else $this->id = $this->input->get->id ? (int) $this->input->get->id : 0;
'ok' => $this->_('Ok'),
'name' => $this->_x('Name', 'list thead'),
'label' => $this->_x('Label', 'list thead'),
'type' => $this->_x('Type', 'list thead'),
'tag' => $this->_('Tag'),
'tags' => $this->_('Tags'),
'manage-tags' => $this->_('Manage Tags'),
'fields' => $this->_('Fields'),
'templates' => $this->_x('Templates', 'list thead quantity'),
'yes' => $this->_x('Yes', 'access'), // General purpose "Yes" label
'no' => $this->_x('No', 'access') // General purpose "No" label
);
$this->id = (int) $this->input->post('id');
if(!$this->id) $this->id = (int) $this->input->get('id');
if($this->id < 1) $this->id = 0;
if($this->id) $this->field = $this->fields->get($this->id);
if(!$this->field) $this->field = new Field();
@@ -197,6 +210,10 @@ class ProcessField extends Process implements ConfigurableModule {
*/
public function ___getListFilterForm() {
/** @var Session $session */
$session = $this->wire('session');
/** @var WireInput $input */
$input = $this->wire('input');
$showAllLabel = $this->_('Show All');
/** @var InputfieldForm $form */
@@ -223,14 +240,20 @@ class ProcessField extends Process implements ConfigurableModule {
if($template->flags & Template::flagSystem) $name .= "*";
$field->addOption($template->id, $name);
}
$this->session->ProcessFieldListTemplatesID = (int) $this->input->get->templates_id;
$field->label = $this->_('Filter by Template');
$inputTemplatesID = $input->get('templates_id');
if($inputTemplatesID !== null) {
$session->setFor($this, 'filterTemplate', (int) $inputTemplatesID);
$inputTemplatesID = null;
}
// $this->session->ProcessFieldListTemplatesID = (int) $this->input->get->templates_id;
$field->label = $this->_('Filter by template');
$field->description = $this->_("When selected, only the fields from a specific template will be shown. Built-in fields are also shown when filtering by template. Asterisk (*) indicates system templates."); // Filter by template description
$value = (int) $this->session->ProcessFieldListTemplatesID;
$field->attr('value', $value);
if($value && $template = $this->templates->get($value)) {
$field->icon = 'cubes';
$filterTemplateID = (int) $session->getFor($this, 'filterTemplate'); // $this->session->ProcessFieldListTemplatesID;
$field->attr('value', $filterTemplateID);
if($filterTemplateID && $template = $this->templates->get($filterTemplateID)) {
$form->description = sprintf($this->_('Showing fields from template: %s'), $template);
$this->wire('processHeadline', $this->_('Fields by Template')); // Page headline when filtering by template
$this->headline($this->_('Fields by Template')); // Page headline when filtering by template
$fieldset->collapsed = Inputfield::collapsedNo;
} else {
$template = null;
@@ -247,12 +270,17 @@ class ProcessField extends Process implements ConfigurableModule {
foreach($this->fieldtypes as $fieldtype) {
$field->addOption($fieldtype->name, $fieldtype->longName);
}
$this->session->set('ProcessFieldListFieldtype', $this->sanitizer->name($this->input->get('fieldtype')));
$field->label = $this->_('Filter by Field Type');
$inputFieldtype = $input->get('fieldtype');
if($inputFieldtype !== null) {
$session->setFor($this, 'filterFieldtype', $this->wire('sanitizer')->name($inputFieldtype));
$inputFieldtype = null;
}
$field->label = $this->_('Filter by type');
$field->description = $this->_('When specified, only fields of the selected type will be shown. Built-in fields are also shown when filtering by field type.'); // Filter by fieldtype description
$value = $this->session->get('ProcessFieldListFieldtype');
$field->attr('value', $value);
if($value && $fieldtype = $this->fieldtypes->get($value)) {
$field->icon = 'plug';
$filterFieldtype = $session->getFor($this, 'filterFieldtype');
$field->attr('value', $filterFieldtype);
if($filterFieldtype && $fieldtype = $this->fieldtypes->get($filterFieldtype)) {
$form->description = sprintf($this->_('Showing fields of type: %s'), $fieldtype->longName);
$fieldset->collapsed = Inputfield::collapsedNo;
} else {
@@ -262,16 +290,24 @@ class ProcessField extends Process implements ConfigurableModule {
// ----------------------------------------------------------------
if(is_null($template) && !$this->session->ProcessFieldListFieldtype) {
/** @var InputfieldCheckbox $field */
$field = $this->modules->get("InputfieldCheckbox");
if(is_null($template) && !$session->getFor($this, 'filterFieldtype')) {
/** @var InputfieldRadios $field */
$field = $this->modules->get("InputfieldRadios");
$field->attr('id+name', 'show_system');
$field->label = $this->_('Show built-in fields?');
$field->label = $this->_('Show system fields?');
$field->description = $this->_("When checked, built-in fields will also be shown. These include system fields and permanent fields. System fields are required by the system and cannot be deleted or have their name changed. Permanent fields are those that cannot be removed from a template. These fields are used internally by ProcessWire."); // Show built-in fields description
$field->value = 1;
$field->addOption(1, $this->labels['yes']);
$field->addOption(0, $this->labels['no']);
$field->optionColumns = 1;
$field->icon = 'gear';
$field->collapsed = Inputfield::collapsedYes;
$this->session->ProcessFieldListShowSystem = (int) $this->input->get->show_system;
if($this->session->ProcessFieldListShowSystem) {
$inputShowSystem = $input->get('show_system');
if($inputShowSystem !== null) {
$session->setFor($this, 'filterShowSystem', (int) $inputShowSystem);
$inputShowSystem = null;
}
$field->value = (int) $session->getFor($this, 'filterShowSystem');
if($session->getFor($this, 'filterShowSystem')) {
$field->attr('checked', 'checked');
$field->collapsed = Inputfield::collapsedNo;
$fieldset->collapsed = Inputfield::collapsedNo;
@@ -279,9 +315,11 @@ class ProcessField extends Process implements ConfigurableModule {
}
$fieldset->add($field);
} else {
$this->session->ProcessFieldListShowSystem = 1;
// $session->setFor($this, 'filterShowSystem', 1);
}
return $form;
}
@@ -294,38 +332,39 @@ class ProcessField extends Process implements ConfigurableModule {
public function ___execute() {
if($this->wire('config')->ajax) return $this->renderListJSON();
/** @var Session $session */
$session = $this->wire('session');
$out = $this->getListFilterForm()->render() . "\n<div id='ProcessFieldList'>\n";
$fieldsByTag = array();
$untaggedLabel = $this->_('Untagged');
$hasFilters = $this->session->ProcessFieldListTemplatesID || $this->session->ProcessFieldListFieldtype;
$collapsedTags = array();
$hasFilters = $session->getFor($this, 'filterTemplate') || $session->getFor($this, 'filterFieldtype');
$showSystem = $session->getFor($this, 'filterShowSystem');
$collapsedTags = $this->wire('modules')->getConfig($this, 'collapsedTags');
$caseTags = array(); // indexed by lowercase version of tag
if(!is_array($collapsedTags)) $collapsedTags = array();
$systemTag = $this->_x('System', 'tag'); // Tag applied to the group of built-in/system fields
if(!$hasFilters) foreach($this->fields as $field) {
if($this->session->ProcessFieldListShowSystem) {
if($field->flags & Field::flagSystem || $field->flags & Field::flagPermanent)
$field->tags .= " " . $this->_x('Built-In', 'tag'); // Tag applied to the group of built-in/system fields
$tags = $field->getTags();
if($showSystem || $hasFilters) {
if($field->flags & Field::flagSystem || $field->flags & Field::flagPermanent) {
$tags['system'] = $systemTag;
$caseTags[$systemTag] = $systemTag;
}
}
if(empty($field->tags)) {
if(empty($tags)) {
$tag = strtolower($untaggedLabel);
if(!isset($fieldsByTag[$tag])) $fieldsByTag[$tag] = array();
$fieldsByTag[$tag][$field->name] = $field;
$caseTags[$tag] = $untaggedLabel;
continue;
}
$tags = explode(' ', trim($field->tags));
foreach($tags as $tag) {
if(empty($tag)) continue;
$caseTag = ltrim($tag, '-');
$tag = strtolower($tag);
if(substr($tag, 0, 1) == '-') {
$tag = ltrim($tag, '-');
$collapsedTags[] = $tag;
}
foreach($tags as $name => $tag) {
if(!isset($fieldsByTag[$tag])) $fieldsByTag[$tag] = array();
$fieldsByTag[$tag][$field->name] = $field;
if(!isset($caseTags[$tag])) $caseTags[$tag] = $caseTag;
if(!isset($caseTags[$tag])) {
$caseTags[$tag] = sprintf($this->_('Tag: %s'), trim($tag, '_-'));
}
}
}
@@ -339,6 +378,7 @@ class ProcessField extends Process implements ConfigurableModule {
$f->entityEncodeLabel = false;
$f->label = $caseTags[$tag];
$f->icon = 'tags';
if($tag == $systemTag) $f->icon = 'gear';
$f->value = $this->getListTable($fields)->render();
if(in_array($tag, $collapsedTags)) $f->collapsed = Inputfield::collapsedYes;
$form->add($f);
@@ -359,6 +399,14 @@ class ProcessField extends Process implements ConfigurableModule {
$button->showInHeader();
$out .= $button->render();
$button = $this->modules->get('InputfieldButton');
$button->id = 'tags_button';
$button->href = './tags/';
$button->icon = 'tags';
$button->value = $this->labels['manage-tags'];
//$button->setSecondary();
$out .= $button->render();
$button = $this->modules->get('InputfieldButton');
$button->id = 'import_button';
$button->href = "./import/";
@@ -376,14 +424,173 @@ class ProcessField extends Process implements ConfigurableModule {
$out .= $button->render();
if($this->input->nosave) {
$this->session->remove('ProcessFieldListTemplatesID');
$this->session->remove('ProcessFieldListFieldtype');
$this->session->remove('ProcessFieldListShowSystem');
$session->removeFor($this, 'filterTemplate');
$session->removeFor($this, 'filterFieldtype');
$session->removeFor($this, 'filterShowSystem');
}
return $out;
}
/**
* Handle the “Manage Tags” actions
*
* @return string
*
*/
public function executeTags() {
/** @var WireInput $input */
$input = $this->wire('input');
/** @var Modules $modules */
$modules = $this->wire('modules');
/** @var Fields $fields */
$fields = $this->wire('fields');
/** @var Sanitizer $sanitizer*/
$sanitizer = $this->wire('sanitizer');
/** @var InputfieldForm $form */
$form = $modules->get('InputfieldForm');
$out = '';
$labels = $this->labels;
$headline = $labels['tags'];
$this->headline($headline);
$this->breadcrumb('../', $labels['fields']);
$tags = $fields->getTags();
$editTag = $input->get->name('edit_tag');
$saveTag = $input->post->name('save_tag');
$collapsedTags = $modules->getConfig($this, 'collapsedTags');
if(!is_array($collapsedTags)) $collapsedTags = array();
if($editTag) {
// edit which fields are assigned to tag
$this->breadcrumb('./', $headline);
$this->headline("$labels[tag] - " . (isset($tags[$editTag]) ? $tags[$editTag] : $editTag));
/** @var InputfieldName $f */
$f = $modules->get('InputfieldName');
$f->attr('name', 'rename_tag');
$f->attr('value', isset($tags[$editTag]) ? $tags[$editTag] : $editTag);
$f->collapsed = Inputfield::collapsedYes;
$f->addClass('InputfieldIsSecondary', 'wrapClass');
$f->icon = 'tag';
$form->add($f);
/** @var InputfieldCheckboxes $f */
$f = $modules->get('InputfieldCheckboxes');
$f->attr('name', 'tag_fields');
$f->label = $this->_('Select all fields that should have this tag');
$f->table = true;
$f->icon = 'cube';
$f->thead = "$labels[name]|$labels[label]|$labels[type]|$labels[tags]";
$value = array();
foreach($fields as $field) {
/** @var Field $field */
if($field->flags & Field::flagSystem && !in_array($field->name, array('title', 'email'))) continue;
$f->addOption($field->name, "**$field->name**|$field->label|{$field->type->shortName}|" . $field->getTags(', '));
if($field->hasTag($editTag)) $value[] = $field->name;
}
$f->attr('value', $value);
$form->add($f);
/** @var InputfieldCheckbox */
$f = $modules->get('InputfieldCheckbox');
$f->attr('name', 'tag_collapsed');
$f->label = $this->_('Display as collapsed in fields list?');
if(in_array($editTag, $collapsedTags)) $f->attr('checked', 'checked');
$form->add($f);
/** @var InputfieldHidden $f */
$f = $modules->get('InputfieldHidden');
$f->attr('name', 'save_tag');
$f->attr('value', $editTag);
$form->appendMarkup = "<p class='detail'>" . wireIconMarkup('trash-o') . ' ' .
$this->_('To delete this tag, remove all fields from it.') . "</p>";
$form->add($f);
} else if($saveTag) {
// save tag
$tagFields = $sanitizer->names($input->post('tag_fields'));
$renameTag = $input->post->fieldName('rename_tag');
$isCollapsed = (int) $input->post('tag_collapsed');
$removeTag = '';
if($renameTag && $renameTag != $saveTag) {
$removeTag = $saveTag;
$saveTag = $renameTag;
}
foreach($fields as $field) {
/** @var Field $field */
if($removeTag && $field->hasTag($removeTag)) {
$field->removeTag($removeTag);
}
if(in_array($field->name, $tagFields)) {
// field should have the given tag
if($field->hasTag($saveTag)) continue;
$field->addTag($saveTag);
$this->message(sprintf($this->_('Added tag “%1$s” to field: %2$s'), $saveTag, $field->name));
} else if($field->hasTag($saveTag)) {
// field should not have the given tag
$field->removeTag($saveTag);
$this->message(sprintf($this->_('Removed tag “%1$s” from field: %2$s'), $saveTag, $field->name));
}
if($field->isChanged('tags')) $field->save();
}
$_collapsedTags = $collapsedTags;
if($isCollapsed) {
if(!in_array($saveTag, $collapsedTags)) $collapsedTags[] = $saveTag;
} else {
$key = array_search($saveTag, $collapsedTags);
if($key !== false) unset($collapsedTags[$key]);
}
if($collapsedTags !== $_collapsedTags) {
$modules->saveConfig($this, 'collapsedTags', $collapsedTags);
}
$this->wire('session')->redirect('./');
return '';
} else {
// list defined tags
$out .= "<p class='description'>" .
$this->_('Tags enable you to create collections of fields for listing or searching.') . "</p>";
/** @var MarkupAdminDataTable $table */
$table = $modules->get('MarkupAdminDataTable');
$table->setSortable(false);
$table->setEncodeEntities(false);
$table->headerRow(array($labels['name'], $labels['fields']));
foreach($tags as $key => $tag) {
$tagFields = $fields->findByTag($tag, true);
$table->row(array(
$tag => "./?edit_tag=$tag",
implode(', ', $tagFields)
));
if($fields->get($key)) {
$this->warning(sprintf($this->_('Warning: tag “%s” has the same name as a Field.'), $tag));
}
}
if(count($tags)) $out .= $table->render();
$form->attr('method', 'get');
/** @var InputfieldName $f */
$f = $modules->get('InputfieldName');
$f->attr('name', 'edit_tag');
$f->label = $this->_('Add new tag');
$f->icon = 'tag';
$f->addClass('InputfieldIsSecondary', 'wrapClass');
$form->add($f);
}
$f = $modules->get('InputfieldSubmit');
$form->add($f);
$out .= $form->render();
return $out;
}
/**
* Get the table that lists fields
*
@@ -395,13 +602,9 @@ class ProcessField extends Process implements ConfigurableModule {
/** @var MarkupAdminDataTable $table */
$table = $this->modules->get("MarkupAdminDataTable");
$labels = $this->labels;
$headerRow = array(
$this->_x('Name', 'list thead'),
$this->_x('Label', 'list thead'),
$this->_x('Type', 'list thead'),
$this->_x('Templates', 'list thead quantity')
);
$headerRow = array($labels['name'], $labels['label'], $labels['type'], $labels['templates']);
$table->headerRow($headerRow);
$table->setEncodeEntities(false);
@@ -464,11 +667,11 @@ class ProcessField extends Process implements ConfigurableModule {
$flags = array();
$builtIn = false;
$templatesID = $this->session->ProcessFieldListTemplatesID;
$templatesID = (int) $this->session->getFor($this, 'filterTemplate');
if($templatesID && $template = $this->templates->get($templatesID)) {
if(!$template->fieldgroup->has($field)) return array();
}
if($fieldtype = $this->session->ProcessFieldListFieldtype) {
if($fieldtype = $this->session->getFor($this, 'filterFieldtype')) {
if($field->type != $fieldtype) return array();
}
@@ -489,8 +692,8 @@ class ProcessField extends Process implements ConfigurableModule {
if($field->showIf) $flags[] = 'Dependency';
if($field->required) $flags[] = 'Required';
if($builtIn && !$templatesID && $field->name != 'title') {
if(!$this->session->ProcessFieldListShowSystem) return array();
if($builtIn && !$templatesID && $field->name != 'title' && $field->name != 'email') {
if(!$this->session->getFor($this, 'filterShowSystem')) return array();
}
foreach($field->getFieldgroups() as $fieldgroup) {
@@ -740,7 +943,7 @@ class ProcessField extends Process implements ConfigurableModule {
$out =
"<div id='fieldgroupContext'>" .
"<select id='fieldgroupContextSelect' name='fieldgroup_id'>" .
"<option value=''>$contextLabel</option>"; // . $this->_x('None (default)', 'context select') . "</option>";
"<option value=''>$contextLabel</option>";
foreach($fieldgroups->sort('name') as $fieldgroup) {
$selected = $this->fieldgroup && $this->fieldgroup->id == $fieldgroup->id ? " selected='selected'" : '';
@@ -979,6 +1182,8 @@ class ProcessField extends Process implements ConfigurableModule {
// allow the following configuration only for advanced mode
if(!$this->wire('config')->advanced) return;
$labels = $this->labels;
/** @var InputfieldCheckboxes $field */
$field = $this->modules->get('InputfieldCheckboxes');
$field->attr('name', 'allowContexts');
@@ -988,10 +1193,7 @@ class ProcessField extends Process implements ConfigurableModule {
$this->_('Checked settings will appear as configuration options when editing this field within the context of a particular template.') . ' ' .
$this->_('**Warning:** doing this for settings beyond those specified by the module author may not always work, or may cause problems.');
$field->table = true;
$field->thead =
$this->_('Label') . '|' .
$this->_('Name') . '|' .
$this->_('Type');
$field->thead = "$labels[label]|$labels[name]|$labels[type]";
$tabNames = array(
'fieldtypeConfig' => $this->_('Details:'),
@@ -1195,7 +1397,7 @@ class ProcessField extends Process implements ConfigurableModule {
/** @var InputfieldSelect $field */
$field = $this->modules->get('InputfieldSelect');
$field->label = $this->_x('Type', 'select label'); // Label for field type select
$field->label = $this->labels['type'];
$field->attr('name', 'type');
$field->required = true;
if($this->field->type) $field->attr('value', $this->field->type->name);
@@ -1245,7 +1447,7 @@ class ProcessField extends Process implements ConfigurableModule {
/** @var InputfieldText $field */
$field = $this->modules->get('InputfieldText');
$field->attr('id+name', 'field_label');
$field->label = $this->_x('Label', 'text input'); // Label for 'field label' text input
$field->label = $this->labels['label'];
$field->attr('value', $this->field->label);
$field->class .= ' asmListItemDesc'; // for modal to populate parent asmSelect desc field with this value (recognized by jquery.asmselect.js)
$field->description = $this->_("This is the label that appears above the entry field. If left blank, the name will be used instead."); // Description for 'field label'
@@ -1324,8 +1526,8 @@ class ProcessField extends Process implements ConfigurableModule {
$field->table = true;
$field->icon = 'check-square';
$field->thead =
$this->_x('Name', 'usage-table-th') . '|' .
$this->_x('Label', 'usage-table-th') . '|' .
$this->labels['name'] . '|' .
$this->labels['label'] . '|' .
$this->_x('Populated pages', 'usage-table-th') .
($multi ? '|' . $this->_x('Rows of data', 'usage-table-th') : '');
$numOmitted = 0;
@@ -1410,8 +1612,8 @@ class ProcessField extends Process implements ConfigurableModule {
$f->label = $this->_('Do you want to manage access control for this field?');
$f->description = $this->_('When enabled, you can limit view and edit access to this field by user role.');
$f->icon = 'key';
$f->addOption(1, $this->_x('Yes', 'access'));
$f->addOption(0, $this->_x('No', 'access'));
$f->addOption(1, $this->labels['yes']);
$f->addOption(0, $this->labels['no']);
$f->attr('value', (int) $this->field->useRoles);
$f->optionColumns = 1;
$tab->add($f);
@@ -1512,15 +1714,17 @@ class ProcessField extends Process implements ConfigurableModule {
/** @var InputfieldText $field */
$field = $this->modules->get("InputfieldText");
$field->attr('name', 'tags');
$field->attr('value', $this->field->tags);
$field->attr('value', $this->field->getTags(true));
$field->icon = 'tags';
$field->label = $this->_('Tags');
$field->label = $this->labels['tags'];
$field->description = $this->_('If you want to visually group this field with others in the fields list, enter a one-word tag. Enter the same tag on other fields you want to group with. To specify multiple tags, separate each with a space. Use of tags may be worthwhile if your site has a large number of fields.'); // Description for field tags
$field->notes =
$this->_('Each tag must be one word (hyphenation is okay).') . " " .
$this->_('To make a tag collapsed in the fields list, prepend a hyphen to it, like this: -hello');
$field->notes = $this->_('Each tag must be one word (underscores are okay).') . ' ' .
'[' . $this->labels['manage-tags'] . '](./tags/)';
$field->collapsed = Inputfield::collapsedBlank;
$form->prepend($field);
$this->wire('modules')->get('JqueryUI')->use('selectize');
return $form;
}
@@ -1953,8 +2157,10 @@ class ProcessField extends Process implements ConfigurableModule {
$this->buildEditForm();
$this->wire('processHeadline', sprintf($this->_('Change type for field: %s'), $this->field->name)); // Page headline when changing type
$this->wire('breadcrumbs')->add(new Breadcrumb('./', 'Fields'))->add(new Breadcrumb("./edit?id={$this->field->id}", $this->field->name));
$this->headline(sprintf($this->_('Change type for field: %s'), $this->field->name)); // Page headline when changing type
$this->wire('breadcrumbs')
->add(new Breadcrumb('./', $this->labels['fields']))
->add(new Breadcrumb("./edit?id={$this->field->id}", $this->field->name));
if(!$this->input->get->type) $this->session->redirect('./');
$newType = $this->wire('sanitizer')->name($this->input->get->type);
@@ -2040,8 +2246,8 @@ class ProcessField extends Process implements ConfigurableModule {
*/
public function ___executeImport() {
$this->wire('processHeadline', $this->labels['import']);
$this->wire('breadcrumbs')->add(new Breadcrumb('../', $this->moduleInfo['title']));
$this->headline($this->labels['import']);
$this->breadcrumb('../', $this->moduleInfo['title']);
require(dirname(__FILE__) . '/ProcessFieldExportImport.php');
@@ -2061,8 +2267,8 @@ class ProcessField extends Process implements ConfigurableModule {
*/
public function ___executeExport() {
$this->wire('processHeadline', $this->labels['export']);
$this->wire('breadcrumbs')->add(new Breadcrumb('../', $this->moduleInfo['title']));
$this->headline($this->labels['export']);
$this->breadcrumb('../', $this->moduleInfo['title']);
require(dirname(__FILE__) . '/ProcessFieldExportImport.php');
/** @var ProcessFieldExportImport $o */
@@ -2144,13 +2350,13 @@ class ProcessField extends Process implements ConfigurableModule {
}
$f->addOption(0, $this->_('Do not add'));
foreach($template->fieldgroup as $field) { // overwrite of $field is OK
if(strpos((string) $field->type, 'FieldtypeFieldset') === 0) continue;
$f->addOption($field->name, array(
"-$field->id" => sprintf($beforeLabel, $field->name),
"$field->id" => sprintf($afterLabel, $field->name)
foreach($template->fieldgroup as $_field) { // overwrite of $field is OK
if(strpos((string) $_field->type, 'FieldtypeFieldset') === 0) continue;
$f->addOption($_field->name, array(
"-$_field->id" => sprintf($beforeLabel, $_field->name),
"$_field->id" => sprintf($afterLabel, $_field->name)
));
$value = "$field->id";
$value = "$_field->id";
}
$f->attr('value', $value);
@@ -2486,5 +2692,24 @@ class ProcessField extends Process implements ConfigurableModule {
return true;
}
/**
* Upgrade module from one version to another
*
* @param int|string $fromVersion
* @param int|string $toVersion
*
*/
public function ___upgrade($fromVersion, $toVersion) {
$collapsedTags = $this->wire('modules')->getConfig($this, 'collapsedTags');
if(!is_array($collapsedTags)) {
$collapsedTags = array();
foreach($this->wire('fields')->getTags() as $tag) {
$c = substr($tag, 0, 1);
if($c === '-' || $c === '_') $collapsedTags[] = $tag;
}
$this->wire('modules')->saveConfig($this, 'collapsedTags', $collapsedTags);
}
}
}

View File

@@ -267,9 +267,10 @@ class ProcessTemplate extends Process {
/** @var InputfieldSelect $field */
$field = $this->modules->get("InputfieldSelect");
$field->attr('id+name', 'filter_field');
$field->label = $this->_("Filter by Field");
$field->label = $this->_('Filter by field');
$field->description = $this->_("Select a field and only templates using that field will be shown."); // Filter by Field, description
$field->addOption('', $this->_x('Show All', 'filter-select'));
$field->icon = 'cube';
foreach($this->fields as $f) {
$name = $f->name;
@@ -299,6 +300,8 @@ class ProcessTemplate extends Process {
$field->attr('name', 'system');
$field->addOption(1, $this->labels['Yes']);
$field->addOption(0, $this->labels['No']);
$field->optionColumns = 1;
$field->icon = 'gear';
if($input->get('system') !== null) {
$filterSystem = (int) $input->get('system');
$this->session->set('ProcessTemplateFilterSystem', $filterSystem);