vendor\project-biz\database-bundle\src\Database\GenericRepository.php line 1843

Open in your IDE?
  1. <?php
  2. namespace ProjectBiz\DatabaseBundle\Database;
  3. use DateTime;
  4. use Doctrine\DBAL\Connection;
  5. use Doctrine\DBAL\Result;
  6. use Doctrine\DBAL\Query\QueryBuilder;
  7. use Exception;
  8. use ProjectBiz\UserBundle\Service\SecurityContextWrapper;
  9. use ProjectBiz\DatabaseBundle\Database\Criteria\CriteriaBuilderInterface;
  10. use ProjectBiz\DatabaseBundle\Database\Criteria\CriteriaComparison;
  11. use ProjectBiz\DatabaseBundle\Database\Criteria\CriteriaComposite;
  12. use ProjectBiz\DatabaseBundle\Database\Criteria\CriteriaConstant;
  13. use ProjectBiz\DatabaseBundle\Database\Criteria\CriteriaHasRights;
  14. use ProjectBiz\DatabaseBundle\Database\Criteria\CriteriaUnmappedColumn;
  15. use ProjectBiz\DatabaseBundle\Database\Platform\MySqlPlatform;
  16. use ProjectBiz\DatabaseBundle\Database\Platform\SqlServerPlatform;
  17. use ProjectBiz\DatabaseBundle\Exceptions\GenericRepositoryDeleteException;
  18. use ProjectBiz\DatabaseBundle\Exceptions\RequiredColumnNotReadableException;
  19. use ProjectBiz\DatabaseBundle\Exceptions\RequiredColumnNotWriteableException;
  20. use ProjectBiz\PortalBundle\Exceptions\UnaccessibleObjectException;
  21. use ProjectBiz\MailerBundle\Notification\MailNotifier;
  22. use ProjectBiz\PortalBundle\Exceptions\PortalException;
  23. use ProjectBiz\PortalBundle\Portal\Messages;
  24. use ProjectBiz\DatabaseBundle\Database\GenericRepositoryFactory;
  25. use Psr\Log\LoggerInterface;
  26. use Symfony\Component\EventDispatcher\EventDispatcher;
  27. use Symfony\Component\DependencyInjection\ContainerInterface;
  28. use Symfony\Component\HttpFoundation\RequestStack;
  29. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  30. use ProjectBiz\PortalBundle\Service\StandardLogHeader;
  31. class GenericRepository extends EventDispatcher
  32. {
  33.     const msgUnsupportedPlatform 'Die Datenbank-Plattform "%platform%" wird nicht unterstützt.';
  34.     const msgAncestorNotFound 'Urahn konnte nicht gefunden werden.';
  35.     const msgLockingColumnsNotWriteable 'Die Spalten zum Sperren des Objekts dürfen nicht geschrieben werden.';
  36.     const msgErrorClone 'Das Klonen des Objekts %id% in der Tabelle "%table%" ist fehlgeschlagen.';
  37.     const msgNoAccess 'Auf das Objekt konnte nicht zugegriffen werden.';
  38.     const msgUnreadableRequired '%column% wird benötigt, ist aber nicht lesbar.';
  39.     const msgMissingColDefs 'Fehlende Spaltendefinitionen';
  40.     const msgMissingColDefFor 'Fehlende Spaltendefinition für %column%';
  41.     const msgMissingParentReleaseParameter 'Eigenschaft parent_release_parameter fehlt.';
  42.     const msgAdminRightsDenied 'Die Zuweisung von ausgewählten Rechten ist nicht erlaubt.';
  43.     const msgMissingWritePermissions 'Sie haben nicht die erforderliche Berechtigung um diesen Datensatz zu speichern.';
  44.     private static $parameterIndex 1;
  45.     private        $db;
  46.     private        $user;
  47.     private        $securityContextWrapper;
  48.     private        $tableInfo;
  49.     private        $options;
  50.     private        $prefix;
  51.     private        $tableHelper;
  52.     private        $logger;
  53.     private        $platform;
  54.     private        $dateTimeFactory;
  55.     private        $mailNotifier;
  56.     private        $repoFactory;
  57.     private        $dateFormat;
  58.     private        $dateTimeFormat;
  59.     private        $schemaCache;
  60.     private        $request;
  61.     private        $container;
  62.     /**
  63.      * Construct.
  64.      *
  65.      * @param Connection $db
  66.      * @param TableInfoInterface $tableInfo
  67.      * @param GenericRepositoryOptions $options
  68.      * @param string $prefix
  69.      * @param TableHelper $tableHelper
  70.      * @param LoggerInterface $logger
  71.      *
  72.      * @throws PortalException
  73.      */
  74.     public function __construct(
  75.         Connection $db,
  76.         SecurityContextWrapper $securityContextWrapper,
  77.         TableInfoInterface $tableInfo,
  78.         GenericRepositoryOptions $options,
  79.         string $prefix,
  80.         TableHelper $tableHelper,
  81.         LoggerInterface $logger,
  82.         DateTimeFactory $dateTimeFactory,
  83.         MailNotifier $mailNotifier,
  84.         GenericRepositoryFactory $repoFactory,
  85.         $dateFormat,
  86.         $dateTimeFormat,
  87.         DatabaseSchemaCache $schemaCache,
  88.         RequestStack $requestStack,
  89.         ContainerInterface $container
  90.     )
  91.     {
  92.         $this->db $db;
  93.         $this->securityContextWrapper $securityContextWrapper;
  94.         $this->user $securityContextWrapper->getUser();
  95.         $this->tableInfo $tableInfo;
  96.         $this->options $options;
  97.         $this->prefix $prefix;
  98.         $this->tableHelper $tableHelper;
  99.         $this->logger $logger;
  100.         $this->dateTimeFactory $dateTimeFactory;
  101.         $this->mailNotifier $mailNotifier;
  102.         $this->repoFactory $repoFactory;
  103.         $this->dateFormat $dateFormat;
  104.         $this->dateTimeFormat $dateTimeFormat;
  105.         $this->schemaCache $schemaCache;
  106.         $this->request $requestStack->getCurrentRequest();
  107.         $this->container $container;
  108.     }
  109.     /**
  110.      * @return Connection The database connection used by this repository
  111.      */
  112.     public function getConnection()
  113.     {
  114.         return $this->db;
  115.     }
  116.     public function findAll(
  117.         array $sort null,
  118.         array $view null,
  119.         $hide_deleted true,
  120.         $group_by_version true,
  121.         $join_refs true,
  122.         $limit null,
  123.         $offset null
  124.     )
  125.     {
  126.         $processedView $this->processView($view);
  127.         $builder $this->findByQuery(
  128.             $columnMap,
  129.             null,
  130.             $sort,
  131.             $processedView,
  132.             $limit,
  133.             $offset,
  134.             $hide_deleted,
  135.             $group_by_version,
  136.             $join_refs
  137.         );
  138.         $select_columns $this->buildSelectColumns($columnMap$processedView);
  139.         if (count($select_columns) > 0) {
  140.             return $builder->select($select_columns)
  141.                 ->execute();
  142.         }
  143.         return null;
  144.     }
  145.     public function findSecondaryBy(
  146.         CriteriaBuilderInterface $filter,
  147.         array $sort null,
  148.         array $view null,
  149.         $limit null,
  150.         $offset null,
  151.         $hide_deleted true,
  152.         $group_by_version true,
  153.         $join_refs true
  154.     )
  155.     {
  156.         /* @todo:   this method should not be in this class; it currently is because of the tight coupling
  157.          *          with the column lookup.
  158.          */
  159.         $processedView $this->processView($view);
  160.         $builder $this->findByQuery(
  161.             $columnMap,
  162.             $filter,
  163.             $sort,
  164.             $processedView,
  165.             $limit,
  166.             $offset,
  167.             $hide_deleted,
  168.             $group_by_version,
  169.             $join_refs
  170.         );
  171.         $builder->resetQueryPart('orderBy'); // MSSQL doesn't like COUNT() with ORDER BY
  172.         $select_columns $this->buildSecondarySelectColumns($processedView);
  173.         if (count($select_columns) > 0) {
  174.             return $builder->select($select_columns)
  175.                 ->execute();
  176.         }
  177.         return null;
  178.     }
  179.     private function processView($view)
  180.     {
  181.         $unprocessedView $this->useAllOrGiven($view);
  182.         $unprocessedView $this->addRequiredColumnsAndPrimaryKey($unprocessedView);
  183.         $processedView = [];
  184.         foreach ($unprocessedView as $column) {
  185.             $splitColumn explode(':'$column);
  186.             if ($this->getTableInfo()
  187.                 ->isReadable($splitColumn[0], $this->getUserRights())
  188.             ) {
  189.                 if (!array_key_exists($splitColumn[0], $processedView)) {
  190.                     $processedView[$splitColumn[0]] = [];
  191.                 }
  192.                 if (count($splitColumn) === 2) {
  193.                     $processedView[$splitColumn[0]][] = $splitColumn[1];
  194.                 } else {
  195.                     if (count($splitColumn) > 2) {
  196.                         // @todo: create meaningful Exception
  197.                         throw new Exception();
  198.                     }
  199.                 }
  200.             } else {
  201.                 if ($this->isRequired($splitColumn[0])) {
  202.                     throw new PortalException(
  203.                         self::msgUnreadableRequired,
  204.                         ['%column%' => $splitColumn[0]]
  205.                     );
  206.                 }
  207.             }
  208.         }
  209.         return $processedView;
  210.     }
  211.     private function useAllOrGiven($view)
  212.     {
  213.         if ((null === $view) || !is_array($view)) {
  214.             return $this->tableInfo->getColumns();
  215.         }
  216.         return $view;
  217.     }
  218.     private function addRequiredColumnsAndPrimaryKey($view)
  219.     {
  220.         if ((null === $view) || !is_array($view)) {
  221.             $view = [];
  222.         }
  223.         $requiredColumns $this->getRequiredColumns();
  224.         foreach ($requiredColumns as $column) {
  225.             $view[] = $column;
  226.         }
  227.         $view[] = $this->getPrimaryKey();
  228.         return array_unique($view);
  229.     }
  230.     private function getRequiredColumns()
  231.     {
  232.         $requiredColumns $this->getOptions()
  233.             ->getOption('required_columns');
  234.         if (null !== $requiredColumns) {
  235.             return $requiredColumns;
  236.         }
  237.         return [];
  238.     }
  239.     /**
  240.      * @return GenericRepositoryOptions
  241.      *
  242.      * Mainly used (in 3 locations to get the system-columns)
  243.      */
  244.     public function getOptions()
  245.     {
  246.         return $this->options;
  247.     }
  248.     /**
  249.      * Get the primary key for the repository
  250.      *
  251.      * @return string The primary key for the repository
  252.      *
  253.      */
  254.     public function getPrimaryKey()
  255.     {
  256.         return $this->getOptions()
  257.             ->getOption('primary_key');
  258.     }
  259.     public function getTableInfo()
  260.     {
  261.         return $this->tableInfo;
  262.     }
  263.     /**
  264.      * @return string
  265.      */
  266.     private function getUserRights()
  267.     {
  268.         return $this->securityContextWrapper->getUserRights();
  269.     }
  270.     private function isRequired($column)
  271.     {
  272.         $requiredColumns $this->getOptions()
  273.             ->getOption('required_columns');
  274.         if (!is_array($requiredColumns)) {
  275.             return false;
  276.         }
  277.         return in_array($column$requiredColumns);
  278.     }
  279.     /**
  280.      * @param $columnMap
  281.      * @param CriteriaBuilderInterface|null $filter
  282.      * @param array|null $sort
  283.      * @param null $processedView
  284.      * @param null $limit
  285.      * @param null $offset
  286.      * @param bool|true $hide_deleted
  287.      * @param bool|true $group_by_version
  288.      * @param bool|true $join_refs
  289.      * @param bool|true $hide_archived
  290.      * @param string $mt_name
  291.      * @return QueryBuilder
  292.      * @throws Exception
  293.      */
  294.     public function findByQuery(
  295.         &$columnMap,
  296.         CriteriaBuilderInterface $filter null,
  297.         array $sort null,
  298.         $processedView null,
  299.         $limit null,
  300.         $offset null,
  301.         $hide_deleted true,
  302.         $group_by_version true,
  303.         $join_refs true,
  304.         $hide_archived true,
  305.         $mt_name 'mt'
  306.     )
  307.     {
  308.         if (null === $processedView) {
  309.             $processedView $this->processView(null);
  310.         }
  311.         $tablename $this->tableInfo->getInternalTablename();
  312.         $builder $this->db->createQueryBuilder();
  313.         $this->tableInfo->buildFrom($builder$mt_name);
  314.         $context = [
  315.             'builder' => $builder,
  316.             'where_used' => false
  317.         ];
  318.         if (null !== $limit) {
  319.             $builder->setMaxResults($limit);
  320.         }
  321.         if (null !== $offset) {
  322.             $builder->setFirstResult($offset);
  323.         }
  324.         $columnMap $this->buildColumnMapForJoins($builder$processedView$join_refs$mt_name);
  325.         /*
  326.          * Users can only see a document if they have the rights to see the _type_ of the document.
  327.          */
  328.         if ($tablename == $this->prefix 'Document' && !$this->getUser()->isAdmin()) {
  329.             $builder
  330.                 ->innerJoin(
  331.                     $mt_name,
  332.                     $this->prefix 'DocumentType',
  333.                     'doctype',
  334.                     '('.$mt_name.'.Document_Link_DocumentType_ID = doctype.DocumentType_ID'
  335.                     ' and (doctype.DocumentType_PermissionRead & ' $this->getUserRights() . ') > 0)'
  336.                 );
  337.         }
  338.         if ($group_by_version && $this->options->useVersioning()) {
  339.             $ancestorColumn $this->options->getAncestorColumn();
  340.             $versionColumn $this->options->getVersionColumn();
  341.             $builder
  342.                 ->leftJoin(
  343.                     $mt_name,
  344.                     $tablename,
  345.                     't2',
  346.                     '(' $mt_name '.'
  347.                     $ancestorColumn
  348.                     " = t2."
  349.                     $ancestorColumn
  350.                     ' AND ' $mt_name '.'
  351.                     $versionColumn
  352.                     ' < t2.'
  353.                     $versionColumn
  354.                     ')'
  355.                 )
  356.                 ->where('t2.' $this->getPrimaryKey() . ' IS NULL');
  357.             $context['where_used'] = true;
  358.             if ($hide_deleted && $this->options->useDeleted()) {
  359.                 $builder->andWhere($mt_name '.' $this->options->getDeletedColumn() . " <> 1");
  360.             }
  361.             if ($hide_archived && $this->options->useArchived()) {
  362.                 $builder->andWhere($mt_name '.' $this->options->getArchivedColumn() . " <> 1");
  363.             }
  364.             /*
  365.             Add filter and sorting
  366.             */
  367.             if (null !== $filter) {
  368.                 $criteria $filter->buildCriteria($builder$columnMap$processedView$mt_name);
  369.                 if ($criteria) {
  370.                     $builder->andWhere($criteria);
  371.                 }
  372.             }
  373.         } else {
  374.             $useAndWhere false;
  375.             if ($hide_deleted && $this->options->useDeleted()) {
  376.                 $useAndWhere true;
  377.                 $context['where_used'] = true;
  378.                 $builder->where($mt_name '.' $this->options->getDeletedColumn() . " <> 1");
  379.             }
  380.             if ($hide_archived && $this->options->useArchived()) {
  381.                 $useAndWhere true;
  382.                 $context['where_used'] = true;
  383.                 $builder->andWhere($mt_name '.' $this->options->getArchivedColumn() . " <> 1");
  384.             }
  385.             /*
  386.             Add filter and sorting
  387.             */
  388.             if (null !== $filter) {
  389.                 $criteria $filter->buildCriteria($builder$columnMap$processedView$mt_name);
  390.                 if ($useAndWhere) {
  391.                     if ($criteria) {
  392.                         $builder->andWhere($criteria);
  393.                     }
  394.                 } else {
  395.                     if ($criteria) {
  396.                         $builder->where($criteria);
  397.                         $context['where_used'] = true;
  398.                     }
  399.                 }
  400.             }
  401.         }
  402.         /*
  403.          * Consider the record rights.
  404.          *
  405.          * A user can see a record if he is the owner or the role matches.
  406.          */
  407.          $user $this->getUser();
  408.          if (isset($user)) {
  409.               if (($this->options->useReadRecordRights() || $this->options->useOwner()) && !$this->getUser()->isAdmin()) {
  410.                   $rightsCriterias = [];
  411.                   if ($this->options->useReadRecordRights()) {
  412.                       $readRecordRightsCriteria = new CriteriaHasRights($this->options->getReadRecordRightsColumn(), $this->getUser()->getRights()->getReadRecordRights());
  413.                       $user=   $this->getUser()->getRights()->getReadRecordRights();
  414.                       $rightsCriterias[] = $readRecordRightsCriteria->buildCriteria($builder$columnMap$processedView);
  415.                   }
  416.                   if ($this->options->useOwner()) {
  417.                       $ownerCriteria = new CriteriaComparison('=', new CriteriaUnmappedColumn($this->options->getOwnerColumn()), new CriteriaConstant($this->getUser()->getId()));
  418.                       $rightsCriterias[] = $ownerCriteria->buildCriteria($builder$columnMap$processedView);
  419.                   }
  420.                   $rightsCriteriaExpression call_user_func_array([$builder->expr(), 'orX'], $rightsCriterias);
  421.                   if ($context['where_used']) {
  422.                       $builder->andWhere($rightsCriteriaExpression);
  423.                   } else {
  424.                       $builder->where($rightsCriteriaExpression);
  425.                       $context['where_used'] = true;
  426.                   }
  427.               }
  428.         }
  429.         $defaultSorting $this->options->getOption('default_sorting');
  430.         $this->applySorting($builder$sort$defaultSorting$columnMap$processedView);
  431.         $this->dispatch(new RepositoryEvent($this$context), RepositoryEvents::FIND_BEFORE_EXECUTE);
  432.         return $builder;
  433.     }
  434.     private function buildColumnMapForJoins(QueryBuilder $builder$processedView$join_refs true$mt_name 'mt')
  435.     {
  436.         $columnMap = [];
  437.         if ($join_refs) {
  438.             $columnDefinitions $this->getColumnDefinitions();
  439.             $viewKeys array_keys($processedView);
  440.             foreach ($viewKeys as $column) {
  441.                 $col = new Column($column'view'$mt_name);
  442.                 $selectName $col->getSelectName();
  443.                 $defName $col->getDefinitionName();
  444.                 if (isset($columnDefinitions[$defName])) {
  445.                     $colDef $columnDefinitions[$defName];
  446.                     $source $colDef->getSource();
  447.                     if (null !== $source) {
  448.                         /*
  449.                          * @todo: Include joins of Syn-Tables!
  450.                          */
  451.                         $tname self::getNextParameterName();
  452.                         $sourceTableInfo $this->tableHelper->getTableInfo($source['table']);
  453.                         $sourceView = (null === $source['view'])
  454.                             ?[]
  455.                             :$source['view'];
  456.                         $columnMap[$column] = [
  457.                             'table' => $tname,
  458.                             'tablename' => $sourceTableInfo->getTablename(),
  459.                             'display' => $source['display_column'],
  460.                             'view' => array_unique(array_merge($sourceView$processedView[$column]))
  461.                         ];
  462.                         if ($colDef->isVirtual()) {
  463.                             $this->getPlatform()
  464.                                 ->refsBuildColumnMapForJoins(
  465.                                     $colDef->getType(),
  466.                                     $builder,
  467.                                     $this->tableInfo->getTablename(),
  468.                                     $this->tableInfo->getPrimaryKey(),
  469.                                     $source['table'],
  470.                                     $column,
  471.                                     $tname,
  472.                                     $colDef->getReverseProperty()
  473.                                 );
  474.                         } else {
  475.                             $condition sprintf(
  476.                                 "%s = %s.%s",
  477.                                 $selectName,
  478.                                 $tname,
  479.                                 $source['foreign_key']
  480.                             );
  481.                             $builder->leftJoin(
  482.                                 $mt_name,
  483.                                 $this->prefix $sourceTableInfo->getTablename(),
  484.                                 $tname,
  485.                                 $condition
  486.                             );
  487.                         }
  488.                     }
  489.                 }
  490.             }
  491.         }
  492.         return $columnMap;
  493.     }
  494.     /**
  495.      * @return \ProjectBiz\DatabaseBundle\Entity\ColumnDefinition[]
  496.      */
  497.     public function getColumnDefinitions()
  498.     {
  499.         return $this->getTableInfo()
  500.             ->getColumnDefinitions();
  501.     }
  502.     public static function getNextParameterName()
  503.     {
  504.         return 'p' . (++self::$parameterIndex);
  505.     }
  506.     public function getPlatform()
  507.     {
  508.         if (!isset($this->platform)) {
  509.             $platform $this->db->getDatabasePlatform();
  510.             if (is_a($platform'Doctrine\DBAL\Platforms\MySqlPlatform')) {
  511.                 $this->platform = new MySqlPlatform($this->prefix);
  512.             } else {
  513.                 if (is_a($platform'Doctrine\DBAL\Platforms\SqlServerPlatform')) {
  514.                     $this->platform = new SqlServerPlatform($this->prefix);
  515.                 } else {
  516.                     throw new PortalException(
  517.                         self::msgUnsupportedPlatform,
  518.                         ['%platform%' => $platform->getName()]
  519.                     );
  520.                 }
  521.             }
  522.         }
  523.         return $this->platform;
  524.     }
  525.     public function applySorting(
  526.         QueryBuilder $builder,
  527.         array $sort null,
  528.         array $defaultSorting null,
  529.         array $columnMap null,
  530.         $processedView null
  531.     )
  532.     {
  533.         if ((null === $sort) && (null === $defaultSorting) && !$this->getOptions()
  534.                 ->useManSort()
  535.         ) {
  536.             return;
  537.         }
  538.         $totalSort array_merge(
  539.             $sort
  540.                 $sort
  541.                 : [],
  542.             $this->getOptions()->useManSort()
  543.                 ? [
  544.                     [
  545.                         'column' => $this->getOptions()->getManSortColumn(),
  546.                         'mapped' => false
  547.                     ]
  548.                 ]
  549.                 : [],
  550.             $defaultSorting
  551.                 $defaultSorting
  552.                 : []
  553.         );
  554.         $usedColumnNames = [];
  555.         foreach ($totalSort as $sortEntry) {
  556.             $columnName $sortEntry['column'];
  557.             /*
  558.              * Make sure there are no duplicated column names for orderby clause.
  559.              * Especially sorting by column "_manSort" leads to sorting duplication.
  560.              */
  561.             {
  562.                 if (in_array($columnName$usedColumnNames)) {
  563.                     continue;
  564.                 }
  565.                 $usedColumnNames[] = $columnName;
  566.             }
  567.             $direction = isset($sortEntry['direction'])
  568.                 ?$sortEntry['direction']
  569.                 :'ASC';
  570.             $mapped = isset($sortEntry['mapped'])
  571.                 ?$sortEntry['mapped']
  572.                 :true;
  573.             if ($this->isVirtualForPropertyPath($columnName)) {
  574.                 return;
  575.             }
  576.             $parts explode(':'$columnName);
  577.             $numParts count($parts);
  578.             switch ($numParts) {
  579.                 case 1:
  580.                     $column = new Column($parts[0], 'view');
  581.                     $mappedColumn $column->getSelectName();
  582.                     if ((null !== $columnMap) && ($mapped)) {
  583.                         $mappedColumn = isset($columnMap[$parts[0]])
  584.                             ?($columnMap[$parts[0]]['table'] . '.' $columnMap[$parts[0]]['display'])
  585.                             :($column->getSelectName());
  586.                     }
  587.                     $builder->addOrderBy($mappedColumn$direction);
  588.                     break;
  589.                 case 2:
  590.                     $mappedColumn $columnMap[$parts[0]]['table'] . '.' $parts[1];
  591.                     $builder->addOrderBy($mappedColumn$direction);
  592.                     break;
  593.             }
  594.         }
  595.     }
  596.     protected function buildSecondarySelectColumns($processedView)
  597.     {
  598.         $columnDefinitions $this->getColumnDefinitions();
  599.         $selectColumns = [];
  600.         foreach ($processedView as $column => $sourceView) {
  601.             $col = new Column($column'view');
  602.             $selectName $col->getSelectName();
  603.             $defName $col->getDefinitionName();
  604.             $colDef $columnDefinitions[$defName];
  605.             $secHeader $colDef->getSecondaryHeader();
  606.             if (!empty($secHeader)) {
  607.                 $expr '';
  608.                 switch ($secHeader) {
  609.                     case 'sum':
  610.                         $expr 'SUM(' $selectName ')';
  611.                         break;
  612.                     case 'avg':
  613.                         $expr 'AVG(' $selectName ')';
  614.                         break;
  615.                     case 'min':
  616.                         $expr 'MIN(' $selectName ')';
  617.                         break;
  618.                     case 'max':
  619.                         $expr 'MAX(' $selectName ')';
  620.                         break;
  621.                     case 'count':
  622.                         $expr 'Count(' $selectName ')';
  623.                         break;
  624.                 }
  625.                 $selectColumns[] = $expr ' AS ' $column;
  626.             }
  627.         }
  628.         return $selectColumns;
  629.     }
  630.     public function countAll(
  631.         array $sort null,
  632.         array $view null,
  633.         $hide_deleted true,
  634.         $group_by_version true,
  635.         $join_refs true
  636.     )
  637.     {
  638.         $processedView $this->processView($view);
  639.         $builder $this->findByQuery(
  640.             $columnMap,
  641.             null,
  642.             $sort,
  643.             $processedView,
  644.             null,
  645.             null,
  646.             $hide_deleted,
  647.             $group_by_version,
  648.             $join_refs
  649.         );
  650.         $select_columns $this->buildSelectColumns($columnMap$processedView);
  651.         if (count($select_columns) > 0) {
  652.             return $builder->select('COUNT(*) AS COUNT')
  653.                 ->execute();
  654.         }
  655.         return 0;
  656.     }
  657.     /**
  658.      * Get the result of a specific column by sql aggregate function.
  659.      *
  660.      * @param type $column
  661.      * @return type
  662.      */
  663.     public function aggregate($column$table$aggregateFunction 'max') {
  664.         // @todo: support multiple columns at once to improve performance
  665.         $result $this->db->createQueryBuilder()
  666.             ->select($aggregateFunction '(' $column ') as aggregate_result')
  667.             ->from($this->prefix $table)
  668.             ->execute()->fetchAll();
  669.         if(count($result)>0) {
  670.             return $result[0]['aggregate_result'];
  671.         }
  672.         // if the result is empty, there are no records in the table.
  673.         return null;
  674.     }
  675.     protected function buildSelectColumns(array $columnMap$processedView)
  676.     {
  677.         $selectColumns = [];
  678.         foreach ($processedView as $viewName => $sourceView) {
  679.             $col = new Column($viewName'view');
  680.             $selectName $col->getSelectName();
  681.             $viewRefName $col->getViewRefName();
  682.             $defName $col->getDefinitionName();
  683.             if (isset($columnMap[$viewName])) {
  684.                 if ($this->getColumnDefinitions()[$defName]->isVirtual()) {
  685.                     $selectColumns[] = $this->getPlatform()
  686.                         ->refsBuildSelectColumnsData(
  687.                             $this->getColumnDefinitions()[$defName]->getType(),
  688.                             $this->tableInfo->getTablename(),
  689.                             $this->tableInfo->getPrimaryKey(),
  690.                             $columnMap[$viewName]['tablename'],
  691.                             $viewName,
  692.                             $columnMap[$viewName]['table'],
  693.                             $this->getColumnDefinitions()[$defName]->getReverseProperty()
  694.                         );
  695.                     $selectColumns[] = $this->getPlatform()
  696.                         ->refsBuildSelectColumnsDisplay(
  697.                             $this->getColumnDefinitions()[$defName]->getType(),
  698.                             $this->tableInfo->getTablename(),
  699.                             $this->tableInfo->getPrimaryKey(),
  700.                             $columnMap[$viewName]['tablename'],
  701.                             $viewName,
  702.                             $columnMap[$viewName]['table'],
  703.                             $this->getColumnDefinitions()[$defName]->getReverseProperty()
  704.                         );
  705.                 } else {
  706.                     // 1. Select original column as 'itself'
  707.                     $selectColumns[] = $selectName ' AS ' $viewName;
  708.                     // 2. Select view-columns
  709.                     foreach ($columnMap[$viewName]['view'] as $viewColumn) {
  710.                         $refTableInfo $this->tableHelper->getTableInfo($columnMap[$viewName]['tablename']);
  711.                         $refColumn = new Column($viewColumn'view');
  712.                         $refDefName $refColumn->getDefinitionName();
  713.                         if (!$refTableInfo->getColumnDefinitions()[$refDefName]->isVirtual()) {
  714.                             $selectColumns[] = $columnMap[$viewName]['table']
  715.                                 . '.'
  716.                                 $viewColumn
  717.                                 ' AS '
  718.                                 $viewRefName
  719.                                 '___'
  720.                                 $viewColumn;
  721.                         }
  722.                         else {
  723.                             $selectColumns[] = '\'nv\' AS '
  724.                                 $viewRefName
  725.                                 '___'
  726.                                 $viewColumn;
  727.                         }
  728.                     }
  729.                     // 3. Select display-column
  730.                     $selectColumns[] = $columnMap[$viewName]['table']
  731.                         . '.'
  732.                         $columnMap[$viewName]['display']
  733.                         . ' AS '
  734.                         $viewRefName
  735.                         '___display';
  736.                 }
  737.             } else {
  738.                 if (!$this->getColumnDefinitions()[$defName]->isVirtual()) {
  739.                     $selectColumns[] = $selectName ' AS ' $viewName;
  740.                 }
  741.             }
  742.         }
  743.         return $selectColumns;
  744.     }
  745.     public function countHistoryBy(
  746.         $id,
  747.         CriteriaBuilderInterface $filter null,
  748.         array $view null
  749.     )
  750.     {
  751.         if (!$this->options->useVersioning()) {
  752.             return false;
  753.         }
  754.         $processedView $this->processView($view);
  755.         $builder $this->historyByQuery(
  756.             $id,
  757.             $columnMap,
  758.             $filter,
  759.             $processedView,
  760.             null,
  761.             null
  762.         );
  763.         $builder->resetQueryPart('orderBy'); // MSSQL doesn't like COUNT() with ORDER BY
  764.         $select_columns $this->buildSelectColumns($columnMap$processedView);
  765.         if (count($select_columns) > 0) {
  766.             return $builder->select('COUNT(*) AS COUNT')
  767.                 ->execute();
  768.         }
  769.         return false;
  770.     }
  771.     public function countBy(
  772.         CriteriaBuilderInterface $filter null,
  773.         array $view null,
  774.         $limit null,
  775.         $offset null,
  776.         $hide_deleted true,
  777.         $group_by_version true,
  778.         $join_refs true
  779.     )
  780.     {
  781.         $processedView $this->processView($view);
  782.         $builder $this->findByQuery(
  783.             $columnMap,
  784.             $filter,
  785.             null,
  786.             $processedView,
  787.             $limit,
  788.             $offset,
  789.             $hide_deleted,
  790.             $group_by_version,
  791.             $join_refs
  792.         );
  793.         $builder->resetQueryPart('orderBy'); // MSSQL doesn't like COUNT() with ORDER BY
  794.         $select_columns $this->buildSelectColumns($columnMap$processedView);
  795.         if (count($select_columns) > 0) {
  796.             return $builder->select('COUNT(*) AS COUNT')
  797.                 ->execute();
  798.         }
  799.         return false;
  800.     }
  801.     public function fetchAll(Result $result null)
  802.     {
  803.         if (null === $result) {
  804.             return null;
  805.         }
  806.         $rows $result->fetchAll();
  807.         $ret = [];
  808.         foreach ($rows as $row) {
  809.             $ret[] = $this->handleRow($row);
  810.         }
  811.         return $ret;
  812.     }
  813.     protected function handleRow($row)
  814.     {
  815.         $columnDefinitions $this->getColumnDefinitions();
  816.         $ret = [];
  817.         foreach ($row as $key => $value) {
  818.             $keyParts explode('___'$key);
  819.             if (count($keyParts) > 1) {
  820.                 $vKey $keyParts[0];
  821.                 $sourceKey $keyParts[1];
  822.                 if (!isset($ret[$vKey])) {
  823.                     $ret[$vKey] = [];
  824.                 }
  825.                 $column = new Column($vKey'view');
  826.                 $baseKey substr($column->getDefinitionName(), 4);
  827.                 $sourceInfo $this->getTableInfo()->getSourceTableInfo($baseKey);
  828.                 $sourceColDefs $sourceInfo->getColumnDefinitions();
  829.                 $ret[$vKey][$sourceKey] = $this->getValueWithColDef($value$sourceKey$sourceColDefs);
  830.             } else {
  831.                 $ret[$key] = $this->getValueWithColDef($value$key$columnDefinitions);
  832.             }
  833.         }
  834.         return $ret;
  835.     }
  836.     private function getValueWithColDef($value$key$columnDefinitions) {
  837.         $column = new Column($key'view');
  838.         if (isset($columnDefinitions[$column->getDefinitionName()])) {
  839.             $colDef $columnDefinitions[$column->getDefinitionName()];
  840.             if ((($colDef->getType() == 'datetime') || ($colDef->getType() == 'date'))
  841.             && $value
  842.             ) {
  843.                 $date $this->dateTimeFactory->create(
  844.                     $this->db->getDatabasePlatform()
  845.                         ->getDateTimeFormatString(),
  846.                     $value
  847.                 );
  848.                 if ((\DateTime::getLastErrors()['warning_count'] ?? null) > 0) {
  849.                     $date false;
  850.                 }
  851.                 if ($date === false) {
  852.                     $date $this->dateTimeFactory->create(
  853.                         $this->db->getDatabasePlatform()
  854.                             ->getDateFormatString(),
  855.                         $value
  856.                     );
  857.                     if ((\DateTime::getLastErrors()['warning_count'] ?? null) > 0) {
  858.                         $date false;
  859.                     }
  860.                     if ($date !== false) {
  861.                         $date->setTime(00);
  862.                     }
  863.                 }
  864.    /* dump([
  865.         'key' => $key,
  866.         'raw' => $value,
  867.         'colDefType' => $colDef->getType(),
  868.         'dbPlatformDateTimeFormat' => $this->db->getDatabasePlatform()->getDateTimeFormatString(),
  869.         'dbPlatformDateFormat' => $this->db->getDatabasePlatform()->getDateFormatString(),
  870.     ]);*/
  871.                 if ($date === false) {
  872.                     return null;
  873.                 }
  874.                 return $date;
  875.             }
  876.                         if (in_array($key, ['MPM_initialized_Date''MPM_Last_Change'])) {
  877. }
  878.             return $value;
  879.         }
  880.         return $value;
  881.     }
  882.     public function lock($id)
  883.     {
  884.         if (!$this->options->useLocking()) {
  885.             return false;
  886.         }
  887.         if (!$this->hasUser()) {
  888.             return false;
  889.         }
  890.         if (!$this->canAccessObject($id)) {
  891.             throw new PortalException(self::msgNoAccess);
  892.         }
  893.         $ancestor 0;
  894.         $tablename $this->tableInfo->getInternalTablename();
  895.         if ($this->options->useVersioning()) {
  896.             $ancestor $this->getAncestor($id);
  897.         }
  898.         $lockedAtColumn $this->options->getLockedAtColumn();
  899.         $lockedByColumn $this->options->getLockedByColumn();
  900.         if ($this->isWriteable($lockedAtColumn) && $this->isWriteable($lockedByColumn)) {
  901.             $builder $this->db->createQueryBuilder();
  902.             $builder
  903.                 ->update($this->tableInfo->getInternalTablename(), '')
  904.                 ->set(
  905.                     $lockedAtColumn,
  906.                     $builder->createNamedParameter(
  907.                         (new DateTime())->format(
  908.                             $this->db->getDatabasePlatform()
  909.                                 ->getDateTimeFormatString()
  910.                         )
  911.                     )
  912.                 )
  913.                 ->set(
  914.                     $lockedByColumn,
  915.                     $builder->createNamedParameter(
  916.                         $this->getUser()
  917.                             ->getId()
  918.                     )
  919.                 )
  920.                 ->where(
  921.                     $builder->expr()
  922.                         ->orX(
  923.                             $builder->expr()
  924.                                 ->eq(
  925.                                     $lockedByColumn,
  926.                                     $this->getUser()
  927.                                         ->getId()
  928.                                 ),
  929.                             $builder->expr()
  930.                                 ->eq($lockedByColumn0),
  931.                             $builder->expr()
  932.                                 ->isNull($lockedByColumn)
  933.                         )
  934.                 );
  935.             if ($this->options->useVersioning()) {
  936.                 $builder->andWhere($this->options->getAncestorColumn() . " = " $ancestor);
  937.             } else {
  938.                 $builder->andWhere($this->getPrimaryKey() . " = " $builder->createNamedParameter($id));
  939.             }
  940.             $context = ['builder' => $builder'where_used' => true];
  941.             $this->dispatch(new RepositoryEvent($this$context), RepositoryEvents::LOCK_BEFORE_EXECUTE);
  942.             $result $builder->execute();
  943.             if ($result == 0) {
  944.                 return false;
  945.             }
  946.         } else {
  947.             //throw new GenericRepositoryDeleteException(1810);
  948.             throw new Exception("Locking columns not writeable.");
  949.         }
  950.         return true;
  951.     }
  952.     /**
  953.      * @return bool
  954.      */
  955.     protected function hasUser()
  956.     {
  957.         return is_a($this->user'Symfony\Component\Security\Core\User\UserInterface');
  958.     }
  959.     public function canAccessObject($id$hide_deleted true$find_latest_version true)
  960.     {
  961.         $pk $this->tableInfo->getPrimaryKey();
  962.         $criteria = new CriteriaComparison('=', new CriteriaUnmappedColumn($pk), new CriteriaConstant($id));
  963.         return (false !== $this->findOneBy($criteria, [$pk], $hide_deleted$find_latest_versionfalse));
  964.     }
  965.     public function findOneBy(
  966.         CriteriaBuilderInterface $filter,
  967.         array $view null,
  968.         $hide_deleted true,
  969.         $group_by_version true,
  970.         $join_refs true
  971.     )
  972.     {
  973.         $processedView $this->processView($view);
  974.         $builder $this->findByQuery(
  975.             $columnMap,
  976.             $filter,
  977.             null,
  978.             $processedView,
  979.             1,
  980.             0,
  981.             $hide_deleted,
  982.             $group_by_version,
  983.             $join_refs
  984.         );
  985.         $select_columns $this->buildSelectColumns($columnMap$processedView);
  986.         if (count($select_columns) > 0) {
  987.             return $this->fetch(
  988.                 $builder->select($select_columns)
  989.                     ->execute()
  990.             );
  991.         }
  992.         return false;
  993.     }
  994.     public function fetch(Result $result)
  995.     {
  996.         if (null === $result) {
  997.             return null;
  998.         }
  999.         $row $result->fetch();
  1000.         if (!$row) {
  1001.             return $row;
  1002.         }
  1003.         // Map refs
  1004.         return $this->handleRow($row);
  1005.     }
  1006.     private function getAncestor($id)
  1007.     {
  1008.         $tablename $this->tableInfo->getInternalTablename();
  1009.         $ancestorColumn $this->options->getAncestorColumn();
  1010.         $refObject $this->db->createQueryBuilder()
  1011.             ->select([$ancestorColumn])
  1012.             ->from($tablename'mt')
  1013.             ->where('mt.' $this->getPrimaryKey() . ' = :id')
  1014.             ->setParameter('id'$id)
  1015.             ->setMaxResults(1)
  1016.             ->execute()
  1017.             ->fetch();
  1018.         if (($refObject !== false) && isset($refObject[$ancestorColumn]) && ($refObject[$ancestorColumn] > 0)) {
  1019.             return $refObject[$ancestorColumn];
  1020.         } else {
  1021.             throw new PortalException(self::msgAncestorNotFound);
  1022.         }
  1023.     }
  1024.     public function isWriteable($column)
  1025.     {
  1026.         return $this->getTableInfo()
  1027.             ->isWriteable($column$this->getUserRights());
  1028.     }
  1029.     /**
  1030.      * @return mixed
  1031.      */
  1032.     public function getUser()
  1033.     {
  1034.         return $this->user;
  1035.     }
  1036.     public function unlock($id)
  1037.     {
  1038.         if (!$this->options->useLocking()) {
  1039.             return false;
  1040.         }
  1041.         if (!$this->hasUser()) {
  1042.             return false;
  1043.         }
  1044.         if (!$this->canAccessObject($id)) {
  1045.             throw new PortalException(self::msgNoAccess);
  1046.         }
  1047.         $ancestor 0;
  1048.         $tablename $this->tableInfo->getInternalTablename();
  1049.         if ($this->options->useVersioning()) {
  1050.             $ancestorColumn $this->options->getAncestorColumn();
  1051.             $refObject $this->db->createQueryBuilder()
  1052.                 ->select([$ancestorColumn])
  1053.                 ->from($tablename'mt')
  1054.                 ->where('mt.' $this->getPrimaryKey() . ' = :id')
  1055.                 ->setParameter('id'$id)
  1056.                 ->setMaxResults(1)
  1057.                 ->execute()
  1058.                 ->fetch();
  1059.             if (($refObject !== false) && isset($refObject[$ancestorColumn]) && ($refObject[$ancestorColumn] > 0)) {
  1060.                 $ancestor $refObject[$ancestorColumn];
  1061.             } else {
  1062.                 //throw new GenericRepositoryDeleteException(1801);
  1063.                 throw new PortalException(self::msgAncestorNotFound);
  1064.             }
  1065.         }
  1066.         $lockedAtColumn $this->options->getLockedAtColumn();
  1067.         $lockedByColumn $this->options->getLockedByColumn();
  1068.         if ($this->isWriteable($lockedAtColumn) && $this->isWriteable($lockedByColumn)) {
  1069.             $builder $this->db->createQueryBuilder();
  1070.             $builder
  1071.                 ->update($this->tableInfo->getInternalTablename(), '')
  1072.                 ->set($this->options->getLockedByColumn(), $builder->createNamedParameter(0));
  1073.             $where_used false;
  1074.             if (!$this->getUser()
  1075.                 ->isAdmin()
  1076.             ) {
  1077.                 $builder->where(
  1078.                     $builder->expr()
  1079.                         ->eq(
  1080.                             $this->options->getLockedByColumn(),
  1081.                             $this->getUser()
  1082.                                 ->getId()
  1083.                         )
  1084.                 );
  1085.                 $where_used true;
  1086.             }
  1087.             if ($this->options->useVersioning()) {
  1088.                 if ($where_used) {
  1089.                     $builder->andWhere($this->options->getAncestorColumn() . " = " $ancestor);
  1090.                 } else {
  1091.                     $builder->where($this->options->getAncestorColumn() . " = " $ancestor);
  1092.                     $where_used true;
  1093.                 }
  1094.             } else {
  1095.                 if ($where_used) {
  1096.                     $builder->andWhere($this->getPrimaryKey() . " = " $builder->createNamedParameter($id));
  1097.                 } else {
  1098.                     $builder->where($this->getPrimaryKey() . " = " $builder->createNamedParameter($id));
  1099.                     $where_used true;
  1100.                 }
  1101.             }
  1102.             $context = ['builder' => $builder'where_used' => $where_used];
  1103.             $this->dispatch(new RepositoryEvent($this$context), RepositoryEvents::UNLOCK_BEFORE_EXECUTE);
  1104.             $result $builder->execute();
  1105.             if ($result == 0) {
  1106.                 throw new PortalException(Messages::msgErrorUnlock);
  1107.             }
  1108.         } else {
  1109.             throw new PortalException(self::msgLockingColumnsNotWriteable);
  1110.         }
  1111.         return true;
  1112.     }
  1113.     public function deleteBy($criteria, &$allAffectedWatchDogNotificationRows null)
  1114.     {
  1115.         // @todo: Implement batch delete
  1116.         $group_by_version $this->options->useDeleted();
  1117.         $objects $this->findBy(
  1118.             $criteria,
  1119.             null,
  1120.             [$this->getPrimaryKey()],
  1121.             null,
  1122.             null,
  1123.             true,
  1124.             $group_by_version
  1125.         );
  1126.         $objects $objects->fetchAll(\PDO::FETCH_ASSOC);
  1127.         if (null !== $objects) {
  1128.             $affectedWatchDogNotificationRows 0;
  1129.             foreach ($objects as $obj) {
  1130.                 $this->delete($obj[$this->getPrimaryKey()], $affectedWatchDogNotificationRows);
  1131.                 if ($allAffectedWatchDogNotificationRows) {
  1132.                     $allAffectedWatchDogNotificationRows += $affectedWatchDogNotificationRows;
  1133.                 }
  1134.             }
  1135.         }
  1136.     }
  1137.     public function findBy(
  1138.         CriteriaBuilderInterface $filter null,
  1139.         array $sort null,
  1140.         array $view null,
  1141.         $limit null,
  1142.         $offset null,
  1143.         $hide_deleted true,
  1144.         $group_by_version true,
  1145.         $join_refs true
  1146.     )
  1147.     {
  1148.         $processedView $this->processView($view);
  1149.         $builder $this->findByQuery(
  1150.             $columnMap,
  1151.             $filter,
  1152.             $sort,
  1153.             $processedView,
  1154.             $limit,
  1155.             $offset,
  1156.             $hide_deleted,
  1157.             $group_by_version,
  1158.             $join_refs
  1159.         );
  1160.         $select_columns $this->buildSelectColumns($columnMap$processedView);
  1161.         if (count($select_columns) > 0) {
  1162.             return $builder->select($select_columns)
  1163.                 ->execute();
  1164.         }
  1165.         return null;
  1166.     }
  1167.     public function historyBy(
  1168.         $id,
  1169.         CriteriaBuilderInterface $filter,
  1170.         array $view null,
  1171.         $limit null,
  1172.         $offset null,
  1173.         $sort null
  1174.     ) {
  1175.         if (!$this->options->useVersioning()) {
  1176.             return null;
  1177.         }
  1178.         $processedView $this->processView($view);
  1179.         $builder $this->historyByQuery(
  1180.             $id,
  1181.             $columnMap,
  1182.             $filter,
  1183.             $processedView,
  1184.             $limit,
  1185.             $offset,
  1186.             $sort
  1187.         );
  1188.         $selectColumns $this->buildSelectColumns($columnMap$processedView);
  1189.         if (count($selectColumns) > 0) {
  1190.             return $builder->select($selectColumns)
  1191.                 ->execute();
  1192.         }
  1193.         return null;
  1194.     }
  1195.     public function historyByQuery(
  1196.         $id,
  1197.         &$columnMap,
  1198.         CriteriaBuilderInterface $filter null,
  1199.         $processedView null,
  1200.         $limit null,
  1201.         $offset null,
  1202.         $sort null,
  1203.         $mt_name 'mt'
  1204.     )
  1205.     {
  1206.         if (null === $processedView) {
  1207.             $processedView $this->processView(null);
  1208.         }
  1209.         $tablename $this->tableInfo->getInternalTablename();
  1210.         $builder $this->db->createQueryBuilder();
  1211.         $this->tableInfo->buildFrom($builder$mt_name);
  1212.         $context = [
  1213.             'builder' => $builder,
  1214.             'where_used' => false
  1215.         ];
  1216.         if (null !== $limit) {
  1217.             $builder->setMaxResults($limit);
  1218.         }
  1219.         if (null !== $offset) {
  1220.             $builder->setFirstResult($offset);
  1221.         }
  1222.         $columnMap $this->buildColumnMapForJoins($builder$processedViewtrue$mt_name);
  1223.         $ancestorColumn $this->options->getAncestorColumn();
  1224.         $versionColumn $this->options->getVersionColumn();
  1225.         $refObject $this->db->createQueryBuilder()
  1226.             ->select([$ancestorColumn])
  1227.             ->from($tablename$mt_name)
  1228.             ->where($mt_name '.' $this->getPrimaryKey() . ' = :id')
  1229.             ->setParameter('id'$id)
  1230.             ->setMaxResults(1)
  1231.             ->execute()
  1232.             ->fetch();
  1233.         if ($refObject !== false) {
  1234.             $ancestor $refObject[$ancestorColumn];
  1235.         } else {
  1236.             throw new \Exception('Missing refObject.');
  1237.         }
  1238.         $builder->andWhere($mt_name '.' $ancestorColumn ' = :ancestor');
  1239.         $context['where_used'] = true;
  1240.         $builder->setParameter('ancestor'$ancestor);
  1241.         /*
  1242.         Add filter and sorting
  1243.         */
  1244.         if (null !== $filter) {
  1245.             $criteria $filter->buildCriteria($builder$columnMap$processedView$mt_name);
  1246.             if ($criteria) {
  1247.                 $builder->andWhere($criteria);
  1248.             }
  1249.         }
  1250.         $this->applySorting($builder$sort, [['column' => $versionColumn'direction' => 'DESC']], $columnMap$processedView);
  1251.         $this->dispatch(new RepositoryEvent($this$context), RepositoryEvents::FIND_BEFORE_EXECUTE);
  1252.         return $builder;
  1253.     }
  1254.     public function delete($id, &$affectedWatchDogNotificationRows null)
  1255.     {
  1256.         $ancestor 0;
  1257.         $tablename $this->tableInfo->getInternalTablename();
  1258.         if (!$this->canAccessObject($id)) {
  1259.             throw new PortalException(self::msgNoAccess);
  1260.         }
  1261.         if ($this->options->useVersioning()) {
  1262.             $ancestorColumn $this->options->getAncestorColumn();
  1263.             $refObject $this->db->createQueryBuilder()
  1264.                 ->select([$ancestorColumn])
  1265.                 ->from($tablename'mt')
  1266.                 ->where('mt.' $this->getPrimaryKey() . ' = :id')
  1267.                 ->setParameter('id'$id)
  1268.                 ->setMaxResults(1)
  1269.                 ->execute()
  1270.                 ->fetch();
  1271.             if ($refObject !== false) {
  1272.                 $ancestor $refObject[$ancestorColumn];
  1273.             } else {
  1274.                 throw new GenericRepositoryDeleteException(1801);
  1275.             }
  1276.         }
  1277.         if ($this->options->useDeleted()) {
  1278.             if ($this->isWriteable($this->options->getDeletedColumn())) {
  1279.                 $obj = [
  1280.                     $this->options->getDeletedColumn() => 1,
  1281.                     $this->getPrimaryKey() => $id
  1282.                 ];
  1283.                 try {
  1284.                     $newId $this->persist($objtrue);
  1285.                     $this->incrementReleaseColumns($newId);
  1286.                     /*
  1287.                      *  Consider encryption if applicable
  1288.                      */
  1289.                     if ($this->container->getParameter('projectbiz.dsgvo.use_encryption')) {
  1290.                         $dsgvo $this->container->get('projectbiz.dsgvo');
  1291.                         if ($dsgvo->getCorrespondingEncryptionTable($this->tableInfo->getTablename())) {
  1292.                             $dsgvo->encryptRecord($id$this->tableInfo->getTablename());
  1293.                         }
  1294.                     }
  1295.                 } catch (Exception $ex) {
  1296.                     throw new GenericRepositoryDeleteException(1802);
  1297.                 }
  1298.             } else {
  1299.                 throw new GenericRepositoryDeleteException(1810);
  1300.             }
  1301.         } else {
  1302.             $this->db->beginTransaction();
  1303.             try {
  1304.                 // @todo: delete Refs
  1305.                 if ($this->options->useManSort()) {
  1306.                     // Get the manSort-Value of the Object
  1307.                     $delObject $this->db->createQueryBuilder()
  1308.                         ->select([$this->options->getManSortColumn()])
  1309.                         ->from($tablename'mt')
  1310.                         ->where('mt.' $this->getPrimaryKey() . ' = :id')
  1311.                         ->setParameter('id'$id)
  1312.                         ->setMaxResults(1)
  1313.                         ->execute()
  1314.                         ->fetch();
  1315.                     $manSortDelete $delObject[$this->options->getManSortColumn()];
  1316.                     if (null !== $manSortDelete) {
  1317.                         // Consider the set ...
  1318.                         $manSortSet $this->options->getManSortSetColumns();
  1319.                         $manSortBuilder $this->db->createQueryBuilder()
  1320.                             ->update($this->tableInfo->getInternalTablename(), '')
  1321.                             ->set($this->options->getManSortColumn(), $this->options->getManSortColumn() . ' - 1')
  1322.                             ->where($this->options->getManSortColumn() . ' > ' $manSortDelete);
  1323.                         for ($i 0$i count($manSortSet); $i++) {
  1324.                             $manSortBuilder->andWhere($manSortSet[$i] . ' = ' $delObject[$manSortSet[$i]]);
  1325.                         }
  1326.                         $manSortBuilder->execute();
  1327.                     }
  1328.                 }
  1329.                 if ($this->options->useVersioning()) {
  1330.                     $builder $this->db->createQueryBuilder()
  1331.                         ->delete($tablename)
  1332.                         ->where($this->options->getAncestorColumn() . ' = ' $ancestor);
  1333.                     $context = ['builder' => $builder'where_used' => true];
  1334.                     $this->dispatch( new RepositoryEvent($this$context), RepositoryEvents::DELETE_BEFORE_EXECUTE);
  1335.                     $num_rows $builder->execute();
  1336.                 } else {
  1337.                     $builder $this->db->createQueryBuilder()
  1338.                         ->delete($tablename)
  1339.                         ->where($this->getPrimaryKey() . ' = ' $id);
  1340.                     $context = ['builder' => $builder'where_used' => true];
  1341.                     $this->dispatch(new RepositoryEvent($this$context), RepositoryEvents::DELETE_BEFORE_EXECUTE);
  1342.                     $num_rows $builder->execute();
  1343.                 }
  1344.                 /*
  1345.                  *  Consider encryption if applicable
  1346.                  */
  1347.                 if ($this->container->getParameter('projectbiz.dsgvo.use_encryption')) {
  1348.                     $dsgvo $this->container->get('projectbiz.dsgvo');
  1349.                     if ($dsgvo->getCorrespondingEncryptionTable($this->tableInfo->getTablename())) {
  1350.                         $useVersioning $this->options->useVersioning();
  1351.                         $dsgvo->deleteRecord($ancestor$this->tableInfo->getTablename(), $useVersioning$useVersioning);
  1352.                     }
  1353.                 }
  1354.             } catch (\Exception $ex) {
  1355.                 $this->db->rollBack();
  1356.                 throw($ex);
  1357.             }
  1358.             $this->db->commit();
  1359.             if ($num_rows == 0) {
  1360.                 throw new GenericRepositoryDeleteException(1803);
  1361.             }
  1362.         }
  1363.         /*
  1364.          * Delete corresponding watchdog-notifications.
  1365.          */
  1366.         {
  1367.             $queryBuilder $this->db->createQueryBuilder();
  1368.             $queryBuilder
  1369.                 ->delete($this->prefix 'WatchDogNotification')
  1370.                 ->where('WatchDogNotification_LINK_Target_ID = ' .
  1371.                     $queryBuilder->createPositionalParameter($this->options->useVersioning() ? $ancestor $id))
  1372.                 ->andWhere('WatchDogNotification_Table_Caption = ' .
  1373.                     $queryBuilder->createPositionalParameter($this->tableInfo->getCaption()));
  1374.             // This variable is a reference
  1375.             $affectedWatchDogNotificationRows $queryBuilder->execute();
  1376.         }
  1377.     }
  1378.     public function persist($persistData$forceSave false$copyAfterCreate null$updateKey null)
  1379.     {
  1380.         $data $persistData;
  1381.         // Remove REFs
  1382.         foreach ($data as $key => $value) {
  1383.             if (strpos($key'REF_') === 0) {
  1384.                 unset($data[$key]);
  1385.             }
  1386.         }
  1387.         $is_new true;
  1388.         $id null;
  1389.         if ($updateKey) {
  1390.             $is_new = !$this->objectExists($updateKey$data[$updateKey]);
  1391.         } elseif (array_key_exists($this->getPrimaryKey(), $data)) {
  1392.             if (null !== $data[$this->getPrimaryKey()]) {
  1393.                 $id $data[$this->getPrimaryKey()];
  1394.                 $is_new = !$this->objectExists($id);
  1395.             } else {
  1396.                 unset($data[$this->getPrimaryKey()]);
  1397.             }
  1398.         }
  1399.         $rights $this->getUserRights();
  1400.         // Strip join-table data - it needs to be written "by hand" in the controller
  1401.         // Strip data that is not writeable
  1402.         foreach (array_keys($data) as $key) {
  1403.             $column = new Column($key'view');
  1404.             if (!$this->tableInfo->isWriteable($key$rights) || ($column->getTableAlias() != 'mt')) {
  1405.                 unset($data[$key]);
  1406.             }
  1407.         }
  1408.         if ($is_new) {
  1409.             return $this->create($data$copyAfterCreate);
  1410.         } else {
  1411.             unset($data[$this->getPrimaryKey()]);
  1412.             return $this->update($id$data$forceSave);
  1413.         }
  1414.     }
  1415.     private function objectExists($value$keyColumn null)
  1416.     {
  1417.         $tablename $this->tableInfo->getInternalTablename();
  1418.         $builder $this->db->createQueryBuilder();
  1419.         $result $builder->select('COUNT(*) AS cnt')
  1420.             ->from($tablename)
  1421.             ->where(($keyColumn ?? $this->tableInfo->getPrimaryKey()) . '=' $builder->createNamedParameter($value))
  1422.             ->execute();
  1423.         $value $result->fetch();
  1424.         return !(($value === false) || ($value['cnt'] == 0));
  1425.     }
  1426.     protected function create($data$copyAfterCreate null)
  1427.     {
  1428.         $needPersist false;
  1429.         $write_data = [];
  1430.         $refs = [];
  1431.         // Join sent data with required special columns
  1432.         $columns array_unique(array_merge(array_keys($data), $this->options->getAutoColumns()));
  1433.         $colDefs $this->getColumnDefinitions();
  1434.         foreach ($columns as $key) {
  1435.             $value = isset($data[$key])
  1436.                 ?$data[$key]
  1437.                 :null;
  1438.             if (array_key_exists($key$colDefs)) {
  1439.                 if ($colDefs[$key]->isVirtual()) {
  1440.                     $refs[] = [
  1441.                         'key' => $key,
  1442.                         'data' => [
  1443.                             'value' => $this->valueOrAutoValue($key$valuenulltrue$data)['value']
  1444.                         ]
  1445.                     ];
  1446.                 } else {
  1447.                     $write_data[$key] = $this->valueOrAutoValue($key$valuenulltrue$data)['value'];
  1448.                 }
  1449.             }
  1450.             $needPersist true;
  1451.         }
  1452.         if ($needPersist) {
  1453.             $this->db->beginTransaction();
  1454.             try {
  1455.                 if ($this->tableInfo->getTablename() === 'UserRights') {
  1456.                     $submittedAdminRights gmp_intval(gmp_and(gmp_init($data['UserRights_UserRight']), gmp_init(1)));
  1457.                     if ($submittedAdminRights == && !$this->securityContextWrapper->isSuperAdmin()) {
  1458.                         throw new PortalException(self::msgAdminRightsDenied, [], 0nullnulltrue);
  1459.                     }
  1460.                 }
  1461.                 $this->db->insert($this->tableInfo->getInternalTablename(), $write_data);
  1462.                 $lastInsertId $this->db->lastInsertId();
  1463.                 foreach ($refs as $ref) {
  1464.                     $this->updateRefs($lastInsertId$ref['key'], $ref['data']);
  1465.                 }
  1466.                 if ($this->options->useVersioning()) {
  1467.                     // Set the ancestor to the newly created id
  1468.                     $this->db->createQueryBuilder()
  1469.                         ->update($this->tableInfo->getInternalTablename(), '')
  1470.                         ->set($this->options->getAncestorColumn(), $lastInsertId)
  1471.                         ->where($this->getPrimaryKey() . " = " $lastInsertId)
  1472.                         ->execute();
  1473.                 }
  1474.                 $this->incrementReleaseColumns($lastInsertId);
  1475.                 if ($this->options->useManSort() && !array_key_exists('SPM_manSort'$data)) {
  1476.                     // Set the manSort-Value of the newly created entry to the next available value
  1477.                     // Consider the set ...
  1478.                     $manSortSet $this->options->getManSortSetColumns();
  1479.                     $manSortBuilder $this->db->createQueryBuilder()
  1480.                         ->from($this->tableInfo->getInternalTablename(), 'mt')
  1481.                         ->select('MAX(mt.' $this->options->getManSortColumn() . ') + 1 AS value');
  1482.                     if (count($manSortSet) > 0) {
  1483.                         $manSortBuilder
  1484.                             ->leftJoin(
  1485.                                 'mt',
  1486.                                 $this->tableInfo->getInternalTablename(),
  1487.                                 'j',
  1488.                                 'j.' $this->getPrimaryKey() . " = " $lastInsertId
  1489.                             )
  1490.                             ->where('mt.' $manSortSet[0] . ' = j.' $manSortSet[0]);
  1491.                         for ($i 1$i count($manSortSet); $i++) {
  1492.                             $manSortBuilder->andWhere('mt.' $manSortSet[$i] . ' = j.' $manSortSet[$i]);
  1493.                         }
  1494.                     }
  1495.                     $manSortValue $manSortBuilder
  1496.                         ->execute()
  1497.                         ->fetch()['value'];
  1498.                     if ($manSortValue == null) {
  1499.                         $manSortValue '1';
  1500.                     }
  1501.                     $this->db->createQueryBuilder()
  1502.                         ->update($this->tableInfo->getInternalTablename(), '')
  1503.                         ->set($this->options->getManSortColumn(), $manSortValue)
  1504.                         ->where($this->getPrimaryKey() . " = " $lastInsertId)
  1505.                         ->execute();
  1506.                 }
  1507.                 if ($copyAfterCreate != null) {
  1508.                     $queryBuilder $this->db->createQueryBuilder()
  1509.                         ->update($this->tableInfo->getInternalTablename(), '')
  1510.                         ->where($this->getPrimaryKey() . " = " $lastInsertId);
  1511.                     foreach ($copyAfterCreate as $source => $target) {
  1512.                         $queryBuilder->set($target$source);
  1513.                     }
  1514.                     $queryBuilder->execute();
  1515.                 }
  1516.                 $object $this->find($lastInsertIdfalsefalsetrue);
  1517.                 $context = [
  1518.                     'id' => $lastInsertId,
  1519.                     'object' => $object,
  1520.                     'database' => $this->db
  1521.                 ];
  1522.                 /*
  1523.                  *  Consider encryption if applicable
  1524.                  */
  1525.                 if ($this->container->getParameter('projectbiz.dsgvo.use_encryption')) {
  1526.                     $dsgvo $this->container->get('projectbiz.dsgvo');
  1527.                     if ($dsgvo->getCorrespondingEncryptionTable($this->tableInfo->getTablename())) {
  1528.                         $dsgvo->encryptRecord($lastInsertId$this->tableInfo->getTablename());
  1529.                     }
  1530.                 }
  1531.                 $this->dispatch(new RepositoryEvent($this$context), RepositoryEvents::CREATE_AFTER_EXECUTE);
  1532.                 $this->db->commit();
  1533.                 return $lastInsertId;
  1534.             } catch (Exception $ex) {
  1535.                 $this->db->rollBack();
  1536.                 throw($ex);
  1537.             }
  1538.         }
  1539.         return 0;
  1540.     }
  1541.     /**
  1542.      * Increment release columns.
  1543.      *
  1544.      * If a column with name *_LINK_*_ReleaseID is present it has a counterpart column with name *_ReleaseId
  1545.      * in a corresponding table. That column gets incremented. The SPM set will be cloned and the new versions
  1546.      * get this new id.
  1547.      *
  1548.      * @param type $lastInsertId
  1549.      * @return type
  1550.      * @throws Exception
  1551.      */
  1552.     public function incrementReleaseColumns($lastInsertId)
  1553.     {
  1554.         if (!$this->options->useReleaseLink()) {
  1555.             return;
  1556.         }
  1557.         $routeParams $this->request->attributes->get('_route_params');
  1558.         if(!isset($routeParams['parent_release_parameter'])) {
  1559.             throw new Exception(self::msgMissingParentReleaseParameter);
  1560.         }
  1561.         $colDefs =  $this->tableInfo->getColumnDefinitions();
  1562.         $releaseLinkColumn $this->options->getReleaseLinkColumn();
  1563.         $primaryKeyColumn $this->getPrimaryKey();
  1564.         $parentTableName $colDefs[$releaseLinkColumn]->getSource()['table'];
  1565.         $parentTableRepo $this->repoFactory->createGenericRepository($parentTableName);
  1566.         $parentTable_releaseColumnName $colDefs[$releaseLinkColumn]->getSource()['foreign_key'];
  1567.         $parentId $routeParams[$routeParams['parent_release_parameter']];
  1568.         $parentReleaseId $parentTableRepo->find($parentId)[$parentTable_releaseColumnName];
  1569.         $repo $this->repoFactory->createGenericRepository($this->tableInfo->getTablename());
  1570.         $incrementedReleaseId $this->aggregate($parentTable_releaseColumnName$parentTableName'max' ) + 1;
  1571.         /*
  1572.          * Get the whole SPM-set. A set has the same release-id.
  1573.          * Do not get the last inserted spm, since it is updated exclusively.
  1574.          */
  1575.         $wholeSpmSet $repo->findBy(
  1576.                 new CriteriaComposite(
  1577.                     'and',
  1578.                     [
  1579.                         new CriteriaComparison(
  1580.                                 '=', new CriteriaUnmappedColumn($releaseLinkColumn), new CriteriaConstant($parentReleaseId)
  1581.                         ),
  1582.                         new CriteriaComparison(
  1583.                                 '<>', new CriteriaUnmappedColumn($primaryKeyColumn), new CriteriaConstant($lastInsertId)
  1584.                         )
  1585.                     ]
  1586.                 ), null, ['SPM_ID'], nullnullnulltruefalse
  1587.             )->fetchAll();
  1588.         /*
  1589.          * Update the SPM-set with the incremented release id.
  1590.          * The funtion versiondUpdate() duplicates a row when updating what is desired.
  1591.          */
  1592.         foreach ($wholeSpmSet as $spm) {
  1593.             $this->versionedUpdate($spm[$this->getPrimaryKey()], [$releaseLinkColumn => $incrementedReleaseId], true);
  1594.         }
  1595.         /*
  1596.          * Update the release id of the last inserted record.
  1597.          */
  1598.         $this->db->createQueryBuilder()
  1599.             ->update($this->tableInfo->getInternalTablename(), '')
  1600.             ->set($releaseLinkColumn$incrementedReleaseId)
  1601.             ->where($this->getPrimaryKey() . " = " $lastInsertId)
  1602.             ->execute();
  1603.         /*
  1604.          * Update the releaseColumn of the MPM record.
  1605.          */
  1606.         $parentTableRepo->versionedUpdate($parentId, [$parentTable_releaseColumnName => $incrementedReleaseId], true);
  1607.     }
  1608.     protected function valueOrAutoValue($key$value$original_value$creation false$data)
  1609.     {
  1610.         $userId 0;
  1611.         if (is_a($this->user'ProjectBiz\UserBundle\Entity\BaseUserInterface')) {
  1612.             /* @var $userEntity \ProjectBiz\UserBundle\Entity\User */
  1613.             $userEntity $this->user;
  1614.             $userId $userEntity->getId();
  1615.         }
  1616.         $param_name self::getNextParameterName();
  1617.         /*
  1618.         if ($creation && ($key == $this->getPrimaryKey())) {
  1619.             return null;
  1620.         }
  1621.         */
  1622.         if ($this->options->useModificationTimestamp()) {
  1623.             if ($key == $this->options->getModifiedByColumn()) {
  1624.                 return $this->simpleValue($param_name$userId);
  1625.             } else {
  1626.                 if ($key == $this->options->getModifiedAtColumn()) {
  1627.                     return $this->simpleValue(
  1628.                         $param_name,
  1629.                         (new DateTime())->format(
  1630.                             $this->db->getDatabasePlatform()
  1631.                                 ->getDateTimeFormatString()
  1632.                         )
  1633.                     );
  1634.                 }
  1635.             }
  1636.         }
  1637.         if ($this->options->useCreationTimestamp()) {
  1638.             if ($creation) {
  1639.                 // @todo: check the "else" case. It might be possible to send in new creation date on modification
  1640.                 if ($key == $this->options->getCreatedByColumn()) {
  1641.                     return $this->simpleValue($param_name$userId);
  1642.                 } else {
  1643.                     if ($key == $this->options->getCreatedAtColumn()) {
  1644.                         return $this->simpleValue(
  1645.                             $param_name,
  1646.                             (new DateTime())->format(
  1647.                                 $this->db->getDatabasePlatform()
  1648.                                     ->getDateTimeFormatString()
  1649.                             )
  1650.                         );
  1651.                     }
  1652.                 }
  1653.             } else {
  1654.                 if ($key == $this->options->getCreatedByColumn()) {
  1655.                     return $this->simpleValue($param_name$original_value);
  1656.                 } else {
  1657.                     if ($key == $this->options->getCreatedAtColumn()) {
  1658.                         return $this->simpleValue($param_name$original_value);
  1659.                     }
  1660.                 }
  1661.             }
  1662.         }
  1663.         if ($this->options->useLog()) {
  1664.             $logPos array_search($key$this->options->getLogColumns());
  1665.             if ($logPos !== false) {
  1666.                 if (!empty($value)) {
  1667.                     if ($this->options->hasLogHeader($logPos)) {
  1668.                         $logHeader $this->options->getLogHeader($logPos);
  1669.                         $value $logHeader->render() . $value;
  1670.                     } else {
  1671.                   // Here must be the standard-log-header in the service-yml declared !!
  1672.                         if (null !== ($this->options->getOption('standard-log-header'))) {
  1673.                             /** @var $standardLogHeader \ProjectBiz\PortalBundle\Service\LogHeaderInterface */
  1674.                             $standardLogHeader $this->options->getOption('standard-log-header');
  1675.                             $value $standardLogHeader->render() . $value;
  1676.                         }
  1677.                         else {
  1678.                           //var_dump(render());
  1679.                           //var_dump($data);
  1680.                           //var_dump($key);
  1681.                           //exit;
  1682.                         }
  1683.                     }
  1684.                 }
  1685.                 // @todo: There is a problem with "write-only" logs:
  1686.                 // only the most recent log entry is written, because the original_value is not readable and thus empty.
  1687.                 // This could be fixed by CONCAT_WS, or something with ISNULL, IFNULL, but not without doing a doctrine
  1688.                 // extension as those are not supported:
  1689.                 if ($creation || empty($original_value)) {
  1690.                     return $this->simpleValue($param_name$value);
  1691.                 } else {
  1692.                     $value "\n\n" $value;
  1693.                     return [
  1694.                         'expr' => $this->db->getDatabasePlatform()
  1695.                             ->getConcatExpression(
  1696.                                 $key,
  1697.                                 $this->db->getDatabasePlatform()
  1698.                                     ->getConcatExpression('CHAR(13)'':' $param_name)
  1699.                             ),
  1700.                         'param' => $param_name,
  1701.                         'value' => $value
  1702.                     ];
  1703.                 }
  1704.             }
  1705.         }
  1706.         if ($this->options->useVersioning()) {
  1707.             if ($creation && ($key == $this->options->getVersionColumn())) {
  1708.                 // @todo: check the "else" case. It might be possible to send in new version on modification
  1709.                 if (null !== $value) {
  1710.                     return $this->simpleValue($param_name$value); // Start with given version ...
  1711.                 } else {
  1712.                     return $this->simpleValue($param_name1); // ... or with default version 1
  1713.                 }
  1714.             }
  1715.         }
  1716.         if ($this->options->useDeleted()) {
  1717.             if ($key == $this->options->getDeletedColumn()) {
  1718.                 if ($value !== null) {
  1719.                     return $this->simpleValue($param_name$value);
  1720.                 } else {
  1721.                     return $this->simpleValue($param_name0);
  1722.                 }
  1723.             }
  1724.         }
  1725.         return $this->simpleValue($param_name$value);
  1726.     }
  1727.     protected function simpleValue($param_name$value)
  1728.     {
  1729.         if (is_a($value'\DateTime')) {
  1730.             /** @var $dataTimeValue \DateTime * */
  1731.             $dataTimeValue $value;
  1732.             $dbValue $dataTimeValue->format(
  1733.                 $this->db->getDatabasePlatform()
  1734.                     ->getDateTimeFormatString()
  1735.             );
  1736.         } else {
  1737.             $dbValue $value;
  1738.         }
  1739.         return [
  1740.             'expr' => ':' $param_name,
  1741.             'param' => $param_name,
  1742.             'value' => $dbValue
  1743.         ];
  1744.     }
  1745.     protected function updateRefs($id$key$modifiedValue)
  1746.     {
  1747.         // Update refs by dropping and rebuilding
  1748.         $sourceTableColumn 'Refs_SourceTable';
  1749.         $sourceIdColumn 'Refs_Source_LINK_ID';
  1750.         $targetTableColumn 'Refs_TargetTable';
  1751.         $targetIdColumn 'Refs_Target_LINK_ID';
  1752.         $refsProperty $key;
  1753.         if ($this->getColumnDefinitions()[$key]->getType() == 'fer') {
  1754.             $sourceTableColumn 'Refs_TargetTable';
  1755.             $sourceIdColumn 'Refs_Target_LINK_ID';
  1756.             $targetTableColumn 'Refs_SourceTable';
  1757.             $targetIdColumn 'Refs_Source_LINK_ID';
  1758.             $refsProperty $this->getColumnDefinitions()[$key]->getReverseProperty();
  1759.         }
  1760.         $refsBuilder $this->db->createQueryBuilder();
  1761.         $refsBuilder
  1762.             ->delete($this->prefix 'Refs')
  1763.             ->where(
  1764.                 $sourceTableColumn .
  1765.                 ' = ' .
  1766.                 $refsBuilder->createNamedParameter(
  1767.                     $this->getTableInfo()
  1768.                         ->getTablename()
  1769.                 )
  1770.             )
  1771.             ->andWhere($sourceIdColumn ' = ' $refsBuilder->createNamedParameter($id))
  1772.             ->andWhere('Refs_Property = ' $refsBuilder->createNamedParameter($refsProperty));
  1773.         $refsBuilder
  1774.             ->execute();
  1775.         if (isset($modifiedValue['value']) && strlen($modifiedValue['value'] > 0)) {
  1776.             $targetTable $this->getColumnDefinitions()[$key]->getSource()['table'];
  1777.             $refs array_map(
  1778.                 function ($value) use (
  1779.                     $key,
  1780.                     $id,
  1781.                     $targetTable,
  1782.                     $sourceTableColumn,
  1783.                     $sourceIdColumn,
  1784.                     $targetTableColumn,
  1785.                     $targetIdColumn,
  1786.                     $refsProperty
  1787.                 ) {
  1788.                     return [
  1789.                         $sourceTableColumn => $this->getTableInfo()
  1790.                             ->getTablename(),
  1791.                         $sourceIdColumn => $id,
  1792.                         $targetTableColumn => $targetTable,
  1793.                         $targetIdColumn => $value,
  1794.                         'Refs_Property' => $refsProperty
  1795.                     ];
  1796.                 },
  1797.                 explode(','$modifiedValue['value'])
  1798.             );
  1799.             foreach ($refs as $ref) {
  1800.                 $this->db->insert(
  1801.                     $this->prefix 'Refs',
  1802.                     $ref
  1803.                 );
  1804.             }
  1805.         }
  1806.     }
  1807.     /**
  1808.      * @param $id
  1809.      * @param $data
  1810.      * @param $forceSave
  1811.      *
  1812.      * @return string
  1813.      *
  1814.      * @throws Exception
  1815.      */
  1816.     protected function update($id$data$forceSave)
  1817.     {
  1818.         if (!$this->canAccessObject($idfalsefalse)) {
  1819.             throw new PortalException(self::msgNoAccess);
  1820.         }
  1821.         $usespecialunversionDoc=false;
  1822.         If ($this->tableInfo->getInternalTablename()=='Tab_Document'){
  1823.           if(!(isset($data['Document_LINK_File_ID']))){
  1824.             $usespecialunversionDoc =true;
  1825.           }
  1826.           $usespecialunversionDoc =false;
  1827.         }
  1828.         if ($this->options->useVersioning() && !$usespecialunversionDoc ) {
  1829.             return $this->versionedUpdate($id$data$forceSave);
  1830.         } else {
  1831.             return $this->unversionedUpdate($id$datanullnull$forceSave);
  1832.         }
  1833.     }
  1834.     /**
  1835.      * @param $id
  1836.      * @param $data
  1837.      * @param $forceSave
  1838.      *
  1839.      * @return string
  1840.      *
  1841.      * @throws Exception
  1842.      * @throws \Doctrine\DBAL\ConnectionException
  1843.      */
  1844.     protected function versionedUpdate($id$data$forceSave$compareWithLatestVersion false)
  1845.     {
  1846.         $original_object $this->find($id$compareWithLatestVersionfalsetrue);
  1847.         $changed_columns $this->getChangedColumns($data$original_object);
  1848.         if ((count($changed_columns) > 0) || $forceSave) {
  1849.             // Get latest object
  1850.             // @todo: object based access rights might lead to no latest object, crashing the saving ... need FIX!
  1851.             $latest_object $this->find($id);
  1852.             // Clone latest object and update all changed columns
  1853.             $this->db->beginTransaction();
  1854.             try {
  1855.                 $cloned_id $this->cloneObject($latest_object[$this->getPrimaryKey()]);
  1856.                 $new_version $latest_object[$this->options->getVersionColumn()] + 1;
  1857.                 $this->db->createQueryBuilder()
  1858.                     ->update($this->tableInfo->getInternalTablename(), '')
  1859.                     ->set($this->options->getVersionColumn(), $new_version)
  1860.                     ->where($this->getPrimaryKey() . " = " $cloned_id)
  1861.                     ->execute();
  1862.                 // Prevent overwriting with old value:
  1863.                 $data[$this->options->getVersionColumn()] = $new_version;
  1864.                 // Prevent overwriting with null:
  1865.                 $data[$this->options->getAncestorColumn()] = $latest_object[$this->options->getAncestorColumn()];
  1866.                 /*
  1867.                  * Prevent overwriting with null for ancestor- and release-columns:
  1868.                  */
  1869.                 {
  1870.                     $data[$this->options->getAncestorColumn()] = $latest_object[$this->options->getAncestorColumn()];
  1871.                     foreach ($this->options->getReleaseColumns() as $releaseColumn) {
  1872.                         if (!isset($data[$releaseColumn])) {
  1873.                             $data[$releaseColumn] = $latest_object[$releaseColumn];
  1874.                         }
  1875.                     }
  1876.                 }
  1877.                 $this->unversionedUpdate($cloned_id$data$changed_columns$original_object$forceSavefalsefalse);
  1878.                 if (!$forceSave) {
  1879.                     $this->incrementReleaseColumns($cloned_id);
  1880.                 }
  1881.                 $new_object $this->find($cloned_idfalsefalsetrue);
  1882.                 $context = [
  1883.                     'id' => $cloned_id,
  1884.                     'prev_id' => $id,
  1885.                     'object' => $new_object,
  1886.                     'prev_object' => $latest_object,
  1887.                     'database' => $this->db
  1888.                 ];
  1889.                 $this->dispatch(new RepositoryEvent($this$context), RepositoryEvents::UPDATE_AFTER_EXECUTE);
  1890.                 $this->db->commit();
  1891.                 // Send notifications about changes in this record
  1892.                 $this->sendNotifications($changed_columns$original_object$new_object);
  1893.                 return $cloned_id;
  1894.             } catch (\Exception $ex) {
  1895.                 if ($this->db->isTransactionActive()) {
  1896.                     $this->db->rollBack();
  1897.                 }
  1898.                 throw($ex);
  1899.             }
  1900.         }
  1901.         return $id;
  1902.     }
  1903.     /**
  1904.      * Examine all changed columns by checking them against the conditions in Tab_MailTrigger.
  1905.      * Send a notification to the particular e-mail adress if the condition applies.
  1906.      *
  1907.      * @param $changedColumns
  1908.      * @param $originalObject
  1909.      * @param $newObject
  1910.      */
  1911.     protected function sendNotifications($changedColumns$originalObject$newObject)
  1912.     {
  1913.         if (!count($changedColumns)) {
  1914.             return;
  1915.         }
  1916.         $colDefs $this->tableInfo->getColumnDefinitions();
  1917.         $columnNames = [];
  1918.         $changedColumnNames = [];
  1919.         /*
  1920.          * Build an array with ColumnDefinition_ID and column-name mapping for both,
  1921.          * $columnNames and $changedColumnNames.
  1922.          * For example:
  1923.          * [
  1924.          *   7  => MPM_Projectname,
  1925.          *   28 => MPM_Text,
  1926.          *      ...
  1927.          * ]
  1928.          *
  1929.          * In $changedColumnNames ignore the objects which have the value 0 at column _MailTrigger_Active.
  1930.          * If _MailTrigger_Active column does not exist, it defaults to active.
  1931.          */
  1932.         {
  1933.             $mailTriggerActiveColumn $colDefs[$changedColumns[0]]->getTable() . '_MailTrigger_Active';
  1934.             foreach ($colDefs as $colName => $colDef) {
  1935.                 $columnNames[$colDef->getId()] = $colName;
  1936.             }
  1937.             foreach ($changedColumns as $column) {
  1938.                 if (isset($colDefs[$mailTriggerActiveColumn])) {
  1939.                     if ($newObject[$mailTriggerActiveColumn] == 0) {
  1940.                         continue;
  1941.                     }
  1942.                 }
  1943.                 $changedColumnNames[$colDefs[$column]->getId()] = $column;
  1944.             }
  1945.         }
  1946.         $allMailtriggers $this->repoFactory->createGenericRepository('MailTrigger')->findAll();
  1947.         if ($allMailtriggers) {
  1948.             $possibleMailTriggers $allMailtriggers->fetchAll();
  1949.         } else {
  1950.             $possibleMailTriggers = [];
  1951.         }
  1952.         $affectedMailTriggers = [];
  1953.         /*
  1954.          * Let's go further and check if their conditions apply.
  1955.          * Filter all applying mail-triggers and store them in $affectedMailTriggers[].
  1956.          */
  1957.         foreach ($possibleMailTriggers as $possibleMailTrigger) {
  1958.             foreach ($changedColumnNames as $changedColumnId => $changedColumnName) {
  1959.                 if ($possibleMailTrigger['MailTrigger_LINK_Column_ID'] != $changedColumnId && $possibleMailTrigger['MailTrigger_LINK_Right_Column_ID'] != $changedColumnId ) {
  1960.                     continue;
  1961.                 }
  1962.                 $comparisonOperator $possibleMailTrigger['REF_MailTrigger_LINK_MailTriggerType_ID___MailTriggerType_Definition'];
  1963.                 /*
  1964.                  * Make sure there is no compromising code injection.
  1965.                  */
  1966.                 if (!$possibleMailTrigger['MailTrigger_Trigger_Changes']) {
  1967.                     $allowedComparisonOperators = [
  1968.                         '==',
  1969.                         '!=',
  1970.                         '<>',
  1971.                         '<',
  1972.                         '>',
  1973.                         '<=',
  1974.                         '>=',
  1975.                     ];
  1976.                     if (!in_array($comparisonOperator$allowedComparisonOperators)) {
  1977.                         throw new PortalException(
  1978.                             'Comparison operator "'.$comparisonOperator.'" in column MailTriggerType_Definition is not allowed. Allowed operators are:'.PHP_EOL.
  1979.                             implode(', '$allowedComparisonOperators)
  1980.                         );
  1981.                     }
  1982.                 }
  1983.                 /*
  1984.                  * MailTrigger_Trigger_Changes is a checkbox to send notifications when the observed value has changed.
  1985.                  */
  1986.                 if ($possibleMailTrigger['MailTrigger_Trigger_Changes']) {
  1987.                     if ($possibleMailTrigger['MailTrigger_LINK_Column_ID'] == $changedColumnId && $newObject[$changedColumnName] !== $originalObject[$changedColumnName]) {
  1988.                         $affectedMailTriggers[] = $possibleMailTrigger;
  1989.                     }
  1990.                     // If MailTrigger_Trigger_Changes is active, the other possible comparison with MailTrigger_LINK_Right_Column_ID is skipped.
  1991.                     continue;
  1992.                 }
  1993.                 /*
  1994.                  * $leftValue is the left operand of the comparison.
  1995.                  */
  1996.                 $leftValueColumnName $possibleMailTrigger['REF_MailTrigger_LINK_Column_ID___ColDefinition_InternalColumnname'];
  1997.                 $leftValue $newObject[$leftValueColumnName];
  1998.                 $leftType $colDefs[$leftValueColumnName]->getType();
  1999.                 /*
  2000.                  * $rightValue is the right operand of the comparison.
  2001.                  * It can either be a constant (else part) or relate to a column-id (if-part).
  2002.                  */
  2003.                 if ($possibleMailTrigger['MailTrigger_LINK_Right_Column_ID']) {
  2004.                     $rightValueColumnName $possibleMailTrigger['REF_MailTrigger_LINK_Right_Column_ID___ColDefinition_InternalColumnname'];
  2005.                     $rightValue $newObject[$rightValueColumnName];
  2006.                     /*
  2007.                      * Some data types need a special handling for a comparison.
  2008.                      * The type of the right operand is not needed. The user should not create a mailtrigger
  2009.                      * with two columns having different data types.
  2010.                      */
  2011.                     if ($leftType == 'date' || $leftType == 'datetime') {
  2012.                         if(!$rightValue || !$leftValue) {
  2013.                             continue;
  2014.                         }
  2015.                         $leftValue $leftValue->getTimestamp();
  2016.                         $rightValue $rightValue->getTimestamp();
  2017.                     } elseif ($leftType == 'link') {
  2018.                         $leftValue $newObject['REF_' $leftValueColumnName]['display'];
  2019.                         $rightValue $newObject['REF_' $rightValueColumnName]['display'];
  2020.                     }
  2021.                 } else {
  2022.                     $rightValue $possibleMailTrigger['MailTrigger_Value'];
  2023.                     /*
  2024.                      * Some data types need a special handling for a comparison.
  2025.                      */
  2026.                     if ($leftType == 'date' || $leftType == 'datetime') {
  2027.                         $rightValue \DateTime::createFromFormat(
  2028.                                 $leftType == 'date' $this->dateFormat $this->dateTimeFormat,
  2029.                                 $rightValue
  2030.                             );
  2031.                         if ($rightValue) {
  2032.                             $rightValue $rightValue->getTimestamp();
  2033.                         }
  2034.                         $leftValue $leftValue->getTimestamp();
  2035.                     } elseif ($leftType == 'link') {
  2036.                         $leftValue $newObject['REF_' $leftValueColumnName]['display'];
  2037.                     } elseif ($leftType == 'percent') {
  2038.                         $rightValue /= 100;
  2039.                     }
  2040.                 }
  2041.                 /*
  2042.                  * MailTrigger_Deviance is a percental variance applicable for columns with type »euro«.
  2043.                  * The deviance can be positive or negative.
  2044.                  */
  2045.                 if ($leftType == 'euro' && $possibleMailTrigger['MailTrigger_Deviance']) {
  2046.                     // Remove whitespaces. '- 10' will be converted to '-10';
  2047.                     $deviance str_replace(' '''$possibleMailTrigger['MailTrigger_Deviance']);
  2048.                     $leftValue += ($leftValue $deviance/100);
  2049.                 }
  2050.                 /*
  2051.                  *  Does the condition of a trigger apply?
  2052.                  *  The eval statement adds up to a comparison, for example:
  2053.                  *  »return '160'>'150';«
  2054.                  *  or
  2055.                  *  »return 'string1'=='string2';«
  2056.                  */
  2057.                 if ($rightValue !== false && eval('return \'' $leftValue '\'' $comparisonOperator '\'' $rightValue '\';')) {
  2058.                     $affectedMailTriggers[] = $possibleMailTrigger;
  2059.                 }
  2060.             }
  2061.         }
  2062.         $userRepo false;
  2063.         /*
  2064.          * Send an email for every trigger that took effect
  2065.          */
  2066.         foreach ($affectedMailTriggers as $affectedMailTrigger) {
  2067.             /*
  2068.              * create object only once, to consider performance.
  2069.              */
  2070.             if(!$userRepo) {
  2071.                 $userRepo $this->repoFactory->createGenericRepository('User');
  2072.             }
  2073.             $columnName $columnNames[$affectedMailTrigger['MailTrigger_LINK_Column_ID']];
  2074.             $infoColumns = [];
  2075.             /*
  2076.              * There are 6 slots which optionally contain columns that are available in the mail template as further info about the changed record.
  2077.              * They can be included in the mail template like: {{infoColumns[1]}}
  2078.              */
  2079.             for ($i 1$i <= 6$i++) {
  2080.                 if($affectedMailTrigger['MailTrigger_LINK_Info' $i '_Column_ID']) {
  2081.                     $infoColumnName $columnNames[$affectedMailTrigger['MailTrigger_LINK_Info' $i '_Column_ID']];
  2082.                     /*
  2083.                      * If the info-column is of type link, grab the target value to avoid displaying an id.
  2084.                      */
  2085.                     if ($colDefs[$infoColumnName]->getType() == 'link') {
  2086.                         $infoColumns[$i] = $newObject['REF_' .$infoColumnName]['display'];
  2087.                     } else {
  2088.                         $infoColumns[$i] = $newObject[$infoColumnName];
  2089.                     }
  2090.                 } else {
  2091.                     $infoColumns[$i] = null;
  2092.                 }
  2093.             }
  2094.             $recipients = [];
  2095.             /*
  2096.              * The e-mail field "MailTrigger_Send_To_Address" can have multiple addresses separated by ';'.
  2097.              * Send an e-mail for each e-mail address seperately, since recipients should not see each others e-mail addresses.
  2098.              */
  2099.             if ($affectedMailTrigger['MailTrigger_Send_To_Address']) {
  2100.                 $recipients explode(';'$affectedMailTrigger['MailTrigger_Send_To_Address']);
  2101.             }
  2102.             /*
  2103.              * There is one more recipient field "MailTrigger_Send_To" that lets the user choose a column containing an user-id. For example "MPM_Owner_LINK_User_ID".
  2104.              * Thereby the user does not need to know the e-mail address of this recipient at all.
  2105.              */
  2106.             if ($affectedMailTrigger['REF_MailTrigger_Send_To___ColDefinition_InternalColumnname'] && $newObject[$affectedMailTrigger['REF_MailTrigger_Send_To___ColDefinition_InternalColumnname']]) {
  2107.                 $recipientUserId $newObject[$affectedMailTrigger['REF_MailTrigger_Send_To___ColDefinition_InternalColumnname']];
  2108.                 $recipients[] = $userRepo->find($recipientUserId)['User_Mail'];
  2109.             }
  2110.             $recipients array_unique($recipients);
  2111.             $logMailTriggerRepo $this->repoFactory->createGenericRepository('LogMailTrigger');
  2112.             /*
  2113.              * Send e-mails and write logs to LogMailTrigger whether or not the e-mail was sent sucessfully.
  2114.              */
  2115.             foreach ($recipients as $recipient) {
  2116.                 $success true;
  2117.                 try {
  2118.                     $this->mailNotifier->notify(
  2119.                         [
  2120.                             'template' => $affectedMailTrigger['MailTrigger_LINK_MailTemplate_ID'],
  2121.                             'to' => $recipient,
  2122.                         ],
  2123.                         [
  2124.                             'columnCaption' => $colDefs[$columnName]->getCaption(),
  2125.                             'infoColumns' => $infoColumns,
  2126.                             'oldValue' => $originalObject[$columnName],
  2127.                             'newValue' => $newObject[$columnName]
  2128.                         ]
  2129.                     );
  2130.                 } catch(\Exception $e) {
  2131.                     $success false;
  2132.                 }
  2133.                 $logMailTriggerRepo->persist(
  2134.                     [
  2135.                         'LogMailTrigger_LINK_MailTrigger_ID' => $affectedMailTrigger['MailTrigger_ID'],
  2136.                         'LogMailTrigger_Sent_To' => $recipient,
  2137.                         'LogMailTrigger_Success' => $success
  2138.                     ]
  2139.                 );
  2140.             }
  2141.         }
  2142.     }
  2143.     public function find($id$find_latest_version true$hide_deleted true$join_refs false)
  2144.     {
  2145.         if ($this->options->useVersioning() && $find_latest_version) {
  2146.             return $this->findLatestById($id$hide_deleted$join_refs);
  2147.         }
  2148.         $criteria = new CriteriaComparison(
  2149.             '=',
  2150.             new CriteriaUnmappedColumn($this->tableInfo->getPrimaryKey()),
  2151.             new CriteriaConstant($id)
  2152.         );
  2153.         return $this->findOneBy($criterianull$hide_deleted$find_latest_version$join_refs);
  2154.     }
  2155.     public function findLatestById($id$hide_deleted true$join_refs false)
  2156.     {
  2157.         if (!$this->options->useVersioning()) {
  2158.             return $this->find($idfalse$hide_deleted);
  2159.         }
  2160.         $ancestor $this->getAncestor($id);
  2161.         $criteria = new CriteriaComparison(
  2162.             '=',
  2163.             new CriteriaUnmappedColumn($this->options->getAncestorColumn()),
  2164.             new CriteriaConstant($ancestor)
  2165.         );
  2166.         return $this->findOneBy($criterianull$hide_deletedtrue$join_refs);
  2167.     }
  2168.     protected function getChangedColumns($data$original_object)
  2169.     {
  2170.         // @todo: check
  2171.         $columns array_diff(
  2172.             $this->getWriteableColumns(),
  2173.             $this->options->getAutoColumns()
  2174.         );
  2175.         // Get Original (unmodified) object from database and calculate changed columns
  2176.         $changed_columns = [];
  2177.         foreach ($data as $key => $value) {
  2178.             if (!$this->options->isAutoColumn($key) && in_array($key$columns)) {
  2179.                 // Special handling of Log-Columns, because every non-empty input changes the column
  2180.                 if ($this->options->useLog() && ($this->options->isLogColumn($key))) {
  2181.                     if (!empty($value)) {
  2182.                         $changed_columns[] = $key;
  2183.                     }
  2184.                 } else { // Special handling of Password-Columns, because an empty input does not changes the column
  2185.                     if ($this->options->usePassword() && ($this->options->isPasswordColumn($key))) {
  2186.                         if (!empty($value)) {
  2187.                             $changed_columns[] = $key;
  2188.                         }
  2189.                     } else {
  2190.                         if (!isset($original_object[$key]) || ($original_object[$key] != $value)) {
  2191.                             $changed_columns[] = $key;
  2192.                         }
  2193.                     }
  2194.                 }
  2195.             }
  2196.         }
  2197.         return $changed_columns;
  2198.     }
  2199.     public function getWriteableColumns()
  2200.     {
  2201.         return $this->getTableInfo()
  2202.             ->getWriteableColumns($this->getUserRights());
  2203.     }
  2204.     /**
  2205.      * @param string $id The new objects id
  2206.      *
  2207.      * @return string
  2208.      *
  2209.      * @throws \Doctrine\DBAL\ConnectionException
  2210.      */
  2211.     protected function cloneObject($id$cloneWholeBunch false)
  2212.     {
  2213.         // Existing Columns get cloned, so no data is trashed when there are missing definitions
  2214.         $colDefs $this->tableInfo->getColumnDefinitions();
  2215.         $columns array_filter(
  2216.             array_diff($this->tableInfo->getColumns(), [$this->getPrimaryKey()]),
  2217.             function ($value) use ($colDefs) {
  2218.                 $col = new Column($value'view');
  2219.                 return !$colDefs[$col->getDefinitionName()]->isVirtual();
  2220.             }
  2221.         );
  2222.         $selectColumns array_map(
  2223.             function ($viewColumnName) {
  2224.                 return (new Column($viewColumnName'view'))->getSelectName();
  2225.             },
  2226.             $columns
  2227.         );
  2228.         $builder $this->db->createQueryBuilder()
  2229.             ->select($selectColumns)
  2230.             ->from($this->tableInfo->getInternalTablename(), 'mt');
  2231.         if($cloneWholeBunch) {
  2232.             $builder->where('mt.' $this->options->getReleaseLinkColumn() . ' = ' $builder->createNamedParameter($id));
  2233.         } else {
  2234.             $builder->where('mt.' $this->getPrimaryKey() . ' = ' $builder->createNamedParameter($id));
  2235.         }
  2236.         $sourceObjects $builder->execute()->fetchAll();
  2237.         $clonedIds = [];
  2238.         $this->db->beginTransaction();
  2239.         foreach ($sourceObjects as  $sourceObject)  {
  2240.             unset($sourceObject['doctrine_rownum']); // @todo: MSSQL-fix. Check if this is the best solution
  2241.             try {
  2242.                 $this->db->insert($this->tableInfo->getInternalTablename(), $sourceObject);
  2243.                 $clonedIds[] = $clonedId $this->db->lastInsertId();
  2244.                 // Clone all refs FROM this object
  2245.                 $this->cloneRefs('Refs_SourceTable'$this->tableInfo->getTablename(), 'Refs_Source_LINK_ID'$id$clonedId);
  2246.                 // Clone all refs TO this object
  2247.                 $this->cloneRefs('Refs_TargetTable'$this->tableInfo->getTablename(), 'Refs_Target_LINK_ID'$id$clonedId);
  2248.             } catch (\Exception $ex) {
  2249.                 if($this->db->isTransactionActive()) {
  2250.                     $this->db->rollBack();
  2251.                 }
  2252.                 throw new PortalException(
  2253.                     self::msgErrorClone,
  2254.                     ['%id%' => $id'%table%' => $this->tableInfo->getInternalTablename()],
  2255.                     0,
  2256.                     $ex
  2257.                 );
  2258.             }
  2259.         }
  2260.         $this->db->commit();
  2261.         if ($cloneWholeBunch) {
  2262.             return $clonedIds;
  2263.         } else {
  2264.             return $clonedId;
  2265.         }
  2266.     }
  2267.     private function cloneRefs($tableColumn$tablename$idColumn$id$clonedId)
  2268.     {
  2269.         $refsBuilder $this->db->createQueryBuilder();
  2270.         $refsBuilder
  2271.             ->select('*')
  2272.             ->from($this->prefix 'Refs''')
  2273.             ->where(
  2274.                 $tableColumn ' = ' $refsBuilder->createNamedParameter($tablename)
  2275.             )
  2276.             ->andWhere($idColumn ' = ' $refsBuilder->createNamedParameter($id));
  2277.         $refs $refsBuilder->execute()
  2278.             ->fetchAll();
  2279.         if ($refs) {
  2280.             $refs array_map(
  2281.                 function ($value) use ($clonedId$idColumn) {
  2282.                     unset($value['Refs_ID']);
  2283.                     $value[$idColumn] = $clonedId;
  2284.                     return $value;
  2285.                 },
  2286.                 $refs
  2287.             );
  2288.             foreach ($refs as $ref) {
  2289.                 $this->db->insert($this->prefix 'Refs'$ref);
  2290.             }
  2291.         }
  2292.     }
  2293.     protected function unversionedUpdate(
  2294.         $id,
  2295.         $data,
  2296.         $changed_columns null,
  2297.         $original_object null,
  2298.         $forceSave false,
  2299.         $finalSave true,
  2300.         $sendNotifications true
  2301.     )
  2302.     {
  2303.         if (!isset($original_object)) {
  2304.             $original_object $this->find($idfalsefalsetrue);
  2305.         }
  2306.         if ($this->options->useWriteRecordRights() && !$this->securityContextWrapper->checkRecordRights($original_object[$this->options->getWriteRecordRightsColumn()], 1)) {
  2307.             throw new UnaccessibleObjectException(self::msgMissingWritePermissions);
  2308.         }
  2309.         if (!isset($changed_columns)) {
  2310.             $changed_columns $this->getChangedColumns($data$original_object);
  2311.         }
  2312.         if ((count($changed_columns) > 0) || $forceSave) {
  2313.             // Add special columns to changed columns to ensure correct automated setting of values
  2314.             $this->db->beginTransaction();
  2315.             try {
  2316.                 if ($this->tableInfo->getTablename() === 'UserRights') {
  2317.                     $originalAdminRights  gmp_and(gmp_init($original_object['UserRights_UserRight']), gmp_init(1));
  2318.                     $submittedAdminRights gmp_and(gmp_init($data['UserRights_UserRight']), gmp_init(1));
  2319.                     if(gmp_cmp($submittedAdminRights$originalAdminRights) > && !$this->securityContextWrapper->isSuperAdmin()) {
  2320.                         throw new PortalException(self::msgAdminRightsDenied, [], 0nullnulltrue);
  2321.                     }
  2322.                 }
  2323.                 $changed_columns array_merge($changed_columns$this->options->getAutoColumns());
  2324.                 $queryBuilder $this->db->createQueryBuilder()
  2325.                     ->update($this->tableInfo->getInternalTablename(), '');
  2326.                 $queryBuilder
  2327.                     ->where($this->getPrimaryKey() . " = " $queryBuilder->createNamedParameter($id));
  2328.                 $needPersist $forceSave;
  2329.                 foreach ($changed_columns as $key) {
  2330.                     $value = isset($data[$key])
  2331.                         ?$data[$key]
  2332.                         :null;
  2333.                     $modifiedValue $this->valueOrAutoValue(
  2334.                         $key,
  2335.                         $value,
  2336.                         isset($original_object[$key])
  2337.                             ?$original_object[$key]
  2338.                             :null,
  2339.                         null,
  2340.                         $data
  2341.                     );
  2342.                     $colDefs $this->getColumnDefinitions();
  2343.                     if ($colDefs[$key]->getType() == 'html') {
  2344.                         $htmlPurifierConfig \HTMLPurifier_Config::createDefault();
  2345.                         $htmlPurifierConfig->set('Attr.AllowedFrameTargets', ['_blank''_top''_self''_parent']);
  2346.                         $purifier = new \HTMLPurifier($htmlPurifierConfig);
  2347.                         $modifiedValue['value'] = $purifier->purify($modifiedValue['value']);
  2348.                     }
  2349.                     if ($colDefs[$key]->isVirtual()) {
  2350.                         $this->updateRefs($id$key$modifiedValue);
  2351.                     } else {
  2352.                         // Standard update
  2353.                         $queryBuilder->set($key$modifiedValue['expr']);
  2354.                         $queryBuilder->setParameter($modifiedValue['param'], $modifiedValue['value']);
  2355.                         $needPersist true;
  2356.                     }
  2357.                 }
  2358.                 if ($needPersist) {
  2359.                     $queryBuilder->execute();
  2360.                     /*
  2361.                      *  Consider encryption if applicable
  2362.                      */
  2363.                     if ($this->container->getParameter('projectbiz.dsgvo.use_encryption')) {
  2364.                         $dsgvo $this->container->get('projectbiz.dsgvo');
  2365.                         if ($dsgvo->getCorrespondingEncryptionTable($this->tableInfo->getTablename())) {
  2366.                             $dsgvo->encryptRecord($id$this->tableInfo->getTablename(), true$this->options->useVersioning());
  2367.                         }
  2368.                     }
  2369.                     if ($finalSave) {
  2370.                         $new_object $this->find($idfalsefalsetrue);
  2371.                         $this->logChangesOfUserTable($original_object$new_object);
  2372.                         $context = [
  2373.                             'id' => $id,
  2374.                             'object' => $new_object,
  2375.                             'prev_object' => $original_object,
  2376.                             'database' => $this->db
  2377.                         ];
  2378.                         $this->dispatch(new RepositoryEvent($this$context), RepositoryEvents::UPDATE_AFTER_EXECUTE);
  2379.                     }
  2380.                 }
  2381.             } catch (\Exception $ex) {
  2382.                 $this->db->rollBack();
  2383.                 throw($ex);
  2384.             }
  2385.             $this->db->commit();
  2386.             if ($sendNotifications) {
  2387.                 $this->sendNotifications($changed_columns$original_object$new_object);
  2388.             }
  2389.         }
  2390.         return $id;
  2391.     }
  2392.     /**
  2393.      * Log the changes that have been made to the »User«-table into table »UserLog«.
  2394.      *
  2395.      * @param array $original_object
  2396.      * @param array $new_object
  2397.      */
  2398.     protected function logChangesOfUserTable($original_object$new_object)
  2399.     {
  2400.         if ($this->tableInfo->getTablename() != 'User') {
  2401.             return;
  2402.         }
  2403.         $changedValues false;
  2404.         /*
  2405.          * Do not log changes within the following columns.
  2406.          */
  2407.         $blacklist = ['User_Last_Change''User_Failure_Count''User_SessionID''User_Logged_In''User_Log''User_Previous_login'];
  2408.         foreach ($new_object as $key => $value) {
  2409.             if ($new_object[$key] != $original_object[$key] && !in_array($key$blacklist)) {
  2410.                 $changedValues .= $key ': »' print_r($original_object[$key], true) . '« => »' print_r($new_object[$key], true) . '«' PHP_EOL;
  2411.             }
  2412.         }
  2413.         if ($changedValues) {
  2414.             $this->db->insert(
  2415.                 $this->prefix 'UserLog',
  2416.                 [
  2417.                     'UserLog_LINK_User_ID'          => $this->securityContextWrapper->getUserId(),
  2418.                     'UserLog_Affected_LINK_User_ID' => $original_object['User_ID'],
  2419.                     'UserLog_initialized_Date'      => (new \DateTime())->format($this->db->getDatabasePlatform()->getDateTimeFormatString()),
  2420.                     'UserLog_Changes'               => $changedValues,
  2421.                 ]
  2422.             );
  2423.         }
  2424.     }
  2425.     public function archive($id)
  2426.     {
  2427.         $ancestor 0;
  2428.         $tablename $this->tableInfo->getInternalTablename();
  2429.         if (!$this->canAccessObject($id)) {
  2430.             throw new PortalException(self::msgNoAccess);
  2431.         }
  2432.         if ($this->options->useVersioning()) {
  2433.             $ancestorColumn $this->options->getAncestorColumn();
  2434.             $refObject $this->db->createQueryBuilder()
  2435.                 ->select([$ancestorColumn])
  2436.                 ->from($tablename'mt')
  2437.                 ->where('mt.' $this->getPrimaryKey() . ' = :id')
  2438.                 ->setParameter('id'$id)
  2439.                 ->setMaxResults(1)
  2440.                 ->execute()
  2441.                 ->fetch();
  2442.             if ($refObject !== false) {
  2443.                 $ancestor $refObject[$ancestorColumn];
  2444.             } else {
  2445.                 throw new GenericRepositoryArchiveException(1901);
  2446.             }
  2447.         }
  2448.         if ($this->options->useArchived()) {
  2449.             if ($this->isWriteable($this->options->getArchivedColumn())) {
  2450.                 $obj = [
  2451.                     $this->options->getArchivedColumn() => 1,
  2452.                     $this->getPrimaryKey() => $id
  2453.                 ];
  2454.                 try {
  2455.                     $this->persist($objtrue);
  2456.                 } catch (Exception $ex) {
  2457.                     throw new GenericRepositoryArchiveException(1902);
  2458.                 }
  2459.             } else {
  2460.                 throw new GenericRepositoryArchiveException(1910);
  2461.             }
  2462.         }
  2463.     }
  2464.     public function duplicate($id$data null)
  2465.     {
  2466.         $colDefs $this->getColumnDefinitions();
  2467.         $criteria = new CriteriaComparison(
  2468.             '=',
  2469.             new CriteriaUnmappedColumn($this->getPrimaryKey()),
  2470.             new CriteriaConstant($id)
  2471.         );
  2472.         $object $this->findOneBy($criteria);
  2473.         if (null !== $data) {
  2474.             foreach (array_keys($object) as $key) {
  2475.                 if (array_key_exists($key$data)) {
  2476.                     $object[$key] = $data[$key];
  2477.                 }
  2478.             }
  2479.         }
  2480.         foreach (array_keys($object) as $column) {
  2481.             /*
  2482.              * If »ColDefinition_ExcludeFromCloning« is true for a column and the user did not change the columns value,
  2483.              * overwrite the data with the default value (ColDefinition_StandardValue).
  2484.              * This prevents the duplication of some values, e.g. unique project numbers.
  2485.              */
  2486.             if (isset($colDefs[$column]) && $colDefs[$column]->getExcludeFromCloning() && (!isset($data[$column]) || $data[$column] === $object[$column])) {
  2487.                 $object[$column] = $colDefs[$column]->getDefault();
  2488.             }
  2489.         }
  2490.         if ($object !== false) {
  2491.             unset($object[$this->getPrimaryKey()]);
  2492.             if ($this->getOptions()
  2493.                 ->useVersioning()
  2494.             ) {
  2495.                 unset($object[$this->getOptions()
  2496.                         ->getVersionColumn()]);
  2497.             }
  2498.             return $this->create($object);
  2499.         }
  2500.         return null;
  2501.     }
  2502.     /**
  2503.      * Restores a version by getting the specified id and saving it as a new version.
  2504.      *
  2505.      * @param int $id primary key id of the version to restore.
  2506.      * @throws Exception
  2507.      */
  2508.     public function restoreVersion($id)
  2509.     {
  2510.         if (!$this->getOptions()->useVersioning()) {
  2511.             throw new \Exception('Cannot restore a version in an unversioned table.');
  2512.         }
  2513.         $targetVersion $this->find($idfalsefalsetrue);
  2514.         unset($targetVersion[$this->getPrimaryKey()]);
  2515.         unset($targetVersion[$this->options->getVersionColumn()]);
  2516.         $this->versionedUpdate($id$targetVersionfalsetrue);
  2517.     }
  2518.     /**
  2519.      * @param array $columns The columns that are required to be readable
  2520.      *
  2521.      * @return GenericRepository
  2522.      *
  2523.      * @throws RequiredColumnNotReadableException
  2524.      */
  2525.     public function requireReadableColumns(array $columns)
  2526.     {
  2527.         foreach ($columns as $column) {
  2528.             if (!$this->isReadable($column)) {
  2529.                 throw new RequiredColumnNotReadableException($column);
  2530.             }
  2531.         }
  2532.         return $this;
  2533.     }
  2534.     public function isReadable($column)
  2535.     {
  2536.         return $this->getTableInfo()
  2537.             ->isReadable($column$this->getUserRights());
  2538.     }
  2539.     /**
  2540.      * @param array $columns The columns that are required to be writeable
  2541.      *
  2542.      * @return GenericRepository
  2543.      *
  2544.      * @throws RequiredColumnNotWriteableException
  2545.      */
  2546.     public function requireWriteableColumns(array $columns)
  2547.     {
  2548.         foreach ($columns as $column) {
  2549.             if (!$this->isReadable($column)) {
  2550.                 throw new RequiredColumnNotWriteableException($column);
  2551.             }
  2552.         }
  2553.         return $this;
  2554.     }
  2555.     /**
  2556.      * @param array $view
  2557.      * @param bool $with_required
  2558.      *
  2559.      * @return array
  2560.      */
  2561.     public function getReadableColumnsInView($view null$with_required true)
  2562.     {
  2563.         $viewColumnsOrAll $this->getColumnsInView($view);
  2564.         $viewAndRequired $viewColumnsOrAll;
  2565.         if ($with_required) {
  2566.             $requiredColumns $this->getRequiredColumns();
  2567.             foreach ($requiredColumns as $column) {
  2568.                 $viewAndRequired[] = $column;
  2569.             }
  2570.             $viewAndRequired[] = $this->getPrimaryKey();
  2571.         }
  2572.         $readableColumns $this->getReadableColumns();
  2573.         return array_unique(array_intersect($viewAndRequired$readableColumns));
  2574.     }
  2575.     public function getColumnsInView($view null)
  2576.     {
  2577.         if (null === $view) {
  2578.             return $this->tableInfo->getColumns();
  2579.         }
  2580.         return array_intersect($view$this->tableInfo->getColumns());
  2581.     }
  2582.     public function getReadableColumns()
  2583.     {
  2584.         return $this->getTableInfo()
  2585.             ->getReadableColumns($this->getUserRights());
  2586.     }
  2587.     public function defaultObject()
  2588.     {
  2589.         $result = [];
  2590.         $columnDefinitions $this->getColumnDefinitions();
  2591.         $readableColumns $this->getReadableColumns();
  2592.         foreach ($readableColumns as $column) {
  2593.             $col = new Column($column'view');
  2594.             $colDef $columnDefinitions[$col->getDefinitionName()];
  2595.             $defaultValue $colDef->getDefault();
  2596.             if ($defaultValue !== null) {
  2597.                 $result[$column] = $defaultValue;
  2598.             }
  2599.         }
  2600.         unset($result[$this->getPrimaryKey()]);
  2601.         return $result;
  2602.     }
  2603.     public function isSystemColumn($column)
  2604.     {
  2605.         return $this->options->isSystemColumn($column);
  2606.     }
  2607.     public function isAutoColumn($column)
  2608.     {
  2609.         return $this->options->isAutoColumn($column);
  2610.     }
  2611.     /**
  2612.      * Return type for special columns
  2613.      *
  2614.      * Returns 'hidden' for primary key columns and 'log' for log-columns.
  2615.      *
  2616.      * @todo    Try to get rid of this function. It is called in RepositoryBlock, TableViewController,
  2617.      *          TableDataViewController and GenericTableType. Try to replace it with something better
  2618.      *
  2619.      * @deprecated
  2620.      *
  2621.      * @param string $columnName
  2622.      *
  2623.      * @return null|string
  2624.      */
  2625.     public function getSpecialColumnType($columnName)
  2626.     {
  2627.         if ($this->getPrimaryKey() == $columnName) {
  2628.             return 'hidden';
  2629.         }
  2630.         if ($this->options->useLog()) {
  2631.             if (in_array($columnName$this->options->getLogColumns())) {
  2632.                 return 'log';
  2633.             }
  2634.         }
  2635.         return null;
  2636.     }
  2637.     public function getLabelForPropertyPath($propertyPath) {
  2638.         return $this->getLabelForPropertyPathWithInfo(explode(':'$propertyPath), $this->tableInfo);
  2639.     }
  2640.     private function getLabelForPropertyPathWithInfo($propertyPath$info) {
  2641.         if (count($propertyPath) == 1) {
  2642.             return $this->getLabelForProperty($propertyPath[0], $info);
  2643.         }
  2644.         else if (count($propertyPath) > 1) {
  2645.             $part0 array_shift($propertyPath);
  2646.             $column = new Column($part0'view');
  2647.             $sourceInfo $this->tableInfo->getSourceTableInfo($column->getDefinitionName());
  2648.             return $this->getLabelForProperty($part0$info) . ': ' $this->getLabelForPropertyPathWithInfo($propertyPath$sourceInfo);
  2649.         }
  2650.     }
  2651.     public function isVirtualForPropertyPath($propertyPath) {
  2652.         return $this->isVirtualForPropertyPathWithInfo(explode(':'$propertyPath), $this->tableInfo);
  2653.     }
  2654.     private function isVirtualForPropertyPathWithInfo($propertyPathTableInfoInterface $info) {
  2655.         if (count($propertyPath) == 1) {
  2656.             $colDefs $info->getColumnDefinitions();
  2657.             $column = new Column($propertyPath[0], 'view');
  2658.             $defName $column->getDefinitionName();
  2659.             if (!array_key_exists($defName$colDefs)) {
  2660.                 throw new \Exception('Missing Column Definition: ' $defName);
  2661.             }
  2662.             return $colDefs[$defName]->isVirtual();
  2663.         }
  2664.         else if (count($propertyPath) > 1) {
  2665.             $part0 array_shift($propertyPath);
  2666.             $column = new Column($part0'view');
  2667.             $sourceInfo $this->tableInfo->getSourceTableInfo($column->getDefinitionName());
  2668.             return $this->isVirtualForPropertyPathWithInfo($propertyPath$sourceInfo);
  2669.         }
  2670.     }
  2671.     private function getLabelForProperty($property$info) {
  2672.         return $info->getLabelForViewColumn($property);
  2673.     }
  2674. }