Bug #70154

Extbase 'sorting' l10nmode

Added by Marc Hirdes about 6 years ago. Updated 7 months ago.

Status:
New
Priority:
Must have
Assignee:
-
Category:
Extbase + l10n
Target version:
-
Start date:
2015-09-28
Due date:
% Done:

0%

Estimated time:
TYPO3 Version:
9
PHP Version:
Tags:
Complexity:
Is Regression:
No
Sprint Focus:

Description

When I use in the TCA ctrl-section of an item

'sortby' => 'sprting'

the translated items are on the same position as the default ones in the backend. But in the frontend the order is wrong.
When I add

protected $defaultOrderings = array(
        'sorting' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
    );

in the repository the sorting worsk correct in the default language. But in another language the sorting is wrong. The problem is, that the sorting in the translated language is another number than in the default language, but it has to be the same because you can't move only the translated language. I hope you can understand the problem.


Files

28-09-_2015_14-02-33.png (71.5 KB) 28-09-_2015_14-02-33.png Marc Hirdes, 2015-09-28 14:03

Related issues

Related to TYPO3 Core - Bug #72988: losing Localization when moving elements (Typo 7.6.2)Closed2016-01-28

Actions
Related to TYPO3 Core - Bug #92093: Sorting of records in frontend with sys_language all (-1) and normalUnder Review2020-08-25

Actions
Related to TYPO3 Core - Bug #85377: Wrong sorting in list module by fields which are relations New2018-06-25

Actions
Related to TYPO3 Core - Bug #86059: Wrong sorting for localized entries in page module with 8.7.16 and higherNeeds Feedback2018-08-31

Actions
#1

Updated by Benni Mack about 6 years ago

  • Target version changed from 6.2.15 to next-patchlevel
#2

Updated by Mathias Schreiber about 6 years ago

  • Target version deleted (next-patchlevel)
#3

Updated by Stephan Brun over 5 years ago

Same problem here. We have translated sys_categories. The default sorting is not present, although there are sorting arrows in the backend. After fix the defaultOrderings in the repository, the localized records get messed up, because they have their own sorting value This doesn't make sense because it is not possible to sort them separatly in the backend.

Tested with 6.2.19 and 7.6.4

At this time we have a workaround with hooks on processDataMap (update,insert,move) which override the sorting on translated records with the sorting of the default language. Not a nice solution, but it works.

#4

Updated by Ralf Merz over 5 years ago

Hi,

we have the same problem here. No possibility to change the sorting value of localized records. If we move the (default) records, the values of the localizations do not get updated. So the order in the frontend is wrong.

@Stephan: Could you please provide the code of your workaround?

Thanks and regards
Ralf

#5

Updated by Ralf Merz about 5 years ago

Hi,

are there any news about the sorting problem?

Thank you very much and regards,
Ralf

#6

Updated by Mona Muzaffar over 4 years ago

  • Related to Bug #72988: losing Localization when moving elements (Typo 7.6.2) added
#7

Updated by Susanne Moog almost 4 years ago

  • Category set to Extbase
#8

Updated by Oliver Weiss almost 4 years ago

  • TYPO3 Version changed from 6.2 to 8

Same here, still not fixed with 8.7

#9

Updated by Julian Hofmann over 3 years ago

Manual workaround:
Disable "Localization view" and sort translation records below theirs parent.
Repeat this each time you're adding/moving a record.

#10

Updated by Riccardo De Contardi about 3 years ago

  • Category changed from Extbase to Extbase + l10n
#11

Updated by Alexander Grein almost 2 years ago

Problem still exist under 9.5.13

#12

Updated by Marc Hirdes over 1 year ago

  • TYPO3 Version changed from 8 to 9

This problem still exists.

#13

Updated by Markus Gerdes over 1 year ago

  • Related to Bug #92093: Sorting of records in frontend with sys_language all (-1) and normal added
#14

Updated by Marc Hirdes about 1 year ago

Any news?

#15

Updated by David Bruchmann about 1 year ago

  • Related to Bug #85377: Wrong sorting in list module by fields which are relations added
