mirror of
https://github.com/grocy/grocy.git
synced 2025-08-20 04:12:59 +00:00
Squashed commit
Always execute migration 9999 (can be used to fix things manually) Optimized meal plan navigation / date range filtering Prepared next release Pulled translations from Transifex Various code optimizations
This commit is contained in:
@@ -9,7 +9,6 @@ class ApplicationService extends BaseService
|
||||
public function GetChangelog()
|
||||
{
|
||||
$changelogItems = [];
|
||||
|
||||
foreach (glob(__DIR__ . '/../changelog/*.md') as $file)
|
||||
{
|
||||
$fileName = basename($file);
|
||||
|
@@ -6,14 +6,9 @@ class BaseService
|
||||
{
|
||||
private static $instances = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function getInstance()
|
||||
{
|
||||
$className = get_called_class();
|
||||
|
||||
if (!isset(self::$instances[$className]))
|
||||
{
|
||||
self::$instances[$className] = new $className();
|
||||
|
@@ -6,6 +6,13 @@ use Grocy\Helpers\UrlManager;
|
||||
|
||||
class CalendarService extends BaseService
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->UrlManager = new UrlManager(GROCY_BASE_URL);
|
||||
}
|
||||
|
||||
private $UrlManager;
|
||||
|
||||
public function GetEvents()
|
||||
{
|
||||
$stockEvents = [];
|
||||
@@ -148,10 +155,4 @@ class CalendarService extends BaseService
|
||||
|
||||
return array_merge($stockEvents, $taskEvents, $choreEvents, $batteryEvents, $mealPlanRecipeEvents, $mealPlanNotesEvents, $mealPlanProductEvents);
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->UrlManager = new UrlManager(GROCY_BASE_URL);
|
||||
}
|
||||
}
|
||||
|
@@ -38,7 +38,6 @@ class ChoresService extends BaseService
|
||||
|
||||
$users = $this->getUsersService()->GetUsersAsDto();
|
||||
$assignedUsers = [];
|
||||
|
||||
foreach ($users as $user)
|
||||
{
|
||||
if (in_array($user->id, explode(',', $chore->assignment_config)))
|
||||
@@ -210,11 +209,6 @@ class ChoresService extends BaseService
|
||||
]);
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
private function ChoreExists($choreId)
|
||||
{
|
||||
$choreRow = $this->getDatabase()->chores()->where('id = :1', $choreId)->fetch();
|
||||
|
@@ -4,6 +4,9 @@ namespace Grocy\Services;
|
||||
|
||||
class DatabaseMigrationService extends BaseService
|
||||
{
|
||||
// This migration will be always execute, can be used to fix things manually
|
||||
const EMERGENCY_MIGRATION_ID = 9999;
|
||||
|
||||
public function MigrateDatabase()
|
||||
{
|
||||
$this->getDatabaseService()->ExecuteDbStatement("CREATE TABLE IF NOT EXISTS migrations (migration INTEGER NOT NULL PRIMARY KEY UNIQUE, execution_time_timestamp DATETIME DEFAULT (datetime('now', 'localtime')))");
|
||||
@@ -36,10 +39,14 @@ class DatabaseMigrationService extends BaseService
|
||||
{
|
||||
$rowCount = $this->getDatabaseService()->ExecuteDbQuery('SELECT COUNT(*) FROM migrations WHERE migration = ' . $migrationId)->fetchColumn();
|
||||
|
||||
if (intval($rowCount) === 0)
|
||||
if (intval($rowCount) === 0 || $migrationId == self::EMERGENCY_MIGRATION_ID)
|
||||
{
|
||||
include $phpFile;
|
||||
$this->getDatabaseService()->ExecuteDbStatement('INSERT INTO migrations (migration) VALUES (' . $migrationId . ')');
|
||||
|
||||
if ($migrationId != self::EMERGENCY_MIGRATION_ID)
|
||||
{
|
||||
$this->getDatabaseService()->ExecuteDbStatement('INSERT INTO migrations (migration) VALUES (' . $migrationId . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,14 +54,18 @@ class DatabaseMigrationService extends BaseService
|
||||
{
|
||||
$rowCount = $this->getDatabaseService()->ExecuteDbQuery('SELECT COUNT(*) FROM migrations WHERE migration = ' . $migrationId)->fetchColumn();
|
||||
|
||||
if (intval($rowCount) === 0)
|
||||
if (intval($rowCount) === 0 || $migrationId == self::EMERGENCY_MIGRATION_ID)
|
||||
{
|
||||
$this->getDatabaseService()->GetDbConnectionRaw()->beginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
$this->getDatabaseService()->ExecuteDbStatement($sql);
|
||||
$this->getDatabaseService()->ExecuteDbStatement('INSERT INTO migrations (migration) VALUES (' . $migrationId . ')');
|
||||
|
||||
if ($migrationId != self::EMERGENCY_MIGRATION_ID)
|
||||
{
|
||||
$this->getDatabaseService()->ExecuteDbStatement('INSERT INTO migrations (migration) VALUES (' . $migrationId . ')');
|
||||
}
|
||||
}
|
||||
catch (Exception $ex)
|
||||
{
|
||||
|
@@ -4,6 +4,11 @@ namespace Grocy\Services;
|
||||
|
||||
class DemoDataGeneratorService extends BaseService
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->LocalizationService = new LocalizationService(GROCY_DEFAULT_LOCALE);
|
||||
}
|
||||
|
||||
protected $LocalizationService;
|
||||
|
||||
private $LastSupermarketId = 1;
|
||||
@@ -343,12 +348,6 @@ class DemoDataGeneratorService extends BaseService
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->LocalizationService = new LocalizationService(GROCY_DEFAULT_LOCALE);
|
||||
}
|
||||
|
||||
private function DownloadFileIfNotAlreadyExists($sourceUrl, $destinationPath)
|
||||
{
|
||||
if (!file_exists($destinationPath))
|
||||
|
@@ -8,6 +8,30 @@ class FilesService extends BaseService
|
||||
{
|
||||
const FILE_SERVE_TYPE_PICTURE = 'picture';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->StoragePath = GROCY_DATAPATH . '/storage';
|
||||
if (!file_exists($this->StoragePath))
|
||||
{
|
||||
mkdir($this->StoragePath);
|
||||
}
|
||||
|
||||
if (GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease')
|
||||
{
|
||||
$dbSuffix = GROCY_DEFAULT_LOCALE;
|
||||
if (defined('GROCY_DEMO_DB_SUFFIX'))
|
||||
{
|
||||
$dbSuffix = GROCY_DEMO_DB_SUFFIX;
|
||||
}
|
||||
|
||||
$this->StoragePath = $this->StoragePath . '/' . $dbSuffix;
|
||||
if (!file_exists($this->StoragePath))
|
||||
{
|
||||
mkdir($this->StoragePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private $StoragePath;
|
||||
|
||||
public function DownscaleImage($group, $fileName, $bestFitHeight = null, $bestFitWidth = null)
|
||||
@@ -58,8 +82,9 @@ class FilesService extends BaseService
|
||||
$fileNameWithoutExtension = pathinfo($filePath, PATHINFO_FILENAME);
|
||||
$fileExtension = pathinfo($filePath, PATHINFO_EXTENSION);
|
||||
|
||||
// Then the file is an image
|
||||
if (getimagesize($filePath) !== false)
|
||||
{ // Then the file is an image
|
||||
{
|
||||
// Also delete all corresponding "__downscaledto" files when deleting an image
|
||||
$groupFolderPath = $this->StoragePath . '/' . $group;
|
||||
$files = scandir($groupFolderPath);
|
||||
@@ -87,32 +112,4 @@ class FilesService extends BaseService
|
||||
|
||||
return $groupFolderPath . '/' . $fileName;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->StoragePath = GROCY_DATAPATH . '/storage';
|
||||
|
||||
if (!file_exists($this->StoragePath))
|
||||
{
|
||||
mkdir($this->StoragePath);
|
||||
}
|
||||
|
||||
if (GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease')
|
||||
{
|
||||
$dbSuffix = GROCY_DEFAULT_LOCALE;
|
||||
if (defined('GROCY_DEMO_DB_SUFFIX'))
|
||||
{
|
||||
$dbSuffix = GROCY_DEMO_DB_SUFFIX;
|
||||
}
|
||||
|
||||
$this->StoragePath = $this->StoragePath . '/' . $dbSuffix;
|
||||
|
||||
if (!file_exists($this->StoragePath))
|
||||
{
|
||||
mkdir($this->StoragePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,13 @@ use Gettext\Translator;
|
||||
|
||||
class LocalizationService
|
||||
{
|
||||
public function __construct(string $culture)
|
||||
{
|
||||
$this->Culture = $culture;
|
||||
|
||||
$this->LoadLocalizations($culture);
|
||||
}
|
||||
|
||||
protected $Po;
|
||||
|
||||
protected $PoUserStrings;
|
||||
@@ -62,13 +69,6 @@ class LocalizationService
|
||||
return $this->Po->toJsonString();
|
||||
}
|
||||
|
||||
public function __construct(string $culture)
|
||||
{
|
||||
$this->Culture = $culture;
|
||||
|
||||
$this->LoadLocalizations($culture);
|
||||
}
|
||||
|
||||
public function __n($number, $singularForm, $pluralForm)
|
||||
{
|
||||
$this->CheckAndAddMissingTranslationToPot($singularForm);
|
||||
|
@@ -10,6 +10,39 @@ use Mike42\Escpos\Printer;
|
||||
|
||||
class PrintService extends BaseService
|
||||
{
|
||||
/**
|
||||
* @param bool $printHeader Printing of Grocy logo
|
||||
* @param string[] $lines Items to print
|
||||
* @return string[] Returns array with result OK if no exception
|
||||
* @throws Exception If unable to print, an exception is thrown
|
||||
*/
|
||||
public function printShoppingList(bool $printHeader, array $lines): array
|
||||
{
|
||||
$printer = self::getPrinterHandle();
|
||||
if ($printer === false)
|
||||
{
|
||||
throw new Exception('Unable to connect to printer');
|
||||
}
|
||||
|
||||
if ($printHeader)
|
||||
{
|
||||
self::printHeader($printer);
|
||||
}
|
||||
|
||||
foreach ($lines as $line)
|
||||
{
|
||||
$printer->text($line);
|
||||
$printer->feed();
|
||||
}
|
||||
|
||||
$printer->feed(3);
|
||||
$printer->cut();
|
||||
$printer->close();
|
||||
return [
|
||||
'result' => 'OK'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises the printer
|
||||
* @return Printer Printer handle
|
||||
@@ -50,37 +83,4 @@ class PrintService extends BaseService
|
||||
$printer->selectPrintMode();
|
||||
$printer->feed(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $printHeader Printing of Grocy logo
|
||||
* @param string[] $lines Items to print
|
||||
* @return string[] Returns array with result OK if no exception
|
||||
* @throws Exception If unable to print, an exception is thrown
|
||||
*/
|
||||
public function printShoppingList(bool $printHeader, array $lines): array
|
||||
{
|
||||
$printer = self::getPrinterHandle();
|
||||
if ($printer === false)
|
||||
{
|
||||
throw new Exception('Unable to connect to printer');
|
||||
}
|
||||
|
||||
if ($printHeader)
|
||||
{
|
||||
self::printHeader($printer);
|
||||
}
|
||||
|
||||
foreach ($lines as $line)
|
||||
{
|
||||
$printer->text($line);
|
||||
$printer->feed();
|
||||
}
|
||||
|
||||
$printer->feed(3);
|
||||
$printer->cut();
|
||||
$printer->close();
|
||||
return [
|
||||
'result' => 'OK'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -63,8 +63,8 @@ class RecipesService extends BaseService
|
||||
}
|
||||
|
||||
$transactionId = uniqid();
|
||||
$recipePositions = $this->getDatabase()->recipes_pos_resolved()->where('recipe_id', $recipeId)->fetchAll();
|
||||
|
||||
$recipePositions = $this->getDatabase()->recipes_pos_resolved()->where('recipe_id', $recipeId)->fetchAll();
|
||||
foreach ($recipePositions as $recipePosition)
|
||||
{
|
||||
if ($recipePosition->only_check_single_unit_in_stock == 0)
|
||||
@@ -129,11 +129,6 @@ class RecipesService extends BaseService
|
||||
return $lastInsertId;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
private function RecipeExists($recipeId)
|
||||
{
|
||||
$recipeRow = $this->getDataBase()->recipes()->where('id = :1', $recipeId)->fetch();
|
||||
|
@@ -12,7 +12,6 @@ class SessionService extends BaseService
|
||||
public function CreateSession($userId, $stayLoggedInPermanently = false)
|
||||
{
|
||||
$newSessionKey = $this->GenerateSessionKey();
|
||||
|
||||
$expires = date('Y-m-d H:i:s', intval(time() + 2592000));
|
||||
|
||||
// Default is that sessions expire in 30 days
|
||||
@@ -39,7 +38,6 @@ class SessionService extends BaseService
|
||||
public function GetUserBySessionKey($sessionKey)
|
||||
{
|
||||
$sessionRow = $this->getDatabase()->sessions()->where('session_key', $sessionKey)->fetch();
|
||||
|
||||
if ($sessionRow !== null)
|
||||
{
|
||||
return $this->getDatabase()->users($sessionRow->user_id);
|
||||
@@ -60,7 +58,6 @@ class SessionService extends BaseService
|
||||
else
|
||||
{
|
||||
$sessionRow = $this->getDatabase()->sessions()->where('session_key = :1 AND expires > :2', $sessionKey, date('Y-m-d H:i:s', time()))->fetch();
|
||||
|
||||
if ($sessionRow !== null)
|
||||
{
|
||||
// This should not change the database file modification time as this is used
|
||||
|
@@ -33,7 +33,6 @@ class StockService extends BaseService
|
||||
}
|
||||
|
||||
$missingProducts = $this->GetMissingProducts();
|
||||
|
||||
foreach ($missingProducts as $missingProduct)
|
||||
{
|
||||
$product = $this->getDatabase()->products()->where('id', $missingProduct->id)->fetch();
|
||||
@@ -42,7 +41,8 @@ class StockService extends BaseService
|
||||
$alreadyExistingEntry = $this->getDatabase()->shopping_list()->where('product_id', $missingProduct->id)->fetch();
|
||||
|
||||
if ($alreadyExistingEntry)
|
||||
{ // Update
|
||||
{
|
||||
// Update
|
||||
if ($alreadyExistingEntry->amount < $amountToAdd)
|
||||
{
|
||||
$alreadyExistingEntry->update([
|
||||
@@ -52,7 +52,8 @@ class StockService extends BaseService
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // Insert
|
||||
{
|
||||
// Insert
|
||||
$shoppinglistRow = $this->getDatabase()->shopping_list()->createRow([
|
||||
'product_id' => $missingProduct->id,
|
||||
'amount' => $amountToAdd,
|
||||
@@ -197,7 +198,8 @@ class StockService extends BaseService
|
||||
{
|
||||
$reps = 1;
|
||||
if ($runWebhook == 2)
|
||||
{ // 2 == run $amount times
|
||||
{
|
||||
// 2 == run $amount times
|
||||
$reps = intval(floor($amount));
|
||||
}
|
||||
|
||||
@@ -428,7 +430,6 @@ class StockService extends BaseService
|
||||
public function EditStockEntry(int $stockRowId, float $amount, $bestBeforeDate, $locationId, $shoppingLocationId, $price, $open, $purchasedDate)
|
||||
{
|
||||
$stockRow = $this->getDatabase()->stock()->where('id = :1', $stockRowId)->fetch();
|
||||
|
||||
if ($stockRow === null)
|
||||
{
|
||||
throw new \Exception('Stock does not exist');
|
||||
@@ -455,7 +456,6 @@ class StockService extends BaseService
|
||||
$logOldRowForStockUpdate->save();
|
||||
|
||||
$openedDate = $stockRow->opened_date;
|
||||
|
||||
if (boolval($open) && $openedDate == null)
|
||||
{
|
||||
$openedDate = date('Y-m-d');
|
||||
@@ -505,7 +505,8 @@ class StockService extends BaseService
|
||||
$pluginOutput = $plugin->Lookup($barcode);
|
||||
|
||||
if ($pluginOutput !== null)
|
||||
{ // Lookup was successful
|
||||
{
|
||||
// Lookup was successful
|
||||
if ($addFoundProduct === true)
|
||||
{
|
||||
// Add product to database and include new product id in output
|
||||
@@ -522,7 +523,6 @@ class StockService extends BaseService
|
||||
public function GetCurrentStock($includeNotInStockButMissingProducts = false)
|
||||
{
|
||||
$sql = 'SELECT * FROM stock_current';
|
||||
|
||||
if ($includeNotInStockButMissingProducts)
|
||||
{
|
||||
$missingProductsView = 'stock_missing_products_including_opened';
|
||||
@@ -538,7 +538,6 @@ class StockService extends BaseService
|
||||
$currentStockMapped = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_GROUP | \PDO::FETCH_OBJ);
|
||||
|
||||
$relevantProducts = $this->getDatabase()->products()->where('id IN (SELECT product_id FROM (' . $sql . ') x)');
|
||||
|
||||
foreach ($relevantProducts as $product)
|
||||
{
|
||||
$currentStockMapped[$product->id][0]->product_id = $product->id;
|
||||
@@ -614,7 +613,6 @@ class StockService extends BaseService
|
||||
}
|
||||
|
||||
$stockCurrentRow = FindObjectinArrayByPropertyValue($this->GetCurrentStock(), 'product_id', $productId);
|
||||
|
||||
if ($stockCurrentRow == null)
|
||||
{
|
||||
$stockCurrentRow = new \stdClass();
|
||||
@@ -665,7 +663,6 @@ class StockService extends BaseService
|
||||
{
|
||||
$consumeCount = 1;
|
||||
}
|
||||
|
||||
$spoilRate = ($consumeCountSpoiled * 100.0) / $consumeCount;
|
||||
|
||||
return [
|
||||
@@ -704,7 +701,6 @@ class StockService extends BaseService
|
||||
}
|
||||
|
||||
$potentialProduct = $this->getDatabase()->product_barcodes()->where('barcode = :1', $barcode)->fetch();
|
||||
|
||||
if ($potentialProduct === null)
|
||||
{
|
||||
throw new \Exception("No product with barcode $barcode found");
|
||||
@@ -722,8 +718,8 @@ class StockService extends BaseService
|
||||
|
||||
$returnData = [];
|
||||
$shoppingLocations = $this->getDatabase()->shopping_locations();
|
||||
$rows = $this->getDatabase()->product_price_history()->where('product_id = :1', $productId)->orderBy('purchased_date', 'DESC');
|
||||
|
||||
$rows = $this->getDatabase()->product_price_history()->where('product_id = :1', $productId)->orderBy('purchased_date', 'DESC');
|
||||
foreach ($rows as $row)
|
||||
{
|
||||
$returnData[] = [
|
||||
@@ -998,6 +994,7 @@ class StockService extends BaseService
|
||||
{
|
||||
$decimals = intval($this->getUsersService()->GetUserSetting(GROCY_USER_ID, 'stock_decimal_places_amounts'));
|
||||
$newAmount = $productRow->amount - $amount;
|
||||
|
||||
if ($newAmount < floatval('0.' . str_repeat('0', $decimals - ($decimals <= 0 ? 0 : 1)) . '1'))
|
||||
{
|
||||
$productRow->delete();
|
||||
@@ -1032,13 +1029,16 @@ class StockService extends BaseService
|
||||
{
|
||||
$product = $this->getDatabase()->products()->where('id = :1', $row->product_id)->fetch();
|
||||
$conversion = $this->getDatabase()->quantity_unit_conversions_resolved()->where('product_id = :1 AND from_qu_id = :2 AND to_qu_id = :3', $product->id, $product->qu_id_stock, $row->qu_id)->fetch();
|
||||
|
||||
$factor = 1.0;
|
||||
if ($conversion != null)
|
||||
{
|
||||
$factor = floatval($conversion->factor);
|
||||
}
|
||||
|
||||
$amount = round($row->amount * $factor);
|
||||
$note = '';
|
||||
|
||||
if (GROCY_TPRINTER_PRINT_NOTES)
|
||||
{
|
||||
if ($row->note != '')
|
||||
@@ -1047,6 +1047,7 @@ class StockService extends BaseService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GROCY_TPRINTER_PRINT_QUANTITY_NAME && $isValidProduct)
|
||||
{
|
||||
$quantityname = $row->qu_name;
|
||||
@@ -1054,6 +1055,7 @@ class StockService extends BaseService
|
||||
{
|
||||
$quantityname = $row->qu_name_plural;
|
||||
}
|
||||
|
||||
array_push($result_quantity, $amount . ' ' . $quantityname);
|
||||
array_push($result_product, $row->product_name . $note);
|
||||
}
|
||||
@@ -1071,6 +1073,7 @@ class StockService extends BaseService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Add padding to look nicer
|
||||
$maxlength = 1;
|
||||
foreach ($result_quantity as $quantity)
|
||||
@@ -1080,6 +1083,7 @@ class StockService extends BaseService
|
||||
$maxlength = strlen($quantity);
|
||||
}
|
||||
}
|
||||
|
||||
$result = [];
|
||||
$length = count($result_quantity);
|
||||
for ($i = 0; $i < $length; $i++)
|
||||
@@ -1087,6 +1091,7 @@ class StockService extends BaseService
|
||||
$quantity = str_pad($result_quantity[$i], $maxlength);
|
||||
array_push($result, $quantity . ' ' . $result_product[$i]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -1222,7 +1227,8 @@ class StockService extends BaseService
|
||||
$amount -= $stockEntry->amount;
|
||||
}
|
||||
else
|
||||
{ // Stock entry amount is > than needed amount -> split the stock entry resp. update the amount
|
||||
{
|
||||
// Stock entry amount is > than needed amount -> split the stock entry resp. update the amount
|
||||
$restStockAmount = $stockEntry->amount - $amount;
|
||||
|
||||
$logRowForLocationFrom = $this->getDatabase()->stock_log()->createRow([
|
||||
|
@@ -149,11 +149,6 @@ class UserfieldsService extends BaseService
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function getOpenApispec()
|
||||
{
|
||||
if ($this->OpenApiSpec == null)
|
||||
|
@@ -97,7 +97,6 @@ class UsersService extends BaseService
|
||||
public function SetUserSetting($userId, $settingKey, $settingValue)
|
||||
{
|
||||
$settingRow = $this->getDatabase()->user_settings()->where('user_id = :1 AND key = :2', $userId, $settingKey)->fetch();
|
||||
|
||||
if ($settingRow !== null)
|
||||
{
|
||||
$settingRow->update([
|
||||
|
Reference in New Issue
Block a user