Files
kupshop/admin/class/class.Window.php
2025-08-02 16:30:27 +02:00

736 lines
22 KiB
PHP

<?php
use Doctrine\DBAL\Types\Type;
use KupShop\AdminBundle\Admin\Actions\ActionsLocator;
use KupShop\AdminBundle\Event\WindowTabsEvent;
use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\AdminBundle\Util\WindowTabLocator;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
use Query\Operator;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class Window extends Frame
{
use DatabaseCommunication;
public const RIGHT_DELETE = 'delete';
public const RIGHT_DUPLICATE = 'duplicate';
public const RIGHT_SAVE = 'save';
protected $nameField = 'name';
protected $fields = [];
protected $defaults = [];
protected $required = [];
protected $types = [];
protected $uniques;
protected $tableName;
// Type of item to display on web
protected $show_on_web;
protected $action;
protected $ID;
protected $type;
protected $tabs;
protected $custom_data;
protected $processedData;
public function getTemplate()
{
if (empty($this->template)) {
$name = lcfirst($this->getClassName());
return "window/{$name}.tpl";
}
return $this->template;
}
public function createSQLFields($tablename)
{
$defaults = [];
$required = [];
$fields = [];
$types = [];
$conn = $this->getDbalConnection();
$tm = $conn->getSchemaManager();
$columns = $tm->listTableColumns($tablename);
foreach ($columns as $column) {
$name = $column->getName();
$fields[] = $name;
$defaults[$name] = $column->getDefault();
$required[$name] = ($column->getNotNull() && is_null($defaults[$name]));
$types[$name] = $column->getType()->getName();
}
$this->defaults = array_merge($defaults, $this->defaults);
$this->required = array_merge($required, $this->required);
$this->fields = array_merge($fields, $this->fields);
$this->types = array_merge($types, $this->types);
}
protected function collectVariables()
{
$acn = $this->getAction();
if ($acn == 'erased') {
return self::get_vars();
}
return $this->get_vars();
}
public function get_vars()
{
$vars = parent::get_vars();
$acn = $this->getAction();
$pageVars = [
'acn' => $acn,
];
$ID = $this->getID();
if (($acn == 'edit' && !empty($ID)) || $acn == 'add') {
if ($acn == 'edit' || ($acn == 'add' && $this->isDuplicate() && !empty($ID))) {
$pageVars['data'] = $this->getObject();
if (getVal('Submit')) {
$data = $this->getProcessedData();
$pageVars['data'] = array_merge($pageVars['data'], $data);
}
if (!empty($this->isDuplicate())) {
$this->getFields();
$this->duplicateObject($pageVars['data']);
$pageVars['duplicate'] = true;
}
} elseif ($acn == 'add') {
$pageVars['data'] = $this->createObject();
}
}
$vars['type'] = $this->getType();
$flap = getVal('flap', null, 1);
$vars['header']['flap'] = empty($flap) ? 1 : $flap;
$vars['header']['flap_next'] = getVal('flap_next');
$vars['header']['force_resize'] = getVal('force_resize');
$vars['tabs'] = $this->getTabs();
$vars['body'] = $pageVars;
$vars['actionsLocator'] = ServiceContainer::getService(ActionsLocator::class);
return $vars;
}
protected function getType()
{
if (empty($this->type)) {
$type = getVal('s');
if (!$type) {
$type = lcfirst($this->getClassName());
} else {
$type = substr($type, 0, -4);
}
$this->type = $type;
}
return $this->type;
}
public function setType($type)
{
$this->type = $type;
}
public function translateType()
{
// try to translate type to czech/english
$type = $this->getType();
// preferred:
// e.g. $txt_str['deliveryPriceLists']['navigation'] = 'Ceníky dopravy'
if ($typeName = translate('navigation', $type, true)) {
return $typeName;
}
// e.g. $txt_str['navigation']['deliveryDelivery'] = 'Dopravy'
if ($typeName = translate($type, 'navigation', true)) {
return $typeName;
}
return $type;
}
protected function getFields()
{
if (empty($this->fields)) {
$this->createSQLFields($this->getTableName());
}
}
protected function getUniques($table = null)
{
if (!is_null($this->uniques)) {
return $this->uniques;
}
if (empty($table)) {
$table = $this->getTableName();
}
$conn = $this->getDbalConnection();
$tm = $conn->getSchemaManager();
$indexes = $tm->listTableIndexes($table);
$this->uniques = [$this->nameField => true];
foreach ($indexes as $index) {
if ($index->isUnique() == true && $index->getName() != 'PRIMARY') {
if (isset($index->getColumns()[1])) {
$this->uniques[$index->getColumns()[1]] = true;
} else {
$this->uniques[$index->getName()] = true;
}
}
}
return $this->uniques;
}
public function getData()
{
$data = getVal('data', null, []);
if (!empty($data)) {
$data['ID'] = getVal('ID');
}
return $data;
}
/** Process all POSTed data from form. Transform data to SQL fields array */
public function processFormData()
{
return $this->getData();
}
final public function getProcessedData()
{
if (isset($this->processedData)) {
return $this->processedData;
}
return $this->processedData = $this->processFormData();
}
public function setCustomData($data)
{
$this->updateSQL($this->getTableName(), [
'data' => empty($data) ? null : json_encode($data),
], ['id' => $this->getID()]);
// reset custom data cache
$this->custom_data = null;
}
public function getCustomData()
{
if (empty($this->custom_data)) {
$object = $this->getObject();
$this->unserializeCustomData($object);
$this->custom_data = $object['data'];
}
return $this->custom_data;
}
public function getTableName()
{
if (empty($this->tableName)) {
$this->tableName = strtolower($this->getClassName());
}
return $this->tableName;
}
private function duplicateObject(&$data)
{
$uniques = $this->getUniques();
foreach ($uniques as $key => $value) {
if (isset($data[$key])) {
if ($this->required[$key] == false) {
$data[$key] = null;
} else {
$data[$key] = stringCopy(trim($data[$key]));
}
}
}
}
protected function createObject()
{
$data = $this->getData();
return array_merge($this->defaults, $data);
}
protected function getObject()
{
$object = $this->fetchObject($this->getTableName(), $this->getID());
if (!$object && $this->getAction() !== 'add') {
$errStr = sprintf(translate('errorNotFound', 'base'), $this->translateType(), $this->getID());
throw new NotFoundHttpException($errStr);
}
return $object;
}
public function getObjectData()
{
return $this->getObject();
}
// Vypisovani aktivit
protected function activityMessage($name, array $additionalData = [])
{
$acn = $this->getAction();
if ($acn == 'add') {
addActivityLog(ActivityLog::SEVERITY_NOTICE, ActivityLog::TYPE_CHANGE, sprintf(translate('activityAdded'), $name),
[
...ActivityLog::addObjectData([$this->getID() => $name], $this->getType()),
...$additionalData,
]);
} elseif ($acn == 'edit') {
addActivityLog(ActivityLog::SEVERITY_NOTICE, ActivityLog::TYPE_CHANGE, sprintf(translate('activityEdited'), $name),
[
...ActivityLog::addObjectData([$this->getID() => $name], $this->getType()),
...$additionalData,
]);
}
}
protected function getAdditionalActivityData(): array
{
return [];
}
public function handle()
{
try {
$acn = $this->getAction();
$ID = $this->getID();
if ($acn == 'add') {
$this->tryRights('ADD');
}
if ($acn == 'edit') {
$this->tryRights('EDIT');
}
if ((($acn == 'edit' && (strlen($ID) > 0)) || $acn == 'add') && getVal('Submit')) {
$data = $this->getProcessedData();
$missing = $this->checkRequired($data);
if (!$missing) {
$activityAdditionalData = $this->getAdditionalActivityData();
$SQL = $this->handleUpdate();
if ($SQL) {
if ((empty($this->getErrors()) && empty($this->getHTMLErrors())) || $acn == 'add') {
if (isset($data[$this->nameField])) {
$this->activityMessage($data[$this->nameField], $activityAdditionalData);
}
$this->returnOK();
}
} else {
$ErrStr = getTextString('status', 'scripterror');
$this->returnError($ErrStr);
}
} else {
$ErrStr = getTextString('base', 'errorNotAllValid');
$ErrStr .= join(',', $missing);
$this->addError($ErrStr);
}
} elseif ($acn == 'erase' && !empty($ID)) {
$this->tryRights('ERASE');
$this->handleDelete();
} else {
$this->handleTabs();
parent::handle();
}
} catch (Doctrine\DBAL\DBALException $e) {
$this->handleException($e);
}
}
public function handleException($e)
{
if (intval($e->getPrevious()->errorInfo[1]) === 1062) {
$ErrStr = 'Duplicitní ';
$badFields = $this->getBadValues($this->getTableName(), ['id' => $this->ID]);
foreach ($badFields as $value) {
$ErrStr .= '{'.$value.'} ';
}
if (!$badFields) {
$ErrStr = "Duplicitní záznam: \n".$e->getMessage();
}
$this->addError($ErrStr);
} elseif (intval($e->getPrevious()->getCode()) === 45000) {
$msg = $e->getPrevious()->getMessage();
$this->addError(mb_substr($msg, mb_strpos($msg, 'EAN')));
} elseif (in_array(intval($e->getPrevious()->getErrorCode()), [1205, 1213])) {
$this->addError('Chyba při zpracování požadavku. Zkuste to prosím znovu.');
$sentryLogger = ServiceContainer::getService(SentryLogger::class);
$data = json_encode($this->getAllSQLProcesses());
$sentryLogger->captureException(new Exception('Deadlock v administraci!', 1205, $e), ['extra' => ['processes' => $data]]);
} else {
throw $e;
}
}
// Kontroluje, jestli v datech z formulare jsou vsechny povinne polozky
public function checkRequired($data)
{
$this->getFields();
$required = [];
foreach ($data as $key => $value) {
if (!is_array($value)) {
$value = trim($value);
}
if (isset($this->required[$key])
&& $this->required[$key] == true
&& $value === ''
&& ((@$this->types[$key] != 'string' && @$this->types[$key] != 'simple_array' && @$this->types[$key] != 'text') || $key == $this->nameField)
) {
$required[] = $key;
}
}
return $required;
}
// Obecna funkce pro update & insert
public function handleUpdate()
{
$acn = $this->getAction();
$SQL = null;
if ($acn == 'add') {
$sqlFields = $this->getSQLFields();
$this->insertSQL($this->getTableName(), $sqlFields);
$SQL = true;
if (empty($ID)) {
if (isset($sqlFields['id'])) {
$this->setID($sqlFields['id']);
} else {
$this->setID(sqlInsertID());
}
}
} elseif ($acn == 'edit') {
$sqlFields = $this->getSQLFields();
$this->updateSQL($this->getTableName(), $sqlFields, ['id' => $this->getID()]);
if (isset($sqlFields['id'])) {
$this->setID($sqlFields['id']);
}
$SQL = true;
}
// reset custom data cache
$this->custom_data = null;
$this->handleTabs(true);
return $SQL;
}
public function forceUpdate()
{
try {
// get actual action
$action = $this->getAction();
// change action to edit
$this->action = 'edit';
// do update
$_REQUEST['Submit'] = 'OK';
$this->createSQLFields($this->getTableName());
$result = $this->handleUpdate();
// return action
$this->action = $action;
return $result;
} catch (Doctrine\DBAL\DBALException $e) {
$this->handleException($e);
return false;
}
}
public function getIdFromDatabase($field)
{
$id = returnSQLResult('SELECT id FROM '.getTableName($this->getTableName())." WHERE {$field[0]}='{$field[1]}' ");
return $id;
}
public function getSQLFields($data = null, $fields = null, $defaults = null, $types = null)
{
if ($data == null) {
$data = $this->getProcessedData();
}
if ($fields == null) {
$fields = $this->fields;
}
if ($defaults == null) {
$defaults = $this->defaults;
}
if ($types == null) {
$types = $this->types;
}
$sqlField = [];
foreach ($fields as $row) {
if (array_key_exists($row, $data) && !is_array($data[$row])) {
if (!is_null($data[$row])) {
$data[$row] = trim($data[$row]);
}
if (array_key_exists($row, $types)) {
$type = $types[$row];
if (($type == Type::DECIMAL) || ($type == Type::FLOAT)) {
$this->preparePrice($data[$row]);
}
}
if (isset($data[$row]) && ($data[$row] === '') && (@$defaults[$row] === null) && @!$this->required[$row]) {
$sqlField[$row] = null;
} else {
$sqlField[$row] = $data[$row];
}
}
}
return $sqlField;
}
// Smazani polozky
public function handleDelete()
{
if ($this->nameField) {
$name = sqlQueryBuilder()->select($this->nameField)->from($this->getTableName())
->andWhere(Operator::equals(['id' => $this->getID()]))
->execute()->fetchOne();
if ($logMessage = translate('activityDeleted', $this->getType(), true)) {
$logMessage = sprintf($logMessage, $name);
} else {
$logMessage = translate('activityDeleted', 'status'); // 'Deleted %s: %s'
$logMessage = sprintf($logMessage, $this->getType(), $name);
}
}
try {
$res = sqlQueryBuilder()->delete($this->getTableName())
->andWhere(Operator::equals(['id' => $this->getID()]))
->execute();
if ($res && !empty($logMessage)) {
addActivityLog(ActivityLog::SEVERITY_WARNING, ActivityLog::TYPE_CHANGE, $logMessage);
}
} catch (Doctrine\DBAL\DBALException $e) {
switch (intval($e->getPrevious()->errorInfo[1])) {
case 1451:
$ErrStr = 'Tento objekt je použit a nelze ho smazat.';
$this->returnError($ErrStr);
break;
default:
$this->handleException($e);
}
}
throw new \KupShop\KupShopBundle\Exception\RedirectException("launch.php?s={$this->getName()}.php&acn=erased");
}
public function hasRights($name = null)
{
switch ($name) {
case self::RIGHT_DUPLICATE:
if ($this->getAction() == 'edit' && $this->getID() != null) {
return true;
}
break;
case self::RIGHT_SAVE:
// Kdyz mam READ a nemam EDIT, tak nezobrazim save button
if (UserRights::hasRights($this->getRightsType(), 'READ') && !UserRights::hasRights($this->getRightsType(), 'EDIT')) {
return false;
}
return true;
default:
return true;
}
return true;
}
public function getBadValues($table = null, $rowIdentifier = [])
{
if (!empty($table)) {
$uniques = $this->getUniques($table);
} else {
$table = $this->getTableName();
$uniques = [];
}
$where = '';
foreach ($rowIdentifier as $key => $value) {
$where .= " AND {$key}!=:{$key}";
}
$data = $this->getData();
$badFields = [];
foreach ($uniques as $key => $value) {
if ($value) {
if (isset($data[$key])) {
$SQL = returnSQLResult('SELECT COUNT(*) FROM '.getTableName($table)." WHERE {$key}=:{$key} {$where}",
array_merge($data, $rowIdentifier));
if ($SQL > 0) {
$badFields[] = $key;
}
}
}
}
return $badFields;
}
public function redirect($params = [])
{
parent::redirect(array_merge(['ID' => $this->getID(), 'acn' => 'edit'], $params));
}
public function getShowOnWeb()
{
if ($this->show_on_web == null) {
return false;
}
if ($this->getID() === null) {
return null;
}
return ['type' => $this->show_on_web, 'id' => $this->getID()];
}
public function getNameField()
{
return $this->nameField;
}
public function handleTabs($update = false)
{
$tabs = $this->getTabs();
foreach ($tabs as $tab) {
$tab->setID($this->getID());
if ($update) {
$tab->handleUpdate();
// reset custom data cache
$this->custom_data = null;
} else {
$tab->handle();
}
}
}
/**
* Return WindowTabs.
*/
protected function getTabs()
{
if (!is_null($this->tabs)) {
return $this->tabs;
}
$windowTabLocator = ServiceContainer::getService(WindowTabLocator::class, 0);
$dispatcher = ServiceContainer::getService('event_dispatcher');
$tabsEvent = new WindowTabsEvent();
$dispatcher->dispatch($tabsEvent, WindowTabsEvent::NAME_PREFIX.$this->getType());
$tabs = array_merge(
$windowTabLocator ? $windowTabLocator->getTabs($this->getType()) : [],
$tabsEvent->getTabs()
);
foreach ($tabs as &$tab) {
$tab->setWindow($this);
}
return $this->tabs = $tabs;
}
public function handleGetActionSnippet()
{
$actionName = getVal('action');
/** @var $massActionsLocator ActionsLocator */
$actionsLocator = ServiceContainer::getService(ActionsLocator::class);
$action = $actionsLocator->getServiceByActionClassName($actionName);
$smarty = createSmarty(true, true);
$smarty->assign($action->getVars());
$smarty->display($action->getTemplate());
exit;
}
public function handleExecuteAction()
{
$actionName = getVal('action');
/** @var $massActionsLocator ActionsLocator */
$actionsLocator = ServiceContainer::getService(ActionsLocator::class);
$action = $actionsLocator->getServiceByActionClassName($actionName);
$action->setWindow($this);
$data = $this->getData();
$result = $action->execute($data, getVal('config', $_POST, []), $this->getType());
if ($result->isSuccessful()) {
if ($result->getHTMLMessage()) {
$this->action = 'edit';
$this->addHTMLError($result->getHTMLMessage());
return;
}
$params = ['action' => null];
if ($result->getRedirect()) {
$params = array_merge($params, $result->getRedirect());
}
$this->returnOK(!empty($result->getMsg()) ? $result->getMsg() : ('Akce ['.$action->getName().'] byla provedena.'),
false,
$params);
} else {
$this->redirect(['acn' => 'edit',
'action' => null,
'ErrStr' => !empty($result->getMsg()) ? $result->getMsg() : ('Akce ['.$action->getName().'] se nezdařila.')]);
}
}
protected function getAllSQLProcesses()
{
return sqlQuery('SHOW FULL PROCESSLIST')->fetchAllAssociative();
}
}