Skip to content

Commit

Permalink
Merge pull request #5146 from craftcms/feature/delta-updates
Browse files Browse the repository at this point in the history
Delta updates

Resolves #4064
Resolves #4149
  • Loading branch information
brandonkelly committed Oct 28, 2019
2 parents 552333e + 8c007a8 commit 9e415c0
Show file tree
Hide file tree
Showing 35 changed files with 927 additions and 476 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG-v3.4.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Running Release Notes for Craft 3.4

### Added
- Added support for delta element updates. ([#4064](https://github.com/craftcms/cms/issues/4064))
- Elements now track which field values have changed since the element was first loaded. ([#4149](https://github.com/craftcms/cms/issues/4149))
- Added the `verifyEmailPath` config setting.
- Added the `maxBackups` config setting. ([#2078](https://github.com/craftcms/cms/issues/2078))
- Added the `{% requireGuest %}` tag, which redirects a user to the path specified by the `postLoginRedirect` config setting if they’re already logged in. ([#5015](https://github.com/craftcms/cms/pull/5015))
Expand All @@ -11,21 +13,31 @@
- Added `craft\base\AssetPreview`.
- Added `craft\base\AssetPreviewInterface`.
- Added `craft\base\AssetPreviewTrait`.
- Added `craft\base\ElementInterface::clearDirtyFields()`.
- Added `craft\base\ElementInterface::getDirtyFields()`.
- Added `craft\base\ElementInterface::isFieldDirty()`.
- Added `craft\db\Connection::DRIVER_MYSQL`.
- Added `craft\db\Connection::DRIVER_PGSQL`.
- Added `craft\elements\MatrixBlock::$dirty`.
- Added `craft\events\AssetPreviewEvent`.
- Added `craft\events\DefineGqlTypeFieldsEvent`.
- Added `craft\events\DefineGqlValidationRulesEvent`.
- Added `craft\events\RegisterGqlPermissionsEvent`.
- Added `craft\gql\TypeManager`.
- Added `craft\helpers\Db::parseDsn()`.
- Added `craft\helpers\Db::url2config()`.
- Added `craft\queue\jobs\UpdateSearchIndex::$fieldHandles`.
- Added `craft\services\Assets::getAssetPreview()`.
- Added `craft\services\Gql::getValidationRules()`.
- Added `craft\services\Search::indexFields()`.
- Added `craft\web\Controller::requireGuest()`.
- Added `craft\web\twig\nodes\RequireGuestNode`.
- Added `craft\web\twig\tokenparsers\RequireGuestTokenParser`.
- Added `craft\web\User::guestRequired()`.
- Added `craft\web\View::getDeltaNames()`.
- Added `craft\web\View::getIsDeltaRegistrationActive()`.
- Added `craft\web\View::registerDeltaName()`.
- Added `craft\web\View::setIsDeltaRegistrationActive()`.
- Added `craft\web\twig\variables\Paginate::getDynamicRangeUrls()`, making it easy to create Google-style pagination links. ([#5005](https://github.com/craftcms/cms/issues/5005))
- Added the `cp.users.edit.prefs` template hook to the Edit User page. ([#5114](https://github.com/craftcms/cms/issues/5114))

Expand All @@ -43,6 +55,10 @@
- The installer now requires `config/db.php` to be setting the `dsn` database config setting with a `DB_DSN` environment variable, if a connection can’t already be established.
- `craft\services\Elements::saveElement()` now has an `$updateSearchIndex` argument (defaults to `true`). ([#4840](https://github.com/craftcms/cms/issues/4840))
- `craft\services\Elements::resaveElements()` now has an `$updateSearchIndex` argument (defaults to `false`). ([#4840](https://github.com/craftcms/cms/issues/4840))
- `craft\services\Search::indexElementAttributes()` now has a `$withFields` argument.
- The `_includes/forms/field.html` template now supports `fieldAttributes`, `labelAttributes`, and `inputAttributes` variables.
- The `_includes/field.html` template now supports a `registerDeltas` variable.
- The `_layouts/cp.html` template now supports `mainAttributes` and `mainFormAttributes` variables.
- Asset previews are now extensible, allowing plugins to add new preview types. ([#5136](https://github.com/craftcms/cms/pull/5136))
- Updated Yii to 2.0.29.

Expand All @@ -52,3 +68,4 @@
- Deprecated `craft\config\DbConfig::DRIVER_PGSQL`.
- Deprecated `craft\config\DbConfig::updateDsn()`.
- Deprecated `craft\elements\Asset::getSupportsPreview()`. Use `craft\services\Assets::getAssetPreview()` instead.
- Deprecated `craft\services\Search::indexElementFields()`.
52 changes: 50 additions & 2 deletions src/base/Element.php
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,11 @@ private static function _indexOrderBy(array $viewState)
*/
protected $revisionNotes;

/**
* @var bool
*/
private $_initialized = false;

/**
* @var
*/
Expand All @@ -878,6 +883,12 @@ private static function _indexOrderBy(array $viewState)
*/
private $_normalizedFieldValues;

/**
* @var array Record of dirty fields.
* @see isFieldDirty()
*/
private $_dirtyFields;

/**
* @var
*/
Expand Down Expand Up @@ -1026,6 +1037,8 @@ public function init()
if ($this->siteId === null && Craft::$app->getIsInstalled()) {
$this->siteId = Craft::$app->getSites()->getPrimarySite()->id;
}

$this->_initialized = true;
}

/**
Expand Down Expand Up @@ -1945,6 +1958,38 @@ public function setFieldValue(string $fieldHandle, $value)

// Don't assume that $value has been normalized
unset($this->_normalizedFieldValues[$fieldHandle]);

// If the element is fully initialized, mark the value as dirty
if ($this->_initialized) {
$this->_dirtyFields[$fieldHandle] = true;
}
}

/**
* @inheritdoc
*/
public function isFieldDirty(string $fieldHandle): bool
{
return isset($this->_dirtyFields[$fieldHandle]);
}

/**
* @inheritdoc
*/
public function getDirtyFields(): array
{
if ($this->_dirtyFields) {
return array_keys($this->_dirtyFields);
}
return [];
}

/**
* @inheritdoc
*/
public function clearDirtyFields()
{
$this->_dirtyFields = null;
}

/**
Expand Down Expand Up @@ -2149,18 +2194,21 @@ public function getEditorHtml(): string
$originalNamespace = $view->getNamespace();
$namespace = $view->namespaceInputName('fields', $originalNamespace);
$view->setNamespace($namespace);
$view->setIsDeltaRegistrationActive(true);

foreach ($fieldLayout->getFields() as $field) {
$fieldHtml = $view->renderTemplate('_includes/field', [
'element' => $this,
'field' => $field,
'required' => $field->required
'required' => $field->required,
'registerDeltas' => true,
]);

$html .= $view->namespaceInputs($fieldHtml, 'fields');
}

Craft::$app->getView()->setNamespace($originalNamespace);
$view->setNamespace($originalNamespace);
$view->setIsDeltaRegistrationActive(false);

$html .= Html::hiddenInput('fieldLayoutId', $fieldLayout->id);
}
Expand Down
24 changes: 24 additions & 0 deletions src/base/ElementInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,30 @@ public function getFieldValue(string $fieldHandle);
*/
public function setFieldValue(string $fieldHandle, $value);

/**
* Returns whether a custom field value has changed since the element was first loaded.
*
* @param string $fieldHandle
* @return bool
* @since 3.4.0
*/
public function isFieldDirty(string $fieldHandle): bool;

/**
* Returns a list of custom field handles that have changed since the element was first loaded.
*
* @return string[]
* @since 3.4.0
*/
public function getDirtyFields(): array;

/**
* Resets the record of dirty fields.
*
* @since 3.4.0
*/
public function clearDirtyFields();

/**
* Sets the element’s custom field values, when the values have come from post data.
*
Expand Down
7 changes: 4 additions & 3 deletions src/controllers/ElementsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,9 @@ private function _getEditorHtmlResponse(ElementInterface $element, bool $include

$response['siteId'] = $element->siteId;

$view = $this->getView();
$namespace = 'editor_' . StringHelper::randomString(10);
$this->getView()->setNamespace($namespace);
$view->setNamespace($namespace);

$response['html'] = '<input type="hidden" name="namespace" value="' . $namespace . '">';

Expand All @@ -410,12 +411,12 @@ private function _getEditorHtmlResponse(ElementInterface $element, bool $include
}

$response['html'] .= '<div class="meta">' .
$this->getView()->namespaceInputs((string)$element->getEditorHtml()) .
$view->namespaceInputs((string)$element->getEditorHtml()) .
'</div>';

$view = $this->getView();
$response['headHtml'] = $view->getHeadHtml();
$response['footHtml'] = $view->getBodyHtml();
$response['deltaNames'] = $view->getDeltaNames();

return $this->asJson($response);
}
Expand Down
7 changes: 7 additions & 0 deletions src/elements/MatrixBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ public static function gqlTypeNameByContext($context): string
*/
public $sortOrder;

/**
* @var bool Whether the block has changed.
* @internal
* @since 3.4.0
*/
public $dirty = false;

/**
* @var bool Collapsed
*/
Expand Down
11 changes: 5 additions & 6 deletions src/elements/db/ElementQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -1749,6 +1749,11 @@ public function createElement(array $row): ElementInterface
$row['trashed'] = $row['dateDeleted'] !== null;
}

// Set the custom field values
if (isset($fieldValues)) {
$row['fieldValues'] = $fieldValues;
}

$behaviors = [];

if ($this->drafts) {
Expand All @@ -1773,12 +1778,6 @@ public function createElement(array $row): ElementInterface
$element = new $class($row);
$element->attachBehaviors($behaviors);

// Set the custom field values
/** @noinspection UnSafeIsSetOverArrayInspection - FP */
if (isset($fieldValues)) {
$element->setFieldValues($fieldValues);
}

// Fire an 'afterPopulateElement' event
if ($this->hasEventHandlers(self::EVENT_AFTER_POPULATE_ELEMENT)) {
$this->trigger(self::EVENT_AFTER_POPULATE_ELEMENT, new PopulateElementEvent([
Expand Down
7 changes: 5 additions & 2 deletions src/fields/BaseRelationField.php
Original file line number Diff line number Diff line change
Expand Up @@ -641,9 +641,12 @@ public function afterSave(bool $isNew)
*/
public function afterElementSave(ElementInterface $element, bool $isNew)
{
// Skip if the element is just propagating, and we're not localizing relations
// Skip if nothing changed, or the element is just propagating and we're not localizing relations
/** @var Element $element */
if (!$element->propagating || $this->localizeRelations) {
if (
$element->isFieldDirty($this->handle) &&
(!$element->propagating || $this->localizeRelations)
) {
/** @var ElementQuery $value */
$value = $element->getFieldValue($this->handle);

Expand Down
Loading

0 comments on commit 9e415c0

Please # to comment.