#16

Updated by David Bruchmann about 1 year ago

Added #85377 even if it's related to the backend.

Concerning frontend it might be annoying or cumbersome having to write own methods in the repository but in an own extension you had full control.

#17

Updated by Marc Hirdes about 1 year ago

If it's not my extension like news it's really annoying. A fix would be to sync the sorting in the backend.

#18

Updated by David Bruchmann about 1 year ago

I think it should be possible to override the repo by x-classing, but the code of news is quite complex, so it won't be an easy task.

#19

Updated by Marc Hirdes about 1 year ago

The solution should be provided by default/core. If I don't have the possibility to change the sorting of a translated record, the sorting number should be the same as in the default language. Otherwise the sorting of the translated record should be ignored.

The different sorting number of a translated record makes no sence.

#20

Updated by Marc Hirdes about 1 year ago

it should works like l10n_mode=exclude for the sorting field. This works if you show the field in the record and press save. But if you use the arrows or cut and paste in the list module it works not.

#21

Updated by Marc Hirdes 12 months ago

A good hook is also missing. When using

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass']['cstemplates_move'] =
    \Clickstorm\CsTemplates\Hook\DataHandlerHook::class;

Then you have the use the whole code from

\TYPO3\CMS\Core\DataHandling\DataHandler->moveRecord_raw()
    /**
     * @param string $table
     * @param int $uid
     * @param int $destPid
     * @param array $propArr
     * @param array $moveRec
     * @param int $resolvedPid
     * @param bool $recordWasMoved
     * @param DataHandler $dataHandler
     */
    public function moveRecord($table, $uid, $destPid, $propArr, $moveRec, $resolvedPid, &$recordWasMoved, $dataHandler) {
        if (!BackendUtility::isTableLocalizable($table)) {
            return;
        }
        $languageFieldColumn = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
        $transOrigPointerFieldColumn = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
        $sortColumn = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';

        $fields = [$languageFieldColumn, $transOrigPointerFieldColumn];
        $record = BackendUtility::getRecord($table,$uid,implode(',', $fields));

        if($record[$languageFieldColumn] > 0 && $record[$transOrigPointerFieldColumn] > 0) {
            $recordOfDefaultLanguage = BackendUtility::getRecord($table,$record[$transOrigPointerFieldColumn],$sortColumn);

            $origDestPid = $destPid;
            // This is the actual pid of the moving to destination
            $resolvedPid = $dataHandler->resolvePid($table, $destPid);
            // Checking if the pid is negative, but no sorting row is defined. In that case, find the correct pid. Basically this check make the error message 4-13 meaning less... But you can always remove this check if you prefer the error instead of a no-good action (which is to move the record to its own page...)
            // $destPid>=0 because we must correct pid in case of versioning "page" types.
            if (($destPid < 0 && !$sortColumn) || $destPid >= 0) {
                $destPid = $resolvedPid;
            }
            // Prepare user defined objects (if any) for hooks which extend this function:
            $hookObjectsArr = [];
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass'] ?? [] as $className) {
                $hookObjectsArr[] = GeneralUtility::makeInstance($className);
            }

            // Timestamp field:
            $updateFields = [];
            if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
                $updateFields[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
            }

            // Check if this is a translation of a page, if so then it just needs to be kept "sorting" in sync
            // Usually called from moveL10nOverlayRecords()
            if ($table === 'pages') {
                // Just return with $recordWasMoved == false -- so pages will be handled by the core
                return;
            }

            // Insert as first element on page (where uid = $destPid)
            if ($destPid >= 0) {
                if ($table !== 'pages' || $dataHandler->destNotInsideSelf($destPid, $uid)) {
                    // Clear cache before moving
                    [$parentUid] = BackendUtility::getTSCpid($table, $uid, '');
                    $dataHandler->registerRecordIdForPageCacheClearing($table, $uid, $parentUid);
                    // Setting PID
                    $updateFields['pid'] = $destPid;
                    // Table is sorted by 'sortby', use sorting of record with default language
                    if ($sortColumn && !isset($updateFields[$sortColumn])) {
                        $updateFields[$sortColumn] = $recordOfDefaultLanguage['sorting'];
                    }
                    // Check for child records that have also to be moved
                    $dataHandler->moveRecord_procFields($table, $uid, $destPid);
                    // Create query for update:
                    GeneralUtility::makeInstance(ConnectionPool::class)
                        ->getConnectionForTable($table)
                        ->update($table, $updateFields, ['uid' => (int)$uid]);
                    // Check for the localizations of that element
                    // Dont use moveL10nOverlayRecords cause we handle the movement of L10nOverlayRecords
                    //$dataHandler->moveL10nOverlayRecords($table, $uid, $destPid, $destPid);
                    // Call post processing hooks:
                    foreach ($hookObjectsArr as $hookObj) {
                        if (method_exists($hookObj, 'moveRecord_firstElementPostProcess')) {
                            $hookObj->moveRecord_firstElementPostProcess($table, $uid, $destPid, $moveRec, $updateFields, $dataHandler);
                        }
                    }

                    $this->getRecordHistoryStore($dataHandler)->moveRecord($table, $uid, ['oldPageId' => $propArr['pid'], 'newPageId' => $destPid, 'oldData' => $propArr, 'newData' => $updateFields]);
                    if ($dataHandler->enableLogging) {
                        // Logging...
                        $oldpagePropArr = $dataHandler->getRecordProperties('pages', $propArr['pid']);
                        if ($destPid != $propArr['pid']) {
                            // Logged to old page
                            $newPropArr = $dataHandler->getRecordProperties($table, $uid);
                            $newpagePropArr = $dataHandler->getRecordProperties('pages', $destPid);
                            $dataHandler->log($table, $uid, 4, $destPid, 0, 'Moved record \'%s\' (%s) to page \'%s\' (%s)', 2, [$propArr['header'], $table . ':' . $uid, $newpagePropArr['header'], $newPropArr['pid']], $propArr['pid']);
                            // Logged to new page
                            $dataHandler->log($table, $uid, 4, $destPid, 0, 'Moved record \'%s\' (%s) from page \'%s\' (%s)', 3, [$propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']], $destPid);
                        } else {
                            // Logged to new page
                            $dataHandler->log($table, $uid, 4, $destPid, 0, 'Moved record \'%s\' (%s) on page \'%s\' (%s)', 4, [$propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']], $destPid);
                        }
                    }
                    // Clear cache after moving
                    $dataHandler->registerRecordIdForPageCacheClearing($table, $uid);
                    $dataHandler->fixUniqueInPid($table, $uid);
                    $this->fixUniqueInSite($table, (int)$uid, $dataHandler);
                    if ($table === 'pages') {
                        $this->fixUniqueInSiteForSubpages((int)$uid, $dataHandler);
                    }
                    // Set recordWasMoved, so record wont be moved after this hook again
                    $recordWasMoved = true;
                } elseif ($dataHandler->enableLogging) {
                    $destPropArr = $dataHandler->getRecordProperties('pages', $destPid);
                    $dataHandler->log($table, $uid, 4, 0, 1, 'Attempt to move page \'%s\' (%s) to inside of its own rootline (at page \'%s\' (%s))', 10, [$propArr['header'], $uid, $destPropArr['header'], $destPid], $propArr['pid']);
                }
            } elseif ($sortColumn) {
                // Put after another record
                // Table is being sorted
                // Save the position to which the original record is requested to be moved
                $originalRecordDestinationPid = $destPid;
                $sortInfo = $dataHandler->getSortNumber($table, $uid, $destPid);
                // Setting the destPid to the new pid of the record.
                $destPid = $sortInfo['pid'];
                // If not an array, there was an error (which is already logged)
                if (is_array($sortInfo)) {
                    if ($table !== 'pages' || $dataHandler->destNotInsideSelf($destPid, $uid)) {
                        // clear cache before moving
                        $dataHandler->registerRecordIdForPageCacheClearing($table, $uid);
                        // We now update the pid and sortnumber (if not set for page translations)
                        $updateFields['pid'] = $destPid;
                        if (!isset($updateFields[$sortColumn])) {
                            // Get updated record of default language and set sorting number
                            $recordOfDefaultLanguage = BackendUtility::getRecord($table,$record[$transOrigPointerFieldColumn],$sortColumn);
                            $updateFields[$sortColumn] = $recordOfDefaultLanguage['sorting'];
                        }
                        // Check for child records that have also to be moved
                        $dataHandler->moveRecord_procFields($table, $uid, $destPid);
                        // Create query for update:
                        GeneralUtility::makeInstance(ConnectionPool::class)
                            ->getConnectionForTable($table)
                            ->update($table, $updateFields, ['uid' => (int)$uid]);
                        // Check for the localizations of that element
                        // Dont use moveL10nOverlayRecords cause we handle the movement of L10nOverlayRecords
                        //$dataHandler->moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid);
                        // Call post processing hooks:
                        foreach ($hookObjectsArr as $hookObj) {
                            if (method_exists($hookObj, 'moveRecord_afterAnotherElementPostProcess')) {
                                $hookObj->moveRecord_afterAnotherElementPostProcess($table, $uid, $destPid, $origDestPid, $moveRec, $updateFields, $dataHandler);
                            }
                        }
                        $this->getRecordHistoryStore($dataHandler)->moveRecord($table, $uid, ['oldPageId' => $propArr['pid'], 'newPageId' => $destPid, 'oldData' => $propArr, 'newData' => $updateFields]);
                        if ($dataHandler->enableLogging) {
                            // Logging...
                            $oldpagePropArr = $dataHandler->getRecordProperties('pages', $propArr['pid']);
                            if ($destPid != $propArr['pid']) {
                                // Logged to old page
                                $newPropArr = $dataHandler->getRecordProperties($table, $uid);
                                $newpagePropArr = $dataHandler->getRecordProperties('pages', $destPid);
                                $dataHandler->log($table, $uid, 4, 0, 0, 'Moved record \'%s\' (%s) to page \'%s\' (%s)', 2, [$propArr['header'], $table . ':' . $uid, $newpagePropArr['header'], $newPropArr['pid']], $propArr['pid']);
                                // Logged to old page
                                $dataHandler->log($table, $uid, 4, 0, 0, 'Moved record \'%s\' (%s) from page \'%s\' (%s)', 3, [$propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']], $destPid);
                            } else {
                                // Logged to old page
                                $dataHandler->log($table, $uid, 4, 0, 0, 'Moved record \'%s\' (%s) on page \'%s\' (%s)', 4, [$propArr['header'], $table . ':' . $uid, $oldpagePropArr['header'], $propArr['pid']], $destPid);
                            }
                        }
                        // Clear cache after moving
                        $dataHandler->registerRecordIdForPageCacheClearing($table, $uid);
                        $dataHandler->fixUniqueInPid($table, $uid);
                        $this->fixUniqueInSite($table, (int)$uid, $dataHandler);
                        if ($table === 'pages') {
                            $this->fixUniqueInSiteForSubpages((int)$uid, $dataHandler);
                        }
                        // Set recordWasMoved, so record wont be moved after this hook again
                        $recordWasMoved = true;
                    } elseif ($dataHandler->enableLogging) {
                        $destPropArr = $dataHandler->getRecordProperties('pages', $destPid);
                        $dataHandler->log($table, $uid, 4, 0, 1, 'Attempt to move page \'%s\' (%s) to inside of its own rootline (at page \'%s\' (%s))', 10, [$propArr['header'], $uid, $destPropArr['header'], $destPid], $propArr['pid']);
                    }
                } else {
                    $dataHandler->log($table, $uid, 4, 0, 1, 'Attempt to move record \'%s\' (%s) to after another record, although the table has no sorting row.', 13, [$propArr['header'], $table . ':' . $uid], $propArr['event_pid']);
                }
            }
        }

This also not works for new records.

Better would be to provide a Hook in

\TYPO3\CMS\Core\DataHandling\DataHandler->getSortNumber()

Best would be to fix this problem in the core.

#22

Updated by Marc Hirdes 12 months ago

Ok, current workaround

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['cstemplates_move_fix'] =
   \Clickstorm\CsTemplates\Hook\DataHandlerHook::class;
<?php declare(strict_types=1);
namespace Clickstorm\CsTemplates\Hook;

/*
 * This file is part of the "cs_templates" Extension for TYPO3 CMS.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 *  (c) 2020 clickstorm GmbH
 */

use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
 * use until https://forge.typo3.org/issues/70154 is solved
 *
 * Class DataHandlerHook
 */
class DataHandlerHook
{
    /**
     * fix sorting of translated records, sync them with default
     *
     * @param string $command
     * @param string $table
     * @param int $uid
     * @param $value
     * @param DataHandler $pObj
     * @param bool $pasteUpdate
     * @param array $pasteDatamap
     */
    public function processCmdmap_postProcess(string $command, string $table, int $uid, $value, DataHandler &$pObj, bool $pasteUpdate, array $pasteDatamap) {

        // first check if table is localizable, languageField and transOrigPointerField are set
        if(BackendUtility::isTableLocalizable($table)) {
            $sortColumn = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?? '';
            $languageFieldColumn = $GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? '';
            $transOrigPointerFieldColumn = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? '';
            $deleteColumn = $GLOBALS['TCA'][$table]['ctrl']['delete'] ?? '';

            // if table has a sort column
            if($sortColumn) {
                // get pid of record
                $record = BackendUtility::getRecord($table,$uid,'pid');

                // if record is not deleted
                if($record) {
                    $pid = $record['pid'];

                    // fetch all uids of translated records with sorting number of default language
                    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);

                    // include hidden records
                    $queryBuilder->getRestrictions()->removeAll();

                    $queryBuilder
                        ->select('l10n.uid', 'default.' . $sortColumn)
                        ->from($table, 'l10n')
                        ->leftJoin(
                            'l10n',
                            $table,
                            'default',
                            $queryBuilder->expr()->eq(
                                'default.uid',
                                $queryBuilder->quoteIdentifier('l10n.' . $transOrigPointerFieldColumn)
                            )
                        )
                        ->where($queryBuilder->expr()->eq('l10n.pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)))
                        ->andWhere($queryBuilder->expr()->gt('l10n.' . $languageFieldColumn, 0))
                        ->andWhere($queryBuilder->expr()->gt('l10n.' . $transOrigPointerFieldColumn, 0));

                    if ($deleteColumn) {
                        $queryBuilder->andWhere($queryBuilder->expr()->eq('l10n.' . $deleteColumn, 0));
                    }

                    $l10nSortingValues = $queryBuilder->execute()->fetchAll();

                    // update all translated records of current pid with sorting number of default language
                    foreach ($l10nSortingValues as $l10nSortingValue) {
                        $queryBuilder->resetQueryParts();
                        $queryBuilder
                            ->update($table)
                            ->set($sortColumn, $l10nSortingValue[$sortColumn])
                            ->where(
                                $queryBuilder->expr()->eq('uid', $l10nSortingValue['uid'])
                            )
                            ->execute();
                    }
                }

            }
        }
    }
}

#23

Updated by Marc Hirdes 10 months ago

The workaround above works well for me. Maybe provide sth. like this in core? It makes no sence to have a different sorting value for translated records in a connected mode.

#24

Updated by Ingo Fabbri 7 months ago

Marc Hirdes wrote in #note-23:

The workaround above works well for me. Maybe provide sth. like this in core? It makes no sence to have a different sorting value for translated records in a connected mode.

You need to provide/submit a patch yourself and get some reviewers. The core team is too busy with other - far more important - topics. Sarcasm intended.

#25

Updated by Riccardo De Contardi 6 months ago

  • Related to Bug #86059: Wrong sorting for localized entries in page module with 8.7.16 and higher added

Also available in: Atom PDF