Skip to content

Commit

Permalink
Merge pull request pkp#10542 from Vitaliy-1/i10292_update_settings
Browse files Browse the repository at this point in the history
pkp#10292 Fix update setting values for a Model
  • Loading branch information
Vitaliy-1 authored Nov 14, 2024
2 parents 8d44e98 + 88ab86b commit 359b87a
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 129 deletions.
22 changes: 15 additions & 7 deletions classes/announcement/Announcement.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,28 @@ public function save(array $options = [])
$newlyCreated = !$this->exists;
$saved = parent::save($options);

// If it's a new model with an image attribute, upload an image
if ($saved && $newlyCreated && $this->hasAttribute('image')) {
$this->handleImageUpload();
if (!$saved) {
return $saved;
}

// If it's updated model and a new image is uploaded, first, delete an old one
Hook::call('Announcement::add', [$this]);

$hasNewImage = $this?->image?->temporaryFileId;
if ($saved && !$newlyCreated && $hasNewImage) {

// if announcement is being inserted and includes new image, upload it
if ($newlyCreated) {
if ($hasNewImage) {
$this->handleImageUpload();
}
return $saved;
}

// The announcement is being updated, check if it contains new image
if ($hasNewImage) {
$this->deleteImage();
$this->handleImageUpload();
}

Hook::call('Announcement::add', [$this]);

return $saved;
}

Expand Down
84 changes: 30 additions & 54 deletions classes/core/EntityDAO.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use Exception;
use Illuminate\Support\Facades\DB;
use PKP\core\traits\EntityUpdate;
use PKP\db\DAO;
use PKP\services\PKPSchemaService;

Expand All @@ -23,6 +24,8 @@
*/
abstract class EntityDAO
{
use EntityUpdate;

/** @var string One of the \PKP\services\PKPSchemaService::SCHEMA_... constants */
public $schema;

Expand Down Expand Up @@ -190,7 +193,6 @@ protected function _insert(DataObject $object): int
protected function _update(DataObject $object): void
{
$schemaService = $this->schemaService;
$schema = $schemaService->get($this->schema);
$sanitizedProps = $schemaService->sanitize($this->schema, $object->_data);

$primaryDbProps = $this->getPrimaryDbProps($object);
Expand All @@ -199,60 +201,11 @@ protected function _update(DataObject $object): void
->where($this->primaryKeyColumn, '=', $object->getId())
->update($primaryDbProps);

if ($this->settingsTable) {
$deleteSettings = [];
foreach ($schema->properties as $propName => $propSchema) {
if (array_key_exists($propName, $this->primaryTableColumns)) {
continue;
} elseif (!isset($sanitizedProps[$propName])) {
$deleteSettings[] = $propName;
continue;
}
if (!empty($propSchema->multilingual)) {
foreach ($sanitizedProps[$propName] as $localeKey => $localeValue) {
// Delete rows with a null value
if (is_null($localeValue)) {
DB::table($this->settingsTable)
->where($this->primaryKeyColumn, '=', $object->getId())
->where('setting_name', '=', $propName)
->where('locale', '=', $localeKey)
->delete();
} else {
DB::table($this->settingsTable)
->updateOrInsert(
[
$this->primaryKeyColumn => $object->getId(),
'locale' => $localeKey,
'setting_name' => $propName,
],
[
'setting_value' => $this->convertToDB($localeValue, $schema->properties->{$propName}->type),
]
);
}
}
} else {
DB::table($this->settingsTable)
->updateOrInsert(
[
$this->primaryKeyColumn => $object->getId(),
'locale' => '',
'setting_name' => $propName,
],
[
'setting_value' => $this->convertToDB($sanitizedProps[$propName], $schema->properties->{$propName}->type),
]
);
}
}

if (count($deleteSettings)) {
DB::table($this->settingsTable)
->where($this->primaryKeyColumn, '=', $object->getId())
->whereIn('setting_name', $deleteSettings)
->delete();
}
if (!$this->settingsTable) {
return;
}

$this->updateSettings($sanitizedProps, $object->getId());
}

/**
Expand Down Expand Up @@ -330,4 +283,27 @@ protected function convertToDB($value, string $type, bool $nullable = false)
{
return $this->deprecatedDao->convertToDB($value, $type, $nullable);
}

public function getSettingsTable(): ?string
{
return $this->settingsTable;
}

public function getPrimaryKeyName(): string
{
return $this->primaryKeyColumn;
}

/**
* @return bool whether the property is a setting
*/
public function isSetting(string $settingName): bool
{
return !array_key_exists($settingName, $this->primaryTableColumns);
}

public function getSchemaName(): ?string
{
return $this->schema;
}
}
142 changes: 76 additions & 66 deletions classes/core/SettingsBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use PKP\core\traits\EntityUpdate;
use stdClass;

class SettingsBuilder extends Builder
{
use EntityUpdate;
/**
* Get the hydrated models without eager loading.
*
Expand Down Expand Up @@ -61,8 +63,6 @@ public function update(array $values)
return parent::update($primaryValues->toArray());
}

$newQuery = clone $this->query;

if ($primaryValues->isNotEmpty()) {
$count = parent::update($primaryValues->toArray());
}
Expand All @@ -72,22 +72,33 @@ public function update(array $values)
fn (mixed $value, string $key) => [Str::camel($key) => $value]
);

$us = $this->model->getSettingsTable();
$primaryKey = $this->model->getKeyName();


$sql = $this->buildUpdateSql($settingValues, $us, $newQuery);
$schema = null;
if (!$this->getSchemaName()) {
$casts = $this->model->getCasts();
foreach ($this->model->getSettings() as $settingName) {
$schema['properties'][$settingName]['multilingual'] = in_array($settingName, $this->model->getMultilingualProps());

if (array_key_exists($settingName, $casts)) {
$type = $casts[$settingName];
} else {
$type = 'string';
trigger_error(
"The setting {$settingName} doesn\'t have a defined type, using {$type} instead",
E_USER_WARNING
);
}
$schema['properties'][$settingName]['type'] = $type;
}
}

// Build a query for update
$settingCount = DB::table($us)->whereIn($us . '.' . $primaryKey, $newQuery->select($primaryKey))
->update([$us . '.setting_value' => DB::raw($sql)]);
$this->updateSettings($settingValues->toArray(), $this->model->getKey(), !is_null($schema) ? json_decode(json_encode($schema)) : null);

return ($count ?? 0) + $settingCount;
return $count ?? 0;
}

/**
* Insert the given attributes and set the ID on the model.
* Overrides Builder's method to insert setting values for a models with
* Overrides Builder's method to insert setting values for a models with settings
*
* @param string|null $sequence
*
Expand All @@ -100,29 +111,15 @@ public function insertGetId(array $values, $sequence = null)
fn (mixed $value, string $key) => in_array(Str::camel($key), $this->model->getSettings())
);


$id = parent::insertGetId($primaryValues->toArray(), $sequence);

if ($settingValues->isEmpty()) {
return $id;
}

$rows = [];
$settingValues->each(function (mixed $settingValue, string $settingName) use ($id, &$rows) {
$settingName = Str::camel($settingName);
if ($this->isMultilingual($settingName)) {
foreach ($settingValue as $locale => $localizedValue) {
$rows[] = [
$this->model->getKeyName() => $id, 'locale' => $locale, 'setting_name' => $settingName, 'setting_value' => $localizedValue
];
}
} else {
$rows[] = [
$this->model->getKeyName() => $id, 'locale' => '', 'setting_name' => $settingName, 'setting_value' => $settingValue
];
}
});

DB::table($this->model->getSettingsTable())->insert($rows);
$rows = $this->getSettingRows($settingValues, $id);
DB::table($this->getSettingsTable())->insert($rows);

return $id;
}
Expand All @@ -137,9 +134,9 @@ public function delete(): int
return $id;
}

DB::table($this->model->getSettingsTable())->where(
$this->model->getKeyName(),
$this->model->getRawOriginal($this->model->getKeyName()) ?? $this->model->getKey()
DB::table($this->getSettingsTable())->where(
$this->getPrimaryKeyName(),
$this->model->getRawOriginal($this->getPrimaryKeyName()) ?? $this->model->getKey()
)->delete();

return $id;
Expand Down Expand Up @@ -202,7 +199,7 @@ public function where($column, $operator = null, $value = null, $boolean = 'and'
$this->query->whereIn(
$this->model->getKeyName(),
fn (QueryBuilder $query) =>
$query->select($this->model->getKeyName())->from($this->model->getSettingsTable())->where($where, null, null, $boolean)
$query->select($this->getPrimaryKeyName())->from($this->getSettingsTable())->where($where, null, null, $boolean)
);

if (!empty($primaryColumn)) {
Expand Down Expand Up @@ -232,15 +229,38 @@ public function whereIn($column, $values, $boolean = 'and', $not = false)
$this->model->getKeyName(),
fn (QueryBuilder $query) =>
$query
->select($this->model->getKeyName())
->from($this->model->getSettingsTable())
->select($this->getPrimaryKeyName())
->from($this->getSettingsTable())
->where('setting_name', $column)
->whereIn('setting_value', $values, $boolean, $not)
);

return $this;
}

public function getSettingsTable(): ?string
{
return $this->model->getSettingsTable();
}

public function getPrimaryKeyName(): string
{
return $this->model->getKeyName();
}

/**
* @return bool whether the property is a setting
*/
public function isSetting(string $settingName): bool
{
return in_array($settingName, $this->model->getSettings());
}

public function getSchemaName(): ?string
{
return $this->model->getSchemaName();
}

/*
* Augment model with data from the settings table
*/
Expand Down Expand Up @@ -298,43 +318,33 @@ protected function filterRow(stdClass $row, string|array $columns = ['*']): stdC
}

/**
* @param Collection $settingValues list of setting names as keys and setting values to be updated
* @param string $us name of the settings table
* @param QueryBuilder $query original query associated with the Model
*
* @return string raw SQL statement
*
* Helper method to build a query to update settings with a conditional statement:
* SET settings_value = CASE WHEN setting_name='' AND locale=''...
* Checks if setting is multilingual
*/
protected function isMultilingual(string $settingName): bool
{
return in_array($settingName, $this->model->getMultilingualProps());
}

/**
* Get correspondent rows from a settings table
*/
protected function buildUpdateSql(Collection $settingValues, string $us, QueryBuilder $query): string
protected function getSettingRows(mixed $settingValues, int $id): array
{
$sql = 'CASE ';
$bindings = [];
$settingValues->each(function (mixed $settingValue, string $settingName) use (&$sql, &$bindings, $us) {
$rows = [];
$settingValues->each(function (mixed $settingValue, string $settingName) use ($id, &$rows) {
$settingName = Str::camel($settingName);
if ($this->isMultilingual($settingName)) {
foreach ($settingValue as $locale => $localizedValue) {
$sql .= 'WHEN ' . $us . '.setting_name=? AND ' . $us . '.locale=? THEN ? ';
$bindings = array_merge($bindings, [$settingName, $locale, $localizedValue]);
$rows[] = [
$this->getPrimaryKeyName() => $id, 'locale' => $locale, 'setting_name' => $settingName, 'setting_value' => $localizedValue
];
}
} else {
$sql .= 'WHEN ' . $us . '.setting_name=? THEN ? ';
$bindings = array_merge($bindings, [$settingName, $settingValue]);
$rows[] = [
$this->getPrimaryKeyName() => $id, 'locale' => '', 'setting_name' => $settingName, 'setting_value' => $settingValue
];
}
});
$sql .= 'ELSE setting_value END';

// Fix the order of bindings in Laravel, user ID in the where statement should be the last
$query->bindings['where'] = array_merge($bindings, $query->bindings['where']);

return $sql;
}

/**
* Checks if setting is multilingual
*/
protected function isMultilingual(string $settingName): bool
{
return in_array($settingName, $this->model->getMultilingualProps());
return $rows;
}
};
}
Loading

0 comments on commit 359b87a

Please sign in to comment.