Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate all element contents to single table #367

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions migrations/m241220_101915_template_elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public function safeUp()
$this->safeCreateTable('custom_pages_template_element_content', [
'id' => $this->primaryKey(),
'element_id' => $this->integer()->notNull(),
'fields' => $this->text(),
'dynAttributes' => $this->text(),
]);
$this->safeAddForeignKey('fk-element_id', 'custom_pages_template_element_content', 'element_id', 'custom_pages_template_element', 'id', 'CASCADE');

Expand Down Expand Up @@ -44,7 +44,7 @@ private function migrateTextElements()
foreach ($textElements->each() as $text) {
$this->insertSilent('custom_pages_template_element_content', [
'element_id' => $text['elementId'],
'fields' => json_encode([
'dynAttributes' => json_encode([
'content' => $text['content'],
'inline_text' => $text['inline_text'],
]),
Expand Down
103 changes: 103 additions & 0 deletions modules/template/components/ActiveRecordDynamicAttributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

/**
* @link https://www.humhub.org/
* @copyright Copyright (c) HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/

namespace humhub\modules\custom_pages\modules\template\components;

use humhub\components\ActiveRecord;
use yii\validators\Validator;

/**
* Abstract ActiveRecord which allows you to use not only the regular AR attributes but also other dynamic attributes.
* These are stored in a JSON field.
*
* @property string|array $dynAttributes
*
* Dynamic attributes:
* (List here all virtual/dynamic for the Record,
* they all are stored in the property $dynAttributes as json encoded array)
*
* @todo Avoid mark `dynAttribute` model attribute as Safe attribute
*/
abstract class ActiveRecordDynamicAttributes extends ActiveRecord
{
/**
* Get all possible dynamic attributes for this element content
*
* @return array Key - element index name, Value - default value
*/
abstract protected function getDynamicAttributes(): array;

/**
* @inheritdoc
*/
public function __get($name)
{
if ($this->hasDynamicAttribute($name)) {
return $this->dynAttributes[$name] ?? $this->getDynamicAttributeDefaultValue($name);
}

$value = parent::__get($name);

if ($name === 'dynAttributes' && !is_array($value)) {
$value = empty($value) ? [] : json_decode($value, true);
$this->setAttribute($name, $value);
}

return $value;
}

/**
* @inheritdoc
*/
public function __set($name, $value)
{
if ($this->hasDynamicAttribute($name)) {
$attrs = $this->dynAttributes;
$attrs[$name] = $value;
$this->setAttribute('dynAttributes', $attrs);
} else {
parent::__set($name, $value);
}
}

/**
* @inheritdoc
* @noinspection PhpMissingReturnTypeInspection
*/
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
$this->dynAttributes = is_array($this->dynAttributes) ? json_encode($this->dynAttributes) : null;
return true;
}

return false;
}

/**
* @inheritdoc
*/
public function createValidators()
{
$validators = parent::createValidators();
$validators->append(Validator::createValidator('safe', $this, 'dynAttributes'));

return $validators;
}

private function hasDynamicAttribute(string $name): bool
{
return array_key_exists($name, $this->getDynamicAttributes());
}

private function getDynamicAttributeDefaultValue(string $name): mixed
{
return $this->getDynamicAttributes()[$name] ?? null;
}

}
98 changes: 8 additions & 90 deletions modules/template/elements/BaseTemplateElementContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@

namespace humhub\modules\custom_pages\modules\template\elements;

use humhub\components\ActiveRecord;
use humhub\interfaces\ViewableInterface;
use humhub\modules\content\components\ContentActiveRecord;
use humhub\modules\custom_pages\models\CustomPage;
use humhub\modules\custom_pages\modules\template\components\ActiveRecordDynamicAttributes;
use humhub\modules\custom_pages\modules\template\models\ContainerContentDefinition;
use humhub\modules\custom_pages\modules\template\models\OwnerContent;
use humhub\modules\custom_pages\permissions\ManagePages;
Expand All @@ -24,15 +24,10 @@
*
* @property int $id
* @property int $element_id
* @property string|array $fields
*
* Element content fields:
* (List here all virtual for the Element Content,
* they all are stored in the property $fields as json encoded array)
*
* @property-read OwnerContent $ownerContent
*/
abstract class BaseTemplateElementContent extends ActiveRecord implements ViewableInterface
abstract class BaseTemplateElementContent extends ActiveRecordDynamicAttributes implements ViewableInterface
{
public const SCENARIO_CREATE = 'create';
public const SCENARIO_EDIT = 'edit';
Expand Down Expand Up @@ -69,13 +64,6 @@ abstract class BaseTemplateElementContent extends ActiveRecord implements Viewab
*/
public $filesSaved = false;

/**
* Get all possible fields for this element content
*
* @return array Key - element index name, Value - default value
*/
abstract protected function getFields(): array;

/**
* @return string rendered content type by means of the given $options.
*/
Expand Down Expand Up @@ -106,71 +94,6 @@ public static function tableName()
return 'custom_pages_template_element_content';
}

/**
* @inheritdoc
*/
public function rules()
{
return [
[['fields'], 'safe'],
];
}

/**
* @inheritdoc
*/
public function __get($name)
{
if ($this->hasField($name)) {
return $this->fields[$name] ?? $this->getFieldDefaultValue($name);
}

$value = parent::__get($name);

if ($name === 'fields' && !is_array($value)) {
$value = empty($value) ? [] : json_decode($value, true);
$this->setAttribute($name, $value);
}

return $value;
}

/**
* @inheritdoc
*/
public function __set($name, $value)
{
if ($this->hasField($name)) {
$fields = $this->fields;
$fields[$name] = $value;
$this->setAttribute('fields', $fields);
} else {
parent::__set($name, $value);
}
}

/**
* Check if this Element Content has the requested field
*
* @param string $name
* @return bool
*/
protected function hasField(string $name): bool
{
return array_key_exists($name, $this->getFields());
}

/**
* Get default value of the requested field
*
* @param string $name
* @return mixed
*/
protected function getFieldDefaultValue(string $name): mixed
{
return $this->getFields()[$name] ?? null;
}

/**
* Copies the values of this content type instance.
* This function can initiate the copy by using `createCopy`.
Expand All @@ -182,7 +105,7 @@ public function copy(): static
{
$clone = new static();
$clone->element_id = $this->element_id;
$clone->fields = $this->fields;
$clone->dynAttributes = $this->dynAttributes;
return $clone;
}

Expand Down Expand Up @@ -220,10 +143,10 @@ protected function createCopy()
public function scenarios()
{
return [
self::SCENARIO_DEFAULT => ['fileList', 'definitionPostData', 'fields'],
self::SCENARIO_CREATE => ['fileList', 'definitionPostData', 'fields'],
self::SCENARIO_EDIT_ADMIN => ['fileList', 'definitionPostData', 'fields'],
self::SCENARIO_EDIT => ['fileList', 'definitionPostData', 'fields'],
self::SCENARIO_DEFAULT => ['fileList', 'definitionPostData', 'dynAttributes'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yurabakhtin Is it really necessary to mark 'dynAttributes' as 'safe attributes'? Isn't that only necessary for attributes that are read in via 'load', for example, or for user input?
It is only used internally and shouldn't be necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luke- Yes, you are right, I have removed that here.

self::SCENARIO_CREATE => ['fileList', 'definitionPostData', 'dynAttributes'],
self::SCENARIO_EDIT_ADMIN => ['fileList', 'definitionPostData', 'dynAttributes'],
self::SCENARIO_EDIT => ['fileList', 'definitionPostData', 'dynAttributes'],
];
}

Expand Down Expand Up @@ -328,12 +251,7 @@ public function beforeSave($insert)
return false;
}

if (parent::beforeSave($insert)) {
$this->fields = is_array($this->fields) ? json_encode($this->fields) : null;
return true;
}

return false;
return parent::beforeSave($insert);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions modules/template/elements/TextElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
/**
* Class to manage content records of the text elements
*
* Element content fields:
* Dynamic attributes:
* @property-read bool $inline_text
* @property-read string $content
*/
Expand All @@ -26,7 +26,7 @@ class TextElement extends BaseTemplateElementContent
/**
* @inheritdoc
*/
protected function getFields(): array
protected function getDynamicAttributes(): array
{
return [
'content' => null,
Expand All @@ -39,11 +39,11 @@ protected function getFields(): array
*/
public function rules()
{
return array_merge(parent::rules(), [
return [
['content', 'trim'],
['inline_text', 'boolean'],
['content', 'string', 'length' => [1, 255]],
]);
];
}

/**
Expand Down
Loading