From 39f9cf42f240847ff4a7ff4b4d5f849376e1a5b5 Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Fri, 21 Jan 2022 12:21:05 -0500 Subject: [PATCH] Add the following new hooks to $templates, $fields and $fieldgroups: renameReady($item, $oldName, $newName); renamed($item, $oldName, $newName); Added the following new hooks to $fieldgroups: fieldAdded($fieldgroup, $field); fieldRemoved($fieldgroup, $field); Make template selectable for Page fields in InputfieldSelector, plus other minor updates. --- wire/core/Fieldgroups.php | 113 +++++++++++++----- wire/core/WireSaveableItems.php | 40 ++++++- wire/core/WireSaveableItemsLookup.php | 36 +++--- wire/modules/Fieldtype/FieldtypePage.module | 39 ++++-- wire/modules/Jquery/JqueryUI/modal.js | 2 +- wire/modules/Jquery/JqueryUI/modal.min.js | 2 +- .../ProcessTemplate/ProcessTemplate.module | 2 +- 7 files changed, 176 insertions(+), 58 deletions(-) diff --git a/wire/core/Fieldgroups.php b/wire/core/Fieldgroups.php index 14e374ee..f1133640 100644 --- a/wire/core/Fieldgroups.php +++ b/wire/core/Fieldgroups.php @@ -7,12 +7,16 @@ * #pw-body For full details on all methods available in a Fieldgroup, be sure to also see the `WireArray` class. * #pw-var $fieldgroups * - * ProcessWire 3.x, Copyright 2016 by Ryan Cramer + * ProcessWire 3.x, Copyright 2022 by Ryan Cramer * https://processwire.com * * @method int saveContext(Fieldgroup $fieldgroup) * @method array getExportData(Fieldgroup $fieldgroup) * @method array setImportData(Fieldgroup $fieldgroup, array $data) + * + * @method void fieldRemoved(Fieldgroup $fieldgroup, Field $field) + * @method void fieldAdded(Fieldgroup $fieldgroup, Field $field) + * @method void sorted(Fieldgroup $fieldgroup, array $oldOrder, array $newOrder) * * */ @@ -25,8 +29,12 @@ class Fieldgroups extends WireSaveableItemsLookup { * @var FieldgroupsArray * */ - protected $fieldgroupsArray; - + protected $fieldgroupsArray; + + /** + * Init + * + */ public function init() { $this->fieldgroupsArray = $this->wire(new FieldgroupsArray()); $this->load($this->fieldgroupsArray); @@ -142,54 +150,80 @@ class Fieldgroups extends WireSaveableItemsLookup { */ public function ___save(Saveable $item) { - $database = $this->wire('database'); + $database = $this->wire()->database; + + /** @var Fieldgroup $fieldgroup */ + $fieldgroup = $item; + $datas = array(); + $fieldsAdded = array(); + $fieldsRemoved = array(); - /** @var Fieldgroup $item */ + if($fieldgroup->id && $fieldgroup->removedFields) { - if($item->id && $item->removedFields) { - - foreach($this->wire('templates') as $template) { - if($template->fieldgroup->id !== $item->id) continue; - foreach($item->removedFields as $field) { + foreach($this->wire()->templates as $template) { + if($template->fieldgroup->id !== $fieldgroup->id) continue; + foreach($fieldgroup->removedFields as $field) { // make sure the field is valid to delete from this template - $error = $this->isFieldNotRemoveable($field, $item, $template); + $error = $this->isFieldNotRemoveable($field, $fieldgroup, $template); if($error !== false) throw new WireException("$error Save of fieldgroup changes aborted."); if($field->type) $field->type->deleteTemplateField($template, $field); - $item->finishRemove($field); + $fieldgroup->finishRemove($field); + $fieldsRemoved[] = $field; } } - $item->resetRemovedFields(); + $fieldgroup->resetRemovedFields(); } - $contextData = array(); - if($item->id) { - // save context data - $query = $database->prepare("SELECT fields_id, data FROM fieldgroups_fields WHERE fieldgroups_id=:item_id"); - $query->bindValue(":item_id", (int) $item->id, \PDO::PARAM_INT); + if($fieldgroup->id) { + // load context data to populate back after fieldgroup save + $sql = 'SELECT fields_id, data FROM fieldgroups_fields WHERE fieldgroups_id=:fieldgroups_id'; + $query = $database->prepare($sql); + $query->bindValue(':fieldgroups_id', (int) $fieldgroup->id, \PDO::PARAM_INT); $query->execute(); /** @noinspection PhpAssignmentInConditionInspection */ while($row = $query->fetch(\PDO::FETCH_ASSOC)) { - $contextData[$row['fields_id']] = $row['data']; + $fields_id = (int) $row['fields_id']; + $datas[$fields_id] = $row['data']; } $query->closeCursor(); } + + $result = parent::___save($fieldgroup); + + // identify any fields added + foreach($fieldgroup as $field) { + if(!array_key_exists($field->id, $datas)) { + $fieldsAdded[] = $field; + } + } - $result = parent::___save($item); - - if(count($contextData)) { + if(count($datas)) { // restore context data - foreach($contextData as $fields_id => $data) { - $fieldgroups_id = (int) $item->id; - $fields_id = (int) $fields_id; - $query = $database->prepare("UPDATE fieldgroups_fields SET data=:data WHERE fieldgroups_id=:fieldgroups_id AND fields_id=:fields_id"); // QA - $query->bindValue(":data", $data, \PDO::PARAM_STR); + $fieldgroups_id = (int) $fieldgroup->id; + foreach($datas as $fields_id => $data) { + $sql = "UPDATE fieldgroups_fields SET data=:data WHERE fieldgroups_id=:fieldgroups_id AND fields_id=:fields_id"; + $query = $database->prepare($sql); + if($data === null) { + $query->bindValue(":data", null, \PDO::PARAM_NULL); + } else { + $query->bindValue(":data", $data, \PDO::PARAM_STR); + } $query->bindValue(":fieldgroups_id", $fieldgroups_id, \PDO::PARAM_INT); $query->bindValue(":fields_id", $fields_id, \PDO::PARAM_INT); $query->execute(); } } + // trigger any fields added + foreach($fieldsAdded as $field) { + $this->fieldAdded($fieldgroup, $field); + } + // trigger any fieldsl removed + foreach($fieldsRemoved as $field) { + $this->fieldRemoved($fieldgroup, $field); + } + return $result; } @@ -270,7 +304,7 @@ class Fieldgroups extends WireSaveableItemsLookup { foreach($contexts as $fieldID => $context) { $field = $fieldgroup->getFieldContext((int) $fieldID); if(!$field) continue; - if($this->wire('fields')->saveFieldgroupContext($field, $fieldgroup)) $numSaved++; + if($this->wire()->fields->saveFieldgroupContext($field, $fieldgroup)) $numSaved++; } return $numSaved; } @@ -486,5 +520,28 @@ class Fieldgroups extends WireSaveableItemsLookup { return false; } + /** + * Hook called when field has been added to fieldgroup + * + * #pw-hooker + * + * @param Fieldgroup $fieldgroup + * @param Field $field + * @since 3.0.193 + * + */ + public function ___fieldAdded(Fieldgroup $fieldgroup, Field $field) { } + + /** + * Hook called when field has been removed from fieldgroup + * + * #pw-hooker + * + * @param Fieldgroup $fieldgroup + * @param Field $field + * @since 3.0.193 + * + */ + public function ___fieldRemoved(Fieldgroup $fieldgroup, Field $field) { } } diff --git a/wire/core/WireSaveableItems.php b/wire/core/WireSaveableItems.php index 15d0cd4f..76d709f0 100644 --- a/wire/core/WireSaveableItems.php +++ b/wire/core/WireSaveableItems.php @@ -6,7 +6,7 @@ * Wire Data Access Object, provides reusable capability for loading, saving, creating, deleting, * and finding items of descending class-defined types. * - * ProcessWire 3.x, Copyright 2016 by Ryan Cramer + * ProcessWire 3.x, Copyright 2022 by Ryan Cramer * https://processwire.com * * @method WireArray load(WireArray $items, $selectors = null) @@ -20,6 +20,8 @@ * @method void added(Saveable $item) #pw-hooker * @method void deleted(Saveable $item) #pw-hooker * @method void cloned(Saveable $item, Saveable $copy) #pw-hooker + * @method void renameReady(Saveable $item, $oldName, $newName) + * @method void renamed(Saveable $item, $oldName, $newName) * * */ @@ -256,6 +258,17 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate { $this->saveReady($item); $data = $item->getTableData(); $binds = array(); + $namePrevious = false; + + if($id && $item->isChanged('name')) { + $query = $database->prepare("SELECT name FROM `$table` WHERE id=:id"); + $query->bindValue(':id', $id, \PDO::PARAM_INT); + $query->execute(); + $oldName = $query->fetchColumn(); + $query->closeCursor(); + if($oldName != $item->name) $namePrevious = $oldName; + if($namePrevious) $this->renameReady($item, $namePrevious, $item->name); + } foreach($data as $key => $value) { if(!$this->saveItemKey($key)) continue; @@ -292,6 +305,7 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate { } if($result) { + if($namePrevious) $this->renamed($item, $namePrevious, $item->name); $this->saved($item); $this->resetTrackChanges(); } else { @@ -475,6 +489,16 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate { */ public function ___cloneReady(Saveable $item, Saveable $copy) { } + /** + * Hook that runs right before item is to be renamed. + * + * @param Saveable $item + * @param string $oldName + * @param string $newName + * + */ + public function ___renameReady(Saveable $item, $oldName, $newName) { } + /** * Hook that runs right after an item has been saved. * @@ -517,8 +541,6 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate { /** * Hook that runs right after an item has been cloned. * - * Unlike after(delete), it has already been confirmed that the item was indeed deleted. - * * @param Saveable $item * @param Saveable $copy * @@ -526,6 +548,18 @@ abstract class WireSaveableItems extends Wire implements \IteratorAggregate { public function ___cloned(Saveable $item, Saveable $copy) { $this->log("Cloned '$item->name' to '$copy->name'", $item); } + + /** + * Hook that runs right after an item has been renamed. + * + * @param Saveable $item + * @param string $oldName + * @param string $newName + * + */ + public function ___renamed(Saveable $item, $oldName, $newName) { + $this->log("Renamed $oldName to $newName", $item); + } /** * Enables use of $apivar('name') or wire()->apivar('name') diff --git a/wire/core/WireSaveableItemsLookup.php b/wire/core/WireSaveableItemsLookup.php index 763775a5..7c75486f 100644 --- a/wire/core/WireSaveableItemsLookup.php +++ b/wire/core/WireSaveableItemsLookup.php @@ -5,7 +5,7 @@ * * Provides same functionality as WireSaveableItems except that this class includes joining/modification of a related lookup table * - * ProcessWire 3.x, Copyright 2016 by Ryan Cramer + * ProcessWire 3.x, Copyright 2022 by Ryan Cramer * https://processwire.com * */ @@ -40,12 +40,13 @@ abstract class WireSaveableItemsLookup extends WireSaveableItems { */ protected function getLoadQuery($selectors = null) { $query = parent::getLoadQuery($selectors); - $database = $this->wire('database'); + $database = $this->wire()->database; $table = $database->escapeTable($this->getTable()); $lookupTable = $database->escapeTable($this->getLookupTable()); $lookupField = $database->escapeCol($this->getLookupField()); $query->select("$lookupTable.$lookupField"); // QA - $query->leftjoin("$lookupTable ON $lookupTable.{$table}_id=$table.id ")->orderby("sort"); // QA + $query->leftjoin("$lookupTable ON $lookupTable.{$table}_id=$table.id ")->orderby("sort"); + // $query->leftjoin("$lookupTable ON $lookupTable.{$table}_id=$table.id ")->orderby("$table.id, $lookupTable.sort"); return $query; } @@ -60,9 +61,9 @@ abstract class WireSaveableItemsLookup extends WireSaveableItems { * */ protected function ___load(WireArray $items, $selectors = null) { - + + $database = $this->wire()->database; $query = $this->getLoadQuery($selectors); - $database = $this->wire('database'); $sql = $query->getQuery(); $stmt = $database->prepare($sql); $stmt->execute(); @@ -70,6 +71,7 @@ abstract class WireSaveableItemsLookup extends WireSaveableItems { while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + /** @var HasLookupItems $item */ $item = $this->makeBlankItem(); $lookupValue = $row[$lookupField]; unset($row[$lookupField]); @@ -93,6 +95,7 @@ abstract class WireSaveableItemsLookup extends WireSaveableItems { $stmt->closeCursor(); $items->setTrackChanges(true); + return $items; } @@ -125,7 +128,7 @@ abstract class WireSaveableItemsLookup extends WireSaveableItems { throw new WireException("$class::save() requires an item that implements HasLookupItems interface"); } - $database = $this->wire('database'); + $database = $this->wire()->database; $lookupTable = $database->escapeTable($this->getLookupTable()); $lookupField = $database->escapeCol($this->getLookupField()); $table = $database->escapeTable($this->getTable()); @@ -141,15 +144,18 @@ abstract class WireSaveableItemsLookup extends WireSaveableItems { $item_id = (int) $item->id; // reload, in case it was 0 before $sort = 0; - if($item_id) foreach($item->getLookupItems() as $key => $value) { - $value_id = (int) $value->id; - $query = $database->prepare("INSERT INTO $lookupTable SET {$table}_id=:item_id, $lookupField=:value_id, sort=:sort"); - $query->bindValue(":item_id", $item_id); - $query->bindValue(":value_id", $value_id); - $query->bindValue(":sort", $sort); - $query->execute(); + if($item_id) { + $sql = "INSERT INTO $lookupTable SET {$table}_id=:item_id, $lookupField=:value_id, sort=:sort"; + $query = $database->prepare($sql); + foreach($item->getLookupItems() as $key => $value) { + $value_id = (int) $value->id; + $query->bindValue(":item_id", $item_id, \PDO::PARAM_INT); + $query->bindValue(":value_id", $value_id, \PDO::PARAM_INT); + $query->bindValue(":sort", $sort, \PDO::PARAM_INT); + $query->execute(); + $sort++; + } $this->resetTrackChanges(); - $sort++; } return $result; @@ -163,7 +169,7 @@ abstract class WireSaveableItemsLookup extends WireSaveableItems { * */ public function ___delete(Saveable $item) { - $database = $this->wire('database'); + $database = $this->wire()->database; $lookupTable = $database->escapeTable($this->getLookupTable()); $table = $database->escapeTable($this->getTable()); $item_id = (int) $item->id; diff --git a/wire/modules/Fieldtype/FieldtypePage.module b/wire/modules/Fieldtype/FieldtypePage.module index c71bd91e..4a01d52a 100644 --- a/wire/modules/Fieldtype/FieldtypePage.module +++ b/wire/modules/Fieldtype/FieldtypePage.module @@ -1055,7 +1055,7 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule $value = date('Y-m-d H:i:s', $value); } else if(in_array($subfield, array('template', 'templates_id'))) { - $template = $this->templates->get($subfield); + $template = $this->templates->get($value); $value = $template ? $template->id : 0; $subfield = 'templates_id'; @@ -1235,6 +1235,9 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule * */ public function ___getSelectorInfo(Field $field, array $data = array()) { + + $pages = $this->wire()->pages; + $templates = $this->wire()->templates; $info = parent::___getSelectorInfo($field, $data); if(!isset($data['level'])) $data['level'] = 0; @@ -1245,29 +1248,34 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule $fieldgroups = array(); $template_ids = self::getTemplateIDs($field); $parent_id = $field->get('parent_id'); + $templateOptions = array(); if($template_ids) { // determine fieldgroup(s) from template setting // template_id can be int or array of ints foreach($template_ids as $tid) { - $template = $this->wire('templates')->get((int) $tid); - if($template) $fieldgroups[] = $template->fieldgroup; + $template = $templates->get((int) $tid); + if(!$template) continue; + $fieldgroups[] = $template->fieldgroup; + $templateOptions[$template->id] = $template->getLabel(); } } else if($parent_id) { // determine fieldgroup(s) from family settings - $parent = $this->wire('pages')->get((int) $parent_id); + $parent = $pages->get((int) $parent_id); if($parent->id) { foreach($parent->template->childTemplates as $template_id) { - $template = $this->wire('templates')->get((int) $template_id); + $template = $templates->get((int) $template_id); if(!$template) continue; $fieldgroups[$template->fieldgroup->id] = $template->fieldgroup; + $templateOptions[$template->id] = $template->getLabel(); } - foreach($this->wire('templates') as $template) { + foreach($templates as $template) { if(!in_array($parent->template->id, $template->parentTemplates)) continue; - if(!$this->wire('pages')->count("parent=$parent_id, template=$template->id, include=all")) continue; - $fieldgroups[$template->fieldgroup->id] = $template->fieldgroup; + if(!$pages->count("parent=$parent_id, template=$template->id, include=all")) continue; + $fieldgroups[$template->fieldgroup->id] = $template->fieldgroup; + $templateOptions[$template->id] = $template->getLabel(); } } } @@ -1276,6 +1284,12 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule // if no fieldgorups found, then we have no choice but to use all fields //$fieldgroups[] = $this->wire('fields'); } + + if(!count($templateOptions)) { + foreach($templates as $template) { + $templateOptions[$template->id] = $template->getLabel(); + } + } foreach($fieldgroups as $fieldgroup) { foreach($fieldgroup as $f) { @@ -1290,7 +1304,14 @@ class FieldtypePage extends FieldtypeMulti implements Module, ConfigurableModule 'name' => 'count', 'label' => $this->_('Count'), // Label for 'count' property of a PageArray 'input' => 'number', - ); + ); + + $subfields['templates_id'] = array( + 'name' => 'templates_id', + 'label' => $this->_('Template'), // Label for 'template' property of a Page + 'input' => 'select', + 'options' => $templateOptions, + ); $info['subfields'] = $subfields; diff --git a/wire/modules/Jquery/JqueryUI/modal.js b/wire/modules/Jquery/JqueryUI/modal.js index a966e5b1..8c1e8bd7 100644 --- a/wire/modules/Jquery/JqueryUI/modal.js +++ b/wire/modules/Jquery/JqueryUI/modal.js @@ -270,7 +270,7 @@ function pwModalWindow(href, options, size) { $iframe.refresh(); }; $iframe.setTitle = function(title) { - $iframe.dialog('option', 'title', title); + $iframe.dialog('option', 'title', jQuery('